@memberjunction/sqlserver-dataprovider 3.4.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * @fileoverview SQL Server Data Provider for MemberJunction
4
3
  *
@@ -11,49 +10,25 @@
11
10
  * @version 2.0
12
11
  * @since 1.0
13
12
  */
14
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- var desc = Object.getOwnPropertyDescriptor(m, k);
17
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
- desc = { enumerable: true, get: function() { return m[k]; } };
19
- }
20
- Object.defineProperty(o, k2, desc);
21
- }) : (function(o, m, k, k2) {
22
- if (k2 === undefined) k2 = k;
23
- o[k2] = m[k];
24
- }));
25
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
- Object.defineProperty(o, "default", { enumerable: true, value: v });
27
- }) : function(o, v) {
28
- o["default"] = v;
29
- });
30
- var __importStar = (this && this.__importStar) || function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.SQLServerDataProvider = void 0;
39
13
  /**************************************************************************************************************
40
14
  * The SQLServerDataProvider provides a data provider for the entities framework that uses SQL Server directly
41
15
  * In practice - this FILE will NOT exist in the entities library, we need to move to its own separate project
42
16
  * so it is only included by the consumer of the entities library if they want to use it.
43
17
  **************************************************************************************************************/
44
- const core_1 = require("@memberjunction/core");
45
- const queryParameterProcessor_1 = require("./queryParameterProcessor");
46
- const core_entities_1 = require("@memberjunction/core-entities");
47
- const aiengine_1 = require("@memberjunction/aiengine");
48
- const queue_1 = require("@memberjunction/queue");
49
- const sql = __importStar(require("mssql"));
50
- const rxjs_1 = require("rxjs");
51
- const SQLServerTransactionGroup_1 = require("./SQLServerTransactionGroup");
52
- const SqlLogger_js_1 = require("./SqlLogger.js");
53
- const actions_1 = require("@memberjunction/actions");
54
- const encryption_1 = require("@memberjunction/encryption");
55
- const uuid_1 = require("uuid");
56
- const global_1 = require("@memberjunction/global");
18
+ import { ApplicationInfo, EntityFieldTSType, ProviderType, UserInfo, AuditLogTypeInfo, AuthorizationInfo, TransactionItem, EntityPermissionType, EntitySaveOptions, LogError, StripStopWords, LogStatus, CompositeKey, EntityDeleteOptions, BaseEntityResult, Metadata, DatabaseProviderBase, QueryCache, InMemoryLocalStorageProvider, } from '@memberjunction/core';
19
+ import { QueryParameterProcessor } from './queryParameterProcessor.js';
20
+ import { NodeFileSystemProvider } from './NodeFileSystemProvider.js';
21
+ import { ViewInfo, } from '@memberjunction/core-entities';
22
+ import { AIEngine } from '@memberjunction/aiengine';
23
+ import { QueueManager } from '@memberjunction/queue';
24
+ import sql from 'mssql';
25
+ import { BehaviorSubject, Subject, concatMap, from, tap, catchError, of } from 'rxjs';
26
+ import { SQLServerTransactionGroup } from './SQLServerTransactionGroup.js';
27
+ import { SqlLoggingSessionImpl } from './SqlLogger.js';
28
+ import { EntityActionEngineServer } from '@memberjunction/actions';
29
+ import { EncryptionEngine } from '@memberjunction/encryption';
30
+ import { v4 as uuidv4 } from 'uuid';
31
+ import { MJGlobal, SQLExpressionValidator } from '@memberjunction/global';
57
32
  /**
58
33
  * Core SQL execution function - handles the actual database query execution
59
34
  * This is outside the class to allow both static and instance methods to use it
@@ -155,24 +130,27 @@ async function executeSQLCore(query, parameters, context, options) {
155
130
  * await provider.Config();
156
131
  * ```
157
132
  */
