@memberjunction/codegen-lib 6.2.0-edge.0 → 6.2.0-edge.1

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 (212) hide show
  1. package/dist/Angular/angular-codegen.d.ts +6 -0
  2. package/dist/Angular/angular-codegen.d.ts.map +1 -1
  3. package/dist/Angular/angular-codegen.js +26 -14
  4. package/dist/Angular/angular-codegen.js.map +1 -1
  5. package/dist/Config/config.d.ts +45 -2
  6. package/dist/Config/config.d.ts.map +1 -1
  7. package/dist/Config/config.js +116 -39
  8. package/dist/Config/config.js.map +1 -1
  9. package/dist/Config/db-connection.d.ts +4 -0
  10. package/dist/Config/db-connection.d.ts.map +1 -1
  11. package/dist/Config/db-connection.js +11 -3
  12. package/dist/Config/db-connection.js.map +1 -1
  13. package/dist/Config/pg-connection.d.ts +2 -0
  14. package/dist/Config/pg-connection.d.ts.map +1 -1
  15. package/dist/Config/pg-connection.js +5 -1
  16. package/dist/Config/pg-connection.js.map +1 -1
  17. package/dist/Config/schema-output.d.ts +8 -0
  18. package/dist/Config/schema-output.d.ts.map +1 -1
  19. package/dist/Config/schema-output.js +19 -7
  20. package/dist/Config/schema-output.js.map +1 -1
  21. package/dist/Database/__tests__/exclude-schema-permissions.test.js +3 -3
  22. package/dist/Database/codeGenDatabaseProvider.d.ts +16 -1
  23. package/dist/Database/codeGenDatabaseProvider.d.ts.map +1 -1
  24. package/dist/Database/codeGenDatabaseProvider.js +37 -9
  25. package/dist/Database/codeGenDatabaseProvider.js.map +1 -1
  26. package/dist/Database/dbSchema.d.ts +4 -0
  27. package/dist/Database/dbSchema.d.ts.map +1 -1
  28. package/dist/Database/dbSchema.js +17 -9
  29. package/dist/Database/dbSchema.js.map +1 -1
  30. package/dist/Database/entity-field-change-tracking.d.ts +4 -0
  31. package/dist/Database/entity-field-change-tracking.d.ts.map +1 -1
  32. package/dist/Database/entity-field-change-tracking.js +12 -4
  33. package/dist/Database/entity-field-change-tracking.js.map +1 -1
  34. package/dist/Database/exclude-tables.d.ts +2 -0
  35. package/dist/Database/exclude-tables.d.ts.map +1 -1
  36. package/dist/Database/exclude-tables.js +5 -1
  37. package/dist/Database/exclude-tables.js.map +1 -1
  38. package/dist/Database/field-metadata-lock.d.ts +8 -6
  39. package/dist/Database/field-metadata-lock.d.ts.map +1 -1
  40. package/dist/Database/field-metadata-lock.js +29 -25
  41. package/dist/Database/field-metadata-lock.js.map +1 -1
  42. package/dist/Database/heal-schema-params.d.ts +14 -2
  43. package/dist/Database/heal-schema-params.d.ts.map +1 -1
  44. package/dist/Database/heal-schema-params.js +21 -5
  45. package/dist/Database/heal-schema-params.js.map +1 -1
  46. package/dist/Database/manage-metadata.d.ts +119 -3
  47. package/dist/Database/manage-metadata.d.ts.map +1 -1
  48. package/dist/Database/manage-metadata.js +529 -364
  49. package/dist/Database/manage-metadata.js.map +1 -1
  50. package/dist/Database/materializationAnalysis.d.ts +61 -2
  51. package/dist/Database/materializationAnalysis.d.ts.map +1 -1
  52. package/dist/Database/materializationAnalysis.js +43 -23
  53. package/dist/Database/materializationAnalysis.js.map +1 -1
  54. package/dist/Database/materializationBroadRender.d.ts +5 -3
  55. package/dist/Database/materializationBroadRender.d.ts.map +1 -1
  56. package/dist/Database/materializationBroadRender.js +17 -13
  57. package/dist/Database/materializationBroadRender.js.map +1 -1
  58. package/dist/Database/materializationDrift.d.ts +2 -0
  59. package/dist/Database/materializationDrift.d.ts.map +1 -1
  60. package/dist/Database/materializationDrift.js +5 -1
  61. package/dist/Database/materializationDrift.js.map +1 -1
  62. package/dist/Database/materializationParamClassifier.d.ts +17 -2
  63. package/dist/Database/materializationParamClassifier.d.ts.map +1 -1
  64. package/dist/Database/materializationParamClassifier.js +22 -10
  65. package/dist/Database/materializationParamClassifier.js.map +1 -1
  66. package/dist/Database/materializationParamVerifier.d.ts +2 -0
  67. package/dist/Database/materializationParamVerifier.d.ts.map +1 -1
  68. package/dist/Database/materializationParamVerifier.js +18 -14
  69. package/dist/Database/materializationParamVerifier.js.map +1 -1
  70. package/dist/Database/materializationSqlAst.d.ts +19 -1
  71. package/dist/Database/materializationSqlAst.d.ts.map +1 -1
  72. package/dist/Database/materializationSqlAst.js +53 -17
  73. package/dist/Database/materializationSqlAst.js.map +1 -1
  74. package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js +30 -30
  75. package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js.map +1 -1
  76. package/dist/Database/providers/postgresql/metadataSupportObjects.d.ts +2 -0
  77. package/dist/Database/providers/postgresql/metadataSupportObjects.d.ts.map +1 -1
  78. package/dist/Database/providers/postgresql/metadataSupportObjects.js +5 -1
  79. package/dist/Database/providers/postgresql/metadataSupportObjects.js.map +1 -1
  80. package/dist/Database/providers/postgresql/viewDependencyCapture.d.ts +10 -0
  81. package/dist/Database/providers/postgresql/viewDependencyCapture.d.ts.map +1 -1
  82. package/dist/Database/providers/postgresql/viewDependencyCapture.js +25 -5
  83. package/dist/Database/providers/postgresql/viewDependencyCapture.js.map +1 -1
  84. package/dist/Database/providers/postgresql/viewFallback.d.ts +11 -7
  85. package/dist/Database/providers/postgresql/viewFallback.d.ts.map +1 -1
  86. package/dist/Database/providers/postgresql/viewFallback.js +18 -12
  87. package/dist/Database/providers/postgresql/viewFallback.js.map +1 -1
  88. package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js +13 -13
  89. package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js.map +1 -1
  90. package/dist/Database/reconcileFieldLevelSecurity.d.ts +2 -0
  91. package/dist/Database/reconcileFieldLevelSecurity.d.ts.map +1 -1
  92. package/dist/Database/reconcileFieldLevelSecurity.js +5 -1
  93. package/dist/Database/reconcileFieldLevelSecurity.js.map +1 -1
  94. package/dist/Database/reorder-columns.d.ts +2 -0
  95. package/dist/Database/reorder-columns.d.ts.map +1 -1
  96. package/dist/Database/reorder-columns.js +5 -1
  97. package/dist/Database/reorder-columns.js.map +1 -1
  98. package/dist/Database/schema-filters.d.ts +6 -0
  99. package/dist/Database/schema-filters.d.ts.map +1 -1
  100. package/dist/Database/schema-filters.js +17 -5
  101. package/dist/Database/schema-filters.js.map +1 -1
  102. package/dist/Database/schema-scope.d.ts +4 -0
  103. package/dist/Database/schema-scope.d.ts.map +1 -1
  104. package/dist/Database/schema-scope.js +13 -5
  105. package/dist/Database/schema-scope.js.map +1 -1
  106. package/dist/Database/search-guardrails.d.ts +42 -0
  107. package/dist/Database/search-guardrails.d.ts.map +1 -1
  108. package/dist/Database/search-guardrails.js +57 -17
  109. package/dist/Database/search-guardrails.js.map +1 -1
  110. package/dist/Database/sql.d.ts +20 -0
  111. package/dist/Database/sql.d.ts.map +1 -1
  112. package/dist/Database/sql.js +72 -32
  113. package/dist/Database/sql.js.map +1 -1
  114. package/dist/Database/sql_codegen.d.ts +92 -0
  115. package/dist/Database/sql_codegen.d.ts.map +1 -1
  116. package/dist/Database/sql_codegen.js +197 -113
  117. package/dist/Database/sql_codegen.js.map +1 -1
  118. package/dist/EntityNameScanner/EntityNameScanner.d.ts +16 -0
  119. package/dist/EntityNameScanner/EntityNameScanner.d.ts.map +1 -1
  120. package/dist/EntityNameScanner/EntityNameScanner.js +47 -15
  121. package/dist/EntityNameScanner/EntityNameScanner.js.map +1 -1
  122. package/dist/EntityNameScanner/HtmlEntityNameScanner.d.ts +6 -0
  123. package/dist/EntityNameScanner/HtmlEntityNameScanner.d.ts.map +1 -1
  124. package/dist/EntityNameScanner/HtmlEntityNameScanner.js +19 -7
  125. package/dist/EntityNameScanner/HtmlEntityNameScanner.js.map +1 -1
  126. package/dist/EntityNameScanner/MetadataNameScanner.d.ts +6 -0
  127. package/dist/EntityNameScanner/MetadataNameScanner.d.ts.map +1 -1
  128. package/dist/EntityNameScanner/MetadataNameScanner.js +19 -7
  129. package/dist/EntityNameScanner/MetadataNameScanner.js.map +1 -1
  130. package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +6 -0
  131. package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -1
  132. package/dist/Manifest/GenerateClassRegistrationsManifest.js +16 -4
  133. package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -1
  134. package/dist/Misc/action_subclasses_codegen.d.ts +4 -0
  135. package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
  136. package/dist/Misc/action_subclasses_codegen.js +11 -3
  137. package/dist/Misc/action_subclasses_codegen.js.map +1 -1
  138. package/dist/Misc/advanced_generation.d.ts +48 -2
  139. package/dist/Misc/advanced_generation.d.ts.map +1 -1
  140. package/dist/Misc/advanced_generation.js +53 -15
  141. package/dist/Misc/advanced_generation.js.map +1 -1
  142. package/dist/Misc/codegen-reporter.d.ts +53 -0
  143. package/dist/Misc/codegen-reporter.d.ts.map +1 -1
  144. package/dist/Misc/codegen-reporter.js +106 -22
  145. package/dist/Misc/codegen-reporter.js.map +1 -1
  146. package/dist/Misc/createNewUser.d.ts +6 -0
  147. package/dist/Misc/createNewUser.d.ts.map +1 -1
  148. package/dist/Misc/createNewUser.js +5 -1
  149. package/dist/Misc/createNewUser.js.map +1 -1
  150. package/dist/Misc/entity_subclasses_codegen.d.ts +8 -0
  151. package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
  152. package/dist/Misc/entity_subclasses_codegen.js +52 -36
  153. package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
  154. package/dist/Misc/externalTypeMapping.d.ts +2 -0
  155. package/dist/Misc/externalTypeMapping.d.ts.map +1 -1
  156. package/dist/Misc/externalTypeMapping.js +5 -1
  157. package/dist/Misc/externalTypeMapping.js.map +1 -1
  158. package/dist/Misc/file-write.d.ts +2 -0
  159. package/dist/Misc/file-write.d.ts.map +1 -1
  160. package/dist/Misc/file-write.js +5 -1
  161. package/dist/Misc/file-write.js.map +1 -1
  162. package/dist/Misc/graphql_server_codegen.d.ts +10 -0
  163. package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
  164. package/dist/Misc/graphql_server_codegen.js +46 -26
  165. package/dist/Misc/graphql_server_codegen.js.map +1 -1
  166. package/dist/Misc/remote_operations_codegen.d.ts +12 -0
  167. package/dist/Misc/remote_operations_codegen.d.ts.map +1 -1
  168. package/dist/Misc/remote_operations_codegen.js +21 -5
  169. package/dist/Misc/remote_operations_codegen.js.map +1 -1
  170. package/dist/Misc/runCommand.d.ts +6 -0
  171. package/dist/Misc/runCommand.d.ts.map +1 -1
  172. package/dist/Misc/runCommand.js +16 -4
  173. package/dist/Misc/runCommand.js.map +1 -1
  174. package/dist/Misc/schema-emit.d.ts +28 -0
  175. package/dist/Misc/schema-emit.d.ts.map +1 -1
  176. package/dist/Misc/schema-emit.js +75 -23
  177. package/dist/Misc/schema-emit.js.map +1 -1
  178. package/dist/Misc/sql_logging.d.ts +31 -9
  179. package/dist/Misc/sql_logging.d.ts.map +1 -1
  180. package/dist/Misc/sql_logging.js +95 -51
  181. package/dist/Misc/sql_logging.js.map +1 -1
  182. package/dist/Misc/sql_text.d.ts +4 -0
  183. package/dist/Misc/sql_text.d.ts.map +1 -1
  184. package/dist/Misc/sql_text.js +10 -2
  185. package/dist/Misc/sql_text.js.map +1 -1
  186. package/dist/Misc/status_logging.d.ts +36 -0
  187. package/dist/Misc/status_logging.d.ts.map +1 -1
  188. package/dist/Misc/status_logging.js +95 -23
  189. package/dist/Misc/status_logging.js.map +1 -1
  190. package/dist/Misc/system_integrity.js +4 -4
  191. package/dist/Misc/system_integrity.js.map +1 -1
  192. package/dist/Misc/temp_batch_file.d.ts +12 -0
  193. package/dist/Misc/temp_batch_file.d.ts.map +1 -1
  194. package/dist/Misc/temp_batch_file.js +35 -11
  195. package/dist/Misc/temp_batch_file.js.map +1 -1
  196. package/dist/Misc/util.d.ts +33 -0
  197. package/dist/Misc/util.d.ts.map +1 -1
  198. package/dist/Misc/util.js +53 -13
  199. package/dist/Misc/util.js.map +1 -1
  200. package/dist/index.d.ts +1 -1
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +1 -1
  203. package/dist/index.js.map +1 -1
  204. package/dist/plugins/index.d.ts +11 -1
  205. package/dist/plugins/index.d.ts.map +1 -1
  206. package/dist/plugins/index.js +9 -1
  207. package/dist/plugins/index.js.map +1 -1
  208. package/dist/runCodeGen.d.ts +6 -0
  209. package/dist/runCodeGen.d.ts.map +1 -1
  210. package/dist/runCodeGen.js +150 -138
  211. package/dist/runCodeGen.js.map +1 -1
  212. package/package.json +29 -29
@@ -1,54 +1,54 @@
1
1
  import { SQLServerDialect, PostgreSQLDialect } from '@memberjunction/sql-dialect';
2
2
  import { CodeGenDatabaseProvider } from './codeGenDatabaseProvider.js';
3
- import { analyzeQueryForMaterialization, detectAggregationKeyColumns, detectAdditiveMeasures, MATERIALIZATION_SURROGATE_COLUMN } from './materializationAnalysis.js';
4
- import { evaluateMaterializationDrift } from './materializationDrift.js';
5
- import { classifyQueryParameters, buildHeldValues } from './materializationParamClassifier.js';
6
- import { buildBroadRowFilterSQL } from './materializationBroadRender.js';
3
+ import { AnalyzeQueryForMaterialization, DetectAggregationKeyColumns, DetectAdditiveMeasures, MATERIALIZATION_SURROGATE_COLUMN } from './materializationAnalysis.js';
4
+ import { EvaluateMaterializationDrift } from './materializationDrift.js';
5
+ import { ClassifyQueryParameters, BuildHeldValues } from './materializationParamClassifier.js';
6
+ import { BuildBroadRowFilterSQL } from './materializationBroadRender.js';
7
7
  import { QueryParameterProcessor } from '@memberjunction/query-processor';
8
8
  // Side-effect import — registers `SQLServerCodeGenProvider` with `MJGlobal.ClassFactory`
9
9
  // under the `'sqlserver'` key via its `@RegisterClass` decorator. Without this import,
10
10
  // `ClassFactory.CreateInstance(CodeGenDatabaseProvider, 'sqlserver')` returns nothing
11
11
  // and the SS code path silently fails.
12
12
  import './providers/sqlserver/SQLServerCodeGenProvider.js';
13
- import { configInfo, currentWorkingDirectory, dbPlatform, getSettingValue, mj_core_schema, outputDir } from '../Config/config.js';
13
+ import { configInfo, currentWorkingDirectory, DbPlatform, GetSettingValue, MjCoreSchema, OutputDir } from '../Config/config.js';
14
14
  import { CodeNameFromString, EntityFieldInfo, EntityInfo, ExternalDataSourceReadRouter, ExtractActualDefaultValue, LogError, LogStatus, Metadata, RunQuerySQLFilterManager, SeverityType } from "@memberjunction/core";
15
15
  import { MJEntityFieldSchema } from "@memberjunction/core-entities";
16
- import { logError, logMessage, logStatus, startSpinner, updateSpinner, succeedSpinner } from "../Misc/status_logging.js";
16
+ import { logError, LogMessage, logStatus, StartSpinner, UpdateSpinner, SucceedSpinner } from "../Misc/status_logging.js";
17
17
  import { SQLUtilityBase } from "./sql.js";
18
- import { applyIncludeSchemaScope } from "./schema-scope.js";
19
- import { buildHealSchemaRoutineParams, getAuthoredExcludeSchemas, snapshotAuthoredExcludeSchemas } from "./heal-schema-params.js";
20
- import { AdvancedGeneration, isPlausibleEntityName } from "../Misc/advanced_generation.js";
18
+ import { ApplyIncludeSchemaScope } from "./schema-scope.js";
19
+ import { BuildHealSchemaRoutineParams, GetAuthoredExcludeSchemas, SnapshotAuthoredExcludeSchemas } from "./heal-schema-params.js";
20
+ import { AdvancedGeneration, IsPlausibleEntityName } from "../Misc/advanced_generation.js";
21
21
  import { CodeGenReporter } from "../Misc/codegen-reporter.js";
22
- import { applySearchableFieldsCap, defaultPredicateFor, entityLevelEnableBlockedReason, isNarrativeFieldName, MAX_SEARCHABLE_FIELDS_PER_ENTITY, NAME_LIKE_FIELD_NAMES, normalizePredicate, normalizeSmartFieldResultShape, } from "./search-guardrails.js";
23
- import { mapExternalNativeTypeToMJ } from "../Misc/externalTypeMapping.js";
22
+ import { ApplySearchableFieldsCap, DefaultPredicateFor, EntityLevelEnableBlockedReason, IsNarrativeFieldName, MAX_SEARCHABLE_FIELDS_PER_ENTITY, NAME_LIKE_FIELD_NAMES, NormalizePredicate, NormalizeSmartFieldResultShape, } from "./search-guardrails.js";
23
+ import { MapExternalNativeTypeToMJ } from "../Misc/externalTypeMapping.js";
24
24
  import { SQLParser } from "@memberjunction/sql-parser";
25
25
  import { createDisplayName, generatePluralName, MJGlobal, ResolveSingleEntityResourceTarget, stripTrailingChars, UUIDsEqual } from "@memberjunction/global";
26
26
  import { v4 as uuidv4, v5 as uuidv5 } from 'uuid';
27
27
  import * as fs from 'fs';
28
28
  import path from 'path';
29
- import { canonicalJSONStringify, deepEqualJSON } from "../Misc/util.js";
30
- import { trimTrailingStatementTerminators } from "../Misc/sql_text.js";
29
+ import { CanonicalJSONStringify, DeepEqualJSON } from "../Misc/util.js";
30
+ import { TrimTrailingStatementTerminators } from "../Misc/sql_text.js";
31
31
  import { SQLLogging } from "../Misc/sql_logging.js";
32
- import { computeFieldMetadataUpdate } from "./field-metadata-lock.js";
32
+ import { ComputeFieldMetadataUpdate } from "./field-metadata-lock.js";
33
33
  import { TRACKED_FIELD_COLUMNS, DISPLAYNAME_REOPEN_REASONS, TYPE_REOPEN_REASONS, diffEntityFieldSnapshots, } from './entity-field-change-tracking.js';
34
- export { TRACKED_FIELD_COLUMNS, DISPLAYNAME_REOPEN_REASONS, TYPE_REOPEN_REASONS, diffEntityFieldSnapshots, } from './entity-field-change-tracking.js';
34
+ export { TRACKED_FIELD_COLUMNS, DISPLAYNAME_REOPEN_REASONS, TYPE_REOPEN_REASONS, DiffEntityFieldSnapshots, diffEntityFieldSnapshots, } from './entity-field-change-tracking.js';
35
35
  export class ValidatorResult {
36
36
  constructor() {
37
- this.entityName = "";
38
- this.sourceCheckConstraint = "";
39
- this.functionText = "";
40
- this.functionName = "";
41
- this.functionDescription = "";
37
+ this.entityName = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
38
+ this.sourceCheckConstraint = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
39
+ this.functionText = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
40
+ this.functionName = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
41
+ this.functionDescription = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
42
42
  /**
43
43
  * The ID value in the Generated Codes entity that was created for this validator.
44
44
  */
45
- this.generatedCodeId = "";
45
+ this.generatedCodeId = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
46
46
  /**
47
47
  * The ID for the AI Model that was used to generate the code
48
48
  */
49
- this.aiModelID = "";
50
- this.wasGenerated = true;
51
- this.success = false;
49
+ this.aiModelID = ""; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
50
+ this.wasGenerated = true; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
51
+ this.success = false; // case-violation-ok-legacy-back-compat: object literals are assigned to this class, so an accessor stub changes what they must supply
52
52
  }
53
53
  }