158
- class SQLServerDataProvider extends core_1.DatabaseProviderBase {
159
- _pool;
160
- // Instance transaction properties
161
- _transaction;
162
- _transactionDepth = 0;
163
- _savepointCounter = 0;
164
- _savepointStack = [];
165
- // Query cache instance
166
- queryCache = new core_1.QueryCache();
167
- // Removed _transactionRequest - creating new Request objects for each query to avoid concurrency issues
168
- _localStorageProvider;
169
- _bAllowRefresh = true;
170
- _recordDupeDetector;
171
- _needsDatetimeOffsetAdjustment = false;
172
- _datetimeOffsetTestComplete = false;
173
- static _sqlLoggingSessionsKey = 'MJ_SQLServerDataProvider_SqlLoggingSessions';
133
+ export class SQLServerDataProvider extends DatabaseProviderBase {
134
+ constructor() {
135
+ super(...arguments);
136
+ this._transactionDepth = 0;
137
+ this._savepointCounter = 0;
138
+ this._savepointStack = [];
139
+ // Query cache instance
140
+ this.queryCache = new QueryCache();
141
+ this._bAllowRefresh = true;
142
+ this._needsDatetimeOffsetAdjustment = false;
143
+ this._datetimeOffsetTestComplete = false;
144
+ // Instance SQL execution queue for serializing transaction queries
145
+ // Non-transactional queries bypass this queue for maximum parallelism
146
+ this._sqlQueue$ = new Subject();
147
+ // Transaction state management
148
+ this._transactionState$ = new BehaviorSubject(false);
149
+ this._deferredTasks = [];
150
+ }
151
+ static { this._sqlLoggingSessionsKey = 'MJ_SQLServerDataProvider_SqlLoggingSessions'; }
174
152
  get _sqlLoggingSessions() {
175
- const g = global_1.MJGlobal.Instance.GetGlobalObjectStore();
153
+ const g = MJGlobal.Instance.GetGlobalObjectStore();
176
154
  if (g) {
177
155
  if (!g[SQLServerDataProvider._sqlLoggingSessionsKey]) {
178
156
  g[SQLServerDataProvider._sqlLoggingSessionsKey] = new Map();
@@ -183,14 +161,6 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
183
161
  throw new Error('No global object store available for SQL logging session');
184
162
  }
185
163
  }
186
- // Instance SQL execution queue for serializing transaction queries
187
- // Non-transactional queries bypass this queue for maximum parallelism
188
- _sqlQueue$ = new rxjs_1.Subject();
189
- // Subscription for the queue processor
190
- _queueSubscription;
191
- // Transaction state management
192
- _transactionState$ = new rxjs_1.BehaviorSubject(false);
193
- _deferredTasks = [];
194
164
  /**
195
165
  * Observable that emits the current transaction state (true when active, false when not)
196
166
  * External code can subscribe to this to know when transactions start and end
@@ -258,7 +228,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
258
228
  return super.Config(configData, providerToUse); // now parent class can do it's config
259
229
  }
260
230
  catch (e) {
261
- (0, core_1.LogError)(e);
231
+ LogError(e);
262
232
  throw e;
263
233
  }
264
234
  }
@@ -270,13 +240,13 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
270
240
  // Each instance gets its own queue processor, but only do this ONCE if we get this method called more than once we don't need to reinit
271
241
  // the sub, taht would cause duplicate rprocessing.
272
242
  if (!this._queueSubscription) {
273
- this._queueSubscription = this._sqlQueue$.pipe((0, rxjs_1.concatMap)(item => (0, rxjs_1.from)(executeSQLCore(item.query, item.parameters, item.context, item.options)).pipe(
243
+ this._queueSubscription = this._sqlQueue$.pipe(concatMap(item => from(executeSQLCore(item.query, item.parameters, item.context, item.options)).pipe(
274
244
  // Handle success
275
- (0, rxjs_1.tap)(result => item.resolve(result)),
245
+ tap(result => item.resolve(result)),
276
246
  // Handle errors
277
- (0, rxjs_1.catchError)(error => {
247
+ catchError(error => {
278
248
  item.reject(error);
279
- return (0, rxjs_1.of)(null); // Continue processing queue even on errors
249
+ return of(null); // Continue processing queue even on errors
280
250
  })))).subscribe();
281
251
  }
282
252
  }
@@ -348,9 +318,9 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
348
318
  * ```
349
319
  */
350
320
  async CreateSqlLogger(filePath, options) {
351
- const sessionId = (0, uuid_1.v4)();
321
+ const sessionId = uuidv4();
352
322
  const mjCoreSchema = this.ConfigData.MJCoreSchemaName;
353
- const session = new SqlLogger_js_1.SqlLoggingSessionImpl(sessionId, filePath, {
323
+ const session = new SqlLoggingSessionImpl(sessionId, filePath, {
354
324
  defaultSchemaName: mjCoreSchema,
355
325
  ...options // if defaultSchemaName is not provided, it will use the MJCoreSchemaName, otherwise
356
326
  // the caller's defaultSchemaName will be used
@@ -504,7 +474,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
504
474
  */
505
475
  static async LogSQLStatement(query, parameters, description, isMutation = false, simpleSQLFallback, contextUser) {
506
476
  // Get the current provider instance
507
- const provider = core_1.Metadata.Provider;
477
+ const provider = Metadata.Provider;
508
478
  if (provider && provider._sqlLoggingSessions.size > 0) {
509
479
  await provider._logSqlStatement(query, parameters, description, false, isMutation, simpleSQLFallback, contextUser);
510
480
  }
@@ -668,7 +638,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
668
638
  };
669
639
  }
670
640
  catch (e) {
671
- (0, core_1.LogError)(e);
641
+ LogError(e);
672
642
  const errorMessage = e instanceof Error ? e.message : String(e);
673
643
  return {
674
644
  Success: false,
@@ -721,7 +691,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
721
691
  let finalSQL = query.SQL;
722
692
  let appliedParameters = {};
723
693
  if (query.UsesTemplate) {
724
- const processingResult = queryParameterProcessor_1.QueryParameterProcessor.processQueryTemplate(query, parameters);
694
+ const processingResult = QueryParameterProcessor.processQueryTemplate(query, parameters);
725
695
  if (!processingResult.success) {
726
696
  throw new Error(processingResult.error);
727
697
  }
@@ -730,7 +700,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
730
700
  }
731
701
  else if (parameters && Object.keys(parameters).length > 0) {
732
702
  // Warn if parameters were provided but query doesn't use templates
733
- (0, core_1.LogStatus)('Warning: Parameters provided but query does not use templates. Parameters will be ignored.');
703
+ LogStatus('Warning: Parameters provided but query does not use templates. Parameters will be ignored.');
734
704
  }
735
705
  return { finalSQL, appliedParameters };
736
706
  }
@@ -746,7 +716,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
746
716
  if (!cachedEntry) {
747
717
  return null;
748
718
  }
749
- (0, core_1.LogStatus)(`Cache hit for query ${query.Name} (${query.ID})`);
719
+ LogStatus(`Cache hit for query ${query.Name} (${query.ID})`);
750
720
  // Apply pagination to cached results
751
721
  const { paginatedResult, totalRowCount } = this.applyQueryPagination(cachedEntry.results, params);
752
722
  const remainingTTL = (cachedEntry.timestamp + (cachedEntry.ttlMinutes * 60 * 1000)) - Date.now();
@@ -829,7 +799,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
829
799
  }
830
800
  // Cache the full result set (before pagination)
831
801
  this.queryCache.set(query.ID, parameters, results, cacheConfig);
832
- (0, core_1.LogStatus)(`Cached results for query ${query.Name} (${query.ID})`);
802
+ LogStatus(`Cached results for query ${query.Name} (${query.ID})`);
833
803
  }
834
804
  /**
835
805
  * Internal implementation of batch query execution.
@@ -951,7 +921,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
951
921
  };
952
922
  }
953
923
  catch (e) {
954
- (0, core_1.LogError)(e);
924
+ LogError(e);
955
925
  return {
956
926
  success: false,
957
927
  results: [],
@@ -1092,7 +1062,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1092
1062
  else
1093
1063
  stack.push(variable); // add to the stack for circular reference detection
1094
1064
  // variable values is the view ID of the view that we want to get its WHERE CLAUSE, so we need to get the view entity
1095
- const innerViewEntity = await core_entities_1.ViewInfo.GetViewEntity(variableValue, user);
1065
+ const innerViewEntity = await ViewInfo.GetViewEntity(variableValue, user);
1096
1066
  if (innerViewEntity) {
1097
1067
  // we have the inner view, so now call this function recursively to get the where clause for the inner view
1098
1068
  const innerWhere = await this.RenderViewWhereClause(innerViewEntity, user, stack);
@@ -1115,7 +1085,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1115
1085
  return sWhere;
1116
1086
  }
1117
1087
  catch (e) {
1118
- (0, core_1.LogError)(e);
1088
+ LogError(e);
1119
1089
  throw e;
1120
1090
  }
1121
1091
  }
@@ -1126,7 +1096,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1126
1096
  // This is the internal implementation - pre/post processing is handled by ProviderBase.RunView()
1127
1097
  // Log aggregate input for debugging
1128
1098
  if (params?.Aggregates?.length) {
1129
- (0, core_1.LogStatus)(`[SQLServerDataProvider] InternalRunView received aggregates: entityName=${params.EntityName}, viewID=${params.ViewID}, viewName=${params.ViewName}, aggregateCount=${params.Aggregates.length}, aggregates=${JSON.stringify(params.Aggregates.map(a => ({ expression: a.expression, alias: a.alias })))}`);
1099
+ LogStatus(`[SQLServerDataProvider] InternalRunView received aggregates: entityName=${params.EntityName}, viewID=${params.ViewID}, viewName=${params.ViewName}, aggregateCount=${params.Aggregates.length}, aggregates=${JSON.stringify(params.Aggregates.map(a => ({ expression: a.expression, alias: a.alias })))}`);
1130
1100
  }
1131
1101
  const startTime = new Date();
1132
1102
  try {
@@ -1138,9 +1108,9 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1138
1108
  if (params.ViewEntity)
1139
1109
  viewEntity = params.ViewEntity;
1140
1110
  else if (params.ViewID && params.ViewID.length > 0)
1141
- viewEntity = await core_entities_1.ViewInfo.GetViewEntity(params.ViewID, contextUser);
1111
+ viewEntity = await ViewInfo.GetViewEntity(params.ViewID, contextUser);
1142
1112
  else if (params.ViewName && params.ViewName.length > 0)
1143
- viewEntity = await core_entities_1.ViewInfo.GetViewEntityByName(params.ViewName, contextUser);
1113
+ viewEntity = await ViewInfo.GetViewEntityByName(params.ViewName, contextUser);
1144
1114
  if (!viewEntity) {
1145
1115
  // if we don't have viewEntity, that means it is a dynamic view, so we need EntityName at a minimum
1146
1116
  if (!params.EntityName || params.EntityName.length === 0)
@@ -1244,9 +1214,9 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1244
1214
  }
1245
1215
  }
1246
1216
  // NEXT, apply Row Level Security (RLS)
1247
- if (!entityInfo.UserExemptFromRowLevelSecurity(user, core_1.EntityPermissionType.Read)) {
1217
+ if (!entityInfo.UserExemptFromRowLevelSecurity(user, EntityPermissionType.Read)) {
1248
1218
  // user is NOT exempt from RLS, so we need to apply it
1249
- const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, core_1.EntityPermissionType.Read, '');
1219
+ const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
1250
1220
  if (rlsWhereClause && rlsWhereClause.length > 0) {
1251
1221
  if (bHasWhere) {
1252
1222
  whereSQL += ` AND (${rlsWhereClause})`;
@@ -1427,7 +1397,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1427
1397
  }
1428
1398
  catch (e) {
1429
1399
  const exceptionStopTime = new Date();
1430
- (0, core_1.LogError)(e);
1400
+ LogError(e);
1431
1401
  return {
1432
1402
  RowCount: 0,
1433
1403
  TotalRowCount: 0,
@@ -1566,7 +1536,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1566
1536
  };
1567
1537
  }
1568
1538
  catch (e) {
1569
- (0, core_1.LogError)(e);
1539
+ LogError(e);
1570
1540
  return {
1571
1541
  success: false,
1572
1542
  results: [],
@@ -1649,8 +1619,8 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1649
1619
  }
1650
1620
  }
1651
1621
  // Row Level Security
1652
- if (!entityInfo.UserExemptFromRowLevelSecurity(user, core_1.EntityPermissionType.Read)) {
1653
- const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, core_1.EntityPermissionType.Read, '');
1622
+ if (!entityInfo.UserExemptFromRowLevelSecurity(user, EntityPermissionType.Read)) {
1623
+ const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
1654
1624
  if (rlsWhereClause && rlsWhereClause.length > 0) {
1655
1625
  if (bHasWhere) {
1656
1626
  whereSQL += ` AND (${rlsWhereClause})`;
@@ -1745,7 +1715,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1745
1715
  return results.map(r => r.RecordID);
1746
1716
  }
1747
1717
  catch (e) {
1748
- (0, core_1.LogError)(e);
1718
+ LogError(e);
1749
1719
  return [];
1750
1720
  }
1751
1721
  }
@@ -1782,7 +1752,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1782
1752
  return results;
1783
1753
  }
1784
1754
  catch (e) {
1785
- (0, core_1.LogError)(e);
1755
+ LogError(e);
1786
1756
  return [];
1787
1757
  }
1788
1758
  }
@@ -1829,7 +1799,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1829
1799
  // Validate: if impliedDeletes < 0, there are unexplained rows on the server
1830
1800
  // This could happen with direct SQL inserts that bypassed MJ's tracking
1831
1801
  if (impliedDeletes < 0) {
1832
- (0, core_1.LogStatus)(`Differential validation failed for ${entityInfo.Name}: impliedDeletes=${impliedDeletes} (negative). ` +
1802
+ LogStatus(`Differential validation failed for ${entityInfo.Name}: impliedDeletes=${impliedDeletes} (negative). ` +
1833
1803
  `clientRowCount=${clientRowCount}, newInserts=${newInserts}, serverRowCount=${serverRowCount}. ` +
1834
1804
  `Falling back to full refresh.`);
1835
1805
  return this.runFullQueryAndReturn(params, viewIndex, contextUser);
@@ -1837,7 +1807,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1837
1807
  // Validate: if impliedDeletes > actualDeletes, there are "hidden" deletes
1838
1808
  // not tracked in RecordChanges (e.g., direct SQL deletes)
1839
1809
  if (impliedDeletes > actualDeletes) {
1840
- (0, core_1.LogStatus)(`Differential validation failed for ${entityInfo.Name}: hidden deletes detected. ` +
1810
+ LogStatus(`Differential validation failed for ${entityInfo.Name}: hidden deletes detected. ` +
1841
1811
  `impliedDeletes=${impliedDeletes}, actualDeletes=${actualDeletes}. ` +
1842
1812
  `clientRowCount=${clientRowCount}, newInserts=${newInserts}, serverRowCount=${serverRowCount}. ` +
1843
1813
  `Falling back to full refresh.`);
@@ -1860,7 +1830,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1860
1830
  };
1861
1831
  }
1862
1832
  catch (e) {
1863
- (0, core_1.LogError)(e);
1833
+ LogError(e);
1864
1834
  return {
1865
1835
  viewIndex,
1866
1836
  status: 'error',
@@ -1915,7 +1885,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
1915
1885
  if (!aggregates || aggregates.length === 0) {
1916
1886
  return { aggregateSQL: null, validationErrors: [] };
1917
1887
  }
1918
- const validator = global_1.SQLExpressionValidator.Instance;
1888
+ const validator = SQLExpressionValidator.Instance;
1919
1889
  const validationErrors = [];
1920
1890
  const validExpressions = [];
1921
1891
  const fieldNames = entityInfo.Fields.map(f => f.Name);
@@ -2059,7 +2029,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2059
2029
  if (field)
2060
2030
  fieldList.push(field);
2061
2031
  else
2062
- (0, core_1.LogError)(`Field ${f} not found in entity ${entityInfo.Name}`);
2032
+ LogError(`Field ${f} not found in entity ${entityInfo.Name}`);
2063
2033
  });
2064
2034
  }
2065
2035
  else {
@@ -2075,7 +2045,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2075
2045
  fieldList.push(c.EntityField);
2076
2046
  }
2077
2047
  else {
2078
- (0, core_1.LogError)(`View Field ${c.Name} doesn't match an Entity Field in entity ${entityInfo.Name}. This can happen if the view was saved with a field that no longer exists in the entity. It is best to update the view to remove this field.`);
2048
+ LogError(`View Field ${c.Name} doesn't match an Entity Field in entity ${entityInfo.Name}. This can happen if the view was saved with a field that no longer exists in the entity. It is best to update the view to remove this field.`);
2079
2049
  }
2080
2050
  }
2081
2051
  });
@@ -2088,7 +2058,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2088
2058
  }
2089
2059
  }
2090
2060
  catch (e) {
2091
- (0, core_1.LogError)(e);
2061
+ LogError(e);
2092
2062
  }
2093
2063
  finally {
2094
2064
  return fieldList;
@@ -2139,7 +2109,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2139
2109
  else {
2140
2110
  // we have multiple words, so we need to convert the spaces to AND
2141
2111
  // but first, let's strip the stopwords out of the string
2142
- u = (0, core_1.StripStopWords)(userSearchString);
2112
+ u = StripStopWords(userSearchString);
2143
2113
  // next, include "AND" between all the words so that we have a full text search on all the words
2144
2114
  u = u.replace(/ /g, ' AND ');
2145
2115
  }
@@ -2200,7 +2170,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2200
2170
  throw new Error(`Error saving audit log record`);
2201
2171
  }
2202
2172
  catch (err) {
2203
- (0, core_1.LogError)(err);
2173
+ LogError(err);
2204
2174
  return null;
2205
2175
  }
2206
2176
  }
@@ -2224,7 +2194,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2224
2194
  // START ---- IEntityDataProvider
2225
2195
  /**************************************************************************/
2226
2196
  get ProviderType() {
2227
- return core_1.ProviderType.Database;
2197
+ return ProviderType.Database;
2228
2198
  }
2229
2199
  async GetRecordFavoriteStatus(userId, entityName, CompositeKey, contextUser) {
2230
2200
  const id = await this.GetRecordFavoriteID(userId, entityName, CompositeKey, contextUser);
@@ -2240,7 +2210,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2240
2210
  return null;
2241
2211
  }
2242
2212
  catch (e) {
2243
- (0, core_1.LogError)(e);
2213
+ LogError(e);
2244
2214
  throw e;
2245
2215
  }
2246
2216
  }
@@ -2273,7 +2243,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2273
2243
  }
2274
2244
  }
2275
2245
  catch (e) {
2276
- (0, core_1.LogError)(e);
2246
+ LogError(e);
2277
2247
  throw e;
2278
2248
  }
2279
2249
  }
@@ -2283,7 +2253,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2283
2253
  return this.ExecuteSQL(sSQL, undefined, undefined, contextUser);
2284
2254
  }
2285
2255
  catch (e) {
2286
- (0, core_1.LogError)(e);
2256
+ LogError(e);
2287
2257
  throw e;
2288
2258
  }
2289
2259
  }