54
54
  export class ManageMetadataBase {
@@ -98,7 +98,7 @@ export class ManageMetadataBase {
98
98
  */
99
99
  get dbProvider() {
100
100
  if (!this._dbProvider) {
101
- const platform = dbPlatform();
101
+ const platform = DbPlatform();
102
102
  const provider = MJGlobal.Instance.ClassFactory.CreateInstance(CodeGenDatabaseProvider, platform);
103
103
  // CreateInstance returns the abstract base class on a missed key (it
104
104
  // doesn't throw), so disambiguate by constructor identity: anything
@@ -191,7 +191,7 @@ export class ManageMetadataBase {
191
191
  */
192
192
  applyTimeEntityFieldSequenceSQL(entityID) {
193
193
  // COALESCE is valid on both platforms and is the form the docs and the gate describe.
194
- return `(SELECT COALESCE(MAX(${this.qi('Sequence')}), 0) + 1 FROM ${this.qs(mj_core_schema(), 'EntityField')} WHERE ${this.qi('EntityID')} = '${entityID}')`;
194
+ return `(SELECT COALESCE(MAX(${this.qi('Sequence')}), 0) + 1 FROM ${this.qs(MjCoreSchema(), 'EntityField')} WHERE ${this.qi('EntityID')} = '${entityID}')`;
195
195
  }
196
196
  /**
197
197
  * Wraps a SELECT query with a row limit.
@@ -213,7 +213,7 @@ export class ManageMetadataBase {
213
213
  * (avoids hardcoding runtime UUIDs that may differ on a fresh database).
214
214
  */
215
215
  entityIdSubquery(tableName, schemaName) {
216
- const schema = mj_core_schema();
216
+ const schema = MjCoreSchema();
217
217
  return `(${this.selectTop(1, 'ID', `FROM ${this.qs(schema, 'vwEntities')} WHERE BaseTable = '${tableName}' AND SchemaName = '${schemaName}'`)})`;
218
218
  }
219
219
  /**
@@ -263,22 +263,38 @@ export class ManageMetadataBase {
263
263
  /**
264
264
  * Globally scoped list of entities that have been created during the metadata management process.
265
265
  */
266
- static get newEntityList() {
266
+ static get NewEntityList() {
267
267
  return this._newEntityList;
268
268
  }
269
- static set newEntityList(value) {
269
+ static set NewEntityList(value) {
270
270
  this._newEntityList = value;
271
271
  }
272
+ /** @deprecated Use {@link NewEntityList}. */
273
+ static get newEntityList() {
274
+ return this.NewEntityList;
275
+ }
276
+ /** @deprecated Use {@link NewEntityList}. */
277
+ static set newEntityList(value) {
278
+ this.NewEntityList = value;
279
+ }
272
280
  static { this._modifiedEntityList = []; }
273
281
  /**
274
282
  * Globally scoped list of entities that have been modified during the metadata management process.
275
283
  */
276
- static get modifiedEntityList() {
284
+ static get ModifiedEntityList() {
277
285
  return this._modifiedEntityList;
278
286
  }
279
- static set modifiedEntityList(value) {
287
+ static set ModifiedEntityList(value) {
280
288
  this._modifiedEntityList = value;
281
289
  }
290
+ /** @deprecated Use {@link ModifiedEntityList}. */
291
+ static get modifiedEntityList() {
292
+ return this.ModifiedEntityList;
293
+ }
294
+ /** @deprecated Use {@link ModifiedEntityList}. */
295
+ static set modifiedEntityList(value) {
296
+ this.ModifiedEntityList = value;
297
+ }
282
298
  static { this._newFieldSet = new Set(); }
283
299
  static { this._changedFields = new Map(); }
284
300
  static { this._changedFieldReport = []; }
@@ -290,13 +306,17 @@ export class ManageMetadataBase {
290
306
  }
291
307
  return `${eid}:${fld}`;
292
308
  }
293
- static registerNewField(entityID, name) {
309
+ static RegisterNewField(entityID, name) {
294
310
  const key = this.fieldKey(entityID, name);
295
311
  if (key) {
296
312
  this._newFieldSet.add(key);
297
313
  }
298
314
  }
299
- static registerFieldChange(entityID, name, reasons) {
315
+ /** @deprecated Use {@link RegisterNewField}. */
316
+ static registerNewField(entityID, name) {
317
+ return this.RegisterNewField(entityID, name);
318
+ }
319
+ static RegisterFieldChange(entityID, name, reasons) {
300
320
  const key = this.fieldKey(entityID, name);
301
321
  if (key) {
302
322
  let set = this._changedFields.get(key);
@@ -309,17 +329,29 @@ export class ManageMetadataBase {
309
329
  }
310
330
  }
311
331
  }
312
- static isFieldNew(entityID, name) {
332
+ /** @deprecated Use {@link RegisterFieldChange}. */
333
+ static registerFieldChange(entityID, name, reasons) {
334
+ return this.RegisterFieldChange(entityID, name, reasons);
335
+ }
336
+ static IsFieldNew(entityID, name) {
313
337
  const key = this.fieldKey(entityID, name);
314
338
  return key ? this._newFieldSet.has(key) : false;
315
339
  }
316
- static fieldChangeReasons(entityID, name) {
340
+ /** @deprecated Use {@link IsFieldNew}. */
341
+ static isFieldNew(entityID, name) {
342
+ return this.IsFieldNew(entityID, name);
343
+ }
344
+ static FieldChangeReasons(entityID, name) {
317
345
  const key = this.fieldKey(entityID, name);
318
346
  const set = key ? this._changedFields.get(key) : undefined;
319
347
  return set ?? new Set();
320
348
  }
321
- static isDisplayNameReopened(entityID, name) {
322
- const reasons = this.fieldChangeReasons(entityID, name);
349
+ /** @deprecated Use {@link FieldChangeReasons}. */
350
+ static fieldChangeReasons(entityID, name) {
351
+ return this.FieldChangeReasons(entityID, name);
352
+ }
353
+ static IsDisplayNameReopened(entityID, name) {
354
+ const reasons = this.FieldChangeReasons(entityID, name);
323
355
  for (const r of reasons) {
324
356
  if (DISPLAYNAME_REOPEN_REASONS.has(r)) {
325
357
  return true;
@@ -327,8 +359,12 @@ export class ManageMetadataBase {
327
359
  }
328
360
  return false;
329
361
  }
330
- static isTypeReopened(entityID, name) {
331
- const reasons = this.fieldChangeReasons(entityID, name);
362
+ /** @deprecated Use {@link IsDisplayNameReopened}. */
363
+ static isDisplayNameReopened(entityID, name) {
364
+ return this.IsDisplayNameReopened(entityID, name);
365
+ }
366
+ static IsTypeReopened(entityID, name) {
367
+ const reasons = this.FieldChangeReasons(entityID, name);
332
368
  for (const r of reasons) {
333
369
  if (TYPE_REOPEN_REASONS.has(r)) {
334
370
  return true;
@@ -336,20 +372,40 @@ export class ManageMetadataBase {
336
372
  }
337
373
  return false;
338
374
  }
339
- static get newFieldCount() {
375
+ /** @deprecated Use {@link IsTypeReopened}. */
376
+ static isTypeReopened(entityID, name) {
377
+ return this.IsTypeReopened(entityID, name);
378
+ }
379
+ static get NewFieldCount() {
340
380
  return this._newFieldSet.size;
341
381
  }
342
- static get changedFieldCount() {
382
+ /** @deprecated Use {@link NewFieldCount}. */
383
+ static get newFieldCount() {
384
+ return this.NewFieldCount;
385
+ }
386
+ static get ChangedFieldCount() {
343
387
  return this._changedFields.size;
344
388
  }
345
- static get changedFieldReport() {
389
+ /** @deprecated Use {@link ChangedFieldCount}. */
390
+ static get changedFieldCount() {
391
+ return this.ChangedFieldCount;
392
+ }
393
+ static get ChangedFieldReport() {
346
394
  return this._changedFieldReport;
347
395
  }
348
- static clearFieldTracking() {
396
+ /** @deprecated Use {@link ChangedFieldReport}. */
397
+ static get changedFieldReport() {
398
+ return this.ChangedFieldReport;
399
+ }
400
+ static ClearFieldTracking() {
349
401
  this._newFieldSet.clear();
350
402
  this._changedFields.clear();
351
403
  this._changedFieldReport = [];
352
404
  }
405
+ /** @deprecated Use {@link ClearFieldTracking}. */
406
+ static clearFieldTracking() {
407
+ return this.ClearFieldTracking();
408
+ }
353
409
  /**
354
410
  * Takes a snapshot of EntityField rows from vwEntityFields for diffing.
355
411
  * Only queries columns in TRACKED_FIELD_COLUMNS (Sequence is excluded).
@@ -361,7 +417,7 @@ export class ManageMetadataBase {
361
417
  : '';
362
418
  const cols = TRACKED_FIELD_COLUMNS.map(c => this.qi(c)).join(', ');
363
419
  const sSQL = `SELECT ${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('Entity')} AS ${this.qi('EntityName')}, ${this.qi('Name')}, ${cols}
364
- FROM ${this.qs(mj_core_schema(), 'vwEntityFields')}
420
+ FROM ${this.qs(MjCoreSchema(), 'vwEntityFields')}
365
421
  ${filter}`;
366
422
  const result = await this.runQuery(pool, sSQL);
367
423
  const map = new Map();
@@ -391,9 +447,13 @@ export class ManageMetadataBase {
391
447
  * the dead class keeps a live `@RegisterClass` registration on disk. The old monolith could
392
448
  * not drift this way: it was rewritten in full every run.
393
449
  */
394
- static get deletedEntitySchemaList() {
450
+ static get DeletedEntitySchemaList() {
395
451
  return this._deletedEntitySchemaList;
396
452
  }
453
+ /** @deprecated Use {@link DeletedEntitySchemaList}. */
454
+ static get deletedEntitySchemaList() {
455
+ return this.DeletedEntitySchemaList;
456
+ }
397
457
  static { this._entitiesRequiringViewRegen = []; }
398
458
  /**
399
459
  * Entities that had late-phase changes requiring their base views to be
@@ -412,9 +472,13 @@ export class ManageMetadataBase {
412
472
  /**
413
473
  * Globally scoped list of validators that have been generated during the metadata management process.
414
474
  */
415
- static get generatedValidators() {
475
+ static get GeneratedValidators() {
416
476
  return this._generatedValidators;
417
477
  }
478
+ /** @deprecated Use {@link GeneratedValidators}. */
479
+ static get generatedValidators() {
480
+ return this.GeneratedValidators;
481
+ }
418
482
  static { this._softPKFKConfigCache = null; }
419
483
  static { this._softPKFKConfigPath = ''; }
420
484
  /**
@@ -429,16 +493,20 @@ export class ManageMetadataBase {
429
493
  * Deterministic + event-driven (no mtime/TOCTOU race). The CLI `Run()` path does not call this, so its
430
494
  * load-once-per-process behavior is unchanged.
431
495
  */
432
- static invalidateSoftPKFKConfigCache() {
496
+ static InvalidateSoftPKFKConfigCache() {
433
497
  ManageMetadataBase._softPKFKConfigCache = null;
434
498
  ManageMetadataBase._softPKFKConfigPath = '';
435
499
  }
500
+ /** @deprecated Use {@link InvalidateSoftPKFKConfigCache}. */
501
+ static invalidateSoftPKFKConfigCache() {
502
+ return this.InvalidateSoftPKFKConfigCache();
503
+ }
436
504
  /**
437
505
  * Loads and caches the soft PK/FK configuration from the additionalSchemaInfo file.
438
506
  * Cached per process to avoid repeated I/O within a CodeGen run; the in-process (RSU) path calls
439
507
  * {@link invalidateSoftPKFKConfigCache} at the start of each run so a rewritten file is picked up.
440
508
  */
441
- static getSoftPKFKConfig() {
509
+ static GetSoftPKFKConfig() {
442
510
  // Return cached config if path hasn't changed
443
511
  const configPath = configInfo.additionalSchemaInfo
444
512
  ? path.join(currentWorkingDirectory, configInfo.additionalSchemaInfo)
@@ -464,6 +532,10 @@ export class ManageMetadataBase {
464
532
  return null;
465
533
  }
466
534
  }
535
+ /** @deprecated Use {@link GetSoftPKFKConfig}. */
536
+ static getSoftPKFKConfig() {
537
+ return this.GetSoftPKFKConfig();
538
+ }
467
539
  /**
468
540
  * Extracts a flat array of table configs from the config file, handling both formats:
469
541
  * 1. Schema-as-key (template format): { "dbo": [{ "TableName": "Orders", ... }] }
@@ -673,7 +745,7 @@ export class ManageMetadataBase {
673
745
  const allOrganicKeys = this.extractOrganicKeysFromConfig(config);
674
746
  if (allOrganicKeys.length === 0)
675
747
  return { success: true, createdCount: 0, updatedCount: 0, failedCount: 0 };
676
- const schema = mj_core_schema();
748
+ const schema = MjCoreSchema();
677
749
  let createdCount = 0;
678
750
  let updatedCount = 0;
679
751
  let failedCount = 0;
@@ -709,7 +781,7 @@ export class ManageMetadataBase {
709
781
  // well as last — and the metadata DML logged just before it (entity-config UPDATEs, the
710
782
  // previous key's INSERTs) has no trailing GO. requiresOwnBatch puts the provider's
711
783
  // separator on both sides in the migration file ('' on PostgreSQL: nothing is added).
712
- await this.LogSQLAndExecute(pool, viewSQL, `Create transitive bridge view ${viewFullName} for organic key "${okConfig.Name}" on ${ownerEntityName}`, false, true, this.dbProvider.BatchSeparator, true);
784
+ await this.logSQLAndExecute(pool, viewSQL, `Create transitive bridge view ${viewFullName} for organic key "${okConfig.Name}" on ${ownerEntityName}`, false, true, this.dbProvider.BatchSeparator, true);
713
785
  // Auto-populate TransitiveObject from the view definition
714
786
  re.TransitiveObject = viewFullName;
715
787
  }
@@ -729,7 +801,7 @@ export class ManageMetadataBase {
729
801
  Sequence = ${okConfig.Sequence ?? 0},
730
802
  Status = 'Active'
731
803
  WHERE ID = '${organicKeyId}'`;
732
- await this.LogSQLAndExecute(pool, updateSQL, `Update organic key "${okConfig.Name}" on ${ownerEntityName}`);
804
+ await this.logSQLAndExecute(pool, updateSQL, `Update organic key "${okConfig.Name}" on ${ownerEntityName}`);
733
805
  updatedCount++;
734
806
  logStatus(` > Organic key: Updated "${okConfig.Name}" on ${ownerEntityName}`);
735
807
  }
@@ -739,7 +811,7 @@ export class ManageMetadataBase {
739
811
  const insertSQL = `INSERT INTO ${this.qs(schema, 'EntityOrganicKey')}
740
812
  (EntityID, Name, ${okConfig.Description ? 'Description, ' : ''}MatchFieldNames, NormalizationStrategy, ${okConfig.CustomNormalizationExpression ? 'CustomNormalizationExpression, ' : ''}Sequence, Status)
741
813
  VALUES (${ownerSubquery}, '${okConfig.Name}', ${okConfig.Description ? `'${okConfig.Description.replace(/'/g, "''")}', ` : ''}'${matchFieldNames}', '${okConfig.NormalizationStrategy || 'LowerCaseTrim'}', ${okConfig.CustomNormalizationExpression ? `'${okConfig.CustomNormalizationExpression.replace(/'/g, "''")}', ` : ''}${okConfig.Sequence ?? 0}, 'Active')`;
742
- await this.LogSQLAndExecute(pool, insertSQL, `Insert organic key "${okConfig.Name}" on ${ownerEntityName}`);
814
+ await this.logSQLAndExecute(pool, insertSQL, `Insert organic key "${okConfig.Name}" on ${ownerEntityName}`);
743
815
  createdCount++;
744
816
  logStatus(` > Organic key: Created "${okConfig.Name}" on ${ownerEntityName}`);
745
817
  // Fetch the new ID
@@ -769,7 +841,7 @@ export class ManageMetadataBase {
769
841
  DisplayLocation = '${reConfig.DisplayLocation || 'After Field Tabs'}',
770
842
  Sequence = ${reConfig.Sequence ?? 0}
771
843
  WHERE ID = '${relId}'`;
772
- await this.LogSQLAndExecute(pool, updateRelSQL, `Update organic key related entity: "${okConfig.Name}" → ${relEntityName}`);
844
+ await this.logSQLAndExecute(pool, updateRelSQL, `Update organic key related entity: "${okConfig.Name}" → ${relEntityName}`);
773
845
  }
774
846
  else {
775
847
  // Use subqueries for FK references so the logged SQL is portable across databases
@@ -789,7 +861,7 @@ export class ManageMetadataBase {
789
861
  ${reConfig.DisplayName ? `'${reConfig.DisplayName.replace(/'/g, "''")}'` : 'NULL'},
790
862
  '${reConfig.DisplayLocation || 'After Field Tabs'}',
791
863
  ${reConfig.Sequence ?? 0})`;
792
- await this.LogSQLAndExecute(pool, insertRelSQL, `Insert organic key related entity: "${okConfig.Name}" → ${relEntityName}`);
864
+ await this.logSQLAndExecute(pool, insertRelSQL, `Insert organic key related entity: "${okConfig.Name}" → ${relEntityName}`);
793
865
  }
794
866
  logStatus(` > Organic key "${okConfig.Name}": ${existingRel.recordset.length > 0 ? 'updated' : 'created'} → ${relEntityName} (${isDirect ? 'direct' : 'transitive'})`);
795
867
  }
@@ -812,7 +884,7 @@ export class ManageMetadataBase {
812
884
  */
813
885
  async findOrganicKeyEntity(pool, schemaName, tableName) {
814
886
  const result = await this.runQueryWithParams(pool, `
815
- ${this.selectTop(1, 'ID, Name', `FROM ${this.qs(mj_core_schema(), 'vwEntities')}
887
+ ${this.selectTop(1, 'ID, Name', `FROM ${this.qs(MjCoreSchema(), 'vwEntities')}
816
888
  WHERE (BaseTable = @TableName AND SchemaName = @SchemaName)
817
889
  OR Name = @TableName`, 'CASE WHEN BaseTable = @TableName AND SchemaName = @SchemaName THEN 0 ELSE 1 END')}
818
890
  `, { 'TableName': tableName, 'SchemaName': schemaName });
@@ -881,7 +953,7 @@ export class ManageMetadataBase {
881
953
  if (relationships.length === 0)
882
954
  return { success: true, updatedCount: 0 };
883
955
  let updatedCount = 0;
884
- const schema = mj_core_schema();
956
+ const schema = MjCoreSchema();
885
957
  for (const rel of relationships) {
886
958
  try {
887
959
  // Look up the parent entity — try by Name first, then by BaseTable within the given schema
@@ -925,7 +997,7 @@ export class ManageMetadataBase {
925
997
  SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()},
926
998
  ${this.qi('ParentID')} = '${parentId}'
927
999
  WHERE ${this.qi('ID')} = '${childId}'`;
928
- await this.LogSQLAndExecute(pool, updateSQL, `Set IS-A ParentID for "${childName}" → "${parentName}"`);
1000
+ await this.logSQLAndExecute(pool, updateSQL, `Set IS-A ParentID for "${childName}" → "${parentName}"`);
929
1001
  if (existingParentId) {
930
1002
  logStatus(` > IS-A: Updated "${childName}" ParentID from previous value to "${parentName}"`);
931
1003
  }
@@ -973,7 +1045,7 @@ export class ManageMetadataBase {
973
1045
  * so a broken declaration fails before it produces generated code.
974
1046
  */
975
1047
  async validateISARelationships(pool) {
976
- const schema = mj_core_schema();
1048
+ const schema = MjCoreSchema();
977
1049
  let errorCount = 0;
978
1050
  try {
979
1051
  const results = await this.runQuery(pool, this.buildISAValidationSQL(schema));
@@ -1064,7 +1136,7 @@ export class ManageMetadataBase {
1064
1136
  if (entityConfigs.length === 0)
1065
1137
  return { success: true, updatedCount: 0 };
1066
1138
  let updatedCount = 0;
1067
- const schema = mj_core_schema();
1139
+ const schema = MjCoreSchema();
1068
1140
  const reservedKeys = new Set(['BaseTable', 'SchemaName']);
1069
1141
  for (const ec of entityConfigs) {
1070
1142
  try {
@@ -1129,7 +1201,7 @@ export class ManageMetadataBase {
1129
1201
  const updateSQL = `UPDATE ${this.qs(schema, 'Entity')}
1130
1202
  SET ${setClauses.join(', ')}
1131
1203
  WHERE ${this.qi('ID')} = '${entityId}'`;
1132
- await this.LogSQLAndExecute(pool, updateSQL, `Update attributes on "${entityName}" from additionalSchemaInfo`);
1204
+ await this.logSQLAndExecute(pool, updateSQL, `Update attributes on "${entityName}" from additionalSchemaInfo`);
1133
1205
  const attrSummary = differingAttrs.map(a => a.summary).join(', ');
1134
1206
  logStatus(` > Entities config: Set ${attrSummary} on "${entityName}"`);
1135
1207
  updatedCount++;
@@ -1155,7 +1227,7 @@ export class ManageMetadataBase {
1155
1227
  if (virtualEntities.length === 0)
1156
1228
  return { success: true, createdCount: 0 };
1157
1229
  let createdCount = 0;
1158
- const schema = mj_core_schema();
1230
+ const schema = MjCoreSchema();
1159
1231
  for (const ve of virtualEntities) {
1160
1232
  const viewSchema = ve.SchemaName || schema;
1161
1233
  const viewName = ve.ViewName;
@@ -1231,7 +1303,7 @@ export class ManageMetadataBase {
1231
1303
  // Refresh so entity.Fields reflect this run's field sync before we snapshot the shape.
1232
1304
  const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
1233
1305
  await md.Refresh();
1234
- const coreSchema = mj_core_schema();
1306
+ const coreSchema = MjCoreSchema();
1235
1307
  const esc = (s) => s.replace(/'/g, "''");
1236
1308
  const lit = (s) => (s ? `'${esc(s)}'` : 'NULL');
1237
1309
  let processedCount = 0;
@@ -1294,8 +1366,8 @@ export class ManageMetadataBase {
1294
1366
  // includeBatchSeparator: each is a single GO-free batch (executed via ds.query), but the
1295
1367
  // migration file needs a GO between statements for Flyway/sqlcmd. Pass the provider's
1296
1368
  // separator, not the 'GO' default: on PostgreSQL it is '' and a literal GO breaks replay.
1297
- await this.LogSQLAndExecute(pool, tableSQL, `Create materialized table for base-view materialization of entity ${entity.Name}`, false, true, this.dbProvider.BatchSeparator);
1298
- await this.LogSQLAndExecute(pool, viewSQL, `Create wrapper view for base-view materialization of entity ${entity.Name}`, false, true, this.dbProvider.BatchSeparator);
1369
+ await this.logSQLAndExecute(pool, tableSQL, `Create materialized table for base-view materialization of entity ${entity.Name}`, false, true, this.dbProvider.BatchSeparator);
1370
+ await this.logSQLAndExecute(pool, viewSQL, `Create wrapper view for base-view materialization of entity ${entity.Name}`, false, true, this.dbProvider.BatchSeparator);
1299
1371
  // 4) Upsert the MJ: Materialized Results row, keyed on (SourceType, SourceEntityID).
1300
1372
  const existing = await this.runQueryWithParams(pool, `SELECT ID FROM ${this.qs(coreSchema, 'MaterializedResult')} WHERE SourceType = @SourceType AND SourceEntityID = @EntityID`, { SourceType: 'EntityBaseView', EntityID: entity.ID });
1301
1373
  if (existing.recordset.length > 0) {
@@ -1303,7 +1375,7 @@ export class ManageMetadataBase {
1303
1375
  SET SchemaName='${esc(matSchema)}', TableName='${esc(tableName)}', ViewName='${esc(viewName)}',
1304
1376
  RefreshSchedule=${lit(decl.RefreshSchedule)}, IntendedWorkload=${lit(decl.IntendedWorkload)}
1305
1377
  WHERE ID='${existing.recordset[0].ID}'`;
1306
- await this.LogSQLAndExecute(pool, sqlUpd, `Update MJ: Materialized Results for base-view materialization of ${entity.Name}`);
1378
+ await this.logSQLAndExecute(pool, sqlUpd, `Update MJ: Materialized Results for base-view materialization of ${entity.Name}`);
1307
1379
  }
1308
1380
  else {
1309
1381
  const newId = this.createNewUUID();
@@ -1315,7 +1387,7 @@ export class ManageMetadataBase {
1315
1387
  VALUES ( '${newId}', 'EntityBaseView', '${entity.ID}', '${esc(matSchema)}', '${esc(tableName)}', '${esc(viewName)}',
1316
1388
  'None', 'FullRebuild', ${lit(decl.RefreshSchedule)}, 'Building', ${lit(decl.IntendedWorkload)},
1317
1389
  ${this.utcNow()}, ${this.utcNow()} )`;
1318
- await this.LogSQLAndExecute(pool, sqlIns, `Insert MJ: Materialized Results for base-view materialization of ${entity.Name}`);
1390
+ await this.logSQLAndExecute(pool, sqlIns, `Insert MJ: Materialized Results for base-view materialization of ${entity.Name}`);
1319
1391
  }
1320
1392
  processedCount++;
1321
1393
  logStatus(` > Base-view materialization ready for entity "${entity.Name}" → [${matSchema}].[${viewName}]`);
@@ -1329,7 +1401,7 @@ export class ManageMetadataBase {
1329
1401
  * fields, or a precise refusal (per-parameter reasons) so non-qualifying queries are skipped loudly.
1330
1402
  */
1331
1403
  async classifyParameterizedQueryForMaterialization(pool, queryId, queryName, outputColumns, currentUser) {
1332
- const coreSchema = mj_core_schema();
1404
+ const coreSchema = MjCoreSchema();
1333
1405
  const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
1334
1406
  // Load the query template + parameter definitions (typed entities for the renderer).
1335
1407
  const qRes = await this.runQueryWithParams(pool, `SELECT SQL, UsesTemplate FROM ${this.qs(coreSchema, 'Query')} WHERE ID = @QID`, { QID: queryId });
@@ -1361,31 +1433,31 @@ export class ManageMetadataBase {
1361
1433
  // Phase 2 is shipped: enable Bucket-1 row-filter broad materialization. The provider now auto-injects
1362
1434
  // the read-time predicate from the persisted ReadFilterSpec (bound params), so a RowFilterBroad
1363
1435
  // materialization is safe to mint. `allowRowFilterBroad` remains the build-level kill switch.
1364
- const classification = classifyQueryParameters({ queryName, params: paramDefs, outputColumns, dialect, render, allowRowFilterBroad: true });
1365
- if (!classification.qualification.qualifies) {
1366
- const detail = classification.perParam.map((pp) => `${pp.name}: ${pp.verdict.reason}`).join('; ');
1367
- return { qualifies: false, reason: `${classification.qualification.reason ?? 'parameters not materializable'}${detail ? ` — [${detail}]` : ''}` };
1436
+ const classification = ClassifyQueryParameters({ queryName, params: paramDefs, outputColumns, dialect, render, allowRowFilterBroad: true });
1437
+ if (!classification.Qualification.qualifies) {
1438
+ const detail = classification.PerParam.map((pp) => `${pp.name}: ${pp.verdict.reason}`).join('; ');
1439
+ return { qualifies: false, reason: `${classification.Qualification.reason ?? 'parameters not materializable'}${detail ? ` — [${detail}]` : ''}` };
1368
1440
  }
1369
- if (classification.qualification.paramMode !== 'RowFilterBroad') {
1370
- return { qualifies: false, reason: `parameterization mode '${classification.qualification.paramMode}' is not supported for materialization in v1 (only RowFilterBroad)` };
1441
+ if (classification.Qualification.paramMode !== 'RowFilterBroad') {
1442
+ return { qualifies: false, reason: `parameterization mode '${classification.Qualification.paramMode}' is not supported for materialization in v1 (only RowFilterBroad)` };
1371
1443
  }
1372
1444
  // Build the broad source SQL: render a concrete instance (held values) then strip the row-filter predicates.
1373
1445
  let renderedHeld;
1374
1446
  try {
1375
- renderedHeld = render(buildHeldValues(paramDefs));
1447
+ renderedHeld = render(BuildHeldValues(paramDefs));
1376
1448
  }
1377
1449
  catch (e) {
1378
1450
  return { qualifies: false, reason: `could not render the query to build broad SQL: ${e instanceof Error ? e.message : String(e)}` };
1379
1451
  }
1380
- const expectedRemovals = classification.qualification.rowFilterColumns.length;
1381
- const broad = buildBroadRowFilterSQL(renderedHeld, classification.qualification.rowFilterColumns, dialect, expectedRemovals);
1382
- if (broad.removedCount === 0) {
1383
- return { qualifies: false, reason: `expected to strip row-filter predicate(s) on [${classification.qualification.rowFilterColumns.join(', ')}] but none were removed from the rendered SQL — refusing to avoid a wrongly-filtered materialization` };
1452
+ const expectedRemovals = classification.Qualification.rowFilterColumns.length;
1453
+ const broad = BuildBroadRowFilterSQL(renderedHeld, classification.Qualification.rowFilterColumns, dialect, expectedRemovals);
1454
+ if (broad.RemovedCount === 0) {
1455
+ return { qualifies: false, reason: `expected to strip row-filter predicate(s) on [${classification.Qualification.rowFilterColumns.join(', ')}] but none were removed from the rendered SQL — refusing to avoid a wrongly-filtered materialization` };
1384
1456
  }
1385
- if (broad.ambiguous) {
1386
- return { qualifies: false, reason: `expected to strip exactly ${expectedRemovals} row-filter parameter predicate(s) on [${classification.qualification.rowFilterColumns.join(', ')}] but matched ${broad.removedCount} — the parameter predicate(s) cannot be cleanly isolated from other static or same-named predicates on those columns, so a broad materialization would include or exclude rows the live query never would. Refusing (query stays live-only).` };
1457
+ if (broad.Ambiguous) {
1458
+ return { qualifies: false, reason: `expected to strip exactly ${expectedRemovals} row-filter parameter predicate(s) on [${classification.Qualification.rowFilterColumns.join(', ')}] but matched ${broad.RemovedCount} — the parameter predicate(s) cannot be cleanly isolated from other static or same-named predicates on those columns, so a broad materialization would include or exclude rows the live query never would. Refusing (query stays live-only).` };
1387
1459
  }
1388
- return { qualifies: true, rowFilterColumns: classification.qualification.rowFilterColumns, broadSQL: broad.sql, readFilterSpec: classification.qualification.readFilterSpec };
1460
+ return { qualifies: true, rowFilterColumns: classification.Qualification.rowFilterColumns, broadSQL: broad.Sql, readFilterSpec: classification.Qualification.readFilterSpec };
1389
1461
  }
1390
1462
  /**
1391
1463
  * Processes query materialization (CodeGen materialization phase, sub-step C — plan §4.2).
@@ -1404,7 +1476,7 @@ export class ManageMetadataBase {
1404
1476
  * Parameterized queries are skipped with a log (deferred to Phase 2).
1405
1477
  */
1406
1478
  async processQueryMaterializations(pool, currentUser) {
1407
- const coreSchema = mj_core_schema();
1479
+ const coreSchema = MjCoreSchema();
1408
1480
  // Gate the whole method on the IsMaterialized column existing — the flagged-query SELECT below filters on it,
1409
1481
  // and it (like the MaterializedResult table) is added by the materialization Foundation migration. On a DB
1410
1482
  // where that migration hasn't run yet the SELECT would throw and abort the entire codegen pass; with no such
@@ -1468,7 +1540,7 @@ export class ManageMetadataBase {
1468
1540
  // Detected BEFORE the shape analysis so the surrogate column can be typed for the keyed case.
1469
1541
  const detectSQL = paramMode === 'RowFilterBroad' ? (broadSQL ?? '') : querySQL;
1470
1542
  const matDialect = this.dbProvider.PlatformKey === 'postgresql' ? new PostgreSQLDialect() : new SQLServerDialect();
1471
- const detectedKeyColumns = detectSQL ? detectAggregationKeyColumns({ sql: detectSQL, dialect: matDialect, fields }) : null;
1543
+ const detectedKeyColumns = detectSQL ? DetectAggregationKeyColumns({ sql: detectSQL, dialect: matDialect, fields }) : null;
1472
1544
  // An EXTERNAL query refreshes via rebuildFromExternalQuery, which fetches all rows through the EDS
1473
1545
  // driver and writes a SYNTHETIC row-index surrogate — it cannot compute the per-group combined-key
1474
1546
  // hash and ignores KeyColumns entirely. So minting a keyed (hash-surrogate) entity for an external
@@ -1494,7 +1566,7 @@ export class ManageMetadataBase {
1494
1566
  // The materialized table's column shape is the query's output columns — independent of
1495
1567
  // parameterization (a row-filter query is materialized broad over the same columns). Pass
1496
1568
  // isParameterized:false so the analyzer yields the shape; the param gate above already ran.
1497
- const analysis = analyzeQueryForMaterialization({ queryName, isParameterized: false, fields, surrogateSQLType });
1569
+ const analysis = AnalyzeQueryForMaterialization({ queryName, isParameterized: false, fields, surrogateSQLType });
1498
1570
  if (!analysis.qualifies) {
1499
1571
  logStatus(` > Skipping materialization of query "${queryName}": ${analysis.reason}`);
1500
1572
  continue;
@@ -1512,7 +1584,7 @@ export class ManageMetadataBase {
1512
1584
  const isKeyedSingleSource = isKeyed && !!detectSQL && SQLParser.ExtractTableRefs(detectSQL, matDialect).length === 1;
1513
1585
  const refreshStrategy = !isKeyedSingleSource
1514
1586
  ? 'FullRebuild'
1515
- : detectAdditiveMeasures(detectSQL)
1587
+ : DetectAdditiveMeasures(detectSQL)
1516
1588
  ? 'Incremental'
1517
1589
  : 'DirtyGroupRecompute';
1518
1590
  // RLS downgrade gate (§6.2, ships day one) — a materialized query entity does NOT inherit its
@@ -1551,10 +1623,10 @@ export class ManageMetadataBase {
1551
1623
  logError(` > REFUSING to materialize query "${queryName}": its derived table [${coreSchema}].[${tableName}] (from CodeName "${codeName}") is already owned by a different materialization (SourceType=${foreignOwner.SourceType}, QueryID=${foreignOwner.QueryID ?? 'n/a'}). Two sources whose names normalize to the same CodeName would clobber each other's snapshot — rename one so their CodeNames differ. Skipping.`);
1552
1624
  continue;
1553
1625
  }
1554
- const tableSQL = this.dbProvider.generateMaterializedTableSQL(coreSchema, tableName, analysis.columns);
1626
+ const tableSQL = this.dbProvider.generateMaterializedTableSQL(coreSchema, tableName, analysis.Columns);
1555
1627
  const viewSQL = this.dbProvider.generateMaterializedWrapperViewSQL(coreSchema, viewName, tableName);
1556
- await this.LogSQLAndExecute(pool, tableSQL, `Create materialized table for query "${queryName}"`, false, true, this.dbProvider.BatchSeparator);
1557
- await this.LogSQLAndExecute(pool, viewSQL, `Create wrapper view for query "${queryName}"`, false, true, this.dbProvider.BatchSeparator);
1628
+ await this.logSQLAndExecute(pool, tableSQL, `Create materialized table for query "${queryName}"`, false, true, this.dbProvider.BatchSeparator);
1629
+ await this.logSQLAndExecute(pool, viewSQL, `Create wrapper view for query "${queryName}"`, false, true, this.dbProvider.BatchSeparator);
1558
1630
  // 3) Mint the read-only Virtual Entity over the wrapper view (idempotent by view).
1559
1631
  const existingVE = await this.runQueryWithParams(pool, `SELECT ID FROM ${this.qs(coreSchema, 'vwEntities')} WHERE BaseView = @V AND SchemaName = @S`, { V: viewName, S: coreSchema });
1560
1632
  let generatedEntityId = existingVE.recordset[0]?.ID;
@@ -1564,7 +1636,7 @@ export class ManageMetadataBase {
1564
1636
  Name: queryName,
1565
1637
  BaseView: viewName,
1566
1638
  SchemaName: coreSchema,
1567
- PrimaryKeyFieldName: analysis.surrogateColumnName,
1639
+ PrimaryKeyFieldName: analysis.SurrogateColumnName,
1568
1640
  Description: `Materialized result of query "${queryName}".`,
1569
1641
  });
1570
1642
  generatedEntityId = createRes.recordset?.[0]?.[''] || createRes.recordset?.[0]?.ID || createRes.recordset?.[0]?.Column0;
@@ -1603,7 +1675,7 @@ export class ManageMetadataBase {
1603
1675
  SET GeneratedEntityID=${idLit(generatedEntityId)}, SchemaName='${esc(coreSchema)}', TableName='${esc(tableName)}', ViewName='${esc(viewName)}',
1604
1676
  ${this.qi('ParamMode')}='${paramMode}', ${this.qi('RowFilterColumns')}=${rowFilterColumnsLit}, ${this.qi('BroadSQL')}=${broadSQLLit}, ${this.qi('ReadFilterSpec')}=${readFilterSpecLit}, ${this.qi('KeyColumns')}=${keyColumnsLit}, ${this.qi('RefreshStrategy')}='${refreshStrategy}'
1605
1677
  WHERE ID='${matResultId}'`;
1606
- await this.LogSQLAndExecute(pool, sqlUpd, `Update MJ: Materialized Results for query "${queryName}"`);
1678
+ await this.logSQLAndExecute(pool, sqlUpd, `Update MJ: Materialized Results for query "${queryName}"`);
1607
1679
  }
1608
1680
  else {
1609
1681
  matResultId = this.createNewUUID();
@@ -1613,13 +1685,13 @@ export class ManageMetadataBase {
1613
1685
  ${c('ParamMode')}, ${c('RowFilterColumns')}, ${c('BroadSQL')}, ${c('ReadFilterSpec')}, ${c('KeyColumns')}, ${c('RefreshStrategy')}, ${c('Status')}, ${c('__mj_CreatedAt')}, ${c('__mj_UpdatedAt')} )
1614
1686
  VALUES ( '${matResultId}', 'Query', ${idLit(generatedEntityId)}, '${esc(coreSchema)}', '${esc(tableName)}', '${esc(viewName)}',
1615
1687
  '${paramMode}', ${rowFilterColumnsLit}, ${broadSQLLit}, ${readFilterSpecLit}, ${keyColumnsLit}, '${refreshStrategy}', 'Building', ${this.utcNow()}, ${this.utcNow()} )`;
1616
- await this.LogSQLAndExecute(pool, sqlIns, `Insert MJ: Materialized Results for query "${queryName}"`);
1688
+ await this.logSQLAndExecute(pool, sqlIns, `Insert MJ: Materialized Results for query "${queryName}"`);
1617
1689
  // Link the new materialization to its source Query via the join table.
1618
1690
  const joinId = this.createNewUUID();
1619
1691
  const sqlJoinIns = `INSERT INTO ${this.qs(coreSchema, 'MaterializedResultQuery')} (
1620
1692
  ${c('ID')}, ${c('MaterializedResultID')}, ${c('QueryID')}, ${c('__mj_CreatedAt')}, ${c('__mj_UpdatedAt')} )
1621
1693
  VALUES ( '${joinId}', '${matResultId}', '${queryId}', ${this.utcNow()}, ${this.utcNow()} )`;
1622
- await this.LogSQLAndExecute(pool, sqlJoinIns, `Link MJ: Materialized Results to Query "${queryName}" via join row`);
1694
+ await this.logSQLAndExecute(pool, sqlJoinIns, `Link MJ: Materialized Results to Query "${queryName}" via join row`);
1623
1695
  }
1624
1696
  processedCount++;
1625
1697
  }
@@ -1636,7 +1708,7 @@ export class ManageMetadataBase {
1636
1708
  * auto-rebuilds. Already-held (`DriftHold`) and `Disabled` rows are skipped.
1637
1709
  */
1638
1710
  async detectMaterializationDrift(pool) {
1639
- const coreSchema = mj_core_schema();
1711
+ const coreSchema = MjCoreSchema();
1640
1712
  // Gate on MaterializedResult existing — the SELECT below reads it. Absent (materialization migration not yet
1641
1713
  // applied on this DB) ⇒ no materializations exist to check for drift; skip cleanly rather than throwing and
1642
1714
  // aborting the codegen pass.
@@ -1741,14 +1813,14 @@ export class ManageMetadataBase {
1741
1813
  // would only stop FUTURE refreshes while leaving the already-populated rows queryable via raw SQL
1742
1814
  // over the wrapper view. Emptying (not dropping) removes the leaked data without breaking any object
1743
1815
  // dependency; the wrapper view remains but returns nothing. Then DriftHold so refresh never refills it.
1744
- await this.LogSQLAndExecute(pool, `DELETE FROM ${this.qs(r.SchemaName, r.TableName)}`, `Empty external RLS base-view mirror "${r.TableName}" (leak guard — mirror exposed RLS-refused rows)`);
1745
- await this.LogSQLAndExecute(pool, `UPDATE ${this.qs(coreSchema, 'MaterializedResult')} SET ${this.qi('Status')}='DriftHold' WHERE ID='${r.ID}'`, `Flag external RLS base-view materialization "${r.TableName}" as DriftHold (leak guard)`);
1816
+ await this.logSQLAndExecute(pool, `DELETE FROM ${this.qs(r.SchemaName, r.TableName)}`, `Empty external RLS base-view mirror "${r.TableName}" (leak guard — mirror exposed RLS-refused rows)`);
1817
+ await this.logSQLAndExecute(pool, `UPDATE ${this.qs(coreSchema, 'MaterializedResult')} SET ${this.qi('Status')}='DriftHold' WHERE ID='${r.ID}'`, `Flag external RLS base-view materialization "${r.TableName}" as DriftHold (leak guard)`);
1746
1818
  logError(` > RLS LEAK GUARD: base-view materialization "${r.TableName}" mirrors an EXTERNAL row-restricted entity ("${bvEntity.Name}" — role RLS and/or an API-key row filter) → mirror EMPTIED + DriftHold (refresh stopped). It exposed rows the live path refuses under RLS.`);
1747
1819
  return true;
1748
1820
  }
1749
1821
  }
1750
1822
  const facts = await this.gatherDriftFacts(pool, md, r, coreSchema);
1751
- const verdict = evaluateMaterializationDrift(facts);
1823
+ const verdict = EvaluateMaterializationDrift(facts);
1752
1824
  if (verdict.drift) {
1753
1825
  // Fail-closed on ANY hold of a QUERY materialization: once held, the row drops out of
1754
1826
  // detectMaterializationDrift's `Status NOT IN ('DriftHold', ...)` scan, so the C1 RLS re-check and the
@@ -1760,7 +1832,7 @@ export class ManageMetadataBase {
1760
1832
  if (r.SourceType === 'Query' && r.GeneratedEntityID) {
1761
1833
  await this.revokeMaterializedEntityReadAccess(pool, r.GeneratedEntityID, r.TableName, `drift hold — ${verdict.reason ?? 'shape/provenance drift'}`);
1762
1834
  }
1763
- await this.LogSQLAndExecute(pool, `UPDATE ${this.qs(coreSchema, 'MaterializedResult')} SET ${this.qi('Status')}='DriftHold' WHERE ID='${r.ID}'`, `Flag materialization "${r.TableName}" as DriftHold`);
1835
+ await this.logSQLAndExecute(pool, `UPDATE ${this.qs(coreSchema, 'MaterializedResult')} SET ${this.qi('Status')}='DriftHold' WHERE ID='${r.ID}'`, `Flag materialization "${r.TableName}" as DriftHold`);
1764
1836
  logStatus(` > DRIFT: materialization "${r.TableName}" → DriftHold — ${verdict.reason}`);
1765
1837
  return true;
1766
1838
  }
@@ -1794,7 +1866,7 @@ export class ManageMetadataBase {
1794
1866
  if (r.GeneratedEntityID) {
1795
1867
  await this.revokeMaterializedEntityReadAccess(pool, r.GeneratedEntityID, r.TableName, reason);
1796
1868
  }
1797
- await this.LogSQLAndExecute(pool, `UPDATE ${this.qs(coreSchema, 'MaterializedResult')} SET ${this.qi('Status')}='DriftHold' WHERE ID='${r.ID}'`, `Flag materialization "${r.TableName}" as DriftHold (${label.toLowerCase()})`);
1869
+ await this.logSQLAndExecute(pool, `UPDATE ${this.qs(coreSchema, 'MaterializedResult')} SET ${this.qi('Status')}='DriftHold' WHERE ID='${r.ID}'`, `Flag materialization "${r.TableName}" as DriftHold (${label.toLowerCase()})`);
1798
1870
  logError(` > ${label}: materialization "${r.TableName}" → read access revoked + DriftHold — ${reason}`);
1799
1871
  }
1800
1872
  /** Gathers the drift-relevant existence facts for one materialization against current metadata. */
@@ -1905,7 +1977,7 @@ export class ManageMetadataBase {
1905
1977
  * @param pool - the ConnectionPool object to use for querying and updating the database
1906
1978
  * @returns
1907
1979
  */
1908
- async manageMetadata(pool, currentUser) {
1980
+ async ManageMetadata(pool, currentUser) {
1909
1981
  const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
1910
1982
  // Auto-exclude platform-specific system schemas.
1911
1983
  // We mutate configInfo.excludeSchemas directly so that all downstream code
@@ -1926,7 +1998,7 @@ export class ManageMetadataBase {
1926
1998
  // machinery — when the provider supplies DDL (PostgreSQL), we install it
1927
1999
  // idempotently here rather than depending on migrations to have shipped
1928
2000
  // it. Nothing below can work without them, so a failure here is fatal.
1929
- const supportObjectsSQL = this.dbProvider.getMetadataSupportObjectsSQL(mj_core_schema());
2001
+ const supportObjectsSQL = this.dbProvider.getMetadataSupportObjectsSQL(MjCoreSchema());
1930
2002
  if (supportObjectsSQL) {
1931
2003
  try {
1932
2004
  await pool.query(supportObjectsSQL);
@@ -1940,7 +2012,7 @@ export class ManageMetadataBase {
1940
2012
  // Authored exclude list (sys, staging, …) must be captured BEFORE includeSchemas is
1941
2013
  // compiled into excludeSchemas. Heal EXEC statements use that original list plus
1942
2014
  // @IncludedSchemaNames — never the sibling snapshot of this machine's database.
1943
- snapshotAuthoredExcludeSchemas(configInfo.excludeSchemas);
2015
+ SnapshotAuthoredExcludeSchemas(configInfo.excludeSchemas);
1944
2016
  // Resolve the opt-in `includeSchemas` positive scope into excludeSchemas, BEFORE the exclude
1945
2017
  // snapshot below and before createNewEntities() runs. The universe is queried from the DATABASE
1946
2018
  // rather than taken from loaded metadata on purpose: createNewEntities() discovers tables with no
@@ -1950,12 +2022,12 @@ export class ManageMetadataBase {
1950
2022
  // every run after, orphaning its entity records). No-op when includeSchemas is unset.
1951
2023
  if (configInfo.includeSchemas && configInfo.includeSchemas.length > 0) {
1952
2024
  try {
1953
- const schemaSQL = `SELECT DISTINCT ${this.qi('SchemaName')} FROM ${this.qs(mj_core_schema(), 'vwSQLTablesAndEntities')}`;
2025
+ const schemaSQL = `SELECT DISTINCT ${this.qi('SchemaName')} FROM ${this.qs(MjCoreSchema(), 'vwSQLTablesAndEntities')}`;
1954
2026
  const schemaResult = await this.runQuery(pool, schemaSQL);
1955
2027
  const allSchemas = (schemaResult.recordset ?? [])
1956
2028
  .map((r) => r.SchemaName)
1957
2029
  .filter((s) => !!s);
1958
- const newlyExcluded = applyIncludeSchemaScope(allSchemas, configInfo);
2030
+ const newlyExcluded = ApplyIncludeSchemaScope(allSchemas, configInfo);
1959
2031
  logStatus(` Applied includeSchemas scope [${configInfo.includeSchemas.join(', ')}] — excluded ${newlyExcluded.length} other schema(s) present in the database`);
1960
2032
  }
1961
2033
  catch (e) {
@@ -2005,12 +2077,12 @@ export class ManageMetadataBase {
2005
2077
  start = new Date();
2006
2078
  logStatus(' Recompiling base views...');
2007
2079
  const sqlUtility = MJGlobal.Instance.ClassFactory.CreateInstance(SQLUtilityBase);
2008
- const adminSchema = getSettingValue('mj_core_schema', '__mj');
2009
- const schemasToExclude = getSettingValue('recompile_mj_views', true)
2080
+ const adminSchema = GetSettingValue('mj_core_schema', '__mj');
2081
+ const schemasToExclude = GetSettingValue('recompile_mj_views', true)
2010
2082
  ? excludeSchemas.filter((s) => s !== adminSchema)
2011
2083
  : excludeSchemas;
2012
2084
  if (!await sqlUtility.recompileAllBaseViews(pool, schemasToExclude, true, ManageMetadataBase._newEntityList /*exclude the newly created entities from the above step the first time we run as those views don't exist yet*/)) {
2013
- logMessage(' Warning: Non-Fatal error recompiling base views', SeverityType.Warning, false);
2085
+ LogMessage(' Warning: Non-Fatal error recompiling base views', SeverityType.Warning, false);
2014
2086
  // many times the former versions of base views will NOT succesfully recompile, so don't consider that scenario to be a
2015
2087
  // failure for this entire function
2016
2088
  }
@@ -2021,7 +2093,7 @@ export class ManageMetadataBase {
2021
2093
  // Also skip deleting unneeded fields on this first pass — base views haven't been regenerated yet,
2022
2094
  // so virtual fields (which come from view JOINs) would be incorrectly identified as orphaned and deleted.
2023
2095
  // Deletion runs on the second pass (in sql_codegen.ts) after views are current.
2024
- if (!await this.manageEntityFields(pool, excludeSchemas, false, false, currentUser, true, true)) {
2096
+ if (!await this.ManageEntityFields(pool, excludeSchemas, false, false, currentUser, true, true)) {
2025
2097
  logError(' Error managing entity fields');
2026
2098
  bSuccess = false;
2027
2099
  }
@@ -2134,6 +2206,10 @@ export class ManageMetadataBase {
2134
2206
  await this.checkEntityFieldSequenceIntegrity(pool, excludeSchemas);
2135
2207
  return bSuccess;
2136
2208
  }
2209
+ /** @deprecated Use {@link ManageMetadata}. */
2210
+ async manageMetadata(pool, currentUser) {
2211
+ return this.ManageMetadata(pool, currentUser);
2212
+ }
2137
2213
  /**
2138
2214
  * INTEGRITY CHECK — in a well-formed entity every base (non-virtual) field sequences BEFORE the
2139
2215
  * virtual/related fields, so the EntityField order matches the base view's `SELECT [base].*, <joins>`
@@ -2147,10 +2223,10 @@ export class ManageMetadataBase {
2147
2223
  async checkEntityFieldSequenceIntegrity(pool, excludeSchemas) {
2148
2224
  try {
2149
2225
  const excl = excludeSchemas.length > 0
2150
- ? `WHERE ${this.qi('EntityID')} NOT IN (SELECT ${this.qi('ID')} FROM ${this.qs(mj_core_schema(), 'Entity')} WHERE ${this.qi('SchemaName')} IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))`
2226
+ ? `WHERE ${this.qi('EntityID')} NOT IN (SELECT ${this.qi('ID')} FROM ${this.qs(MjCoreSchema(), 'Entity')} WHERE ${this.qi('SchemaName')} IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))`
2151
2227
  : '';
2152
2228
  const sSQL = `SELECT ${this.qi('EntityID')}, ${this.qi('Entity')}, ${this.qi('Name')}, ${this.qi('Sequence')}, ${this.qi('IsVirtual')}
2153
- FROM ${this.qs(mj_core_schema(), 'vwEntityFields')}
2229
+ FROM ${this.qs(MjCoreSchema(), 'vwEntityFields')}
2154
2230
  ${excl}
2155
2231
  ORDER BY ${this.qi('EntityID')}, ${this.qi('Sequence')}, ${this.qi('Name')}`;
2156
2232
  const result = await this.runQuery(pool, sSQL);
@@ -2202,7 +2278,7 @@ export class ManageMetadataBase {
2202
2278
  // virtual entities are records defined in the entity metadata and do NOT define a distinct base table
2203
2279
  // but they do specify a base view. We DO NOT generate a base view for a virtual entity, we simply use it to figure
2204
2280
  // out the fields that should be in the entity definition and add/update/delete the entity definition to match what's in the view when this runs
2205
- const sql = `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntities')} WHERE VirtualEntity = ${this.boolLit(true)}`;
2281
+ const sql = `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntities')} WHERE VirtualEntity = ${this.boolLit(true)}`;
2206
2282
  const virtualEntitiesResult = await this.runQuery(pool, sql);
2207
2283
  const virtualEntities = virtualEntitiesResult.recordset;
2208
2284
  let anyUpdates = false;
@@ -2262,7 +2338,7 @@ export class ManageMetadataBase {
2262
2338
  async materializedResultTableExists(pool) {
2263
2339
  if (this._materializedResultTableExists === null) {
2264
2340
  const sql = `SELECT COUNT(*) AS TblExists FROM INFORMATION_SCHEMA.TABLES ` +
2265
- `WHERE TABLE_SCHEMA = '${mj_core_schema()}' AND TABLE_NAME = 'MaterializedResult'`;
2341
+ `WHERE TABLE_SCHEMA = '${MjCoreSchema()}' AND TABLE_NAME = 'MaterializedResult'`;
2266
2342
  const result = await this.runQuery(pool, sql);
2267
2343
  const row = (result.recordset?.[0] ?? {});
2268
2344
  const cnt = row.TblExists ?? row.tblexists ?? 0;
@@ -2280,7 +2356,7 @@ export class ManageMetadataBase {
2280
2356
  if (!(await this.entityHasExternalDataSourceColumn(pool))) {
2281
2357
  return { success: true, anyUpdates: false, relationshipsUpdated: false };
2282
2358
  }
2283
- const sql = `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntities')} WHERE ExternalDataSourceID IS NOT NULL`;
2359
+ const sql = `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntities')} WHERE ExternalDataSourceID IS NOT NULL`;
2284
2360
  const result = await this.runQuery(pool, sql);
2285
2361
  const externalEntities = result.recordset;
2286
2362
  if (!externalEntities || externalEntities.length === 0) {
@@ -2397,16 +2473,16 @@ export class ManageMetadataBase {
2397
2473
  }
2398
2474
  // Map each introspected column into the veField shape that manageSingleVirtualEntityField consumes.
2399
2475
  const eeFields = obj.Columns.map(c => {
2400
- const t = mapExternalNativeTypeToMJ(c.NativeType);
2476
+ const t = MapExternalNativeTypeToMJ(c.NativeType);
2401
2477
  return { FieldName: c.Name, Type: t.Type, Length: t.Length, Precision: t.Precision, Scale: t.Scale, AllowsNull: c.Nullable, IsPrimaryKey: c.IsPrimaryKey };
2402
2478
  });
2403
2479
  const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
2404
2480
  const entity = md.EntityByName(externalEntity.Name);
2405
2481
  if (entity) {
2406
2482
  // remove EntityFields no longer present in the remote object
2407
- const sqlRemove = this.buildExternalFieldRemoveSQL(mj_core_schema(), entity.Fields, eeFields.map(ef => ef.FieldName));
2483
+ const sqlRemove = this.buildExternalFieldRemoveSQL(MjCoreSchema(), entity.Fields, eeFields.map(ef => ef.FieldName));
2408
2484
  if (sqlRemove) {
2409
- await this.LogSQLAndExecute(pool, sqlRemove, `SQL text to remove fields from external entity ${externalEntity.Name}`);
2485
+ await this.logSQLAndExecute(pool, sqlRemove, `SQL text to remove fields from external entity ${externalEntity.Name}`);
2410
2486
  bUpdated = true;
2411
2487
  }
2412
2488
  // create/update each remote column. Use the real PK info from introspection; fall back
@@ -2437,8 +2513,8 @@ export class ManageMetadataBase {
2437
2513
  }
2438
2514
  }
2439
2515
  if (bUpdated) {
2440
- const sqlUpdate = `UPDATE ${this.qs(mj_core_schema(), 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${externalEntity.ID}'`;
2441
- await this.LogSQLAndExecute(pool, sqlUpdate, `SQL text to update external entity updated date for ${externalEntity.Name}`);
2516
+ const sqlUpdate = `UPDATE ${this.qs(MjCoreSchema(), 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${externalEntity.ID}'`;
2517
+ await this.logSQLAndExecute(pool, sqlUpdate, `SQL text to update external entity updated date for ${externalEntity.Name}`);
2442
2518
  }
2443
2519
  return { success: bSuccess, updatedEntity: bUpdated, relationshipsUpdated: bRelationshipsUpdated };
2444
2520
  }
@@ -2495,7 +2571,7 @@ export class ManageMetadataBase {
2495
2571
  if (alreadySet) {
2496
2572
  continue;
2497
2573
  }
2498
- sqlStatements.push(`UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
2574
+ sqlStatements.push(`UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
2499
2575
  SET RelatedEntityID='${relatedEntity.ID}',
2500
2576
  RelatedEntityFieldName='${refColumn.replace(/'/g, "''")}',
2501
2577
  IsSoftForeignKey=1
@@ -2505,7 +2581,7 @@ export class ManageMetadataBase {
2505
2581
  if (sqlStatements.length === 0) {
2506
2582
  return false;
2507
2583
  }
2508
- await this.LogSQLBatchAndExecute(pool, sqlStatements, `Set external foreign keys for ${externalEntity.Name}`);
2584
+ await this.logSQLBatchAndExecute(pool, sqlStatements, `Set external foreign keys for ${externalEntity.Name}`);
2509
2585
  return true;
2510
2586
  }
2511
2587
  async manageSingleVirtualEntity(pool, virtualEntity) {
@@ -2529,9 +2605,9 @@ export class ManageMetadataBase {
2529
2605
  removeList.push(f.ID);
2530
2606
  }
2531
2607
  if (removeList.length > 0) {
2532
- const sqlRemove = `DELETE FROM ${this.qs(mj_core_schema(), 'EntityField')} WHERE ID IN (${removeList.map(removeId => `'${removeId}'`).join(',')})`;
2608
+ const sqlRemove = `DELETE FROM ${this.qs(MjCoreSchema(), 'EntityField')} WHERE ID IN (${removeList.map(removeId => `'${removeId}'`).join(',')})`;
2533
2609
  // this removes the fields that shouldn't be there anymore
2534
- await this.LogSQLAndExecute(pool, sqlRemove, `SQL text to remove fields from entity ${virtualEntity.Name}`);
2610
+ await this.logSQLAndExecute(pool, sqlRemove, `SQL text to remove fields from entity ${virtualEntity.Name}`);
2535
2611
  bUpdated = true;
2536
2612
  }
2537
2613
  // check to see if any of the fields in the virtual entity have Pkey attribute set. If not, we will default to the first field
@@ -2551,8 +2627,8 @@ export class ManageMetadataBase {
2551
2627
  }
2552
2628
  if (bUpdated) {
2553
2629
  // finally make sure we update the UpdatedAt field for the entity if we made changes to its fields
2554
- const sqlUpdate = `UPDATE ${this.qs(mj_core_schema(), 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${virtualEntity.ID}'`;
2555
- await this.LogSQLAndExecute(pool, sqlUpdate, `SQL text to update virtual entity updated date for ${virtualEntity.Name}`);
2630
+ const sqlUpdate = `UPDATE ${this.qs(MjCoreSchema(), 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${virtualEntity.ID}'`;
2631
+ await this.logSQLAndExecute(pool, sqlUpdate, `SQL text to update virtual entity updated date for ${virtualEntity.Name}`);
2556
2632
  }
2557
2633
  return { success: bSuccess, updatedEntity: bUpdated };
2558
2634
  }
@@ -2620,7 +2696,7 @@ export class ManageMetadataBase {
2620
2696
  field.Sequence !== fieldSequence) {
2621
2697
  // the field needs to be updated, so update it
2622
2698
  const sqlUpdate = `UPDATE
2623
- ${this.qs(mj_core_schema(), 'EntityField')}
2699
+ ${this.qs(MjCoreSchema(), 'EntityField')}
2624
2700
  SET
2625
2701
  Sequence=${fieldSequence},
2626
2702
  Type='${veField.Type}',
@@ -2631,7 +2707,7 @@ export class ManageMetadataBase {
2631
2707
  Scale=${veField.Scale}
2632
2708
  WHERE
2633
2709
  ID = '${field.ID}'`; // don't need to update the __mj_UpdatedAt field here, that happens automatically via the trigger
2634
- await this.LogSQLAndExecute(pool, sqlUpdate, `SQL text to update virtual entity field ${veField.FieldName} for entity ${virtualEntity.Name}`);
2710
+ await this.logSQLAndExecute(pool, sqlUpdate, `SQL text to update virtual entity field ${veField.FieldName} for entity ${virtualEntity.Name}`);
2635
2711
  didUpdate = true;
2636
2712
  }
2637
2713
  }
@@ -2641,7 +2717,7 @@ export class ManageMetadataBase {
2641
2717
  // Apply-time Sequence: see applyTimeEntityFieldSequenceSQL. A literal here would be
2642
2718
  // replayed verbatim from the CodeGen capture and collide on a fresh install.
2643
2719
  const q = (n) => this.qi(n);
2644
- const sqlAdd = `INSERT INTO ${this.qs(mj_core_schema(), 'EntityField')} (
2720
+ const sqlAdd = `INSERT INTO ${this.qs(MjCoreSchema(), 'EntityField')} (
2645
2721
  ${q('ID')}, ${q('EntityID')}, ${q('Name')}, ${q('Type')}, ${q('AllowsNull')},
2646
2722
  ${q('Length')}, ${q('Precision')}, ${q('Scale')},
2647
2723
  ${q('Sequence')}, ${q('IsPrimaryKey')}, ${q('IsUnique')},
@@ -2651,7 +2727,7 @@ export class ManageMetadataBase {
2651
2727
  ${this.applyTimeEntityFieldSequenceSQL(entity.ID)}, ${this.boolLit(wantPrimaryKey)}, ${this.boolLit(wantUnique)},
2652
2728
  ${this.utcNow()}, ${this.utcNow()}
2653
2729
  )`;
2654
- await this.LogSQLAndExecute(pool, sqlAdd, `SQL text to add virtual entity field ${veField.FieldName} for entity ${virtualEntity.Name}`);
2730
+ await this.logSQLAndExecute(pool, sqlAdd, `SQL text to add virtual entity field ${veField.FieldName} for entity ${virtualEntity.Name}`);
2655
2731
  ManageMetadataBase.registerNewField(entity.ID, veField.FieldName);
2656
2732
  didUpdate = true;
2657
2733
  }
@@ -2747,7 +2823,7 @@ export class ManageMetadataBase {
2747
2823
  return { decorated: false, skipped: false };
2748
2824
  }
2749
2825
  // Apply results to EntityField records
2750
- const schema = mj_core_schema();
2826
+ const schema = MjCoreSchema();
2751
2827
  let anyUpdated = false;
2752
2828
  // Apply primary keys
2753
2829
  anyUpdated = await this.applyLLMPrimaryKeys(pool, entity, result.primaryKeys, schema) || anyUpdated;
@@ -2759,7 +2835,7 @@ export class ManageMetadataBase {
2759
2835
  anyUpdated = await this.applyVEFieldCategories(pool, entity, result) || anyUpdated;
2760
2836
  if (anyUpdated) {
2761
2837
  const sqlUpdate = `UPDATE ${this.qs(schema, 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${entity.ID}'`;
2762
- await this.LogSQLAndExecute(pool, sqlUpdate, `Update entity timestamp for ${entity.Name} after LLM decoration`);
2838
+ await this.logSQLAndExecute(pool, sqlUpdate, `Update entity timestamp for ${entity.Name} after LLM decoration`);
2763
2839
  }
2764
2840
  return { decorated: anyUpdated, skipped: false };
2765
2841
  }
@@ -2813,7 +2889,7 @@ export class ManageMetadataBase {
2813
2889
  return false;
2814
2890
  }
2815
2891
  // Load VE EntityField rows from DB (we need the ID and auto-update flags)
2816
- const schema = mj_core_schema();
2892
+ const schema = MjCoreSchema();
2817
2893
  const fieldsSQL = `
2818
2894
  SELECT ID, Name, Category, AutoUpdateCategory, AutoUpdateDisplayName, AutoUpdateExtendedType, GeneratedFormSection, DisplayName, ExtendedType, CodeType, ValueListType
2819
2895
  FROM ${this.qs(schema, 'EntityField')}
@@ -2877,7 +2953,7 @@ export class ManageMetadataBase {
2877
2953
  WHERE EntityID='${entity.ID}' AND Name='${pk}'`);
2878
2954
  logStatus(` ✓ Set PK for ${entity.Name}.${pk} (LLM-identified)`);
2879
2955
  }
2880
- await this.LogSQLBatchAndExecute(pool, sqlStatements, `Set LLM-identified PKs for ${entity.Name}: ${validPKs.join(', ')}`);
2956
+ await this.logSQLBatchAndExecute(pool, sqlStatements, `Set LLM-identified PKs for ${entity.Name}: ${validPKs.join(', ')}`);
2881
2957
  return true;
2882
2958
  }
2883
2959
  /**
@@ -2922,7 +2998,7 @@ export class ManageMetadataBase {
2922
2998
  if (sqlStatements.length === 0) {
2923
2999
  return false;
2924
3000
  }
2925
- await this.LogSQLBatchAndExecute(pool, sqlStatements, `Set LLM-identified FKs for ${entity.Name}`);
3001
+ await this.logSQLBatchAndExecute(pool, sqlStatements, `Set LLM-identified FKs for ${entity.Name}`);
2926
3002
  return true;
2927
3003
  }
2928
3004
  /**
@@ -2962,7 +3038,7 @@ export class ManageMetadataBase {
2962
3038
  if (sqlStatements.length === 0) {
2963
3039
  return false;
2964
3040
  }
2965
- await this.LogSQLBatchAndExecute(pool, sqlStatements, `Set LLM-generated descriptions for ${entity.Name} (${sqlStatements.length} fields)`);
3041
+ await this.logSQLBatchAndExecute(pool, sqlStatements, `Set LLM-generated descriptions for ${entity.Name} (${sqlStatements.length} fields)`);
2966
3042
  return true;
2967
3043
  }
2968
3044
  /**
@@ -3087,7 +3163,7 @@ export class ManageMetadataBase {
3087
3163
  // unquoted reference folds to lower case on PostgreSQL — `column "length" does not exist`.
3088
3164
  // SQL Server's case-insensitive resolution hid this for as long as IS-A ran only there.
3089
3165
  const existsResult = await this.runQueryWithParams(pool, `SELECT ${this.qi('ID')}, ${this.qi('IsVirtual')}, ${this.qi('Type')}, ${this.qi('Length')}, ${this.qi('Precision')}, ${this.qi('Scale')}, ${this.qi('AllowsNull')}, ${this.qi('AllowUpdateAPI')}
3090
- FROM ${this.qs(mj_core_schema(), 'EntityField')}
3166
+ FROM ${this.qs(MjCoreSchema(), 'EntityField')}
3091
3167
  WHERE ${this.qi('EntityID')} = @EntityID AND ${this.qi('Name')} = @FieldName`, { 'EntityID': childEntity.ID, 'FieldName': parentField.Name });
3092
3168
  if (existsResult.recordset.length > 0) {
3093
3169
  // Field already exists — update it to ensure it's marked as a virtual IS-A field
@@ -3100,7 +3176,7 @@ export class ManageMetadataBase {
3100
3176
  existingRow.AllowsNull !== parentField.AllowsNull ||
3101
3177
  !existingRow.AllowUpdateAPI;
3102
3178
  if (needsUpdate) {
3103
- const sqlUpdate = `UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
3179
+ const sqlUpdate = `UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
3104
3180
  SET ${this.qi('IsVirtual')}=${this.boolLit(true)},
3105
3181
  ${this.qi('Type')}='${parentField.Type}',
3106
3182
  ${this.qi('Length')}=${parentField.Length},
@@ -3109,7 +3185,7 @@ export class ManageMetadataBase {
3109
3185
  ${this.qi('AllowsNull')}=${this.boolLit(parentField.AllowsNull)},
3110
3186
  ${this.qi('AllowUpdateAPI')}=${this.boolLit(true)}
3111
3187
  WHERE ${this.qi('ID')}='${existingRow.ID}'`;
3112
- await this.LogSQLAndExecute(pool, sqlUpdate, `Update IS-A parent field ${parentField.Name} on ${childEntity.Name}`);
3188
+ await this.logSQLAndExecute(pool, sqlUpdate, `Update IS-A parent field ${parentField.Name} on ${childEntity.Name}`);
3113
3189
  bUpdated = true;
3114
3190
  }
3115
3191
  }
@@ -3119,7 +3195,7 @@ export class ManageMetadataBase {
3119
3195
  // Apply-time Sequence (see applyTimeEntityFieldSequenceSQL); reordered by
3120
3196
  // updateExistingEntityFieldsFromSchema afterwards.
3121
3197
  const q = (n) => this.qi(n);
3122
- const sqlInsert = `INSERT INTO ${this.qs(mj_core_schema(), 'EntityField')} (
3198
+ const sqlInsert = `INSERT INTO ${this.qs(MjCoreSchema(), 'EntityField')} (
3123
3199
  ${q('ID')}, ${q('EntityID')}, ${q('Name')}, ${q('Type')}, ${q('AllowsNull')},
3124
3200
  ${q('Length')}, ${q('Precision')}, ${q('Scale')},
3125
3201
  ${q('Sequence')}, ${q('IsVirtual')}, ${q('AllowUpdateAPI')},
@@ -3131,7 +3207,7 @@ export class ManageMetadataBase {
3131
3207
  ${parentField.Length}, ${parentField.Precision}, ${parentField.Scale},
3132
3208
  ${this.applyTimeEntityFieldSequenceSQL(childEntity.ID)}, ${this.boolLit(true)}, ${this.boolLit(true)}, ${this.boolLit(false)}, ${this.boolLit(false)},
3133
3209
  ${this.utcNow()}, ${this.utcNow()})`;
3134
- await this.LogSQLAndExecute(pool, sqlInsert, `Create IS-A parent field ${parentField.Name} on ${childEntity.Name}`);
3210
+ await this.logSQLAndExecute(pool, sqlInsert, `Create IS-A parent field ${parentField.Name} on ${childEntity.Name}`);
3135
3211
  ManageMetadataBase.registerNewField(childEntity.ID, parentField.Name);
3136
3212
  bUpdated = true;
3137
3213
  }
@@ -3143,13 +3219,13 @@ export class ManageMetadataBase {
3143
3219
  !f.IsPrimaryKey && !f.Name.startsWith('__mj_') &&
3144
3220
  !currentParentFieldNames.has(f.Name.toLowerCase()));
3145
3221
  for (const staleField of staleFields) {
3146
- const sqlDelete = `DELETE FROM ${this.qs(mj_core_schema(), 'EntityField')} WHERE ID='${staleField.ID}'`;
3147
- await this.LogSQLAndExecute(pool, sqlDelete, `Remove stale IS-A parent field ${staleField.Name} from ${childEntity.Name}`);
3222
+ const sqlDelete = `DELETE FROM ${this.qs(MjCoreSchema(), 'EntityField')} WHERE ID='${staleField.ID}'`;
3223
+ await this.logSQLAndExecute(pool, sqlDelete, `Remove stale IS-A parent field ${staleField.Name} from ${childEntity.Name}`);
3148
3224
  bUpdated = true;
3149
3225
  }
3150
3226
  if (bUpdated) {
3151
- const sqlUpdate = `UPDATE ${this.qs(mj_core_schema(), 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${childEntity.ID}'`;
3152
- await this.LogSQLAndExecute(pool, sqlUpdate, `Update entity timestamp for ${childEntity.Name} after IS-A field sync`);
3227
+ const sqlUpdate = `UPDATE ${this.qs(MjCoreSchema(), 'Entity')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()} WHERE ID='${childEntity.ID}'`;
3228
+ await this.logSQLAndExecute(pool, sqlUpdate, `Update entity timestamp for ${childEntity.Name} after IS-A field sync`);
3153
3229
  }
3154
3230
  return { success: true, updated: bUpdated };
3155
3231
  }
@@ -3200,16 +3276,16 @@ export class ManageMetadataBase {
3200
3276
  try {
3201
3277
  // STEP 1 - search for all foreign keys in the vwEntityFields view, we use the RelatedEntityID field to determine our FKs
3202
3278
  const sSQL = `SELECT *
3203
- FROM ${this.qs(mj_core_schema(), 'vwEntityFields')}
3279
+ FROM ${this.qs(MjCoreSchema(), 'vwEntityFields')}
3204
3280
  WHERE
3205
3281
  RelatedEntityID IS NOT NULL AND
3206
3282
  IsVirtual = ${this.boolLit(false)} AND
3207
- EntityID NOT IN (SELECT ID FROM ${this.qs(mj_core_schema(), 'Entity')} WHERE SchemaName IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))
3283
+ EntityID NOT IN (SELECT ID FROM ${this.qs(MjCoreSchema(), 'Entity')} WHERE SchemaName IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))
3208
3284
  ORDER BY RelatedEntityID, EntityID, Sequence, ID`;
3209
3285
  const entityFieldsResult = await this.runQuery(pool, sSQL);
3210
3286
  const entityFields = entityFieldsResult.recordset;
3211
3287
  // Get the relationship counts for each entity
3212
- const sSQLRelationshipCount = `SELECT ${this.qi('EntityID')}, COUNT(*) AS ${this.qi('Count')} FROM ${this.qs(mj_core_schema(), 'EntityRelationship')} GROUP BY ${this.qi('EntityID')}`;
3288
+ const sSQLRelationshipCount = `SELECT ${this.qi('EntityID')}, COUNT(*) AS ${this.qi('Count')} FROM ${this.qs(MjCoreSchema(), 'EntityRelationship')} GROUP BY ${this.qi('EntityID')}`;
3213
3289
  const relationshipCountsResult = await this.runQuery(pool, sSQLRelationshipCount);
3214
3290
  const relationshipCounts = relationshipCountsResult.recordset;
3215
3291
  const relationshipCountMap = new Map();
@@ -3218,7 +3294,7 @@ export class ManageMetadataBase {
3218
3294
  relationshipCountMap.set(rc.EntityID, Number(rc.Count));
3219
3295
  }
3220
3296
  // get all relationships in one query for performance improvement
3221
- const sSQLRelationship = `SELECT * FROM ${this.qs(mj_core_schema(), 'EntityRelationship')}`;
3297
+ const sSQLRelationship = `SELECT * FROM ${this.qs(MjCoreSchema(), 'EntityRelationship')}`;
3222
3298
  const allRelationshipsResult = await this.runQuery(pool, sSQLRelationship);
3223
3299
  const allRelationships = allRelationshipsResult.recordset;
3224
3300
  // Group FK fields by entity pair (ParentEntityID -> ChildEntityID) so we can
@@ -3244,12 +3320,12 @@ export class ManageMetadataBase {
3244
3320
  }
3245
3321
  batchCount++;
3246
3322
  if (batchCount % batchItems === 0 && batchSQL.length > 0) {
3247
- await this.LogSQLAndExecute(pool, batchSQL);
3323
+ await this.logSQLAndExecute(pool, batchSQL);
3248
3324
  batchSQL = '';
3249
3325
  }
3250
3326
  }
3251
3327
  if (batchSQL.length > 0) {
3252
- await this.LogSQLAndExecute(pool, batchSQL);
3328
+ await this.logSQLAndExecute(pool, batchSQL);
3253
3329
  }
3254
3330
  // NOTE: Stale relationship cleanup is intentionally NOT done here because this
3255
3331
  // method runs BEFORE deleteUnneededEntityFields(). Stale EntityField records
@@ -3290,8 +3366,8 @@ export class ManageMetadataBase {
3290
3366
  const relCount = relationshipCountMap.get(parentEntityID) || 0;
3291
3367
  const sequence = relCount + 1;
3292
3368
  const newEntityRelationshipUUID = this.createNewUUID();
3293
- const checkQuery = `SELECT 1 FROM ${this.qs(mj_core_schema(), 'EntityRelationship')} WHERE ${this.qi('ID')} = '${newEntityRelationshipUUID}'`;
3294
- const insertSQL = `INSERT INTO ${this.qs(mj_core_schema(), 'EntityRelationship')} (${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('RelatedEntityID')}, ${this.qi('RelatedEntityJoinField')}, ${this.qi('Type')}, ${this.qi('BundleInAPI')}, ${this.qi('DisplayInForm')}, ${this.qi('Sequence')}, ${this.qi('__mj_CreatedAt')}, ${this.qi('__mj_UpdatedAt')})
3369
+ const checkQuery = `SELECT 1 FROM ${this.qs(MjCoreSchema(), 'EntityRelationship')} WHERE ${this.qi('ID')} = '${newEntityRelationshipUUID}'`;
3370
+ const insertSQL = `INSERT INTO ${this.qs(MjCoreSchema(), 'EntityRelationship')} (${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('RelatedEntityID')}, ${this.qi('RelatedEntityJoinField')}, ${this.qi('Type')}, ${this.qi('BundleInAPI')}, ${this.qi('DisplayInForm')}, ${this.qi('Sequence')}, ${this.qi('__mj_CreatedAt')}, ${this.qi('__mj_UpdatedAt')})
3295
3371
  VALUES ('${newEntityRelationshipUUID}', '${f.RelatedEntityID}', '${f.EntityID}', '${f.Name}', 'One To Many', ${this.boolLit(true)}, ${this.boolLit(true)}, ${sequence}, ${this.utcNow()}, ${this.utcNow()})`;
3296
3372
  relationshipCountMap.set(parentEntityID, sequence);
3297
3373
  return `
@@ -3353,7 +3429,7 @@ export class ManageMetadataBase {
3353
3429
  logStatus(` > Updating EntityRelationship join field: ${oldJoinField} -> ${newJoinField} (ID: ${r.ID})`);
3354
3430
  sql += `
3355
3431
  /* Update EntityRelationship join field from '${oldJoinField}' to '${newJoinField}' */
3356
- UPDATE ${this.qs(mj_core_schema(), 'EntityRelationship')}
3432
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityRelationship')}
3357
3433
  SET ${this.qi('RelatedEntityJoinField')} = '${newJoinField}',
3358
3434
  ${this.qi('__mj_UpdatedAt')} = ${this.utcNow()}
3359
3435
  WHERE ${this.qi('ID')} = '${r.ID}';
@@ -3375,18 +3451,18 @@ export class ManageMetadataBase {
3375
3451
  * @param pool - database connection
3376
3452
  * @param excludeSchemas - schemas to exclude from FK field lookup
3377
3453
  */
3378
- async cleanupStaleEntityRelationships(pool, excludeSchemas) {
3454
+ async CleanupStaleEntityRelationships(pool, excludeSchemas) {
3379
3455
  try {
3380
3456
  const sSQL = `SELECT *
3381
- FROM ${this.qs(mj_core_schema(), 'vwEntityFields')}
3457
+ FROM ${this.qs(MjCoreSchema(), 'vwEntityFields')}
3382
3458
  WHERE
3383
3459
  RelatedEntityID IS NOT NULL AND
3384
3460
  IsVirtual = ${this.boolLit(false)} AND
3385
- EntityID NOT IN (SELECT ID FROM ${this.qs(mj_core_schema(), 'Entity')} WHERE SchemaName IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))
3461
+ EntityID NOT IN (SELECT ID FROM ${this.qs(MjCoreSchema(), 'Entity')} WHERE SchemaName IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))
3386
3462
  ORDER BY RelatedEntityID, EntityID, Sequence, ID`;
3387
3463
  const entityFieldsResult = await this.runQuery(pool, sSQL);
3388
3464
  const entityFields = entityFieldsResult.recordset;
3389
- const sSQLRelationship = `SELECT * FROM ${this.qs(mj_core_schema(), 'EntityRelationship')}`;
3465
+ const sSQLRelationship = `SELECT * FROM ${this.qs(MjCoreSchema(), 'EntityRelationship')}`;
3390
3466
  const allRelationshipsResult = await this.runQuery(pool, sSQLRelationship);
3391
3467
  const allRelationships = allRelationshipsResult.recordset;
3392
3468
  await this.removeStaleOneToManyRelationships(pool, allRelationships, entityFields);
@@ -3397,6 +3473,10 @@ export class ManageMetadataBase {
3397
3473
  return false;
3398
3474
  }
3399
3475
  }
3476
+ /** @deprecated Use {@link CleanupStaleEntityRelationships}. */
3477
+ async cleanupStaleEntityRelationships(pool, excludeSchemas) {
3478
+ return this.CleanupStaleEntityRelationships(pool, excludeSchemas);
3479
+ }
3400
3480
  /**
3401
3481
  * Removes stale One-To-Many EntityRelationship records whose RelatedEntityJoinField no longer
3402
3482
  * corresponds to a valid FK field in the database. Only removes relationships where
@@ -3419,10 +3499,10 @@ export class ManageMetadataBase {
3419
3499
  logStatus(` > Removing stale EntityRelationship: ${r.Entity} -> ${r.RelatedEntity} via '${r.RelatedEntityJoinField}' (ID: ${r.ID})`);
3420
3500
  deleteSQL += `
3421
3501
  /* Remove stale EntityRelationship: ${r.Entity} -> ${r.RelatedEntity} (FK field '${r.RelatedEntityJoinField}' no longer exists) */
3422
- DELETE FROM ${this.qs(mj_core_schema(), 'EntityRelationship')} WHERE ${this.qi('ID')} = '${r.ID}';
3502
+ DELETE FROM ${this.qs(MjCoreSchema(), 'EntityRelationship')} WHERE ${this.qi('ID')} = '${r.ID}';
3423
3503
  `;
3424
3504
  }
3425
- await this.LogSQLAndExecute(pool, deleteSQL, 'Remove stale One-To-Many EntityRelationships');
3505
+ await this.logSQLAndExecute(pool, deleteSQL, 'Remove stale One-To-Many EntityRelationships');
3426
3506
  }
3427
3507
  }
3428
3508
  /**
@@ -3433,7 +3513,7 @@ export class ManageMetadataBase {
3433
3513
  */
3434
3514
  async checkAndRemoveMetadataForDeletedTables(pool, excludeSchemas) {
3435
3515
  try {
3436
- const sql = `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntitiesWithMissingBaseTables')}${this.dbProvider.getEntitiesWithMissingBaseTablesFilter()}`;
3516
+ const sql = `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntitiesWithMissingBaseTables')}${this.dbProvider.getEntitiesWithMissingBaseTablesFilter()}`;
3437
3517
  const entitiesResult = await this.runQuery(pool, sql);
3438
3518
  const entities = entitiesResult.recordset;
3439
3519
  if (entities && entities.length > 0) {
@@ -3456,8 +3536,8 @@ export class ManageMetadataBase {
3456
3536
  // discardResult: this routine only performs deletions — nothing below reads rows
3457
3537
  // from it — and on PostgreSQL it is declared RETURNS SETOF record, which cannot be
3458
3538
  // invoked through the default `SELECT * FROM` form at all.
3459
- const sqlDelete = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spDeleteEntityWithCoreDependencies', [`'${e.ID}'`], ['EntityID'], true);
3460
- await this.LogSQLAndExecute(pool, sqlDelete, `SQL text to remove entity ${e.Name}`);
3539
+ const sqlDelete = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spDeleteEntityWithCoreDependencies', [`'${e.ID}'`], ['EntityID'], true);
3540
+ await this.logSQLAndExecute(pool, sqlDelete, `SQL text to remove entity ${e.Name}`);
3461
3541
  logStatus(` > Removed metadata for table ${e.SchemaName}.${e.BaseTable}`);
3462
3542
  // Recorded only after the delete succeeds, so a failed removal (logged below for
3463
3543
  // an admin to handle) does not mark a schema dirty for an entity still present.
@@ -3494,9 +3574,9 @@ export class ManageMetadataBase {
3494
3574
  // Object type codes: P = Stored Procedure, V = View, FN = Scalar Function, IF/TF = Table-Valued Function
3495
3575
  const upperType = type.toUpperCase();
3496
3576
  const sqlDelete = this.dbProvider.dropObjectSQL(upperType, schemaName, name);
3497
- await this.LogSQLAndExecute(pool, sqlDelete, `SQL text to remove ${type} ${schemaName}.${name}`);
3577
+ await this.logSQLAndExecute(pool, sqlDelete, `SQL text to remove ${type} ${schemaName}.${name}`);
3498
3578
  // next up, we need to clean up the cache of saved DB objects that may exist for this entity in the appropriate sub-directory.
3499
- const sqlOutputDir = outputDir('SQL', true);
3579
+ const sqlOutputDir = OutputDir('SQL', true);
3500
3580
  if (sqlOutputDir) {
3501
3581
  // now do the same thing for the /schema directory within the provided directory
3502
3582
  const fType = type === 'procedure' ? 'sp' : type === 'view' ? 'view' : 'full_text_search_function';
@@ -3570,7 +3650,7 @@ export class ManageMetadataBase {
3570
3650
  * which is the typical "no schema changes since last run" case in Pass 2.
3571
3651
  * `undefined` (default) preserves prior full-scan behavior.
3572
3652
  */
3573
- async manageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser, skipAdvancedGeneration, skipDeleteUnneededFields = false, entityFilter) {
3653
+ async ManageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser, skipAdvancedGeneration, skipDeleteUnneededFields = false, entityFilter) {
3574
3654
  let bSuccess = true;
3575
3655
  const startTime = new Date();
3576
3656
  // Fast-exit: an explicit empty filter means "no entities changed in Pass 1, so Pass 2 has nothing to do"
@@ -3623,9 +3703,9 @@ export class ManageMetadataBase {
3623
3703
  // Fix: derive virtual field AllowsNull from the FK column that drives the JOIN.
3624
3704
  // The virtual field name = FK field name minus trailing "ID" (e.g. ClassID → Class).
3625
3705
  if (this.dbProvider.NeedsVirtualFieldNullabilityFix) {
3626
- const fixSQL = this.dbProvider.getFixVirtualFieldNullabilitySQL(mj_core_schema());
3706
+ const fixSQL = this.dbProvider.getFixVirtualFieldNullabilitySQL(MjCoreSchema());
3627
3707
  if (fixSQL) {
3628
- await this.LogSQLAndExecute(pool, fixSQL, 'SQL to fix virtual field nullability');
3708
+ await this.logSQLAndExecute(pool, fixSQL, 'SQL to fix virtual field nullability');
3629
3709
  }
3630
3710
  }
3631
3711
  // AN: 14-June-2025 - we are now running this AFTER we create new entity fields from schema
@@ -3694,13 +3774,13 @@ export class ManageMetadataBase {
3694
3774
  // Advanced Generation - Smart field identification and form layout
3695
3775
  if (!skipAdvancedGeneration) {
3696
3776
  const step7StartTime = new Date();
3697
- startSpinner('Applying AI-powered advanced generation (smart fields, form layout)...');
3777
+ StartSpinner('Applying AI-powered advanced generation (smart fields, form layout)...');
3698
3778
  if (!await this.applyAdvancedGeneration(pool, excludeSchemas, currentUser)) {
3699
3779
  logError('Error applying advanced generation features');
3700
3780
  // Don't fail the entire process - advanced generation is optional
3701
3781
  }
3702
3782
  const step7Elapsed = ((new Date().getTime() - step7StartTime.getTime()) / 1000).toFixed(1);
3703
- succeedSpinner(`Advanced generation completed (${step7Elapsed}s)`);
3783
+ SucceedSpinner(`Advanced generation completed (${step7Elapsed}s)`);
3704
3784
  }
3705
3785
  // Deterministic search-flag hygiene. Deliberately OUTSIDE the `skipAdvancedGeneration`
3706
3786
  // guard above: advanced generation is off by default, and an entity created under that
@@ -3721,6 +3801,10 @@ export class ManageMetadataBase {
3721
3801
  logStatus(` Total time to manage entity fields: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
3722
3802
  return bSuccess;
3723
3803
  }
3804
+ /** @deprecated Use {@link ManageEntityFields}. */
3805
+ async manageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser, skipAdvancedGeneration, skipDeleteUnneededFields = false, entityFilter) {
3806
+ return this.ManageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser, skipAdvancedGeneration, skipDeleteUnneededFields, entityFilter);
3807
+ }
3724
3808
  /**
3725
3809
  * This method ensures that the __mj_DeletedAt field exists in each entity that has DeleteType=Soft. If the field does not exist, it is created.
3726
3810
  */
@@ -3729,7 +3813,7 @@ export class ManageMetadataBase {
3729
3813
  const sqlEntities = `SELECT
3730
3814
  *
3731
3815
  FROM
3732
- ${this.qs(mj_core_schema(), 'vwEntities')}
3816
+ ${this.qs(MjCoreSchema(), 'vwEntities')}
3733
3817
  WHERE
3734
3818
  VirtualEntity=${this.boolLit(false)} AND
3735
3819
  DeleteType='Soft' AND
@@ -3790,7 +3874,7 @@ export class ManageMetadataBase {
3790
3874
  }
3791
3875
  let totalPKs = 0;
3792
3876
  let totalFKs = 0;
3793
- const schema = mj_core_schema();
3877
+ const schema = MjCoreSchema();
3794
3878
  // Config supports two formats:
3795
3879
  // 1. Schema-as-key (template format): { "dbo": [{ "TableName": "Orders", ... }] }
3796
3880
  // 2. Flat tables array (legacy format): { "tables": [{ "SchemaName": "dbo", "TableName": "Orders", ... }] }
@@ -3829,7 +3913,7 @@ export class ManageMetadataBase {
3829
3913
  ${this.qi('IsPrimaryKey')} = ${this.boolLit(true)},
3830
3914
  ${this.qi('IsSoftPrimaryKey')} = ${this.boolLit(true)}
3831
3915
  WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = '${pk.FieldName}'`;
3832
- const result = await this.LogSQLAndExecute(pool, sSQL, `Set soft PK for ${tableSchema}.${tableName}.${pk.FieldName}`);
3916
+ const result = await this.logSQLAndExecute(pool, sSQL, `Set soft PK for ${tableSchema}.${tableName}.${pk.FieldName}`);
3833
3917
  if (result !== null) {
3834
3918
  logStatus(` ✓ Set IsPrimaryKey=1, IsSoftPrimaryKey=1 for ${tableName}.${pk.FieldName}`);
3835
3919
  totalPKs++;
@@ -3873,7 +3957,7 @@ export class ManageMetadataBase {
3873
3957
  ${this.qi('IsSoftForeignKey')} = ${this.boolLit(true)},
3874
3958
  ${this.qi('AutoUpdateRelatedEntityInfo')} = ${this.boolLit(false)}
3875
3959
  WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = '${fk.FieldName}'`;
3876
- const result = await this.LogSQLAndExecute(pool, sSQL, `Set soft FK for ${tableSchema}.${tableName}.${fk.FieldName} → ${fk.RelatedTable}.${fk.RelatedField}`);
3960
+ const result = await this.logSQLAndExecute(pool, sSQL, `Set soft FK for ${tableSchema}.${tableName}.${fk.FieldName} → ${fk.RelatedTable}.${fk.RelatedField}`);
3877
3961
  if (result !== null) {
3878
3962
  logStatus(` ✓ Set soft FK for ${tableName}.${fk.FieldName} → ${fk.RelatedTable}.${fk.RelatedField}`);
3879
3963
  totalFKs++;
@@ -3904,7 +3988,7 @@ export class ManageMetadataBase {
3904
3988
  return true;
3905
3989
  try {
3906
3990
  // Load all existing EntityFieldValue rows for dedup checking
3907
- const efvSQL = `SELECT * FROM ${this.qs(mj_core_schema(), 'EntityFieldValue')} ORDER BY EntityFieldID, Sequence, Value, ID`;
3991
+ const efvSQL = `SELECT * FROM ${this.qs(MjCoreSchema(), 'EntityFieldValue')} ORDER BY EntityFieldID, Sequence, Value, ID`;
3908
3992
  const allEFVResult = await this.runQuery(pool, efvSQL);
3909
3993
  const allEntityFieldValues = allEFVResult.recordset;
3910
3994
  let totalApplied = 0;
@@ -3930,7 +4014,7 @@ export class ManageMetadataBase {
3930
4014
  * EntityFieldValue rows already exist (from CHECK constraints) and skips if so.
3931
4015
  */
3932
4016
  async applyValueListsForTable(pool, schemaName, tableName, fields, allEntityFieldValues) {
3933
- const schema = mj_core_schema();
4017
+ const schema = MjCoreSchema();
3934
4018
  let applied = 0;
3935
4019
  let skipped = 0;
3936
4020
  // Look up entity ID
@@ -3976,7 +4060,7 @@ export class ManageMetadataBase {
3976
4060
  const newType = fieldCfg.ValueListType;
3977
4061
  if (currentValueListType !== newType.toLowerCase()) {
3978
4062
  const updateSQL = `UPDATE ${this.qs(schema, 'EntityField')} SET ValueListType='${newType}' WHERE ID='${entityFieldID}'`;
3979
- await this.LogSQLAndExecute(pool, updateSQL, `Set soft ValueListType='${newType}' for ${schemaName}.${tableName}.${fieldCfg.FieldName}`);
4063
+ await this.logSQLAndExecute(pool, updateSQL, `Set soft ValueListType='${newType}' for ${schemaName}.${tableName}.${fieldCfg.FieldName}`);
3980
4064
  }
3981
4065
  logStatus(` ✓ Applied soft value-list for ${tableName}.${fieldCfg.FieldName} (${sortedValues.length} values, type=${newType})`);
3982
4066
  return 'applied';
@@ -3998,7 +4082,7 @@ export class ManageMetadataBase {
3998
4082
  const sqlEntities = `SELECT
3999
4083
  *
4000
4084
  FROM
4001
- ${this.qs(mj_core_schema(), 'vwEntities')}
4085
+ ${this.qs(MjCoreSchema(), 'vwEntities')}
4002
4086
  WHERE
4003
4087
  VirtualEntity = ${this.boolLit(false)} AND
4004
4088
  ${externalEntityClause}
@@ -4062,7 +4146,7 @@ export class ManageMetadataBase {
4062
4146
  // and the subsequent UPDATE referencing that column run in separate batches.
4063
4147
  // SQL Server compiles an entire batch before executing, so without the separator
4064
4148
  // the UPDATE fails with "Invalid column name" since the column doesn't exist yet at compile time.
4065
- await this.LogSQLAndExecute(pool, stmt, `SQL text to add special date field ${fieldName} to entity ${entity.SchemaName}.${entity.BaseTable}`, false, true, this.dbProvider.BatchSeparator);
4149
+ await this.logSQLAndExecute(pool, stmt, `SQL text to add special date field ${fieldName} to entity ${entity.SchemaName}.${entity.BaseTable}`, false, true, this.dbProvider.BatchSeparator);
4066
4150
  }
4067
4151
  }
4068
4152
  else {
@@ -4075,7 +4159,7 @@ export class ManageMetadataBase {
4075
4159
  // modifying the column, and finally adding the default constraint back in.
4076
4160
  await this.dropExistingDefaultConstraint(pool, entity, fieldName);
4077
4161
  const sql = this.dbProvider.alterColumnTypeAndNullabilitySQL(entity.SchemaName, entity.BaseTable, fieldName, this.timestampType, allowNull);
4078
- await this.LogSQLAndExecute(pool, sql, `SQL text to update special date field ${fieldName} in entity ${entity.SchemaName}.${entity.BaseTable}`);
4162
+ await this.logSQLAndExecute(pool, sql, `SQL text to update special date field ${fieldName} in entity ${entity.SchemaName}.${entity.BaseTable}`);
4079
4163
  if (!allowNull)
4080
4164
  await this.createDefaultConstraintForSpecialDateField(pool, entity, fieldName);
4081
4165
  }
@@ -4105,7 +4189,7 @@ export class ManageMetadataBase {
4105
4189
  async createDefaultConstraintForSpecialDateField(pool, entity, fieldName) {
4106
4190
  try {
4107
4191
  const sqlAddDefaultConstraint = this.dbProvider.addDefaultConstraintSQL(entity.SchemaName, entity.BaseTable, fieldName, this.utcNow());
4108
- await this.LogSQLAndExecute(pool, sqlAddDefaultConstraint, `SQL text to add default constraint for special date field ${fieldName} in entity ${entity.SchemaName}.${entity.BaseTable}`);
4192
+ await this.logSQLAndExecute(pool, sqlAddDefaultConstraint, `SQL text to add default constraint for special date field ${fieldName} in entity ${entity.SchemaName}.${entity.BaseTable}`);
4109
4193
  }
4110
4194
  catch (e) {
4111
4195
  logError(e);
@@ -4132,7 +4216,7 @@ export class ManageMetadataBase {
4132
4216
  try {
4133
4217
  const sqlDropDefaultConstraint = this.dbProvider.dropDefaultConstraintSQL(entity.SchemaName, entity.BaseTable, fieldName);
4134
4218
  // DECLARE-bearing block: close its batch in the replayable log (see SQLLogging.appendToSQLLogFile).
4135
- await this.LogSQLAndExecute(pool, sqlDropDefaultConstraint, `SQL text to drop default existing default constraints in entity ${entity.SchemaName}.${entity.BaseTable}`, false, true, this.dbProvider.BatchSeparator);
4219
+ await this.logSQLAndExecute(pool, sqlDropDefaultConstraint, `SQL text to drop default existing default constraints in entity ${entity.SchemaName}.${entity.BaseTable}`, false, true, this.dbProvider.BatchSeparator);
4136
4220
  }
4137
4221
  catch (e) {
4138
4222
  logError(e);
@@ -4157,15 +4241,15 @@ export class ManageMetadataBase {
4157
4241
  // "Unclosed quotation mark" (SQL Server) at the very end of an otherwise-complete
4158
4242
  // pass. '' doubling is correct on both supported dialects.
4159
4243
  const esc = (s) => s.replace(/'/g, "''");
4160
- const dataResult = await this.runQuery(pool, `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntities')} WHERE Name = '${esc(e)}'`);
4244
+ const dataResult = await this.runQuery(pool, `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntities')} WHERE Name = '${esc(e)}'`);
4161
4245
  const data = dataResult.recordset;
4162
- const fieldsResult = await this.runQuery(pool, `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntityFields')} WHERE EntityID='${data[0].ID}'`);
4246
+ const fieldsResult = await this.runQuery(pool, `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntityFields')} WHERE EntityID='${data[0].ID}'`);
4163
4247
  const fields = fieldsResult.recordset;
4164
4248
  // Use new API to generate entity description
4165
4249
  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);
4166
4250
  if (result?.entityDescription && result.entityDescription.length > 0) {
4167
- const sSQL = `UPDATE ${this.qs(mj_core_schema(), 'Entity')} SET ${this.qi('Description')} = '${esc(result.entityDescription)}', ${this.qi('AutoUpdateDescription')} = ${this.boolLit(false)} WHERE ${this.qi('Name')} = '${esc(e)}'`;
4168
- await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
4251
+ const sSQL = `UPDATE ${this.qs(MjCoreSchema(), 'Entity')} SET ${this.qi('Description')} = '${esc(result.entityDescription)}', ${this.qi('AutoUpdateDescription')} = ${this.boolLit(false)} WHERE ${this.qi('Name')} = '${esc(e)}'`;
4252
+ await this.logSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
4169
4253
  }
4170
4254
  else {
4171
4255
  console.warn(' >>> Advanced Generation Error: LLM returned invalid result, skipping entity description for entity ' + e);
@@ -4186,9 +4270,9 @@ export class ManageMetadataBase {
4186
4270
  const sql = `SELECT
4187
4271
  ef.ID, ef.Name
4188
4272
  FROM
4189
- ${this.qs(mj_core_schema(), 'vwEntityFields')} ef
4273
+ ${this.qs(MjCoreSchema(), 'vwEntityFields')} ef
4190
4274
  INNER JOIN
4191
- ${this.qs(mj_core_schema(), 'vwEntities')} e
4275
+ ${this.qs(MjCoreSchema(), 'vwEntities')} e
4192
4276
  ON
4193
4277
  ef.EntityID = e.ID
4194
4278
  WHERE
@@ -4203,8 +4287,8 @@ export class ManageMetadataBase {
4203
4287
  const namingOptions = configInfo.entityNaming?.normalizeFieldNames !== false ? this.getEntityNamingOptions() : undefined;
4204
4288
  const sDisplayName = stripTrailingChars(createDisplayName(field.Name, namingOptions), 'ID', true).trim();
4205
4289
  if (sDisplayName.length > 0 && sDisplayName.toLowerCase().trim() !== field.Name.toLowerCase().trim()) {
4206
- const sSQL = `UPDATE ${this.qs(mj_core_schema(), 'EntityField')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()}, DisplayName = '${sDisplayName}' WHERE ID = '${field.ID}'`;
4207
- await this.LogSQLAndExecute(pool, sSQL, `SQL text to update display name for field ${field.Name}`);
4290
+ const sSQL = `UPDATE ${this.qs(MjCoreSchema(), 'EntityField')} SET ${this.qi(EntityInfo.UpdatedAtFieldName)}=${this.utcNow()}, DisplayName = '${sDisplayName}' WHERE ID = '${field.ID}'`;
4291
+ await this.logSQLAndExecute(pool, sSQL, `SQL text to update display name for field ${field.Name}`);
4208
4292
  }
4209
4293
  }
4210
4294
  return true;
@@ -4224,12 +4308,12 @@ export class ManageMetadataBase {
4224
4308
  */
4225
4309
  async setDefaultColumnWidthWhereNeeded(pool, excludeSchemas) {
4226
4310
  try {
4227
- const heal = buildHealSchemaRoutineParams({
4228
- authoredExclude: getAuthoredExcludeSchemas(excludeSchemas),
4311
+ const heal = BuildHealSchemaRoutineParams({
4312
+ authoredExclude: GetAuthoredExcludeSchemas(excludeSchemas),
4229
4313
  includeSchemas: configInfo.includeSchemas,
4230
4314
  });
4231
- const sSQL = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spSetDefaultColumnWidthWhereNeeded', heal.values, heal.names);
4232
- await this.LogSQLAndExecute(pool, sSQL, `SQL text to set default column width where needed`, true);
4315
+ const sSQL = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spSetDefaultColumnWidthWhereNeeded', heal.Values, heal.Names);
4316
+ await this.logSQLAndExecute(pool, sSQL, `SQL text to set default column width where needed`, true);
4233
4317
  return true;
4234
4318
  }
4235
4319
  catch (e) {
@@ -4249,7 +4333,7 @@ export class ManageMetadataBase {
4249
4333
  * @returns {string} - The SQL statement to retrieve pending entity fields.
4250
4334
  */
4251
4335
  getPendingEntityFieldsSELECTSQL(entityIDs, excludeSchemas) {
4252
- const schema = mj_core_schema();
4336
+ const schema = MjCoreSchema();
4253
4337
  return this.dbProvider.getPendingEntityFieldsSQL(schema, entityIDs, excludeSchemas);
4254
4338
  }
4255
4339
  /**
@@ -4292,7 +4376,7 @@ export class ManageMetadataBase {
4292
4376
  const quotedDefaultValue = (parsedDefaultValue == null || parsedDefaultValue.trim().length === 0)
4293
4377
  ? 'NULL'
4294
4378
  : (parsedDefaultValue.trim().toLowerCase() === 'null' ? 'NULL' : `'${escapedParsedDefault}'`);
4295
- const conflictCheck = `SELECT 1 FROM ${this.qs(mj_core_schema(), 'EntityField')} WHERE ID = '${newEntityFieldUUID}' OR (EntityID = '${n.EntityID}' AND Name = '${n.FieldName}')`;
4379
+ const conflictCheck = `SELECT 1 FROM ${this.qs(MjCoreSchema(), 'EntityField')} WHERE ID = '${newEntityFieldUUID}' OR (EntityID = '${n.EntityID}' AND Name = '${n.FieldName}')`;
4296
4380
  const guard = this.dbProvider.wrapInsertWithConflictGuard(conflictCheck);
4297
4381
  // Sequence is an apply-time expression, never a literal: see applyTimeEntityFieldSequenceSQL.
4298
4382
  // `n.Sequence` (MAX+100000+ordinal from the pending SELECT) is only that query's ORDER BY key
@@ -4300,7 +4384,7 @@ export class ManageMetadataBase {
4300
4384
  const sequenceExpr = this.applyTimeEntityFieldSequenceSQL(n.EntityID);
4301
4385
  return `
4302
4386
  ${guard.prefix}
4303
- INSERT INTO ${this.qs(mj_core_schema(), 'EntityField')}
4387
+ INSERT INTO ${this.qs(MjCoreSchema(), 'EntityField')}
4304
4388
  (
4305
4389
  ${this.qi('ID')},
4306
4390
  ${this.qi('EntityID')},
@@ -4415,7 +4499,7 @@ export class ManageMetadataBase {
4415
4499
  for (let i = 0; i < inserts.length; i += CHUNK_SIZE) {
4416
4500
  const chunk = inserts.slice(i, i + CHUNK_SIZE);
4417
4501
  try {
4418
- await this.LogSQLBatchAndExecute(pool, chunk, `SQL text to insert ${chunk.length} new entity field(s)`);
4502
+ await this.logSQLBatchAndExecute(pool, chunk, `SQL text to insert ${chunk.length} new entity field(s)`);
4419
4503
  // an error blows up the transaction (all-or-nothing), which is what we want
4420
4504
  }
4421
4505
  catch (e) {
@@ -4449,10 +4533,10 @@ export class ManageMetadataBase {
4449
4533
  * @param relatedEntityNameFieldMap
4450
4534
  * @returns
4451
4535
  */
4452
- async updateEntityFieldRelatedEntityNameFieldMap(pool, entityFieldID, relatedEntityNameFieldMap) {
4536
+ async UpdateEntityFieldRelatedEntityNameFieldMap(pool, entityFieldID, relatedEntityNameFieldMap) {
4453
4537
  try {
4454
- const sSQL = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spUpdateEntityFieldRelatedEntityNameFieldMap', [`'${entityFieldID}'`, `'${relatedEntityNameFieldMap}'`], ['EntityFieldID', 'RelatedEntityNameFieldMap']);
4455
- await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity field related entity name field map for entity field ID ${entityFieldID}`);
4538
+ const sSQL = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spUpdateEntityFieldRelatedEntityNameFieldMap', [`'${entityFieldID}'`, `'${relatedEntityNameFieldMap}'`], ['EntityFieldID', 'RelatedEntityNameFieldMap']);
4539
+ await this.logSQLAndExecute(pool, sSQL, `SQL text to update entity field related entity name field map for entity field ID ${entityFieldID}`);
4456
4540
  return true;
4457
4541
  }
4458
4542
  catch (e) {
@@ -4460,14 +4544,18 @@ export class ManageMetadataBase {
4460
4544
  return false;
4461
4545
  }
4462
4546
  }
4547
+ /** @deprecated Use {@link UpdateEntityFieldRelatedEntityNameFieldMap}. */
4548
+ async updateEntityFieldRelatedEntityNameFieldMap(pool, entityFieldID, relatedEntityNameFieldMap) {
4549
+ return this.UpdateEntityFieldRelatedEntityNameFieldMap(pool, entityFieldID, relatedEntityNameFieldMap);
4550
+ }
4463
4551
  async updateExistingEntitiesFromSchema(pool, excludeSchemas) {
4464
4552
  try {
4465
- const heal = buildHealSchemaRoutineParams({
4466
- authoredExclude: getAuthoredExcludeSchemas(excludeSchemas),
4553
+ const heal = BuildHealSchemaRoutineParams({
4554
+ authoredExclude: GetAuthoredExcludeSchemas(excludeSchemas),
4467
4555
  includeSchemas: configInfo.includeSchemas,
4468
4556
  });
4469
- const sSQL = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spUpdateExistingEntitiesFromSchema', heal.values, heal.names);
4470
- const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to update existing entities from schema`, true);
4557
+ const sSQL = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spUpdateExistingEntitiesFromSchema', heal.Values, heal.Names);
4558
+ const result = await this.logSQLAndExecute(pool, sSQL, `SQL text to update existing entities from schema`, true);
4471
4559
  // result contains the updated entities, and there is a property of each row called Name which has the entity name that was modified
4472
4560
  // add these to the modified entity list if they're not already in there
4473
4561
  if (result && result.length > 0) {
@@ -4483,30 +4571,34 @@ export class ManageMetadataBase {
4483
4571
  /**
4484
4572
  * Adds a list of entity names to the modified entity list if they're not already in there
4485
4573
  */
4486
- static addNewEntitiesToModifiedList(entityNames) {
4574
+ static AddNewEntitiesToModifiedList(entityNames) {
4487
4575
  const distinctEntityNames = [...new Set(entityNames)];
4488
4576
  const newlyModifiedEntityNames = distinctEntityNames.filter((e) => !ManageMetadataBase._modifiedEntityList.includes(e));
4489
4577
  // now make sure that each of these entity names is in the modified entity list
4490
4578
  ManageMetadataBase._modifiedEntityList = ManageMetadataBase._modifiedEntityList.concat(newlyModifiedEntityNames);
4491
4579
  }
4580
+ /** @deprecated Use {@link AddNewEntitiesToModifiedList}. */
4581
+ static addNewEntitiesToModifiedList(entityNames) {
4582
+ return this.AddNewEntitiesToModifiedList(entityNames);
4583
+ }
4492
4584
  async updateExistingEntityFieldsFromSchema(pool, excludeSchemas, entityIDs) {
4493
4585
  try {
4494
4586
  // One SP call regardless of scope: pass the entire entity ID list as a comma-delimited
4495
4587
  // string (mirrors the @ExcludedSchemaNames pattern). The SP fans the list out into
4496
4588
  // a table variable via STRING_SPLIT and joins once. This avoids both per-entity
4497
4589
  // round-trips (slow) and parallel calls (page-level lock contention on EntityField).
4498
- const heal = buildHealSchemaRoutineParams({
4499
- authoredExclude: getAuthoredExcludeSchemas(excludeSchemas),
4590
+ const heal = BuildHealSchemaRoutineParams({
4591
+ authoredExclude: GetAuthoredExcludeSchemas(excludeSchemas),
4500
4592
  includeSchemas: configInfo.includeSchemas,
4501
4593
  entityIDs,
4502
4594
  });
4503
- const sSQL = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spUpdateExistingEntityFieldsFromSchema', heal.values, heal.names);
4595
+ const sSQL = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spUpdateExistingEntityFieldsFromSchema', heal.Values, heal.Names);
4504
4596
  const isScoped = entityIDs !== undefined && entityIDs.length > 0;
4505
4597
  const label = isScoped
4506
4598
  ? `SQL text to update existing entity fields from schema (${entityIDs.length} scoped entities)`
4507
4599
  : `SQL text to update existing entity fields from schema`;
4508
4600
  const before = await this.snapshotEntityFields(pool, entityIDs);
4509
- await this.LogSQLAndExecute(pool, sSQL, label, true);
4601
+ await this.logSQLAndExecute(pool, sSQL, label, true);
4510
4602
  const after = await this.snapshotEntityFields(pool, entityIDs);
4511
4603
  const changes = diffEntityFieldSnapshots(before, after, (e, n) => ManageMetadataBase.isFieldNew(e, n));
4512
4604
  if (changes.length > 0) {
@@ -4537,12 +4629,12 @@ export class ManageMetadataBase {
4537
4629
  */
4538
4630
  async updateSchemaInfoFromDatabase(pool, excludeSchemas) {
4539
4631
  try {
4540
- const heal = buildHealSchemaRoutineParams({
4541
- authoredExclude: getAuthoredExcludeSchemas(excludeSchemas),
4632
+ const heal = BuildHealSchemaRoutineParams({
4633
+ authoredExclude: GetAuthoredExcludeSchemas(excludeSchemas),
4542
4634
  includeSchemas: configInfo.includeSchemas,
4543
4635
  });
4544
- const sSQL = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spUpdateSchemaInfoFromDatabase', heal.values, heal.names);
4545
- const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to sync schema info from database schemas`, true);
4636
+ const sSQL = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spUpdateSchemaInfoFromDatabase', heal.Values, heal.Names);
4637
+ const result = await this.logSQLAndExecute(pool, sSQL, `SQL text to sync schema info from database schemas`, true);
4546
4638
  if (result && result.length > 0) {
4547
4639
  logStatus(` > Updated/created ${result.length} SchemaInfo records`);
4548
4640
  this.cacheSchemaInfoRecords(result);
@@ -4572,7 +4664,7 @@ export class ManageMetadataBase {
4572
4664
  */
4573
4665
  async loadSchemaInfoRecords(pool) {
4574
4666
  try {
4575
- const sSQL = `SELECT * FROM ${this.qs(mj_core_schema(), 'SchemaInfo')}`;
4667
+ const sSQL = `SELECT * FROM ${this.qs(MjCoreSchema(), 'SchemaInfo')}`;
4576
4668
  const result = await this.runQuery(pool, sSQL);
4577
4669
  if (result?.recordset?.length > 0) {
4578
4670
  this.cacheSchemaInfoRecords(result.recordset);
@@ -4638,17 +4730,17 @@ export class ManageMetadataBase {
4638
4730
  // string (mirrors the @ExcludedSchemaNames pattern). The SP fans the list out into
4639
4731
  // a table variable via STRING_SPLIT and filters once. Avoids both per-entity
4640
4732
  // round-trips and parallel calls (page-level lock contention on EntityField).
4641
- const heal = buildHealSchemaRoutineParams({
4642
- authoredExclude: getAuthoredExcludeSchemas(excludeSchemas),
4733
+ const heal = BuildHealSchemaRoutineParams({
4734
+ authoredExclude: GetAuthoredExcludeSchemas(excludeSchemas),
4643
4735
  includeSchemas: configInfo.includeSchemas,
4644
4736
  entityIDs,
4645
4737
  });
4646
- const sSQL = this.dbProvider.callRoutineSQL(mj_core_schema(), 'spDeleteUnneededEntityFields', heal.values, heal.names);
4738
+ const sSQL = this.dbProvider.callRoutineSQL(MjCoreSchema(), 'spDeleteUnneededEntityFields', heal.Values, heal.Names);
4647
4739
  const isScoped = entityIDs !== undefined && entityIDs.length > 0;
4648
4740
  const label = isScoped
4649
4741
  ? `SQL text to delete unneeded entity fields (${entityIDs.length} scoped entities)`
4650
4742
  : `SQL text to delete unneeded entity fields`;
4651
- const result = await this.LogSQLAndExecute(pool, sSQL, label, true);
4743
+ const result = await this.logSQLAndExecute(pool, sSQL, label, true);
4652
4744
  // result contains the DELETED entity fields. Get a distinct list of entity names
4653
4745
  // and add them to the modified entity list if they're not already in there.
4654
4746
  if (result && result.length > 0) {
@@ -4668,13 +4760,13 @@ export class ManageMetadataBase {
4668
4760
  // for the field and sync that up with the EntityFieldValue table. If it is not a simple series of OR statements, we will not be able to parse it and we'll
4669
4761
  // just ignore it.
4670
4762
  const filter = this.dbProvider.getCheckConstraintsSchemaFilter(excludeSchemas);
4671
- const sSQL = `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntityFieldsWithCheckConstraints')}${filter}`;
4763
+ const sSQL = `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntityFieldsWithCheckConstraints')}${filter}`;
4672
4764
  const resultResult = await this.runQuery(pool, sSQL);
4673
4765
  const result = resultResult.recordset;
4674
- const efvSQL = `SELECT * FROM ${this.qs(mj_core_schema(), 'EntityFieldValue')} ORDER BY EntityFieldID, Sequence, Value, ID`;
4766
+ const efvSQL = `SELECT * FROM ${this.qs(MjCoreSchema(), 'EntityFieldValue')} ORDER BY EntityFieldID, Sequence, Value, ID`;
4675
4767
  const allEntityFieldValuesResult = await this.runQuery(pool, efvSQL);
4676
4768
  const allEntityFieldValues = allEntityFieldValuesResult.recordset;
4677
- const efSQL = `SELECT * FROM ${this.qs(mj_core_schema(), 'vwEntityFields')} ORDER BY EntityID, Sequence, Name, ID`;
4769
+ const efSQL = `SELECT * FROM ${this.qs(MjCoreSchema(), 'vwEntityFields')} ORDER BY EntityID, Sequence, Name, ID`;
4678
4770
  const allEntityFieldsResult = await this.runQuery(pool, efSQL);
4679
4771
  const allEntityFields = allEntityFieldsResult.recordset;
4680
4772
  const generationPromises = [];
@@ -4692,25 +4784,30 @@ export class ManageMetadataBase {
4692
4784
  for (const r of columnLevelResults) {
4693
4785
  // now, for each of the constraints we get back here, loop through and evaluate if they're simple and if they're simple, parse and sync with entity field values for that field
4694
4786
  if (r.ConstraintDefinition && r.ConstraintDefinition.length > 0) {
4695
- const parsedValues = this.parseCheckConstraintValues(r.ConstraintDefinition, r.ColumnName, r.EntityName);
4787
+ // parse first, then decide whether THIS field should carry the list (bit, PK singleton guards).
4788
+ // UUIDsEqual, not ===: PG returns lowercase UUIDs and SQL Server uppercase, and a missed
4789
+ // lookup here fails OPEN (the exclusions stop applying), so a case mismatch would be silent.
4790
+ const field = allEntityFields.find((f) => UUIDsEqual(f.ID, r.EntityFieldID));
4791
+ const parsedValues = ManageMetadataBase.valueListForField(field, this.parseCheckConstraintValues(r.ConstraintDefinition, r.ColumnName, r.EntityName));
4696
4792
  if (parsedValues) {
4697
4793
  if (!skipDBUpdate) {
4698
4794
  // we only do this part if we are not skiping the database update as this code will sync values from the CHECK
4699
4795
  // with the EntityFieldValues in the database.
4700
- // Sort values alphabetically to ensure consistent sequences across all databases
4701
- // This guarantees the same value always gets the same sequence number regardless of
4702
- // how SQL Server returns CHECK constraint values (which can vary)
4703
- parsedValues.sort();
4796
+ // Sort the values to ensure consistent sequences across all databases. This guarantees the
4797
+ // same value always gets the same sequence number regardless of how SQL Server returns
4798
+ // CHECK constraint values (which can vary). An all-numeric list sorts numerically so the
4799
+ // dropdown reads 1, 2, 10 rather than the lexical 1, 10, 2 — still fully deterministic.
4800
+ ManageMetadataBase.sortCheckConstraintValues(parsedValues);
4704
4801
  // we have parsed values from the check constraint, so sync them with the entity field values
4705
4802
  await this.syncEntityFieldValues(pool, r.EntityFieldID, parsedValues, allEntityFieldValues);
4706
4803
  // finally, make sure the ValueListType column within the EntityField table is set to "List" because for check constraints we only allow the values specified in the list.
4707
4804
  // check to see if the ValueListType is already set to "List", if not, update it
4708
- const sSQLCheck = `SELECT ValueListType FROM ${this.qs(mj_core_schema(), 'EntityField')} WHERE ID='${r.EntityFieldID}'`;
4805
+ const sSQLCheck = `SELECT ValueListType FROM ${this.qs(MjCoreSchema(), 'EntityField')} WHERE ID='${r.EntityFieldID}'`;
4709
4806
  const checkResultResult = await this.runQuery(pool, sSQLCheck);
4710
4807
  const checkResult = checkResultResult.recordset;
4711
4808
  if (checkResult && checkResult.length > 0 && checkResult[0].ValueListType.trim().toLowerCase() !== 'list') {
4712
- const sSQL = `UPDATE ${this.qs(mj_core_schema(), 'EntityField')} SET ValueListType='List' WHERE ID='${r.EntityFieldID}'`;
4713
- await this.LogSQLAndExecute(pool, sSQL, `SQL text to update ValueListType for entity field ID ${r.EntityFieldID}`);
4809
+ const sSQL = `UPDATE ${this.qs(MjCoreSchema(), 'EntityField')} SET ValueListType='List' WHERE ID='${r.EntityFieldID}'`;
4810
+ await this.logSQLAndExecute(pool, sSQL, `SQL text to update ValueListType for entity field ID ${r.EntityFieldID}`);
4714
4811
  }
4715
4812
  }
4716
4813
  else {
@@ -4752,7 +4849,7 @@ export class ManageMetadataBase {
4752
4849
  * @param pool
4753
4850
  * @param currentUser
4754
4851
  */
4755
- async loadGeneratedCode(pool, currentUser) {
4852
+ async LoadGeneratedCode(pool, currentUser) {
4756
4853
  try {
4757
4854
  // right now we're just doing validator functions which are handled here
4758
4855
  return await this.manageEntityFieldValuesAndValidatorFunctions(pool, [], currentUser, true);
@@ -4762,6 +4859,10 @@ export class ManageMetadataBase {
4762
4859
  return false;
4763
4860
  }
4764
4861
  }
4862
+ /** @deprecated Use {@link LoadGeneratedCode}. */
4863
+ async loadGeneratedCode(pool, currentUser) {
4864
+ return this.LoadGeneratedCode(pool, currentUser);
4865
+ }
4765
4866
  async runValidationGeneration(r, allEntityFields, generateNewCode, currentUser) {
4766
4867
  const generatedFunction = await this.generateValidatorFunctionFromCheckConstraint(r, allEntityFields, currentUser, generateNewCode);
4767
4868
  if (generatedFunction?.success) {
@@ -4842,8 +4943,8 @@ export class ManageMetadataBase {
4842
4943
  for (const ev of existingValues) {
4843
4944
  if (!possibleValues.find(v => v === ev.Value)) {
4844
4945
  // delete the value from the database
4845
- const sSQLDelete = `DELETE FROM ${this.qs(mj_core_schema(), 'EntityFieldValue')} WHERE ID='${ev.ID}'`;
4846
- await this.LogSQLAndExecute(ds, sSQLDelete, `SQL text to delete entity field value ID ${ev.ID}`);
4946
+ const sSQLDelete = `DELETE FROM ${this.qs(MjCoreSchema(), 'EntityFieldValue')} WHERE ID='${ev.ID}'`;
4947
+ await this.logSQLAndExecute(ds, sSQLDelete, `SQL text to delete entity field value ID ${ev.ID}`);
4847
4948
  numRemoved++;
4848
4949
  }
4849
4950
  }
@@ -4854,11 +4955,11 @@ export class ManageMetadataBase {
4854
4955
  // Generate a UUID for this new EntityFieldValue record
4855
4956
  const newId = uuidv4();
4856
4957
  // add the value to the database with explicit ID
4857
- const sSQLInsert = `INSERT INTO ${this.qs(mj_core_schema(), 'EntityFieldValue')}
4958
+ const sSQLInsert = `INSERT INTO ${this.qs(MjCoreSchema(), 'EntityFieldValue')}
4858
4959
  (${this.qi('ID')}, ${this.qi('EntityFieldID')}, ${this.qi('Sequence')}, ${this.qi('Value')}, ${this.qi('Code')}, ${this.qi('__mj_CreatedAt')}, ${this.qi('__mj_UpdatedAt')})
4859
4960
  VALUES
4860
4961
  ('${newId}', '${entityFieldID}', ${1 + possibleValues.indexOf(v)}, '${v}', '${v}', ${this.utcNow()}, ${this.utcNow()})`;
4861
- await this.LogSQLAndExecute(ds, sSQLInsert, `SQL text to insert entity field value with ID ${newId}`);
4962
+ await this.logSQLAndExecute(ds, sSQLInsert, `SQL text to insert entity field value with ID ${newId}`);
4862
4963
  numAdded++;
4863
4964
  }
4864
4965
  }
@@ -4870,8 +4971,8 @@ export class ManageMetadataBase {
4870
4971
  // DB returns a string for Sequence instead of a number.
4871
4972
  if (ev && ev.Sequence != 1 + possibleValues.indexOf(v)) {
4872
4973
  // update the sequence to match the order in the possible values list, if it doesn't already match
4873
- const sSQLUpdate = `UPDATE ${this.qs(mj_core_schema(), 'EntityFieldValue')} SET Sequence=${1 + possibleValues.indexOf(v)} WHERE ID='${ev.ID}'`;
4874
- await this.LogSQLAndExecute(ds, sSQLUpdate, `SQL text to update entity field value sequence`);
4974
+ const sSQLUpdate = `UPDATE ${this.qs(MjCoreSchema(), 'EntityFieldValue')} SET Sequence=${1 + possibleValues.indexOf(v)} WHERE ID='${ev.ID}'`;
4975
+ await this.logSQLAndExecute(ds, sSQLUpdate, `SQL text to update entity field value sequence`);
4875
4976
  numUpdated++;
4876
4977
  }
4877
4978
  }
@@ -4888,10 +4989,24 @@ export class ManageMetadataBase {
4888
4989
  return false;
4889
4990
  }
4890
4991
  }
4992
+ /**
4993
+ * Parses a column-level CHECK constraint into the list of values it permits, or returns null when the
4994
+ * constraint is not a simple value list (a range, a `LEN()` predicate, a mixed condition, and so on).
4995
+ *
4996
+ * Both of the ways `IN (...)` is rendered by SQL Server are handled. String, date and GUID literals come
4997
+ * back quoted — `([Status]='Active' OR [Status]='Inactive')` — but numeric and `bit` literals come back
4998
+ * **unquoted and parenthesized**: `([Level]=(3) OR [Level]=(2) OR [Level]=(1))`. Matching only the quoted
4999
+ * form left a numeric IN-list with no value list at all, and so no dropdown in Explorer either (#3978).
5000
+ * PostgreSQL's `= ANY (ARRAY[...])` rendering is handled separately, by parsePgArrayConstraint.
5001
+ *
5002
+ * A single-value list is captured too: `CHECK (Status='Active')` — which is also how SQL Server renders
5003
+ * `IN ('Active')` — permits exactly one value, which is a one-item list.
5004
+ *
5005
+ * This is a parser, not a policy: it reports what the constraint permits. WHICH fields get a value list
5006
+ * out of that is decided after the fact, by valueListForField.
5007
+ */
4891
5008
  parseCheckConstraintValues(constraintDefinition, fieldName, entityName) {
4892
- // This regex checks for the overall structure including field name and 'OR' sequences
4893
- // SQL Server uses [FieldName]='Value' quoting, PostgreSQL uses "FieldName" or unquoted FieldName
4894
- // We handle both: [FieldName], "FieldName", or bare FieldName
5009
+ // Normalize N'literal' to 'literal' so one pattern handles both.
4895
5010
  // Note: Assuming fieldName does not contain regex special characters; otherwise, it needs to be escaped as well.
4896
5011
  const processedConstraint = constraintDefinition.replace(/(^|[=(\s])N'([^']*)'/g, "$1'$2'");
4897
5012
  // PostgreSQL ANY (ARRAY[...]) pattern:
@@ -4904,46 +5019,92 @@ export class ManageMetadataBase {
4904
5019
  // Build a regex fragment that matches the field name in any quoting style:
4905
5020
  // [FieldName] (SQL Server) or "FieldName" (PostgreSQL) or bare FieldName
4906
5021
  const quotedField = `(?:\\[${fieldName}\\]|"${fieldName}"|${fieldName})`;
4907
- // Check for nested pattern: (Field IS NULL OR (Field='Value1' OR ...))
4908
- const nestedNullRegex = new RegExp(`^\\(${quotedField} IS NULL OR \\(${quotedField}='[^']+'(?: OR ${quotedField}='[^']+?')+\\)\\)$`);
4909
- if (nestedNullRegex.test(processedConstraint)) {
4910
- // Extract values from nested pattern - same extraction logic works
4911
- const valueRegex = new RegExp(`${quotedField}='([^']+)\'`, 'g');
4912
- let match;
4913
- const possibleValues = [];
4914
- while ((match = valueRegex.exec(processedConstraint)) !== null) {
4915
- if (match.index === valueRegex.lastIndex) {
4916
- valueRegex.lastIndex++;
4917
- }
4918
- if (match[1]) {
4919
- possibleValues.push(match[1]);
4920
- }
5022
+ // One literal, either quoting style. The unquoted numeric forms are the ones sys.check_constraints
5023
+ // actually stores: (3) (-1) (1.00) (2.5) (1.0000000000000000e+030) and (12345678901234567890.) —
5024
+ // an integral literal with a trailing point once the precision is large enough.
5025
+ const literal = `(?:'[^']+'|${ManageMetadataBase.NUMERIC_CHECK_LITERAL})`;
5026
+ const assignment = `${quotedField}=${literal}`;
5027
+ // Nested pattern: (Field IS NULL OR (Field=<literal> OR ...)) — how `X IS NULL OR X IN (...)` renders
5028
+ const nestedNullRegex = new RegExp(`^\\(${quotedField} IS NULL OR \\(${assignment}(?: OR ${assignment})*\\)\\)$`);
5029
+ // Standard pattern, with an optional trailing IS NULL: (Field=<literal> OR ... [OR Field IS NULL])
5030
+ const structureRegex = new RegExp(`^\\(${assignment}(?: OR ${assignment})*(?: OR ${quotedField} IS NULL)?\\)$`);
5031
+ if (!nestedNullRegex.test(processedConstraint) && !structureRegex.test(processedConstraint)) {
5032
+ return null;
5033
+ }
5034
+ // Extract the values: group 1 is a quoted literal, group 2 an unquoted numeric one.
5035
+ const valueRegex = new RegExp(`${quotedField}=(?:'([^']+)'|(${ManageMetadataBase.NUMERIC_CHECK_LITERAL}))`, 'g');
5036
+ const possibleValues = [];
5037
+ let match;
5038
+ while ((match = valueRegex.exec(processedConstraint)) !== null) {
5039
+ // This is necessary to avoid infinite loops with zero-width matches
5040
+ if (match.index === valueRegex.lastIndex) {
5041
+ valueRegex.lastIndex++;
5042
+ }
5043
+ const value = match[1] ?? (match[2] ? ManageMetadataBase.normalizeNumericCheckLiteral(match[2]) : undefined);
5044
+ if (value && value.length > 0) {
5045
+ possibleValues.push(value);
4921
5046
  }
4922
- return possibleValues.length > 0 ? possibleValues : null;
4923
5047
  }
4924
- // Check for standard pattern with optional trailing IS NULL
4925
- const structureRegex = new RegExp(`^\\(${quotedField}='[^']+'(?: OR ${quotedField}='[^']+?')+(?: OR ${quotedField} IS NULL)?\\)$`);
4926
- if (!structureRegex.test(processedConstraint)) {
5048
+ // An empty result is not a value list — returning [] here would set ValueListType='List' on a field
5049
+ // with no EntityFieldValue rows behind it.
5050
+ return possibleValues.length > 0 ? possibleValues : null;
5051
+ }
5052
+ /**
5053
+ * The value list to actually store for a field, or null when a parsed list should NOT become
5054
+ * EntityFieldValue rows (and so should not become a dropdown in Explorer either).
5055
+ *
5056
+ * Runs AFTER parsing rather than gating it, so each exclusion is no broader than its own reason and
5057
+ * neither one can drop a list that was captured before #3978:
5058
+ *
5059
+ * - `bit`: `IN (0,1)` is vacuous — bit already permits exactly 0 and 1 — and `= 1` is a validator,
5060
+ * not a two-item dropdown over what renders as a checkbox. Every bit list is rendered unquoted, so
5061
+ * none of them was reachable before this change and nothing regresses.
5062
+ * - a PRIMARY KEY carrying a SINGLE value: `CHECK (ID=1)` is the single-row-table guard MJ's own
5063
+ * sequence tables use — a structural invariant, not a set of values a user picks from. A
5064
+ * MULTI-value list on a primary key is left alone: `CHECK (Code IN ('US','CA'))` on a natural-key
5065
+ * PK is a real domain list, it was captured before this change, and CodeGen also runs over customer
5066
+ * schemas where natural-key primary keys are ordinary.
5067
+ *
5068
+ * A field whose metadata row was not found keeps whatever parsed, which is the pre-#3978 behaviour.
5069
+ */
5070
+ static valueListForField(field, parsedValues) {
5071
+ if (!parsedValues || !field) {
5072
+ return parsedValues;
5073
+ }
5074
+ if (field.Type?.trim().toLowerCase() === 'bit') {
4927
5075
  return null;
4928
5076
  }
4929
- else {
4930
- // Regular expression to match the values within the single quotes specifically for the field
4931
- const valueRegex = new RegExp(`${quotedField}='([^']+)\'`, 'g');
4932
- let match;
4933
- const possibleValues = [];
4934
- // Use regex to find matches and extract the values
4935
- while ((match = valueRegex.exec(processedConstraint)) !== null) {
4936
- // This is necessary to avoid infinite loops with zero-width matches
4937
- if (match.index === valueRegex.lastIndex) {
4938
- valueRegex.lastIndex++;
4939
- }
4940
- // The first captured group contains the value
4941
- if (match[1]) {
4942
- possibleValues.push(match[1]);
4943
- }
4944
- }
4945
- return possibleValues;
5077
+ if (field.IsPrimaryKey && parsedValues.length === 1) {
5078
+ return null;
4946
5079
  }
5080
+ return parsedValues;
5081
+ }
5082
+ /**
5083
+ * Sorts a parsed value list in place, so the sequence a value gets is stable across databases and runs.
5084
+ * An all-numeric list is compared as numbers (1, 2, 10); anything else keeps the default lexical order.
5085
+ *
5086
+ * Keyed off the VALUES, not the column type — so a string column whose list happens to be all-numeric
5087
+ * (`CHECK (Code IN ('1','2','10'))` on nvarchar) would re-sequence too. Measured over every column CHECK
5088
+ * constraint in a live 6.x database (MJ core + BizApps accounting/orders): of the 431 lists the previous
5089
+ * parser already captured, ZERO re-sequence under this comparison. If one ever turns up and the churn is
5090
+ * unwanted, gate `allNumeric` on the field being numeric as well.
5091
+ */
5092
+ static sortCheckConstraintValues(values) {
5093
+ const allNumeric = values.length > 0 && values.every(v => /^-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?$/.test(v));
5094
+ return allNumeric ? values.sort((a, b) => Number(a) - Number(b)) : values.sort();
5095
+ }
5096
+ /**
5097
+ * A numeric literal as SQL Server renders it inside a CHECK constraint: parenthesized, unquoted, and
5098
+ * possibly signed, fractional or exponential — `(3)`, `(-1)`, `(1.00)`, `(2.5)`, `(1.0e+030)`.
5099
+ */
5100
+ static { this.NUMERIC_CHECK_LITERAL = `\\(-?\\d+(?:\\.\\d*)?(?:[eE][+-]?\\d+)?\\)`; }
5101
+ /**
5102
+ * Turns a matched numeric literal into the value to store: strips the wrapping parentheses, and the
5103
+ * trailing point SQL Server adds to a large integral literal (`(12345678901234567890.)`).
5104
+ */
5105
+ static normalizeNumericCheckLiteral(literal) {
5106
+ const inner = literal.slice(1, -1);
5107
+ return inner.endsWith('.') ? inner.slice(0, -1) : inner;
4947
5108
  }
4948
5109
  /**
4949
5110
  * Parses PostgreSQL ANY (ARRAY[...]) CHECK constraint syntax.
@@ -5006,7 +5167,7 @@ export class ManageMetadataBase {
5006
5167
  // it — user-visible AND editable, letting someone mutate the snapshot the refresher overwrites. The
5007
5168
  // wrapper view is already excluded (it carries the minted entity, so EntityID IS NOT NULL). The outer
5008
5169
  // view is aliased `t` so the correlated MaterializedResult subquery can't resolve columns ambiguously.
5009
- const coreSchema = mj_core_schema();
5170
+ const coreSchema = MjCoreSchema();
5010
5171
  // Gate the MaterializedResult reference on the table actually existing. Referencing it unconditionally
5011
5172
  // would throw — breaking new-entity creation for EVERY entity — on any DB where MaterializedResult
5012
5173
  // isn't present yet (e.g. the PostgreSQL parallel world's object-availability lag). If it's absent we
@@ -5150,7 +5311,7 @@ export class ManageMetadataBase {
5150
5311
  const result = await ag.generateEntityName(newEntity.TableName, currentUser);
5151
5312
  // Checked here as well as inside generateEntityName: a subclass or a stub can return anything, and
5152
5313
  // a non-name that reaches the INSERT fails it (see createNewEntity's catch for why that used to be silent).
5153
- if (result && isPlausibleEntityName(result.entityName)) {
5314
+ if (result && IsPlausibleEntityName(result.entityName)) {
5154
5315
  return this.markupEntityName(newEntity.SchemaName, result.entityName);
5155
5316
  }
5156
5317
  else {
@@ -5215,7 +5376,7 @@ export class ManageMetadataBase {
5215
5376
  return configInfo.newEntityDefaults?.NameRulesBySchema?.find(r => {
5216
5377
  let schemaNameToUse = r.SchemaName;
5217
5378
  if (schemaNameToUse?.trim().toLowerCase() === '${mj_core_schema}') {
5218
- schemaNameToUse = mj_core_schema();
5379
+ schemaNameToUse = MjCoreSchema();
5219
5380
  }
5220
5381
  return schemaNameToUse.trim().toLowerCase() === schemaName.trim().toLowerCase();
5221
5382
  });
@@ -5265,7 +5426,7 @@ export class ManageMetadataBase {
5265
5426
  // process a single new entity
5266
5427
  let newEntityName = await this.createNewEntityName(newEntity, currentUser);
5267
5428
  const newEntityDisplayName = this.createNewEntityDisplayName(newEntity, newEntityName);
5268
- const { name: uniqueName, suffix: uniqueSuffix } = this.resolveUniqueEntityName(newEntityName, newEntity.SchemaName, md.Entities.map(e => e.Name));
5429
+ const { name: uniqueName, suffix: uniqueSuffix } = this.ResolveUniqueEntityName(newEntityName, newEntity.SchemaName, md.Entities.map(e => e.Name));
5269
5430
  if (uniqueName !== newEntityName) {
5270
5431
  LogError(` >>>> WARNING: Entity name already exists, so using ${uniqueName} instead. If you did not intend for this, please rename the ${newEntity.SchemaName}.${newEntity.TableName} table in the database.`);
5271
5432
  }
@@ -5274,7 +5435,7 @@ export class ManageMetadataBase {
5274
5435
  const isNewSchema = await this.isSchemaNew(pool, newEntity.SchemaName);
5275
5436
  const newEntityID = this.createNewUUID();
5276
5437
  const sSQLInsert = this.createNewEntityInsertSQL(newEntityID, newEntityName, newEntity, suffix, newEntityDisplayName);
5277
- await this.LogSQLAndExecute(pool, sSQLInsert, `SQL generated to create new entity ${newEntityName}`);
5438
+ await this.logSQLAndExecute(pool, sSQLInsert, `SQL generated to create new entity ${newEntityName}`);
5278
5439
  // if we get here we created a new entity safely, otherwise we get exception
5279
5440
  // add it to the new entity list
5280
5441
  ManageMetadataBase.newEntityList.push(newEntityName);
@@ -5307,10 +5468,10 @@ export class ManageMetadataBase {
5307
5468
  if (configInfo.newEntityDefaults.AddToApplicationWithSchemaName) {
5308
5469
  // only do this if the configuration setting is set to add new entities to applications for schema names
5309
5470
  for (const appUUID of apps) {
5310
- const sSQLInsertApplicationEntity = `INSERT INTO ${this.qs(mj_core_schema(), 'ApplicationEntity')}
5471
+ const sSQLInsertApplicationEntity = `INSERT INTO ${this.qs(MjCoreSchema(), 'ApplicationEntity')}
5311
5472
  (${this.qi('ApplicationID')}, ${this.qi('EntityID')}, ${this.qi('Sequence')}, ${this.qi('__mj_CreatedAt')}, ${this.qi('__mj_UpdatedAt')}) VALUES
5312
- ('${appUUID}', '${newEntityID}', (SELECT COALESCE(MAX(${this.qi('Sequence')}),0)+1 FROM ${this.qs(mj_core_schema(), 'ApplicationEntity')} WHERE ${this.qi('ApplicationID')} = '${appUUID}'), ${this.utcNow()}, ${this.utcNow()})`;
5313
- await this.LogSQLAndExecute(pool, sSQLInsertApplicationEntity, `SQL generated to add new entity ${newEntityName} to application ID: '${appUUID}'`);
5473
+ ('${appUUID}', '${newEntityID}', (SELECT COALESCE(MAX(${this.qi('Sequence')}),0)+1 FROM ${this.qs(MjCoreSchema(), 'ApplicationEntity')} WHERE ${this.qi('ApplicationID')} = '${appUUID}'), ${this.utcNow()}, ${this.utcNow()})`;
5474
+ await this.logSQLAndExecute(pool, sSQLInsertApplicationEntity, `SQL generated to add new entity ${newEntityName} to application ID: '${appUUID}'`);
5314
5475
  }
5315
5476
  }
5316
5477
  else {
@@ -5329,7 +5490,7 @@ export class ManageMetadataBase {
5329
5490
  const RoleID = md.Roles.find(r => r.Name.trim().toLowerCase() === p.RoleName.trim().toLowerCase())?.ID;
5330
5491
  if (RoleID) {
5331
5492
  const sSQLInsertPermission = this.buildEntityPermissionInsertSQL(newEntityID, RoleID, p.CanRead, p.CanCreate, p.CanUpdate, p.CanDelete);
5332
- await this.LogSQLAndExecute(pool, sSQLInsertPermission, `SQL generated to add new permission for entity ${newEntityName} for role ${p.RoleName}`);
5493
+ await this.logSQLAndExecute(pool, sSQLInsertPermission, `SQL generated to add new permission for entity ${newEntityName} for role ${p.RoleName}`);
5333
5494
  }
5334
5495
  else
5335
5496
  LogError(` >>>> ERROR: Unable to find Role ID for role ${p.RoleName} to add permissions for new entity ${newEntityName}`);
@@ -5363,7 +5524,7 @@ export class ManageMetadataBase {
5363
5524
  async isSchemaNew(pool, schemaName) {
5364
5525
  // check to see if there are any entities in the db with this schema name
5365
5526
  // Quote the alias so PostgreSQL preserves PascalCase (unquoted aliases are lowercased in PG)
5366
- const sSQL = `SELECT COUNT(*) AS ${this.qi('Count')} FROM ${this.qs(mj_core_schema(), 'Entity')} WHERE ${this.qi('SchemaName')} = '${schemaName}'`;
5527
+ const sSQL = `SELECT COUNT(*) AS ${this.qi('Count')} FROM ${this.qs(MjCoreSchema(), 'Entity')} WHERE ${this.qi('SchemaName')} = '${schemaName}'`;
5367
5528
  const resultResult = await this.runQuery(pool, sSQL);
5368
5529
  const result = resultResult.recordset;
5369
5530
  // Use Number() because PG returns COUNT(*) as bigint, which the pg driver delivers as a string
@@ -5395,7 +5556,7 @@ export class ManageMetadataBase {
5395
5556
  // Guard the INSERT with an existence check so the emitted (and re-runnable) migration SQL is
5396
5557
  // idempotent. drop-schema clears an app's ApplicationEntity/EntityPermission/Entity rows but NOT
5397
5558
  // its Application row, so replaying this block would otherwise collide on the Application PK.
5398
- const appCheckQuery = `SELECT 1 FROM ${this.qs(mj_core_schema(), 'Application')} WHERE ${this.qi('ID')} = '${appID}'`;
5559
+ const appCheckQuery = `SELECT 1 FROM ${this.qs(MjCoreSchema(), 'Application')} WHERE ${this.qi('ID')} = '${appID}'`;
5399
5560
  // Schema-named bucket apps are plumbing (entity links, role grants, SchemaAutoAddNewEntities),
5400
5561
  // not products — hide them from new users. Application.DefaultForNewUser defaults to 1 in the
5401
5562
  // DB, so omitting the column here is what put raw '__mj_*'-named apps in every new user's
@@ -5404,10 +5565,10 @@ export class ManageMetadataBase {
5404
5565
  // statement in PG's `DO $$ ... $$` block, and the identifier auto-quoter skips dollar-quoted
5405
5566
  // blocks wholesale (they can legally contain arbitrary text), so nothing downstream will quote
5406
5567
  // these for us. Bare `ID` reaches PG folded to `id` and the INSERT fails on every run.
5407
- const appInsert = `INSERT INTO ${this.qs(mj_core_schema(), 'Application')} (${this.qi('ID')}, ${this.qi('Name')}, ${this.qi('Description')}, ${this.qi('SchemaAutoAddNewEntities')}, ${this.qi('Path')}, ${this.qi('AutoUpdatePath')}, ${this.qi('DefaultForNewUser')})
5568
+ const appInsert = `INSERT INTO ${this.qs(MjCoreSchema(), 'Application')} (${this.qi('ID')}, ${this.qi('Name')}, ${this.qi('Description')}, ${this.qi('SchemaAutoAddNewEntities')}, ${this.qi('Path')}, ${this.qi('AutoUpdatePath')}, ${this.qi('DefaultForNewUser')})
5408
5569
  VALUES ('${appID}', '${appName}', 'Generated for schema', '${schemaName}', '${path}', ${this.dialect.BooleanLiteral(true)}, ${this.dialect.BooleanLiteral(false)})`;
5409
5570
  const sSQL = this.conditionalInsert(appCheckQuery, appInsert);
5410
- await this.LogSQLAndExecute(pool, sSQL, `SQL generated to create new application ${appName}`);
5571
+ await this.logSQLAndExecute(pool, sSQL, `SQL generated to create new application ${appName}`);
5411
5572
  LogStatus(`Created new application ${appName} with Path: ${path}`);
5412
5573
  // Auto-assign default roles to the new application
5413
5574
  await this.addDefaultRolesForApplication(pool, appID, appName);
@@ -5435,12 +5596,12 @@ export class ManageMetadataBase {
5435
5596
  // This covers BOTH re-running the block after drop-schema (which leaves ApplicationRole rows
5436
5597
  // intact) AND the collision with the Integration role that manageMetadata recreates
5437
5598
  // out-of-band — either path would otherwise violate UQ_ApplicationRole_App_Role.
5438
- const roleCheckQuery = `SELECT 1 FROM ${this.qs(mj_core_schema(), 'ApplicationRole')} WHERE ${this.qi('ApplicationID')} = '${appId}' AND ${this.qi('RoleID')} = '${role.ID}'`;
5439
- const roleInsert = `INSERT INTO ${this.qs(mj_core_schema(), 'ApplicationRole')}
5599
+ const roleCheckQuery = `SELECT 1 FROM ${this.qs(MjCoreSchema(), 'ApplicationRole')} WHERE ${this.qi('ApplicationID')} = '${appId}' AND ${this.qi('RoleID')} = '${role.ID}'`;
5600
+ const roleInsert = `INSERT INTO ${this.qs(MjCoreSchema(), 'ApplicationRole')}
5440
5601
  (${this.qi('ApplicationID')}, ${this.qi('RoleID')}, ${this.qi('CanAccess')}, ${this.qi('CanAdmin')}) VALUES
5441
5602
  ('${appId}', '${role.ID}', ${this.boolLit(roleDef.CanAccess)}, ${this.boolLit(roleDef.CanAdmin)})`;
5442
5603
  const sSQLInsert = this.conditionalInsert(roleCheckQuery, roleInsert);
5443
- await this.LogSQLAndExecute(pool, sSQLInsert, `Adding role ${roleDef.RoleName} to application ${appName}`);
5604
+ await this.logSQLAndExecute(pool, sSQLInsert, `Adding role ${roleDef.RoleName} to application ${appName}`);
5444
5605
  }
5445
5606
  else {
5446
5607
  LogError(`Unable to find Role '${roleDef.RoleName}' for application ${appName}`);
@@ -5448,14 +5609,14 @@ export class ManageMetadataBase {
5448
5609
  }
5449
5610
  }
5450
5611
  async applicationExists(pool, applicationName) {
5451
- const sSQL = `SELECT ID FROM ${this.qs(mj_core_schema(), 'Application')} WHERE Name = '${applicationName}'`;
5612
+ const sSQL = `SELECT ID FROM ${this.qs(MjCoreSchema(), 'Application')} WHERE Name = '${applicationName}'`;
5452
5613
  const resultResult = await this.runQuery(pool, sSQL);
5453
5614
  const result = resultResult.recordset;
5454
5615
  return result && result.length > 0 ? result[0].ID.length > 0 : false;
5455
5616
  }
5456
5617
  async getApplicationIDForSchema(pool, schemaName) {
5457
5618
  // get all the apps each time from DB as we might be adding, don't use Metadata here for that reason
5458
- const sSQL = `SELECT ID, Name, SchemaAutoAddNewEntities FROM ${this.qs(mj_core_schema(), 'vwApplications')}`;
5619
+ const sSQL = `SELECT ID, Name, SchemaAutoAddNewEntities FROM ${this.qs(MjCoreSchema(), 'vwApplications')}`;
5459
5620
  const resultResult = await this.runQuery(pool, sSQL);
5460
5621
  const result = resultResult.recordset;
5461
5622
  if (!result || result.length === 0) {
@@ -5497,10 +5658,10 @@ export class ManageMetadataBase {
5497
5658
  if (apps && apps.length > 0) {
5498
5659
  if (configInfo.newEntityDefaults.AddToApplicationWithSchemaName) {
5499
5660
  for (const appUUID of apps) {
5500
- const sSQLInsert = `INSERT INTO ${this.qs(mj_core_schema(), 'ApplicationEntity')}
5661
+ const sSQLInsert = `INSERT INTO ${this.qs(MjCoreSchema(), 'ApplicationEntity')}
5501
5662
  (${this.qi('ApplicationID')}, ${this.qi('EntityID')}, ${this.qi('Sequence')}, ${this.qi('__mj_CreatedAt')}, ${this.qi('__mj_UpdatedAt')}) VALUES
5502
- ('${appUUID}', '${entityId}', (SELECT COALESCE(MAX(${this.qi('Sequence')}),0)+1 FROM ${this.qs(mj_core_schema(), 'ApplicationEntity')} WHERE ${this.qi('ApplicationID')} = '${appUUID}'), ${this.utcNow()}, ${this.utcNow()})`;
5503
- await this.LogSQLAndExecute(pool, sSQLInsert, `SQL generated to add entity ${entityName} to application ID: '${appUUID}'`);
5663
+ ('${appUUID}', '${entityId}', (SELECT COALESCE(MAX(${this.qi('Sequence')}),0)+1 FROM ${this.qs(MjCoreSchema(), 'ApplicationEntity')} WHERE ${this.qi('ApplicationID')} = '${appUUID}'), ${this.utcNow()}, ${this.utcNow()})`;
5664
+ await this.logSQLAndExecute(pool, sSQLInsert, `SQL generated to add entity ${entityName} to application ID: '${appUUID}'`);
5504
5665
  }
5505
5666
  }
5506
5667
  }
@@ -5526,7 +5687,7 @@ export class ManageMetadataBase {
5526
5687
  * valid on both SQL Server and PostgreSQL, so no provider branch is needed.
5527
5688
  */
5528
5689
  buildEntityPermissionInsertSQL(entityId, roleId, canRead, canCreate, canUpdate, canDelete) {
5529
- const table = this.qs(mj_core_schema(), 'EntityPermission');
5690
+ const table = this.qs(MjCoreSchema(), 'EntityPermission');
5530
5691
  const entityLit = this.uuidLit(entityId);
5531
5692
  const roleLit = this.uuidLit(roleId);
5532
5693
  return `INSERT INTO ${table}
@@ -5551,7 +5712,7 @@ export class ManageMetadataBase {
5551
5712
  const RoleID = md.Roles.find(r => r.Name.trim().toLowerCase() === p.RoleName.trim().toLowerCase())?.ID;
5552
5713
  if (RoleID) {
5553
5714
  const sSQLInsert = this.buildEntityPermissionInsertSQL(entityId, RoleID, p.CanRead, p.CanCreate, p.CanUpdate, p.CanDelete);
5554
- await this.LogSQLAndExecute(pool, sSQLInsert, `SQL generated to add permission for entity ${entityName} for role ${p.RoleName}`);
5715
+ await this.logSQLAndExecute(pool, sSQLInsert, `SQL generated to add permission for entity ${entityName} for role ${p.RoleName}`);
5555
5716
  }
5556
5717
  else {
5557
5718
  LogError(` >>>> ERROR: Unable to find Role ID for role ${p.RoleName} to add permissions for entity ${entityName}`);
@@ -5713,7 +5874,7 @@ export class ManageMetadataBase {
5713
5874
  continue;
5714
5875
  }
5715
5876
  const sSQLInsert = this.buildEntityPermissionInsertSQL(entityId, roleId, true, false, false, false);
5716
- await this.LogSQLAndExecute(pool, sSQLInsert, `SQL generated to add read permission for materialized entity ${entityName} for role ${p.RoleName}`);
5877
+ await this.logSQLAndExecute(pool, sSQLInsert, `SQL generated to add read permission for materialized entity ${entityName} for role ${p.RoleName}`);
5717
5878
  }
5718
5879
  }
5719
5880
  /**
@@ -5723,8 +5884,8 @@ export class ManageMetadataBase {
5723
5884
  * human authors protection. Non-destructive (rows are kept, just flipped) so the grant can be restored.
5724
5885
  */
5725
5886
  async revokeMaterializedEntityReadAccess(pool, entityId, entityLabel, reason) {
5726
- const sql = `UPDATE ${this.qs(mj_core_schema(), 'EntityPermission')} SET ${this.qi('CanRead')}=${this.boolLit(false)} WHERE ${this.qi('EntityID')}='${entityId}' AND ${this.qi('CanRead')}=${this.boolLit(true)}`;
5727
- await this.LogSQLAndExecute(pool, sql, `Revoke read access on materialized entity "${entityLabel}" (${reason})`);
5887
+ const sql = `UPDATE ${this.qs(MjCoreSchema(), 'EntityPermission')} SET ${this.qi('CanRead')}=${this.boolLit(false)} WHERE ${this.qi('EntityID')}='${entityId}' AND ${this.qi('CanRead')}=${this.boolLit(true)}`;
5888
+ await this.logSQLAndExecute(pool, sql, `Revoke read access on materialized entity "${entityLabel}" (${reason})`);
5728
5889
  }
5729
5890
  /**
5730
5891
  * ROLE-RLS LAYER ONLY — true if any of the entity's role permissions carries a non-empty `ReadRLSFilterID`.
@@ -5795,7 +5956,7 @@ export class ManageMetadataBase {
5795
5956
  for (const table of ['APIKeyScope', 'APIApplicationScope']) {
5796
5957
  if (!(await this.columnExistsInCoreSchema(pool, table, 'RowFilterID')))
5797
5958
  continue;
5798
- const res = await this.runQuery(pool, `SELECT DISTINCT ${this.qi('ResourcePattern')} AS ResourcePattern FROM ${this.qs(mj_core_schema(), table)} WHERE ${this.qi('RowFilterID')} IS NOT NULL`);
5959
+ const res = await this.runQuery(pool, `SELECT DISTINCT ${this.qi('ResourcePattern')} AS ResourcePattern FROM ${this.qs(MjCoreSchema(), table)} WHERE ${this.qi('RowFilterID')} IS NOT NULL`);
5799
5960
  for (const row of res.recordset ?? []) {
5800
5961
  const r = row;
5801
5962
  const pattern = (r.ResourcePattern ?? r.resourcepattern) ?? '';
@@ -5828,7 +5989,7 @@ export class ManageMetadataBase {
5828
5989
  if (cached !== undefined)
5829
5990
  return cached;
5830
5991
  const sql = `SELECT COUNT(*) AS ColExists FROM INFORMATION_SCHEMA.COLUMNS ` +
5831
- `WHERE TABLE_SCHEMA = '${mj_core_schema()}' AND TABLE_NAME = '${table}' AND COLUMN_NAME = '${column}'`;
5992
+ `WHERE TABLE_SCHEMA = '${MjCoreSchema()}' AND TABLE_NAME = '${table}' AND COLUMN_NAME = '${column}'`;
5832
5993
  const result = await this.runQuery(pool, sql);
5833
5994
  const row = (result.recordset?.[0] ?? {});
5834
5995
  const cnt = row.ColExists ?? row.colexists ?? 0;
@@ -5848,14 +6009,14 @@ export class ManageMetadataBase {
5848
6009
  if (sourceEntities.length === 0)
5849
6010
  return 0; // no sources to intersect against — the RLS/empty gate handles this
5850
6011
  const roleCanReadAllSources = (roleId) => sourceEntities.every((e) => e.Permissions.some((p) => UUIDsEqual(p.RoleID, roleId) && p.CanRead));
5851
- const grants = await this.runQuery(pool, `SELECT ${this.qi('RoleID')} FROM ${this.qs(mj_core_schema(), 'EntityPermission')} WHERE ${this.qi('EntityID')}='${entityId}' AND ${this.qi('CanRead')}=${this.boolLit(true)}`);
6012
+ const grants = await this.runQuery(pool, `SELECT ${this.qi('RoleID')} FROM ${this.qs(MjCoreSchema(), 'EntityPermission')} WHERE ${this.qi('EntityID')}='${entityId}' AND ${this.qi('CanRead')}=${this.boolLit(true)}`);
5852
6013
  let revoked = 0;
5853
6014
  for (const g of grants.recordset) {
5854
6015
  const roleId = g.RoleID;
5855
6016
  if (!roleId)
5856
6017
  continue;
5857
6018
  if (!roleCanReadAllSources(roleId)) {
5858
- await this.LogSQLAndExecute(pool, `UPDATE ${this.qs(mj_core_schema(), 'EntityPermission')} SET ${this.qi('CanRead')}=${this.boolLit(false)} WHERE ${this.qi('EntityID')}='${entityId}' AND ${this.qi('RoleID')}='${roleId}' AND ${this.qi('CanRead')}=${this.boolLit(true)}`, `Narrow read grant on materialized entity "${entityLabel}": role ${roleId} can no longer read every source (C2 intersection re-narrowed)`);
6019
+ await this.logSQLAndExecute(pool, `UPDATE ${this.qs(MjCoreSchema(), 'EntityPermission')} SET ${this.qi('CanRead')}=${this.boolLit(false)} WHERE ${this.qi('EntityID')}='${entityId}' AND ${this.qi('RoleID')}='${roleId}' AND ${this.qi('CanRead')}=${this.boolLit(true)}`, `Narrow read grant on materialized entity "${entityLabel}": role ${roleId} can no longer read every source (C2 intersection re-narrowed)`);
5859
6020
  revoked++;
5860
6021
  }
5861
6022
  }
@@ -5885,7 +6046,7 @@ export class ManageMetadataBase {
5885
6046
  * earlier in this run (which are not in metadata yet)
5886
6047
  * @returns the free name and the suffix used to reach it ('' when the name was already free)
5887
6048
  */
5888
- resolveUniqueEntityName(desiredName, schemaName, takenNames) {
6049
+ ResolveUniqueEntityName(desiredName, schemaName, takenNames) {
5889
6050
  const taken = new Set([...takenNames, ...ManageMetadataBase.newEntityList].map(n => n.toLowerCase()));
5890
6051
  const isTaken = (candidate) => taken.has(candidate.toLowerCase());
5891
6052
  if (!isTaken(desiredName)) {
@@ -5904,13 +6065,17 @@ export class ManageMetadataBase {
5904
6065
  }
5905
6066
  return { name, suffix };
5906
6067
  }
6068
+ /** @deprecated Use {@link ResolveUniqueEntityName}. */
6069
+ resolveUniqueEntityName(desiredName, schemaName, takenNames) {
6070
+ return this.ResolveUniqueEntityName(desiredName, schemaName, takenNames);
6071
+ }
5907
6072
  createNewEntityInsertSQL(newEntityUUID, newEntityName, newEntity, newEntitySuffix, newEntityDisplayName) {
5908
6073
  const newEntityDefaults = configInfo.newEntityDefaults;
5909
6074
  const newEntityDescriptionEscaped = newEntity.EntityDescription ? `'${newEntity.EntityDescription.replace(/'/g, "''")}'` : null;
5910
6075
  const allowCaching = this.resolveAllowCachingForSchema(newEntity.SchemaName);
5911
6076
  const q = (name) => this.qi(name);
5912
6077
  const sSQLInsert = `
5913
- INSERT INTO ${this.qs(mj_core_schema(), 'Entity')} (
6078
+ INSERT INTO ${this.qs(MjCoreSchema(), 'Entity')} (
5914
6079
  ${q('ID')},
5915
6080
  ${q('Name')},
5916
6081
  ${q('DisplayName')},
@@ -5971,7 +6136,7 @@ export class ManageMetadataBase {
5971
6136
  const match = overrides.find(entry => {
5972
6137
  let candidate = entry.SchemaName;
5973
6138
  if (candidate?.trim().toLowerCase() === '${mj_core_schema}') {
5974
- candidate = mj_core_schema();
6139
+ candidate = MjCoreSchema();
5975
6140
  }
5976
6141
  return candidate.trim().toLowerCase() === schemaName.trim().toLowerCase();
5977
6142
  });
@@ -6032,7 +6197,7 @@ export class ManageMetadataBase {
6032
6197
  e.FullTextSearchEnabled,
6033
6198
  e.AutoUpdateFullTextSearch
6034
6199
  FROM
6035
- ${this.qs(mj_core_schema(), 'vwEntities')} e
6200
+ ${this.qs(MjCoreSchema(), 'vwEntities')} e
6036
6201
  WHERE
6037
6202
  ${whereClause}
6038
6203
  ORDER BY
@@ -6081,7 +6246,7 @@ export class ManageMetadataBase {
6081
6246
  ef.AutoUpdateFullTextSearch,
6082
6247
  ef.${this.qi('Length')} as MaxLength
6083
6248
  FROM
6084
- ${this.qs(mj_core_schema(), 'vwEntityFields')} ef
6249
+ ${this.qs(MjCoreSchema(), 'vwEntityFields')} ef
6085
6250
  WHERE
6086
6251
  ef.EntityID IN (${entityIds})
6087
6252
  ORDER BY
@@ -6099,7 +6264,7 @@ export class ManageMetadataBase {
6099
6264
  es.Name,
6100
6265
  es.Value
6101
6266
  FROM
6102
- ${this.qs(mj_core_schema(), 'EntitySetting')} es
6267
+ ${this.qs(MjCoreSchema(), 'EntitySetting')} es
6103
6268
  WHERE
6104
6269
  es.EntityID IN (${entityIds})
6105
6270
  AND es.Name = 'FieldCategoryInfo'
@@ -6174,13 +6339,13 @@ export class ManageMetadataBase {
6174
6339
  }
6175
6340
  }
6176
6341
  const pct = Math.round((processedCount / total) * 100);
6177
- updateSpinner(`Advanced generation: ${processedCount}/${total} entities (${pct}%)${errorCount > 0 ? ` — ${errorCount} error(s)` : ''}`);
6342
+ UpdateSpinner(`Advanced generation: ${processedCount}/${total} entities (${pct}%)${errorCount > 0 ? ` — ${errorCount} error(s)` : ''}`);
6178
6343
  // Credential circuit tripped (e.g. keyless / mis-credentialed env) — stop issuing further
6179
6344
  // AI calls; the remaining entities would only produce doomed round-trips. One clear message
6180
6345
  // was already logged when the circuit opened.
6181
6346
  if (ag.AICircuitOpen) {
6182
6347
  const skipped = total - processedCount;
6183
- updateSpinner(`Advanced generation: AI credential circuit open — skipping remaining ${skipped} entit${skipped === 1 ? 'y' : 'ies'} (check AI credentials).`);
6348
+ UpdateSpinner(`Advanced generation: AI credential circuit open — skipping remaining ${skipped} entit${skipped === 1 ? 'y' : 'ies'} (check AI credentials).`);
6184
6349
  break;
6185
6350
  }
6186
6351
  }
@@ -6351,7 +6516,7 @@ export class ManageMetadataBase {
6351
6516
  // Execute all updates in one batch
6352
6517
  if (sqlStatements.length > 0) {
6353
6518
  try {
6354
- await this.LogSQLBatchAndExecute(pool, sqlStatements, `Set field properties for entity`, false);
6519
+ await this.logSQLBatchAndExecute(pool, sqlStatements, `Set field properties for entity`, false);
6355
6520
  }
6356
6521
  catch (ex) {
6357
6522
  logError('Error executing combined smart field SQL: ', ex);
@@ -6379,7 +6544,7 @@ export class ManageMetadataBase {
6379
6544
  const proposedSearchableCount = (result.searchableFields ?? []).length;
6380
6545
  const proposedAllowUserSearch = result.allowUserSearch === true;
6381
6546
  // 1. Shape cleanup (defensive — identifyFields() also runs this).
6382
- const cleaned = normalizeSmartFieldResultShape(result);
6547
+ const cleaned = NormalizeSmartFieldResultShape(result);
6383
6548
  result.searchableFields = cleaned.searchableFields ?? [];
6384
6549
  result.searchPredicates = cleaned.searchPredicates ?? [];
6385
6550
  result.allowUserSearch = cleaned.allowUserSearch;
@@ -6400,14 +6565,14 @@ export class ManageMetadataBase {
6400
6565
  droppedIneligibleCount += 1;
6401
6566
  continue;
6402
6567
  }
6403
- if (isNarrativeFieldName(name) && !ftxEnabled) {
6568
+ if (IsNarrativeFieldName(name) && !ftxEnabled) {
6404
6569
  droppedNarrativeCount += 1;
6405
6570
  continue;
6406
6571
  }
6407
6572
  eligible.push(name);
6408
6573
  }
6409
6574
  // 3. Per-entity cap.
6410
- const { accepted, dropped: droppedByCap } = applySearchableFieldsCap(eligible);
6575
+ const { accepted, dropped: droppedByCap } = ApplySearchableFieldsCap(eligible);
6411
6576
  result.searchableFields = accepted;
6412
6577
  // 4. Predicate normalization. Build a fresh searchPredicates list keyed
6413
6578
  // on the accepted fields. The LLM's proposed predicates are the input
@@ -6422,7 +6587,7 @@ export class ManageMetadataBase {
6422
6587
  const normalizedPredicates = [];
6423
6588
  let predicatesRewrittenCount = 0;
6424
6589
  for (const fieldName of accepted) {
6425
- const { predicate, rewritten } = normalizePredicate({
6590
+ const { predicate, rewritten } = NormalizePredicate({
6426
6591
  fieldName,
6427
6592
  proposed: proposedByName.get(fieldName),
6428
6593
  isInFullTextSearchFields: ftsFieldSet.has(fieldName),
@@ -6497,7 +6662,7 @@ export class ManageMetadataBase {
6497
6662
  // Set the winner when it isn't flagged yet (and auto-update is allowed).
6498
6663
  if (winner && winner.AutoUpdateIsNameField && winner.ID && !winner.IsNameField) {
6499
6664
  sqlStatements.push(`
6500
- UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
6665
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
6501
6666
  SET IsNameField = ${this.boolLit(true)}
6502
6667
  WHERE ID = '${winner.ID}'
6503
6668
  AND AutoUpdateIsNameField = ${this.boolLit(true)}
@@ -6509,7 +6674,7 @@ export class ManageMetadataBase {
6509
6674
  continue;
6510
6675
  }
6511
6676
  sqlStatements.push(`
6512
- UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
6677
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
6513
6678
  SET IsNameField = ${this.boolLit(false)}
6514
6679
  WHERE ID = '${f.ID}'
6515
6680
  AND AutoUpdateIsNameField = ${this.boolLit(true)}
@@ -6658,7 +6823,7 @@ export class ManageMetadataBase {
6658
6823
  for (const field of defaultInViewFields) {
6659
6824
  if (!field.DefaultInView) {
6660
6825
  sqlStatements.push(`
6661
- UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
6826
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
6662
6827
  SET DefaultInView = ${this.boolLit(true)}
6663
6828
  WHERE ID = '${field.ID}'
6664
6829
  AND AutoUpdateDefaultInView = ${this.boolLit(true)}
@@ -6696,7 +6861,7 @@ export class ManageMetadataBase {
6696
6861
  }
6697
6862
  if (!field.IncludeInUserSearchAPI) {
6698
6863
  sqlStatements.push(`
6699
- UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
6864
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
6700
6865
  SET IncludeInUserSearchAPI = ${this.boolLit(true)}
6701
6866
  WHERE ID = '${field.ID}'
6702
6867
  AND AutoUpdateIncludeInUserSearchAPI = ${this.boolLit(true)}
@@ -6751,7 +6916,7 @@ export class ManageMetadataBase {
6751
6916
  * which is T-SQL-only. The subquery form is ANSI and runs unchanged on both platforms.
6752
6917
  */
6753
6918
  buildSearchFlagHygieneSQL(excludeSchemas) {
6754
- const coreSchema = mj_core_schema();
6919
+ const coreSchema = MjCoreSchema();
6755
6920
  const entity = this.qs(coreSchema, 'Entity');
6756
6921
  const entityField = this.qs(coreSchema, 'EntityField');
6757
6922
  const yes = this.boolLit(true);
@@ -6764,7 +6929,7 @@ export class ManageMetadataBase {
6764
6929
  // to 'Contains' in the database, which is `LIKE '%term%'` — the unindexable scan the
6765
6930
  // guardrails exist to prevent. Seeding the flag without the predicate would have made every
6766
6931
  // seeded entity a full scan on every keystroke.
6767
- const seedPredicate = defaultPredicateFor(NAME_LIKE_FIELD_NAMES[0]);
6932
+ const seedPredicate = DefaultPredicateFor(NAME_LIKE_FIELD_NAMES[0]);
6768
6933
  // The entity-shape guardrails the LLM path applies (`entityLevelEnableBlockedReason`).
6769
6934
  // A log / audit / run-history table grows without bound and a detail / line-item child is
6770
6935
  // reached through its parent; neither is a global-search target, whichever columns it has.
@@ -6917,7 +7082,7 @@ export class ManageMetadataBase {
6917
7082
  const seedCount = await this.searchFlagHygieneCandidateCount(pool, seedProbeSQL);
6918
7083
  if (seedCount > 0) {
6919
7084
  logStatus(` Search-flag hygiene: seeding ${seedCount} name field(s)`);
6920
- await this.LogSQLBatchAndExecute(pool, [seedSQL], 'Deterministic search-flag hygiene — seed name fields', false);
7085
+ await this.logSQLBatchAndExecute(pool, [seedSQL], 'Deterministic search-flag hygiene — seed name fields', false);
6921
7086
  }
6922
7087
  // Probed AFTER the seed has run, not alongside it. Seeding changes which entities still
6923
7088
  // have nothing searchable, so a clear probe taken before it would count entities the seed
@@ -6930,7 +7095,7 @@ export class ManageMetadataBase {
6930
7095
  const shown = clearNames.slice(0, 25).join(', ');
6931
7096
  const more = clearNames.length > 25 ? `, ... and ${clearNames.length - 25} more` : '';
6932
7097
  logStatus(` Search-flag hygiene: turning AllowUserSearchAPI OFF on ${clearNames.length} entity(ies): ${shown}${more}`);
6933
- await this.LogSQLBatchAndExecute(pool, [clearSQL], 'Deterministic search-flag hygiene — clear AllowUserSearchAPI', false);
7098
+ await this.logSQLBatchAndExecute(pool, [clearSQL], 'Deterministic search-flag hygiene — clear AllowUserSearchAPI', false);
6934
7099
  }
6935
7100
  return true;
6936
7101
  }
@@ -7018,7 +7183,7 @@ export class ManageMetadataBase {
7018
7183
  // Only update if the current value differs from the recommended predicate
7019
7184
  if (field.UserSearchPredicateAPI !== sp.predicate) {
7020
7185
  sqlStatements.push(`
7021
- UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
7186
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
7022
7187
  SET UserSearchPredicateAPI = '${sp.predicate}'
7023
7188
  WHERE ID = '${field.ID}'
7024
7189
  AND AutoUpdateUserSearchPredicate = ${this.boolLit(true)}
@@ -7049,7 +7214,7 @@ export class ManageMetadataBase {
7049
7214
  // the field-level guardrails, and refuses log/audit and detail/line-
7050
7215
  // item-shaped entities. Anything blocked here gets a one-line note in
7051
7216
  // the CodeGen run report so the team can audit proposals.
7052
- const blocked = entityLevelEnableBlockedReason({
7217
+ const blocked = EntityLevelEnableBlockedReason({
7053
7218
  entityName: entity.Name,
7054
7219
  confidence: result.confidence,
7055
7220
  acceptedSearchableFieldsCount: (result.searchableFields ?? []).length,
@@ -7065,7 +7230,7 @@ export class ManageMetadataBase {
7065
7230
  }
7066
7231
  if (newValue !== currentValue) {
7067
7232
  sqlStatements.push(`
7068
- UPDATE ${this.qs(mj_core_schema(), 'Entity')}
7233
+ UPDATE ${this.qs(MjCoreSchema(), 'Entity')}
7069
7234
  SET AllowUserSearchAPI = ${this.boolLit(newValue)}
7070
7235
  WHERE ID = '${entity.ID}'
7071
7236
  AND AutoUpdateAllowUserSearchAPI = ${this.boolLit(true)}
@@ -7108,7 +7273,7 @@ export class ManageMetadataBase {
7108
7273
  const currentValue = !!entity.FullTextSearchEnabled;
7109
7274
  if (newValue !== currentValue) {
7110
7275
  sqlStatements.push(`
7111
- UPDATE ${this.qs(mj_core_schema(), 'Entity')}
7276
+ UPDATE ${this.qs(MjCoreSchema(), 'Entity')}
7112
7277
  SET FullTextSearchEnabled = ${this.boolLit(newValue)}
7113
7278
  WHERE ID = '${entity.ID}'
7114
7279
  AND AutoUpdateFullTextSearch = ${this.boolLit(true)}
@@ -7130,7 +7295,7 @@ export class ManageMetadataBase {
7130
7295
  }
7131
7296
  if (!field.FullTextSearchEnabled) {
7132
7297
  sqlStatements.push(`
7133
- UPDATE ${this.qs(mj_core_schema(), 'EntityField')}
7298
+ UPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
7134
7299
  SET FullTextSearchEnabled = ${this.boolLit(true)}
7135
7300
  WHERE ID = '${field.ID}'
7136
7301
  AND AutoUpdateFullTextSearch = ${this.boolLit(true)}
@@ -7175,7 +7340,7 @@ export class ManageMetadataBase {
7175
7340
  * Respects the AutoUpdateSupportsGeoCoding flag — if 0, the value is locked.
7176
7341
  */
7177
7342
  async detectAndSetGeoCodingSupport(pool, entity) {
7178
- const schema = mj_core_schema();
7343
+ const schema = MjCoreSchema();
7179
7344
  // Check if the entity's AutoUpdateSupportsGeoCoding flag allows us to modify it
7180
7345
  const autoUpdateResult = await pool.query(`
7181
7346
  SELECT ${this.qi('AutoUpdateSupportsGeoCoding')}, ${this.qi('SupportsGeoCoding')}
@@ -7198,7 +7363,7 @@ export class ManageMetadataBase {
7198
7363
  const shouldSupportGeo = existingGeoCount > 0;
7199
7364
  const currentValue = row.SupportsGeoCoding ? true : false;
7200
7365
  if (shouldSupportGeo !== currentValue) {
7201
- await this.LogSQLAndExecute(pool, `
7366
+ await this.logSQLAndExecute(pool, `
7202
7367
  UPDATE ${this.qs(schema, 'Entity')}
7203
7368
  SET ${this.qi('SupportsGeoCoding')} = ${this.boolLit(shouldSupportGeo)}
7204
7369
  WHERE ${this.qi('ID')} = '${entity.ID}' AND ${this.qi('AutoUpdateSupportsGeoCoding')} = ${this.boolLit(true)}
@@ -7272,14 +7437,14 @@ export class ManageMetadataBase {
7272
7437
  continue;
7273
7438
  }
7274
7439
  const lockCtx = {
7275
- isNewEntity: ctx.isNewEntity,
7276
- isNewField: ManageMetadataBase.isFieldNew(entity.ID, field.Name),
7277
- descriptionReopened: ManageMetadataBase.isDisplayNameReopened(entity.ID, field.Name),
7278
- typeReopened: ManageMetadataBase.isTypeReopened(entity.ID, field.Name),
7279
- existingCategories,
7440
+ IsNewEntity: ctx.isNewEntity,
7441
+ IsNewField: ManageMetadataBase.isFieldNew(entity.ID, field.Name),
7442
+ DescriptionReopened: ManageMetadataBase.isDisplayNameReopened(entity.ID, field.Name),
7443
+ TypeReopened: ManageMetadataBase.isTypeReopened(entity.ID, field.Name),
7444
+ ExistingCategories: existingCategories,
7280
7445
  };
7281
- const update = computeFieldMetadataUpdate(field, fieldCategory, lockCtx, this.validateExtendedType.bind(this), (val, fn) => this.sanitizeCodeType(val, fn ?? field.Name, entity.Name));
7282
- for (const skip of update.skipped) {
7446
+ const update = ComputeFieldMetadataUpdate(field, fieldCategory, lockCtx, this.validateExtendedType.bind(this), (val, fn) => this.sanitizeCodeType(val, fn ?? field.Name, entity.Name));
7447
+ for (const skip of update.Skipped) {
7283
7448
  if (skip.reason === 'locked') {
7284
7449
  reporter.counter('ai.fieldsLocked');
7285
7450
  }
@@ -7306,7 +7471,7 @@ export class ManageMetadataBase {
7306
7471
  setClauses.push(`CodeType = ${codeVal}`);
7307
7472
  }
7308
7473
  if (setClauses.length > 0) {
7309
- sqlStatements.push(`\n-- UPDATE Entity Field Category Info ${entity.Name}.${field.Name} \nUPDATE ${this.qs(mj_core_schema(), 'EntityField')}
7474
+ sqlStatements.push(`\n-- UPDATE Entity Field Category Info ${entity.Name}.${field.Name} \nUPDATE ${this.qs(MjCoreSchema(), 'EntityField')}
7310
7475
  SET
7311
7476
  ${setClauses.join(',\n ')}
7312
7477
  WHERE
@@ -7315,7 +7480,7 @@ WHERE
7315
7480
  }
7316
7481
  if (sqlStatements.length > 0) {
7317
7482
  try {
7318
- await this.LogSQLBatchAndExecute(pool, sqlStatements, `Set categories for ${sqlStatements.length} fields`, false);
7483
+ await this.logSQLBatchAndExecute(pool, sqlStatements, `Set categories for ${sqlStatements.length} fields`, false);
7319
7484
  }
7320
7485
  catch (ex) {
7321
7486
  logError('Error Applying Field Categories', ex);
@@ -7328,19 +7493,19 @@ WHERE
7328
7493
  async applyEntityIcon(pool, entityId, entityIcon, entityName) {
7329
7494
  if (!entityIcon || entityIcon.trim().length === 0)
7330
7495
  return;
7331
- const checkSQL = `SELECT Icon FROM ${this.qs(mj_core_schema(), 'Entity')} WHERE ID = '${entityId}'`;
7496
+ const checkSQL = `SELECT Icon FROM ${this.qs(MjCoreSchema(), 'Entity')} WHERE ID = '${entityId}'`;
7332
7497
  const entityCheck = await this.runQuery(pool, checkSQL);
7333
7498
  if (entityCheck.recordset.length > 0) {
7334
7499
  const currentIcon = entityCheck.recordset[0].Icon;
7335
7500
  if (!currentIcon || currentIcon.trim().length === 0) {
7336
7501
  const escapedIcon = entityIcon.replace(/'/g, "''");
7337
7502
  const updateSQL = `
7338
- UPDATE ${this.qs(mj_core_schema(), 'Entity')}
7503
+ UPDATE ${this.qs(MjCoreSchema(), 'Entity')}
7339
7504
  SET ${this.qi('Icon')} = '${escapedIcon}', ${this.qi(EntityInfo.UpdatedAtFieldName)} = ${this.utcNow()}
7340
7505
  WHERE ${this.qi('ID')} = '${entityId}'
7341
7506
  `;
7342
7507
  try {
7343
- await this.LogSQLAndExecute(pool, updateSQL, `Set entity icon to ${entityIcon}`, false);
7508
+ await this.logSQLAndExecute(pool, updateSQL, `Set entity icon to ${entityIcon}`, false);
7344
7509
  logStatus(` Set entity icon: ${entityIcon}`);
7345
7510
  }
7346
7511
  catch (ex) {
@@ -7355,26 +7520,26 @@ WHERE
7355
7520
  async applyCategoryInfoSettings(pool, entityId, categoryInfo, _entityName) {
7356
7521
  if (!categoryInfo || Object.keys(categoryInfo).length === 0)
7357
7522
  return;
7358
- const canonicalInfo = canonicalJSONStringify(categoryInfo, 2);
7523
+ const canonicalInfo = CanonicalJSONStringify(categoryInfo, 2);
7359
7524
  const infoJSON = canonicalInfo.replace(/'/g, "''");
7360
7525
  const ENTITY_SETTING_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
7361
7526
  // Upsert FieldCategoryInfo (new format)
7362
- const checkNewSQL = `SELECT ${this.qi('ID')}, ${this.qi('Value')} FROM ${this.qs(mj_core_schema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryInfo'`;
7527
+ const checkNewSQL = `SELECT ${this.qi('ID')}, ${this.qi('Value')} FROM ${this.qs(MjCoreSchema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryInfo'`;
7363
7528
  const existingNew = await this.runQuery(pool, checkNewSQL);
7364
7529
  if (existingNew.recordset.length > 0) {
7365
7530
  let isMatch = false;
7366
7531
  try {
7367
7532
  const rawVal = existingNew.recordset[0].Value;
7368
7533
  const parsed = typeof rawVal === 'string' ? JSON.parse(rawVal) : rawVal;
7369
- isMatch = deepEqualJSON(parsed, categoryInfo);
7534
+ isMatch = DeepEqualJSON(parsed, categoryInfo);
7370
7535
  }
7371
7536
  catch {
7372
7537
  isMatch = false;
7373
7538
  }
7374
7539
  if (!isMatch) {
7375
7540
  try {
7376
- await this.LogSQLAndExecute(pool, `
7377
- UPDATE ${this.qs(mj_core_schema(), 'EntitySetting')}
7541
+ await this.logSQLAndExecute(pool, `
7542
+ UPDATE ${this.qs(MjCoreSchema(), 'EntitySetting')}
7378
7543
  SET ${this.qi('Value')} = '${infoJSON}', ${this.qi(EntityInfo.UpdatedAtFieldName)} = ${this.utcNow()}
7379
7544
  WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryInfo'
7380
7545
  `, `Update FieldCategoryInfo setting for entity`, false);
@@ -7387,11 +7552,11 @@ WHERE
7387
7552
  else {
7388
7553
  const newId = uuidv5(`${entityId}|FieldCategoryInfo`, ENTITY_SETTING_NAMESPACE);
7389
7554
  try {
7390
- const checkQuery = `SELECT 1 FROM ${this.qs(mj_core_schema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryInfo'`;
7391
- const insertSQL = `INSERT INTO ${this.qs(mj_core_schema(), 'EntitySetting')} (${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('Name')}, ${this.qi('Value')}, ${this.qi(EntityInfo.CreatedAtFieldName)}, ${this.qi(EntityInfo.UpdatedAtFieldName)})
7555
+ const checkQuery = `SELECT 1 FROM ${this.qs(MjCoreSchema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryInfo'`;
7556
+ const insertSQL = `INSERT INTO ${this.qs(MjCoreSchema(), 'EntitySetting')} (${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('Name')}, ${this.qi('Value')}, ${this.qi(EntityInfo.CreatedAtFieldName)}, ${this.qi(EntityInfo.UpdatedAtFieldName)})
7392
7557
  VALUES ('${newId}', '${entityId}', 'FieldCategoryInfo', '${infoJSON}', ${this.utcNow()}, ${this.utcNow()})`;
7393
7558
  const sSQL = this.conditionalInsert(checkQuery, insertSQL);
7394
- await this.LogSQLAndExecute(pool, sSQL, `Insert FieldCategoryInfo setting for entity`, false);
7559
+ await this.logSQLAndExecute(pool, sSQL, `Insert FieldCategoryInfo setting for entity`, false);
7395
7560
  }
7396
7561
  catch (ex) {
7397
7562
  logError('Error Applying Category Info Settings: Part 2', ex);
@@ -7404,24 +7569,24 @@ WHERE
7404
7569
  iconsOnly[category] = info.icon;
7405
7570
  }
7406
7571
  }
7407
- const canonicalIcons = canonicalJSONStringify(iconsOnly, 2);
7572
+ const canonicalIcons = CanonicalJSONStringify(iconsOnly, 2);
7408
7573
  const iconsJSON = canonicalIcons.replace(/'/g, "''");
7409
- const checkLegacySQL = `SELECT ${this.qi('ID')}, ${this.qi('Value')} FROM ${this.qs(mj_core_schema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryIcons'`;
7574
+ const checkLegacySQL = `SELECT ${this.qi('ID')}, ${this.qi('Value')} FROM ${this.qs(MjCoreSchema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryIcons'`;
7410
7575
  const existingLegacy = await this.runQuery(pool, checkLegacySQL);
7411
7576
  if (existingLegacy.recordset.length > 0) {
7412
7577
  let isMatch = false;
7413
7578
  try {
7414
7579
  const rawVal = existingLegacy.recordset[0].Value;
7415
7580
  const parsed = typeof rawVal === 'string' ? JSON.parse(rawVal) : rawVal;
7416
- isMatch = deepEqualJSON(parsed, iconsOnly);
7581
+ isMatch = DeepEqualJSON(parsed, iconsOnly);
7417
7582
  }
7418
7583
  catch {
7419
7584
  isMatch = false;
7420
7585
  }
7421
7586
  if (!isMatch) {
7422
7587
  try {
7423
- await this.LogSQLAndExecute(pool, `
7424
- UPDATE ${this.qs(mj_core_schema(), 'EntitySetting')}
7588
+ await this.logSQLAndExecute(pool, `
7589
+ UPDATE ${this.qs(MjCoreSchema(), 'EntitySetting')}
7425
7590
  SET ${this.qi('Value')} = '${iconsJSON}', ${this.qi(EntityInfo.UpdatedAtFieldName)} = ${this.utcNow()}
7426
7591
  WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryIcons'
7427
7592
  `, `Update FieldCategoryIcons setting (legacy)`, false);
@@ -7434,11 +7599,11 @@ WHERE
7434
7599
  else {
7435
7600
  const newId = uuidv5(`${entityId}|FieldCategoryIcons`, ENTITY_SETTING_NAMESPACE);
7436
7601
  try {
7437
- const checkQuery = `SELECT 1 FROM ${this.qs(mj_core_schema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryIcons'`;
7438
- const insertSQL = `INSERT INTO ${this.qs(mj_core_schema(), 'EntitySetting')} (${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('Name')}, ${this.qi('Value')}, ${this.qi(EntityInfo.CreatedAtFieldName)}, ${this.qi(EntityInfo.UpdatedAtFieldName)})
7602
+ const checkQuery = `SELECT 1 FROM ${this.qs(MjCoreSchema(), 'EntitySetting')} WHERE ${this.qi('EntityID')} = '${entityId}' AND ${this.qi('Name')} = 'FieldCategoryIcons'`;
7603
+ const insertSQL = `INSERT INTO ${this.qs(MjCoreSchema(), 'EntitySetting')} (${this.qi('ID')}, ${this.qi('EntityID')}, ${this.qi('Name')}, ${this.qi('Value')}, ${this.qi(EntityInfo.CreatedAtFieldName)}, ${this.qi(EntityInfo.UpdatedAtFieldName)})
7439
7604
  VALUES ('${newId}', '${entityId}', 'FieldCategoryIcons', '${iconsJSON}', ${this.utcNow()}, ${this.utcNow()})`;
7440
7605
  const sSQL = this.conditionalInsert(checkQuery, insertSQL);
7441
- await this.LogSQLAndExecute(pool, sSQL, `Insert FieldCategoryIcons setting (legacy)`, false);
7606
+ await this.logSQLAndExecute(pool, sSQL, `Insert FieldCategoryIcons setting (legacy)`, false);
7442
7607
  }
7443
7608
  catch (ex) {
7444
7609
  logError('Error Applying Category Info Settings: Part 4', ex);
@@ -7451,12 +7616,12 @@ WHERE
7451
7616
  */
7452
7617
  async applyEntityImportance(pool, entityId, importance, entityName) {
7453
7618
  const updateSQL = `
7454
- UPDATE ${this.qs(mj_core_schema(), 'ApplicationEntity')}
7619
+ UPDATE ${this.qs(MjCoreSchema(), 'ApplicationEntity')}
7455
7620
  SET ${this.qi('DefaultForNewUser')} = ${this.boolLit(importance.defaultForNewUser)}, ${this.qi(EntityInfo.UpdatedAtFieldName)} = ${this.utcNow()}
7456
7621
  WHERE ${this.qi('EntityID')} = '${entityId}'
7457
7622
  `;
7458
7623
  try {
7459
- await this.LogSQLAndExecute(pool, updateSQL, `Set DefaultForNewUser=${importance.defaultForNewUser} for NEW entity (category: ${importance.entityCategory}, confidence: ${importance.confidence})`, false);
7624
+ await this.logSQLAndExecute(pool, updateSQL, `Set DefaultForNewUser=${importance.defaultForNewUser} for NEW entity (category: ${importance.entityCategory}, confidence: ${importance.confidence})`, false);
7460
7625
  logStatus(` Entity importance (NEW Entity): ${importance.entityCategory} (defaultForNewUser: ${importance.defaultForNewUser}, confidence: ${importance.confidence})`);
7461
7626
  logStatus(` Reasoning: ${importance.reasoning}`);
7462
7627
  }
@@ -7474,7 +7639,7 @@ WHERE
7474
7639
  * @param isRecurringScript - if set to true tells the logger that the provided SQL represents a recurring script meaning it is something that is executed, generally, for all CodeGen runs. In these cases, the Config settings can result in omitting these recurring scripts from being logged because the configuration environment may have those recurring scripts already set to run after all run-specific migrations get run.
7475
7640
  * @returns - The result of the query execution.
7476
7641
  */
7477
- async LogSQLAndExecute(pool, query, description, isRecurringScript = false, includeBatchSeparator = false, batchSeparator = 'GO', requiresOwnBatch = false) {
7642
+ async logSQLAndExecute(pool, query, description, isRecurringScript = false, includeBatchSeparator = false, batchSeparator = 'GO', requiresOwnBatch = false) {
7478
7643
  return await SQLLogging.LogSQLAndExecute(pool, this.qsql(query), description, isRecurringScript, includeBatchSeparator, batchSeparator, requiresOwnBatch);
7479
7644
  }
7480
7645
  /**
@@ -7493,17 +7658,17 @@ WHERE
7493
7658
  * Empty / whitespace-only statements are filtered out. Returns
7494
7659
  * `undefined` (no execution) when nothing remains.
7495
7660
  */
7496
- async LogSQLBatchAndExecute(pool, statements, description, isRecurringScript = false, includeBatchSeparator = false, batchSeparator = 'GO') {
7661
+ async logSQLBatchAndExecute(pool, statements, description, isRecurringScript = false, includeBatchSeparator = false, batchSeparator = 'GO') {
7497
7662
  const terminated = [];
7498
7663
  for (const s of statements) {
7499
- const trimmed = trimTrailingStatementTerminators(s ?? '');
7664
+ const trimmed = TrimTrailingStatementTerminators(s ?? '');
7500
7665
  if (trimmed.length === 0)
7501
7666
  continue;
7502
7667
  terminated.push(`${trimmed};`);
7503
7668
  }
7504
7669
  if (terminated.length === 0)
7505
7670
  return undefined;
7506
- return await this.LogSQLAndExecute(pool, terminated.join('\n'), description, isRecurringScript, includeBatchSeparator, batchSeparator);
7671
+ return await this.logSQLAndExecute(pool, terminated.join('\n'), description, isRecurringScript, includeBatchSeparator, batchSeparator);
7507
7672
  }
7508
7673
  }
7509
7674
  //# sourceMappingURL=manage-metadata.js.map