@@ -2303,7 +2273,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2303
2273
  // we do this in SQL by combining the pirmary key name and value for each row using the default separator defined by the CompositeKey class
2304
2274
  // the output of this should be like the following 'Field1|Value1||Field2|Value2||Field3|Value3' where the || is the CompositeKey.DefaultFieldDelimiter and the | is the CompositeKey.DefaultValueDelimiter
2305
2275
  const quotes = entity.FirstPrimaryKey.NeedsQuotes ? "'" : '';
2306
- const primaryKeySelectString = `CONCAT(${entity.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${core_1.CompositeKey.DefaultFieldDelimiter}',`)})`;
2276
+ const primaryKeySelectString = `CONCAT(${entity.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${CompositeKey.DefaultFieldDelimiter}',`)})`;
2307
2277
  // for this entity, check to see if it has any fields that are soft links, and for each of those, generate the SQL
2308
2278
  entity.Fields.filter((f) => f.EntityIDFieldName && f.EntityIDFieldName.length > 0).forEach((f) => {
2309
2279
  // each field in f must be processed
@@ -2332,7 +2302,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2332
2302
  const entityInfo = this.Entities.find((e) => e.Name.trim().toLowerCase() === entityDependency.EntityName?.trim().toLowerCase());
2333
2303
  const quotes = entityInfo.FirstPrimaryKey.NeedsQuotes ? "'" : '';
2334
2304
  const relatedEntityInfo = this.Entities.find((e) => e.Name.trim().toLowerCase() === entityDependency.RelatedEntityName?.trim().toLowerCase());
2335
- const primaryKeySelectString = `CONCAT(${entityInfo.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${core_1.CompositeKey.DefaultFieldDelimiter}',`)})`;
2305
+ const primaryKeySelectString = `CONCAT(${entityInfo.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${CompositeKey.DefaultFieldDelimiter}',`)})`;
2336
2306
  if (sSQL.length > 0)
2337
2307
  sSQL += ' UNION ALL ';
2338
2308
  sSQL += `SELECT
@@ -2382,13 +2352,13 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2382
2352
  // entityInfo.PrimaryKeys.forEach((pk) => {
2383
2353
  // pkeyValues.push({FieldName: pk.Name, Value: r[pk.Name]}) // add all of the primary keys, which often is as simple as just "ID", but this is generic way to do it
2384
2354
  // })
2385
- const compositeKey = new core_1.CompositeKey();
2355
+ const compositeKey = new CompositeKey();
2386
2356
  // the row r will have a PrimaryKeyValue field that is a string that is a concatenation of the primary key field names and values
2387
2357
  // we need to parse that out so that we can then pass it to the CompositeKey object
2388
2358
  const pkeys = {};
2389
- const keyValues = r.PrimaryKeyValue.split(core_1.CompositeKey.DefaultFieldDelimiter);
2359
+ const keyValues = r.PrimaryKeyValue.split(CompositeKey.DefaultFieldDelimiter);
2390
2360
  keyValues.forEach((kv) => {
2391
- const parts = kv.split(core_1.CompositeKey.DefaultValueDelimiter);
2361
+ const parts = kv.split(CompositeKey.DefaultValueDelimiter);
2392
2362
  pkeys[parts[0]] = parts[1];
2393
2363
  });
2394
2364
  compositeKey.LoadFromEntityInfoAndRecord(entityInfo, pkeys);
@@ -2404,7 +2374,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2404
2374
  }
2405
2375
  catch (e) {
2406
2376
  // log and throw
2407
- (0, core_1.LogError)(e);
2377
+ LogError(e);
2408
2378
  throw e;
2409
2379
  }
2410
2380
  }
@@ -2536,7 +2506,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2536
2506
  return result;
2537
2507
  }
2538
2508
  catch (e) {
2539
- (0, core_1.LogError)(e);
2509
+ LogError(e);
2540
2510
  await this.RollbackTransaction();
2541
2511
  // attempt to persist the status to the DB, although that might fail
2542
2512
  await this.CompleteMergeLogging(mergeRecordLog, result, contextUser);
@@ -2568,7 +2538,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2568
2538
  throw new Error(`Error saving record merge log`);
2569
2539
  }
2570
2540
  catch (e) {
2571
- (0, core_1.LogError)(e);
2541
+ LogError(e);
2572
2542
  throw e;
2573
2543
  }
2574
2544
  }
@@ -2600,7 +2570,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2600
2570
  }
2601
2571
  catch (e) {
2602
2572
  // do nothing here because we often will get here since some conditions lead to no DB updates possible...
2603
- (0, core_1.LogError)(e);
2573
+ LogError(e);
2604
2574
  // don't bubble up the error here as we're sometimes already in an exception block in caller
2605
2575
  }
2606
2576
  }
@@ -2653,7 +2623,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2653
2623
  let oldData = null;
2654
2624
  // use SQL Server CONCAT function to combine all of the primary key values and then combine them together
2655
2625
  // using the default field delimiter and default value delimiter as defined in the CompositeKey class
2656
- const concatPKIDString = `CONCAT(${entity.EntityInfo.PrimaryKeys.map((pk) => `'${pk.CodeName}','${core_1.CompositeKey.DefaultValueDelimiter}',${pk.Name}`).join(`,'${core_1.CompositeKey.DefaultFieldDelimiter}',`)})`;
2626
+ const concatPKIDString = `CONCAT(${entity.EntityInfo.PrimaryKeys.map((pk) => `'${pk.CodeName}','${CompositeKey.DefaultValueDelimiter}',${pk.Name}`).join(`,'${CompositeKey.DefaultFieldDelimiter}',`)})`;
2657
2627
  if (!bNewRecord)
2658
2628
  oldData = entity.GetAll(true); // get all the OLD values, only do for existing records, for new records, not relevant
2659
2629
  const logRecordChangeSQL = this.GetLogRecordChangeSQL(entity.GetAll(false), oldData, entity.EntityInfo.Name, '@ID', entity.EntityInfo, bNewRecord ? 'Create' : 'Update', user, false);
@@ -2711,7 +2681,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2711
2681
  * @internal
2712
2682
  */
2713
2683
  GetEntityAIActions(entityInfo, before) {
2714
- return aiengine_1.AIEngine.Instance.EntityAIActions.filter((a) => a.EntityID === entityInfo.ID && a.TriggerEvent.toLowerCase().trim() === (before ? 'before save' : 'after save'));
2684
+ return AIEngine.Instance.EntityAIActions.filter((a) => a.EntityID === entityInfo.ID && a.TriggerEvent.toLowerCase().trim() === (before ? 'before save' : 'after save'));
2715
2685
  }
2716
2686
  /**
2717
2687
  * Handles entity actions (non-AI) for save, delete, or validate operations
@@ -2726,14 +2696,14 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2726
2696
  async HandleEntityActions(entity, baseType, before, user) {
2727
2697
  // use the EntityActionEngine for this
2728
2698
  try {
2729
- const engine = actions_1.EntityActionEngineServer.Instance;
2699
+ const engine = EntityActionEngineServer.Instance;
2730
2700
  await engine.Config(false, user);
2731
2701
  const newRecord = entity.IsSaved ? false : true;
2732
2702
  const baseTypeType = baseType === 'save' ? (newRecord ? 'Create' : 'Update') : 'Delete';
2733
2703
  const invocationType = baseType === 'validate' ? 'Validate' : before ? 'Before' + baseTypeType : 'After' + baseTypeType;
2734
2704
  const invocationTypeEntity = engine.InvocationTypes.find((i) => i.Name === invocationType);
2735
2705
  if (!invocationTypeEntity) {
2736
- (0, core_1.LogError)(`Invocation Type ${invocationType} not found in metadata`);
2706
+ LogError(`Invocation Type ${invocationType} not found in metadata`);
2737
2707
  return [];
2738
2708
  // throw new Error(`Invocation Type ${invocationType} not found in metadata`);
2739
2709
  }
@@ -2751,7 +2721,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2751
2721
  return results;
2752
2722
  }
2753
2723
  catch (e) {
2754
- (0, core_1.LogError)(e);
2724
+ LogError(e);
2755
2725
  return [];
2756
2726
  }
2757
2727
  }
@@ -2770,10 +2740,10 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2770
2740
  if (baseType === 'delete')
2771
2741
  return;
2772
2742
  // Make sure AI Metadata is loaded here...
2773
- await aiengine_1.AIEngine.Instance.Config(false, user);
2743
+ await AIEngine.Instance.Config(false, user);
2774
2744
  const actions = this.GetEntityAIActions(entity.EntityInfo, before); // get the actions we need to do for this entity
2775
2745
  if (actions && actions.length > 0) {
2776
- const ai = aiengine_1.AIEngine.Instance;
2746
+ const ai = AIEngine.Instance;
2777
2747
  for (let i = 0; i < actions.length; i++) {
2778
2748
  const a = actions[i];
2779
2749
  if ((a.TriggerEvent === 'before save' && before) || (a.TriggerEvent === 'after save' && !before)) {
@@ -2796,11 +2766,11 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2796
2766
  }
2797
2767
  else {
2798
2768
  // No transaction active, add the task immediately
2799
- queue_1.QueueManager.AddTask('Entity AI Action', p, null, user);
2769
+ QueueManager.AddTask('Entity AI Action', p, null, user);
2800
2770
  }
2801
2771
  }
2802
2772
  catch (e) {
2803
- (0, core_1.LogError)(e.message);
2773
+ LogError(e.message);
2804
2774
  }
2805
2775
  }
2806
2776
  }
@@ -2808,16 +2778,16 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2808
2778
  }
2809
2779
  }
2810
2780
  catch (e) {
2811
- (0, core_1.LogError)(e);
2781
+ LogError(e);
2812
2782
  }
2813
2783
  }
2814
2784
  async Save(entity, user, options) {
2815
- const entityResult = new core_1.BaseEntityResult();
2785
+ const entityResult = new BaseEntityResult();
2816
2786
  try {
2817
2787
  entity.RegisterTransactionPreprocessing();
2818
2788
  const bNewRecord = !entity.IsSaved;
2819
2789
  if (!options)
2820
- options = new core_1.EntitySaveOptions();
2790
+ options = new EntitySaveOptions();
2821
2791
  const bReplay = !!options.ReplayOnly;
2822
2792
  if (!bReplay && !bNewRecord && !entity.EntityInfo.AllowUpdateAPI) {
2823
2793
  // existing record and not allowed to update
@@ -2885,7 +2855,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2885
2855
  // we are part of a transaction group, so just add our query to the list
2886
2856
  // and when the transaction is committed, we will send all the queries at once
2887
2857
  this._bAllowRefresh = false; // stop refreshes of metadata while we're doing work
2888
- entity.TransactionGroup.AddTransaction(new core_1.TransactionItem(entity, entityResult.Type === 'create' ? 'Create' : 'Update', sSQL, null, {
2858
+ entity.TransactionGroup.AddTransaction(new TransactionItem(entity, entityResult.Type === 'create' ? 'Create' : 'Update', sSQL, null, {
2889
2859
  dataSource: this._pool,
2890
2860
  simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
2891
2861
  entityName: entity.EntityInfo.Name
@@ -2927,10 +2897,12 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2927
2897
  else {
2928
2898
  try {
2929
2899
  // Execute SQL with optional simple SQL fallback for loggers
2900
+ // IS-A: use entity's ProviderTransaction when available for shared transaction
2930
2901
  const rawResult = await this.ExecuteSQL(sSQL, null, {
2931
2902
  isMutation: true,
2932
2903
  description: `Save ${entity.EntityInfo.Name}`,
2933
- simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined
2904
+ simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
2905
+ connectionSource: entity.ProviderTransaction ?? undefined
2934
2906
  }, user);
2935
2907
  // Process rows with user context for decryption
2936
2908
  result = await this.ProcessEntityRows(rawResult, entity.EntityInfo, user);
@@ -2971,7 +2943,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
2971
2943
  this._bAllowRefresh = true; // allow refreshes again if we get a failure here
2972
2944
  entityResult.EndedAt = new Date();
2973
2945
  entityResult.Message = e.message;
2974
- (0, core_1.LogError)(e);
2946
+ LogError(e);
2975
2947
  throw e; // rethrow the error
2976
2948
  }
2977
2949
  }
@@ -3028,7 +3000,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3028
3000
  */
3029
3001
  async generateSPParams(entity, isUpdate, contextUser) {
3030
3002
  // Generate a unique suffix for variable names to avoid collisions in batch scripts
3031
- const uniqueSuffix = '_' + (0, uuid_1.v4)().substring(0, 8).replace(/-/g, '');
3003
+ const uniqueSuffix = '_' + uuidv4().substring(0, 8).replace(/-/g, '');
3032
3004
  const declarations = [];
3033
3005
  const setStatements = [];
3034
3006
  const execParams = [];
@@ -3074,7 +3046,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3074
3046
  if (f.Encrypt && f.EncryptionKeyID && value !== null && value !== undefined) {
3075
3047
  // Lazy-load encryption engine only when needed
3076
3048
  if (!encryptionEngine) {
3077
- encryptionEngine = encryption_1.EncryptionEngine.Instance;
3049
+ encryptionEngine = EncryptionEngine.Instance;
3078
3050
  await encryptionEngine.Config(false, contextUser);
3079
3051
  }
3080
3052
  // Only encrypt if the value is not already encrypted
@@ -3148,7 +3120,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3148
3120
  generateSetStatementValue(f, value) {
3149
3121
  let val = value;
3150
3122
  switch (f.TSType) {
3151
- case core_1.EntityFieldTSType.Boolean:
3123
+ case EntityFieldTSType.Boolean:
3152
3124
  // check to see if the value is a string and if it is equal to true, if so, set the value to 1
3153
3125
  if (typeof value === 'string' && value.trim().toLowerCase() === 'true')
3154
3126
  val = 1;
@@ -3157,7 +3129,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3157
3129
  else
3158
3130
  val = value ? 1 : 0;
3159
3131
  return val.toString();
3160
- case core_1.EntityFieldTSType.String:
3132
+ case EntityFieldTSType.String:
3161
3133
  // Handle string escaping for SET statements
3162
3134
  if (typeof val === 'string') {
3163
3135
  val = val.replace(/'/g, "''");
@@ -3169,7 +3141,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3169
3141
  val = val.replace(/'/g, "''");
3170
3142
  }
3171
3143
  return `${f.UnicodePrefix}'${val}'`;
3172
- case core_1.EntityFieldTSType.Date:
3144
+ case EntityFieldTSType.Date:
3173
3145
  if (val !== null && val !== undefined) {
3174
3146
  if (typeof val === 'number') {
3175
3147
  // we have a timestamp - milliseconds since Unix Epoch
@@ -3182,7 +3154,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3182
3154
  val = val.toISOString(); // convert the date to ISO format for storage in the DB
3183
3155
  }
3184
3156
  return `'${val}'`;
3185
- case core_1.EntityFieldTSType.Number:
3157
+ case EntityFieldTSType.Number:
3186
3158
  return val.toString();
3187
3159
  default:
3188
3160
  // For other types, convert to string and quote if needed
@@ -3200,7 +3172,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3200
3172
  let quotes = '';
3201
3173
  let val = value;
3202
3174
  switch (f.TSType) {
3203
- case core_1.EntityFieldTSType.Boolean:
3175
+ case EntityFieldTSType.Boolean:
3204
3176
  // check to see if the value is a string and if it is equal to true, if so, set the value to 1
3205
3177
  if (typeof value === 'string' && value.trim().toLowerCase() === 'true')
3206
3178
  val = 1;
@@ -3209,10 +3181,10 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3209
3181
  else
3210
3182
  val = value ? 1 : 0;
3211
3183
  break;
3212
- case core_1.EntityFieldTSType.String:
3184
+ case EntityFieldTSType.String:
3213
3185
  quotes = "'";
3214
3186
  break;
3215
- case core_1.EntityFieldTSType.Date:
3187
+ case EntityFieldTSType.Date:
3216
3188
  quotes = "'";
3217
3189
  if (val !== null && val !== undefined) {
3218
3190
  if (typeof val === 'number') {
@@ -3369,6 +3341,10 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3369
3341
  if (Array.isArray(obj)) {
3370
3342
  return obj.map(item => this.escapeQuotesInProperties(item, quoteToEscape));
3371
3343
  }
3344
+ // Handle Date objects - convert to ISO string before they lose their value
3345
+ if (obj instanceof Date) {
3346
+ return obj.toISOString();
3347
+ }
3372
3348
  // Handle objects recursively
3373
3349
  if (typeof obj === 'object') {
3374
3350
  const sRet = {};
@@ -3441,14 +3417,14 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3441
3417
  bDiff = false; // this branch of logic ensures that undefined and null are treated the same
3442
3418
  else {
3443
3419
  switch (f.TSType) {
3444
- case core_1.EntityFieldTSType.String:
3420
+ case EntityFieldTSType.String:
3445
3421
  bDiff = oldData[key] !== newData[key];
3446
3422
  break;
3447
- case core_1.EntityFieldTSType.Date:
3423
+ case EntityFieldTSType.Date:
3448
3424
  bDiff = new Date(oldData[key]).getTime() !== new Date(newData[key]).getTime();
3449
3425
  break;
3450
- case core_1.EntityFieldTSType.Number:
3451
- case core_1.EntityFieldTSType.Boolean:
3426
+ case EntityFieldTSType.Number:
3427
+ case EntityFieldTSType.Boolean:
3452
3428
  bDiff = oldData[key] !== newData[key];
3453
3429
  break;
3454
3430
  }
@@ -3502,7 +3478,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3502
3478
  const ret = d[0];
3503
3479
  // we need to post process the retrieval to see if we have any char or nchar fields and we need to remove their trailing spaces
3504
3480
  for (const field of entity.EntityInfo.Fields) {
3505
- if (field.TSType === core_1.EntityFieldTSType.String &&
3481
+ if (field.TSType === EntityFieldTSType.String &&
3506
3482
  field.Type.toLowerCase().includes('char') &&
3507
3483
  !field.Type.toLowerCase().includes('varchar')) {
3508
3484
  // trim trailing spaces for char and nchar fields
@@ -3634,11 +3610,11 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3634
3610
  return { fullSQL: sSQL, simpleSQL: sSimpleSQL };
3635
3611
  }
3636
3612
  async Delete(entity, options, user) {
3637
- const result = new core_1.BaseEntityResult();
3613
+ const result = new BaseEntityResult();
3638
3614
  try {
3639
3615
  entity.RegisterTransactionPreprocessing();
3640
3616
  if (!options)
3641
- options = new core_1.EntityDeleteOptions();
3617
+ options = new EntityDeleteOptions();
3642
3618
  const bReplay = options.ReplayOnly;
3643
3619
  if (!entity.IsSaved && !bReplay)
3644
3620
  // existing record and not allowed to update
@@ -3666,7 +3642,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3666
3642
  entity.RaiseReadyForTransaction();
3667
3643
  // we are part of a transaction group, so just add our query to the list
3668
3644
  // and when the transaction is committed, we will send all the queries at once
3669
- entity.TransactionGroup.AddTransaction(new core_1.TransactionItem(entity, 'Delete', sSQL, null, {
3645
+ entity.TransactionGroup.AddTransaction(new TransactionItem(entity, 'Delete', sSQL, null, {
3670
3646
  dataSource: this._pool,
3671
3647
  simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
3672
3648
  entityName: entity.EntityInfo.Name
@@ -3707,10 +3683,12 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3707
3683
  d = [entity.GetAll()]; // just return the entity as it was before the save as we are NOT saving anything as we are in replay mode
3708
3684
  }
3709
3685
  else {
3686
+ // IS-A: use entity's ProviderTransaction when available for shared transaction
3710
3687
  d = await this.ExecuteSQL(sSQL, null, {
3711
3688
  isMutation: true,
3712
3689
  description: `Delete ${entity.EntityInfo.Name}`,
3713
- simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined
3690
+ simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
3691
+ connectionSource: entity.ProviderTransaction ?? undefined
3714
3692
  }, user);
3715
3693
  }
3716
3694
  if (d && d.length > 0) {
@@ -3747,7 +3725,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3747
3725
  }
3748
3726
  }
3749
3727
  catch (e) {
3750
- (0, core_1.LogError)(e);
3728
+ LogError(e);
3751
3729
  result.Message = e.message;
3752
3730
  result.Success = false;
3753
3731
  result.EndedAt = new Date();
@@ -3964,7 +3942,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3964
3942
  // the reason we continue below if we have NOT loaded Entities is that when the system first bootstraps, DATASET gets loaded
3965
3943
  // FIRST before Entities are loaded to load the entity metadata so this would ALWAYS fail :)
3966
3944
  // entity not found, return a failed result, shouldn't ever get here due to the foreign key constraint on the table
3967
- (0, core_1.LogError)(`Entity not found for dataset item ${item.Code} in dataset ${datasetName}`);
3945
+ LogError(`Entity not found for dataset item ${item.Code} in dataset ${datasetName}`);
3968
3946
  return null;
3969
3947
  }
3970
3948
  else {
@@ -3978,7 +3956,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
3978
3956
  }
3979
3957
  });
3980
3958
  if (invalidColumns.length > 0) {
3981
- (0, core_1.LogError)(`Invalid columns specified for dataset item ${item.Code} in dataset ${datasetName}: ${invalidColumns.join(', ')}`);
3959
+ LogError(`Invalid columns specified for dataset item ${item.Code} in dataset ${datasetName}: ${invalidColumns.join(', ')}`);
3982
3960
  return null;
3983
3961
  }
3984
3962
  }
@@ -4097,7 +4075,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4097
4075
  const appEntities = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwApplicationEntities ORDER BY ApplicationName`, undefined, undefined, contextUser);
4098
4076
  const ret = [];
4099
4077
  for (let i = 0; i < apps.length; i++) {
4100
- ret.push(new core_1.ApplicationInfo(this, {
4078
+ ret.push(new ApplicationInfo(this, {
4101
4079
  ...apps[i],
4102
4080
  ApplicationEntities: appEntities.filter((ae) => ae.ApplicationName.trim().toLowerCase() === apps[i].Name.trim().toLowerCase()),
4103
4081
  }));
@@ -4108,7 +4086,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4108
4086
  const alts = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwAuditLogTypes`, null, undefined, contextUser);
4109
4087
  const ret = [];
4110
4088
  for (let i = 0; i < alts.length; i++) {
4111
- const alt = new core_1.AuditLogTypeInfo(alts[i]);
4089
+ const alt = new AuditLogTypeInfo(alts[i]);
4112
4090
  ret.push(alt);
4113
4091
  }
4114
4092
  return ret;
@@ -4118,7 +4096,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4118
4096
  const userRoles = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwUserRoles ORDER BY UserID`, undefined, undefined, contextUser);
4119
4097
  const ret = [];
4120
4098
  for (let i = 0; i < users.length; i++) {
4121
- ret.push(new core_1.UserInfo(this, {
4099
+ ret.push(new UserInfo(this, {
4122
4100
  ...users[i],
4123
4101
  UserRoles: userRoles.filter((ur) => ur.UserID === users[i].ID),
4124
4102
  }));
@@ -4130,7 +4108,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4130
4108
  const authRoles = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwAuthorizationRoles ORDER BY AuthorizationName`, undefined, undefined, contextUser);
4131
4109
  const ret = [];
4132
4110
  for (let i = 0; i < auths.length; i++) {
4133
- ret.push(new core_1.AuthorizationInfo(this, {
4111
+ ret.push(new AuthorizationInfo(this, {
4134
4112
  ...auths[i],
4135
4113
  AuthorizationRoles: authRoles.filter((ar) => ar.AuthorizationName.trim().toLowerCase() === auths[i].Name.trim().toLowerCase()),
4136
4114
  }));
@@ -4161,7 +4139,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4161
4139
  return rows;
4162
4140
  }
4163
4141
  // Find all datetime fields in the entity
4164
- const datetimeFields = entityInfo.Fields.filter((field) => field.TSType === core_1.EntityFieldTSType.Date);
4142
+ const datetimeFields = entityInfo.Fields.filter((field) => field.TSType === EntityFieldTSType.Date);
4165
4143
  // Find all encrypted fields in the entity
4166
4144
  const encryptedFields = entityInfo.Fields.filter((field) => field.Encrypt && field.EncryptionKeyID);
4167
4145
  // If there are no fields requiring processing, return the rows as-is
@@ -4173,7 +4151,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4173
4151
  // Get encryption engine instance (lazy - only if we have encrypted fields)
4174
4152
  let encryptionEngine = null;
4175
4153
  if (encryptedFields.length > 0) {
4176
- encryptionEngine = encryption_1.EncryptionEngine.Instance;
4154
+ encryptionEngine = EncryptionEngine.Instance;
4177
4155
  await encryptionEngine.Config(false, contextUser);
4178
4156
  }
4179
4157
  // Process each row - need to use Promise.all for async decryption
@@ -4262,7 +4240,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4262
4240
  // Log error but don't fail the entire operation
4263
4241
  // Return the encrypted value so the caller knows something is wrong
4264
4242
  const message = decryptError instanceof Error ? decryptError.message : String(decryptError);
4265
- (0, core_1.LogError)(`Failed to decrypt field "${field.Name}" on entity "${entityInfo.Name}": ${message}. ` +
4243
+ LogError(`Failed to decrypt field "${field.Name}" on entity "${entityInfo.Name}": ${message}. ` +
4266
4244
  'The encrypted value will be returned unchanged.');
4267
4245
  // Keep the encrypted value in the row - let the caller decide what to do
4268
4246
  }
@@ -4299,7 +4277,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4299
4277
  // This prevents EREQINPROG errors when multiple queries try to use the same transaction
4300
4278
  return new Promise((resolve, reject) => {
4301
4279
  this._sqlQueue$.next({
4302
- id: (0, uuid_1.v4)(),
4280
+ id: uuidv4(),
4303
4281
  query,
4304
4282
  parameters,
4305
4283
  context,
@@ -4384,7 +4362,8 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4384
4362
  async ExecuteSQL(query, parameters = null, options, contextUser) {
4385
4363
  try {
4386
4364
  // Use internal method with logging options
4387
- const result = await this._internalExecuteSQL(query, parameters, undefined, {
4365
+ // Pass connectionSource if provided (used by IS-A chain orchestration for shared transactions)
4366
+ const result = await this._internalExecuteSQL(query, parameters, options?.connectionSource, {
4388
4367
  description: options?.description,
4389
4368
  ignoreLogging: options?.ignoreLogging,
4390
4369
  isMutation: options?.isMutation,
@@ -4618,7 +4597,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4618
4597
  }
4619
4598
  }
4620
4599
  catch (e) {
4621
- (0, core_1.LogError)(e);
4600
+ LogError(e);
4622
4601
  throw e;
4623
4602
  }
4624
4603
  }
@@ -4703,6 +4682,35 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4703
4682
  this._datetimeOffsetTestComplete = true;
4704
4683
  }
4705
4684
  }
4685
+ /**
4686
+ * Begin an independent transaction for IS-A chain orchestration.
4687
+ * Returns a new sql.Transaction object that is NOT linked to the provider's
4688
+ * internal transaction state (used by TransactionGroup). Each IS-A chain
4689
+ * gets its own transaction to avoid interference with other operations.
4690
+ */
4691
+ async BeginISATransaction() {
4692
+ const transaction = new sql.Transaction(this._pool);
4693
+ await transaction.begin();
4694
+ return transaction;
4695
+ }
4696
+ /**
4697
+ * Commit an IS-A chain transaction.
4698
+ * @param txn The sql.Transaction object returned from BeginISATransaction()
4699
+ */
4700
+ async CommitISATransaction(txn) {
4701
+ if (txn && txn instanceof sql.Transaction) {
4702
+ await txn.commit();
4703
+ }
4704
+ }
4705
+ /**
4706
+ * Rollback an IS-A chain transaction.
4707
+ * @param txn The sql.Transaction object returned from BeginISATransaction()
4708
+ */
4709
+ async RollbackISATransaction(txn) {
4710
+ if (txn && txn instanceof sql.Transaction) {
4711
+ await txn.rollback();
4712
+ }
4713
+ }
4706
4714
  async BeginTransaction() {
4707
4715
  try {
4708
4716
  this._transactionDepth++;
@@ -4726,7 +4734,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4726
4734
  }
4727
4735
  catch (e) {
4728
4736
  this._transactionDepth--; // Restore depth on error
4729
- (0, core_1.LogError)(e);
4737
+ LogError(e);
4730
4738
  throw e; // force caller to handle
4731
4739
  }
4732
4740
  }
@@ -4757,7 +4765,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4757
4765
  }
4758
4766
  }
4759
4767
  catch (e) {
4760
- (0, core_1.LogError)(e);
4768
+ LogError(e);
4761
4769
  throw e; // force caller to handle
4762
4770
  }
4763
4771
  }
@@ -4783,7 +4791,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4783
4791
  const deferredCount = this._deferredTasks.length;
4784
4792
  this._deferredTasks = [];
4785
4793
  if (deferredCount > 0) {
4786
- (0, core_1.LogStatus)(`Cleared ${deferredCount} deferred tasks after transaction rollback`);
4794
+ LogStatus(`Cleared ${deferredCount} deferred tasks after transaction rollback`);
4787
4795
  }
4788
4796
  }
4789
4797
  else {
@@ -4809,7 +4817,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4809
4817
  this._savepointCounter = 0;
4810
4818
  this._transactionState$.next(false);
4811
4819
  }
4812
- (0, core_1.LogError)(e);
4820
+ LogError(e);
4813
4821
  throw e; // force caller to handle
4814
4822
  }
4815
4823
  }
@@ -4821,7 +4829,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4821
4829
  async RefreshIfNeeded() {
4822
4830
  // Skip refresh if a transaction is active
4823
4831
  if (this.isTransactionActive) {
4824
- (0, core_1.LogStatus)('Skipping metadata refresh - transaction is active');
4832
+ LogStatus('Skipping metadata refresh - transaction is active');
4825
4833
  return false;
4826
4834
  }
4827
4835
  // Call parent implementation if no transaction
@@ -4835,7 +4843,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4835
4843
  async processDeferredTasks() {
4836
4844
  if (this._deferredTasks.length === 0)
4837
4845
  return;
4838
- (0, core_1.LogStatus)(`Processing ${this._deferredTasks.length} deferred tasks after transaction commit`);
4846
+ LogStatus(`Processing ${this._deferredTasks.length} deferred tasks after transaction commit`);
4839
4847
  // Copy and clear the deferred tasks array
4840
4848
  const tasksToProcess = [...this._deferredTasks];
4841
4849
  this._deferredTasks = [];
@@ -4844,25 +4852,30 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4844
4852
  try {
4845
4853
  if (task.type === 'Entity AI Action') {
4846
4854
  // Process the AI action now that we're outside the transaction
4847
- await queue_1.QueueManager.AddTask('Entity AI Action', task.data, task.options, task.user);
4855
+ await QueueManager.AddTask('Entity AI Action', task.data, task.options, task.user);
4848
4856
  }
4849
4857
  // Add other task types here as needed
4850
4858
  }
4851
4859
  catch (error) {
4852
- (0, core_1.LogError)(`Failed to process deferred ${task.type} task: ${error}`);
4860
+ LogError(`Failed to process deferred ${task.type} task: ${error}`);
4853
4861
  // Continue processing other tasks even if one fails
4854
4862
  }
4855
4863
  }
4856
- (0, core_1.LogStatus)(`Completed processing deferred tasks`);
4864
+ LogStatus(`Completed processing deferred tasks`);
4857
4865
  }
4858
4866
  get LocalStorageProvider() {
4859
4867
  if (!this._localStorageProvider)
4860
- this._localStorageProvider = new core_1.InMemoryLocalStorageProvider();
4868
+ this._localStorageProvider = new InMemoryLocalStorageProvider();
4861
4869
  return this._localStorageProvider;
4862
4870
  }
4863
- async GetEntityRecordNames(info, contextUser) {
4871
+ get FileSystemProvider() {
4872
+ if (!this._fileSystemProvider)
4873
+ this._fileSystemProvider = new NodeFileSystemProvider();
4874
+ return this._fileSystemProvider;
4875
+ }
4876
+ async InternalGetEntityRecordNames(info, contextUser) {
4864
4877
  const promises = info.map(async (item) => {
4865
- const r = await this.GetEntityRecordName(item.EntityName, item.CompositeKey, contextUser);
4878
+ const r = await this.InternalGetEntityRecordName(item.EntityName, item.CompositeKey, contextUser);
4866
4879
  return {
4867
4880
  EntityName: item.EntityName,
4868
4881
  CompositeKey: item.CompositeKey,
@@ -4873,7 +4886,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4873
4886
  });
4874
4887
  return Promise.all(promises);
4875
4888
  }
4876
- async GetEntityRecordName(entityName, CompositeKey, contextUser) {
4889
+ async InternalGetEntityRecordName(entityName, CompositeKey, contextUser) {
4877
4890
  try {
4878
4891
  const sql = this.GetEntityRecordNameSQL(entityName, CompositeKey);
4879
4892
  if (sql) {
@@ -4883,13 +4896,13 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4883
4896
  return data[0][fields[0]]; // return first field
4884
4897
  }
4885
4898
  else {
4886
- (0, core_1.LogError)(`Entity ${entityName} record ${CompositeKey.ToString()} not found, returning null`);
4899
+ LogError(`Entity ${entityName} record ${CompositeKey.ToString()} not found, returning null`);
4887
4900
  return null;
4888
4901
  }
4889
4902
  }
4890
4903
  }
4891
4904
  catch (e) {
4892
- (0, core_1.LogError)(e);
4905
+ LogError(e);
4893
4906
  return null;
4894
4907
  }
4895
4908
  }
@@ -4900,7 +4913,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4900
4913
  else {
4901
4914
  const f = e.NameField;
4902
4915
  if (!f) {
4903
- (0, core_1.LogError)(`Entity ${entityName} does not have an IsNameField or a field with the column name of Name, returning null, use recordId`);
4916
+ LogError(`Entity ${entityName} does not have an IsNameField or a field with the column name of Name, returning null, use recordId`);
4904
4917
  return null;
4905
4918
  }
4906
4919
  else {
@@ -4919,7 +4932,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4919
4932
  }
4920
4933
  }
4921
4934
  async CreateTransactionGroup() {
4922
- return new SQLServerTransactionGroup_1.SQLServerTransactionGroup();
4935
+ return new SQLServerTransactionGroup();
4923
4936
  }
4924
4937
  /**************************************************************************/
4925
4938
  // END ---- IMetadataProvider
@@ -4928,5 +4941,4 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
4928
4941
  return this;
4929
4942
  }
4930
4943
  }
4931
- exports.SQLServerDataProvider = SQLServerDataProvider;
4932
4944
  //# sourceMappingURL=SQLServerDataProvider.js.map