@memberjunction/server 6.1.0-edge.0 → 6.1.0-edge.2

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 (175) hide show
  1. package/dist/auth/exampleNewUserSubClass.js +1 -1
  2. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  3. package/dist/auth/index.d.ts.map +1 -1
  4. package/dist/auth/index.js +9 -5
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/auth/magicLink/MagicLinkService.js +1 -1
  7. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  8. package/dist/auth/newUsers.js +1 -1
  9. package/dist/auth/newUsers.js.map +1 -1
  10. package/dist/context.js +2 -2
  11. package/dist/context.js.map +1 -1
  12. package/dist/generated/generated.d.ts +208 -646
  13. package/dist/generated/generated.d.ts.map +1 -1
  14. package/dist/generated/generated.js +1123 -3773
  15. package/dist/generated/generated.js.map +1 -1
  16. package/dist/generic/ResolverBase.d.ts +19 -3
  17. package/dist/generic/ResolverBase.d.ts.map +1 -1
  18. package/dist/generic/ResolverBase.js +51 -17
  19. package/dist/generic/ResolverBase.js.map +1 -1
  20. package/dist/index.d.ts +6 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +100 -42
  23. package/dist/index.js.map +1 -1
  24. package/dist/logging/secretRedactor.d.ts +9 -0
  25. package/dist/logging/secretRedactor.d.ts.map +1 -1
  26. package/dist/logging/secretRedactor.js +39 -0
  27. package/dist/logging/secretRedactor.js.map +1 -1
  28. package/dist/middleware/BaseServerMiddleware.d.ts +5 -0
  29. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
  30. package/dist/middleware/BaseServerMiddleware.js +5 -0
  31. package/dist/middleware/BaseServerMiddleware.js.map +1 -1
  32. package/dist/realtimeWidget/WidgetSessionService.js +1 -1
  33. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  34. package/dist/realtimeWidget/widgetGuestElevation.js +1 -1
  35. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  36. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  37. package/dist/resolvers/ActionResolver.js +12 -0
  38. package/dist/resolvers/ActionResolver.js.map +1 -1
  39. package/dist/resolvers/ComponentRegistryResolver.js +1 -1
  40. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  41. package/dist/resolvers/EntityPermissionResolver.js +1 -1
  42. package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
  43. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +9 -0
  44. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  45. package/dist/resolvers/IntegrationDiscoveryResolver.js +3 -1
  46. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  47. package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
  48. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  49. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
  50. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  51. package/dist/resolvers/RealtimeClientSessionResolver.js +5 -0
  52. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  53. package/dist/resolvers/SearchEntitiesResolver.js +1 -1
  54. package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
  55. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  56. package/dist/resolvers/SqlLoggingConfigResolver.js +1 -1
  57. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  58. package/dist/resolvers/SyncRolesUsersResolver.js +2 -2
  59. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  60. package/dist/resolvers/TaskGraphFrameResolver.d.ts +85 -0
  61. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -0
  62. package/dist/resolvers/TaskGraphFrameResolver.js +163 -0
  63. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -0
  64. package/dist/resolvers/UserFavoriteResolver.js +1 -1
  65. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  66. package/dist/rest/OAuthCallbackHandler.js +1 -1
  67. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  68. package/dist/rest/RESTEndpointHandler.d.ts +0 -4
  69. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  70. package/dist/rest/RESTEndpointHandler.js +1 -15
  71. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  72. package/dist/rest/SignatureWebhookHandler.js +1 -1
  73. package/dist/rest/SignatureWebhookHandler.js.map +1 -1
  74. package/dist/services/DurableEntityActionTaskSubmitter.d.ts +26 -0
  75. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -0
  76. package/dist/services/DurableEntityActionTaskSubmitter.js +55 -0
  77. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -0
  78. package/dist/services/ScheduledJobsService.js +1 -1
  79. package/dist/services/ScheduledJobsService.js.map +1 -1
  80. package/dist/services/StartTaskGraphDispatcher.d.ts +33 -0
  81. package/dist/services/StartTaskGraphDispatcher.d.ts.map +1 -0
  82. package/dist/services/StartTaskGraphDispatcher.js +46 -0
  83. package/dist/services/StartTaskGraphDispatcher.js.map +1 -0
  84. package/dist/services/TaskGraphActionRunner.d.ts +34 -0
  85. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -0
  86. package/dist/services/TaskGraphActionRunner.js +95 -0
  87. package/dist/services/TaskGraphActionRunner.js.map +1 -0
  88. package/dist/services/TaskGraphAgentRunner.d.ts +28 -0
  89. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -0
  90. package/dist/services/TaskGraphAgentRunner.js +104 -0
  91. package/dist/services/TaskGraphAgentRunner.js.map +1 -0
  92. package/dist/services/TaskGraphContinuationDeliverer.d.ts +70 -0
  93. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -0
  94. package/dist/services/TaskGraphContinuationDeliverer.js +162 -0
  95. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -0
  96. package/dist/services/TaskGraphPromptRunner.d.ts +31 -0
  97. package/dist/services/TaskGraphPromptRunner.d.ts.map +1 -0
  98. package/dist/services/TaskGraphPromptRunner.js +134 -0
  99. package/dist/services/TaskGraphPromptRunner.js.map +1 -0
  100. package/dist/services/TaskGraphProviderFactory.d.ts +39 -0
  101. package/dist/services/TaskGraphProviderFactory.d.ts.map +1 -0
  102. package/dist/services/TaskGraphProviderFactory.js +28 -0
  103. package/dist/services/TaskGraphProviderFactory.js.map +1 -0
  104. package/dist/telephony/RingCentralTelephonyService.js +1 -1
  105. package/dist/telephony/RingCentralTelephonyService.js.map +1 -1
  106. package/dist/telephony/TwilioTelephonyRouter.js +1 -1
  107. package/dist/telephony/TwilioTelephonyRouter.js.map +1 -1
  108. package/dist/telephony/VonageTelephonyRouter.js +1 -1
  109. package/dist/telephony/VonageTelephonyRouter.js.map +1 -1
  110. package/package.json +92 -89
  111. package/src/__tests__/RealtimeClientSessionResolver.test.ts +1 -1
  112. package/src/__tests__/ResolverBase.frozenRecordMapping.test.ts +133 -0
  113. package/src/__tests__/ResolverBase.transportMapping.test.ts +289 -0
  114. package/src/__tests__/SessionManager.test.ts +1 -1
  115. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +249 -0
  116. package/src/__tests__/TaskGraphFrameResolver.test.ts +136 -0
  117. package/src/__tests__/WidgetSessionService.test.ts +1 -1
  118. package/src/__tests__/databaseAbstraction.test.ts +1 -1
  119. package/src/__tests__/magicLinkService.provisionLog.test.ts +1 -1
  120. package/src/__tests__/resolverBase.rls.test.ts +3 -0
  121. package/src/__tests__/secretRedactor.failOpen.test.ts +135 -0
  122. package/src/__tests__/widgetGuestElevation.test.ts +1 -1
  123. package/src/auth/exampleNewUserSubClass.ts +1 -1
  124. package/src/auth/index.ts +10 -6
  125. package/src/auth/magicLink/MagicLinkService.ts +1 -1
  126. package/src/auth/newUsers.ts +1 -1
  127. package/src/context.ts +2 -2
  128. package/src/generated/generated.ts +955 -2735
  129. package/src/generic/ResolverBase.ts +52 -17
  130. package/src/index.ts +108 -43
  131. package/src/logging/secretRedactor.ts +43 -0
  132. package/src/middleware/BaseServerMiddleware.ts +5 -0
  133. package/src/realtimeWidget/WidgetSessionService.ts +1 -1
  134. package/src/realtimeWidget/widgetGuestElevation.ts +1 -1
  135. package/src/resolvers/ActionResolver.ts +13 -0
  136. package/src/resolvers/ComponentRegistryResolver.ts +1 -1
  137. package/src/resolvers/EntityPermissionResolver.ts +1 -1
  138. package/src/resolvers/IntegrationDiscoveryResolver.ts +12 -2
  139. package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
  140. package/src/resolvers/RealtimeClientSessionResolver.ts +13 -0
  141. package/src/resolvers/SearchEntitiesResolver.ts +1 -1
  142. package/src/resolvers/SqlLoggingConfigResolver.ts +1 -2
  143. package/src/resolvers/SyncRolesUsersResolver.ts +2 -2
  144. package/src/resolvers/TaskGraphFrameResolver.ts +160 -0
  145. package/src/resolvers/UserFavoriteResolver.ts +1 -1
  146. package/src/rest/OAuthCallbackHandler.ts +1 -1
  147. package/src/rest/RESTEndpointHandler.ts +1 -15
  148. package/src/rest/SignatureWebhookHandler.ts +1 -1
  149. package/src/services/DurableEntityActionTaskSubmitter.ts +78 -0
  150. package/src/services/ScheduledJobsService.ts +1 -1
  151. package/src/services/StartTaskGraphDispatcher.ts +78 -0
  152. package/src/services/TaskGraphActionRunner.ts +99 -0
  153. package/src/services/TaskGraphAgentRunner.ts +115 -0
  154. package/src/services/TaskGraphContinuationDeliverer.ts +181 -0
  155. package/src/services/TaskGraphPromptRunner.ts +144 -0
  156. package/src/services/TaskGraphProviderFactory.ts +53 -0
  157. package/src/telephony/RingCentralTelephonyService.ts +1 -1
  158. package/src/telephony/TwilioTelephonyRouter.ts +1 -1
  159. package/src/telephony/VonageTelephonyRouter.ts +1 -1
  160. package/dist/resolvers/ReportResolver.d.ts +0 -24
  161. package/dist/resolvers/ReportResolver.d.ts.map +0 -1
  162. package/dist/resolvers/ReportResolver.js +0 -198
  163. package/dist/resolvers/ReportResolver.js.map +0 -1
  164. package/dist/resolvers/TaskResolver.d.ts +0 -31
  165. package/dist/resolvers/TaskResolver.d.ts.map +0 -1
  166. package/dist/resolvers/TaskResolver.js +0 -157
  167. package/dist/resolvers/TaskResolver.js.map +0 -1
  168. package/dist/services/TaskOrchestrator.d.ts +0 -130
  169. package/dist/services/TaskOrchestrator.d.ts.map +0 -1
  170. package/dist/services/TaskOrchestrator.js +0 -692
  171. package/dist/services/TaskOrchestrator.js.map +0 -1
  172. package/src/resolvers/ReportResolver.ts +0 -165
  173. package/src/resolvers/TaskResolver.ts +0 -148
  174. package/src/resolvers/__tests__/ReportResolver.test.ts +0 -279
  175. package/src/services/TaskOrchestrator.ts +0 -873
@@ -20,7 +20,8 @@ import {
20
20
  UserInfo,
21
21
  } from '@memberjunction/core';
22
22
  import { MJAuditLogEntity, MJErrorLogEntity, MJUserViewEntityExtended } from '@memberjunction/core-entities';
23
- import { SQLServerDataProvider, UserCache } from '@memberjunction/sqlserver-dataprovider';
23
+ import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
24
+ import { UserCache } from '@memberjunction/generic-database-provider';
24
25
  import { PubSubEngine, AuthorizationError } from 'type-graphql';
25
26
  import { GraphQLError } from 'graphql';
26
27
  import { GetAPIKeyEngine } from '@memberjunction/api-keys';
@@ -62,16 +63,28 @@ export class ResolverBase {
62
63
  * - AllowDecryptInAPI=false + SendEncryptedValue=true: Keep encrypted ciphertext
63
64
  * - AllowDecryptInAPI=false + SendEncryptedValue=false: Replace with sentinel
64
65
  *
66
+ * Returns a COPY — `dataObject` is never written to. Callers routinely pass rows straight
67
+ * from `findBy`/`RunView`, which are the server cache's own objects held by reference, and
68
+ * `LocalCacheManager` deep-freezes them. Renaming in place therefore threw
69
+ * `Cannot add property _mj__CreatedAt, object is not extensible` on every `UserByEmail` /
70
+ * `UserByID` / `UserByEmployeeID` call and on every generated single-record resolver whose
71
+ * entity has caching enabled. (Before the freeze it did something worse but quieter: it
72
+ * rewrote the cached row's keys, so later readers were served transport-shaped rows that
73
+ * `BaseEntity.SetMany` rejects.) Copying here fixes every call site at once and makes the
74
+ * hazard unreachable for future ones.
75
+ *
65
76
  * @param entityName - The entity name
66
- * @param dataObject - The data object with field values
77
+ * @param dataObject - The data object with field values. Not modified.
67
78
  * @param contextUser - Optional user context for decryption (required for encrypted fields)
68
- * @returns The processed data object
79
+ * @returns A new object in transport shape, or null when there is nothing to map
69
80
  */
70
81
  protected async MapFieldNamesToCodeNames(entityName: string, dataObject: any, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<any> {
71
82
  // Return null for empty objects (e.g. when no rows found due to RLS filtering)
72
83
  if (!dataObject || Object.keys(dataObject).length === 0) {
73
84
  return null;
74
85
  }
86
+ // Shallow copy up front so every write below lands on our object, never the caller's.
87
+ dataObject = { ...dataObject };
75
88
 
76
89
  // for the given entity name provided, check to see if there are any fields
77
90
  // where the code name is different from the field name, and for just those
@@ -184,14 +197,21 @@ export class ResolverBase {
184
197
  return true;
185
198
  }
186
199
 
187
- protected async ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: any[], contextUser?: UserInfo): Promise<any[]> {
188
- // iterate through the array and call MapFieldNamesToCodeNames for each element
189
- if (dataObjectArray && dataObjectArray.length > 0) {
190
- for (const element of dataObjectArray) {
191
- await this.MapFieldNamesToCodeNames(entityName, element, contextUser);
192
- }
200
+ /**
201
+ * Array form of {@link MapFieldNamesToCodeNames}. Returns a NEW array of NEW objects; neither
202
+ * the input array nor its rows are modified. Both matter: the cache freezes the array as well
203
+ * as the rows it contains, so collecting the mapped copies (rather than mapping in place and
204
+ * returning the original) is what makes this safe on cache-served input.
205
+ */
206
+ protected async ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: any[], contextUser?: UserInfo, provider?: IMetadataProvider): Promise<any[]> {
207
+ if (!dataObjectArray || dataObjectArray.length === 0) {
208
+ return dataObjectArray;
193
209
  }
194
- return dataObjectArray;
210
+ const mapped: any[] = [];
211
+ for (const element of dataObjectArray) {
212
+ mapped.push(await this.MapFieldNamesToCodeNames(entityName, element, contextUser, provider));
213
+ }
214
+ return mapped;
195
215
  }
196
216
 
197
217
  /**
@@ -826,12 +846,27 @@ export class ResolverBase {
826
846
  LogStatus(`[ResolverBase] RunView result aggregate info: entityName=${viewInfo.Entity}, hasAggregateResults=${!!result?.AggregateResults}, aggregateResultCount=${result?.AggregateResults?.length || 0}, aggregateExecutionTime=${result?.AggregateExecutionTime}, aggregateResults=${JSON.stringify(result?.AggregateResults)}`);
827
847
  }
828
848
 
829
- // Process results for GraphQL transport
849
+ // Process results for GraphQL transport.
850
+ //
851
+ // Map onto COPIES, never in place. `FieldMapper.MapFields` renames keys by
852
+ // mutating (`obj[mapped] = obj[k]; delete obj[k]`), and these rows are the
853
+ // provider's own result objects — which the server cache holds BY REFERENCE
854
+ // under a reference-sharing storage provider. Mapping them in place rewrote
855
+ // `__mj_CreatedAt` to the transport alias `_mj__CreatedAt` inside the live
856
+ // cache, and because that cache is process-wide, one GraphQL response made
857
+ // every later read hand back transport-shaped rows that `BaseEntity.SetMany`
858
+ // rejects. `ArrayFilterEncryptedFieldsForAPI` mutates too, so it must also
859
+ // see the copies. (FileResolver already maps a spread copy.)
860
+ //
861
+ // These copies are LOAD-BEARING, not merely defensive: LocalCacheManager now
862
+ // deep-freezes cached rows (see ILocalStorageProvider.SharesReferences), so
863
+ // mapping in place would throw rather than silently corrupt. `{ ...r }` is a
864
+ // SHALLOW copy, which is sufficient here because the only post-map mutators
865
+ // rename top-level keys and redact scalar fields — and the cache's freeze is
866
+ // deep, so a nested value can no longer be reached through the copy either.
830
867
  const mapper = new FieldMapper();
831
868
  if (result?.Success && result.Results?.length) {
832
- for (const r of result.Results) {
833
- mapper.MapFields(r);
834
- }
869
+ result.Results = result.Results.map(r => mapper.MapFields({ ...r }));
835
870
  // Filter encrypted fields before sending to API client
836
871
  await this.ArrayFilterEncryptedFieldsForAPI(
837
872
  viewInfo.Entity,
@@ -948,9 +983,9 @@ export class ResolverBase {
948
983
  for (let i = 0; i < runViewResults.length; i++) {
949
984
  const runViewResult = runViewResults[i];
950
985
  if (runViewResult?.Success && runViewResult.Results?.length) {
951
- for (const result of runViewResult.Results) {
952
- mapper.MapFields(result);
953
- }
986
+ // Copy-then-map, same reason as the single-view path above: these rows
987
+ // are cache-held references and MapFields renames keys in place.
988
+ runViewResult.Results = runViewResult.Results.map(r => mapper.MapFields({ ...r }));
954
989
  // Filter encrypted fields before sending to API client
955
990
  // Use the corresponding param's entity name
956
991
  const entityName = params[i]?.viewInfo?.Entity;
package/src/index.ts CHANGED
@@ -5,9 +5,9 @@ dotenv.config({ quiet: true });
5
5
  import { expressMiddleware } from '@as-integrations/express5';
6
6
  import { mergeSchemas } from '@graphql-tools/schema';
7
7
  import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ResolveStartupMode } from '@memberjunction/core';
8
- import { resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
8
+ import { UserCache, resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
9
9
  import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
10
- import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData, UserCache } from '@memberjunction/sqlserver-dataprovider';
10
+ import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
11
11
  import { extendConnectionPoolWithQuery } from './util.js';
12
12
  import { registerIntegrationCustomColumnPromoter } from './integration/CustomColumnPromoter.js';
13
13
  import { DisableUnselectedEntityMaps, ReenableFieldMapsForEntityMap } from './integration/EntityMapLifecycle.js';
@@ -19,11 +19,12 @@ import { default as fg } from 'fast-glob';
19
19
  import { useServer } from 'graphql-ws/lib/use/ws';
20
20
  import { createServer } from 'node:http';
21
21
  import { fileURLToPath } from 'node:url';
22
- import { readFileSync } from 'node:fs';
22
+ import { readFileSync, writeFileSync } from 'node:fs';
23
+ import { Session as InspectorSession } from 'node:inspector';
23
24
  import { sep } from 'node:path';
24
25
  import 'reflect-metadata';
25
26
  import { ReplaySubject } from 'rxjs';
26
- import { BuildSchemaOptions, buildSchemaSync, GraphQLTimestamp, PubSubEngine } from 'type-graphql';
27
+ import { BuildSchemaOptions, buildSchemaSync, getMetadataStorage, GraphQLTimestamp, PubSubEngine } from 'type-graphql';
27
28
  import { PubSub } from 'graphql-subscriptions';
28
29
  import sql from 'mssql';
29
30
  import { WebSocketServer } from 'ws';
@@ -66,6 +67,7 @@ import { IntegrationProgressEmitter } from '@memberjunction/integration-progress
66
67
  import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
67
68
  import { ClientToolRequestManager, AgentRunWatchdog } from '@memberjunction/ai-agents';
68
69
  import { SessionJanitor } from './agentSessions/index.js';
70
+ import { StartTaskGraphDispatcher } from './services/StartTaskGraphDispatcher.js';
69
71
  import { CACHE_INVALIDATION_TOPIC } from './generic/CacheInvalidationResolver.js';
70
72
  import { ConnectorFactory, IntegrationEngine, IntegrationSyncOptions } from '@memberjunction/integration-engine';
71
73
  import { CronExpressionHelper } from '@memberjunction/scheduling-engine';
@@ -136,7 +138,6 @@ export * from './resolvers/IntegrationProgressResolver.js';
136
138
  export * from './resolvers/ClientToolRequestResolver.js';
137
139
  export * from './resolvers/AutotagPipelineResolver.js';
138
140
  export * from './resolvers/TagGovernanceResolver.js';
139
- export * from './resolvers/TaskResolver.js';
140
141
  export * from './generic/KeyValuePairInput.js';
141
142
  export * from './generic/KeyInputOutputTypes.js';
142
143
  export * from './generic/DeleteOptionsInput.js';
@@ -149,8 +150,13 @@ export * from './resolvers/ComponentRegistryResolver.js';
149
150
  export * from './resolvers/DatasetResolver.js';
150
151
  export * from './resolvers/EntityRecordNameResolver.js';
151
152
  export * from './resolvers/MergeRecordsResolver.js';
152
- export * from './resolvers/ReportResolver.js';
153
153
  export * from './resolvers/QueryResolver.js';
154
+ export * from './services/TaskGraphProviderFactory.js';
155
+ export * from './services/TaskGraphAgentRunner.js';
156
+ export * from './services/StartTaskGraphDispatcher.js';
157
+ export * from './services/TaskGraphPromptRunner.js';
158
+ export * from './services/TaskGraphContinuationDeliverer.js';
159
+ export * from './resolvers/TaskGraphFrameResolver.js';
154
160
  export * from './resolvers/TestQuerySQLResolver.js';
155
161
  export * from './resolvers/SqlLoggingConfigResolver.js';
156
162
  export * from './resolvers/SyncRolesUsersResolver.js';
@@ -324,8 +330,8 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
324
330
  await provider.Config(pgConfigData);
325
331
  SetProvider(provider);
326
332
 
327
- // Refresh user cache using PostgreSQL
328
- await refreshUserCacheFromPG(pgPool, mj_core_schema);
333
+ // Warm the user cache dialect-neutral, same call the SQL Server path makes
334
+ await UserCache.Instance.Refresh(provider);
329
335
 
330
336
  // Run startup actions — same 'full' entry-point default as the SQL Server path
331
337
  const sysUser = UserCache.Instance.GetSystemUser();
@@ -854,23 +860,73 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
854
860
  lap('Resolver + Middleware Discovery', tServe);
855
861
  tServe = startupLog.BeginPhase('Building GraphQL schema');
856
862
 
863
+ // ── Scale-runtime instrumentation (fix #1 profiling) ─────────────────────────
864
+ // Split the previously-monolithic "Schema Build" phase into its three real
865
+ // sub-steps so we can see which one dominates at high entity counts, and capture
866
+ // the type-graphql metadata cardinality (the O(entities×fields) signal). Guarded
867
+ // by MJ_SCHEMA_PROFILE, and emits ONE explicit console line (independent of the
868
+ // startup logger's level, and NOT via EndPhase, so the "schema" summary token and
869
+ // its total math stay clean). No-op in normal deployments.
870
+ const schemaProfile = process.env.MJ_SCHEMA_PROFILE === '1';
871
+ let objectTypeCount = -1, fieldCount = -1;
872
+ if (schemaProfile) {
873
+ try {
874
+ const meta = getMetadataStorage();
875
+ // Read BEFORE buildSchemaSync — buildSchemaSync's internal build() resets these arrays.
876
+ objectTypeCount = meta.objectTypes.length;
877
+ fieldCount = meta.fields.length;
878
+ } catch {
879
+ /* metadata cardinality is best-effort; leave sentinels */
880
+ }
881
+ }
882
+
883
+ // Surgical CPU profile of ONLY buildSchemaSync (behind MJ_SCHEMA_CPUPROF=1), so we can see
884
+ // whether the ~66s at scale is uniform per-field work (cache is the only fix) or a patchable
885
+ // hotspot in type-graphql's generator. Deterministic flush (writes before serve continues) —
886
+ // no dependency on --cpu-prof surviving a process kill.
887
+ const cpuProf = process.env.MJ_SCHEMA_CPUPROF === '1';
888
+ let profSession: InspectorSession | undefined;
889
+ if (cpuProf) {
890
+ profSession = new InspectorSession();
891
+ profSession.connect();
892
+ await new Promise<void>((res, rej) => profSession!.post('Profiler.enable', (e) => (e ? rej(e) : res())));
893
+ await new Promise<void>((res, rej) => profSession!.post('Profiler.start', (e) => (e ? rej(e) : res())));
894
+ }
895
+
896
+ const tBuild = performance.now();
897
+ const builtSchema = buildSchemaSync({
898
+ resolvers: allResolvers,
899
+ validate: false,
900
+ scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
901
+ emitSchemaFile: websiteRunFromPackage !== 1,
902
+ pubSub,
903
+ globalMiddlewares: [variablesLoggingMiddleware],
904
+ });
905
+ const buildMs = performance.now() - tBuild;
906
+
907
+ if (cpuProf && profSession) {
908
+ const profile = await new Promise<unknown>((res, rej) =>
909
+ profSession!.post('Profiler.stop', (e, r) => (e ? rej(e) : res((r as { profile: unknown }).profile)))
910
+ );
911
+ const outPath = `/tmp/mjapi_schema_build_${process.pid}.cpuprofile`;
912
+ writeFileSync(outPath, JSON.stringify(profile));
913
+ profSession.disconnect();
914
+ // eslint-disable-next-line no-console
915
+ console.log(`[SCHEMA-CPUPROF] wrote ${outPath} (buildSchemaSync=${buildMs.toFixed(0)}ms)`);
916
+ }
917
+
918
+ const tMerge = performance.now();
857
919
  let schema = mergeSchemas({
858
- schemas: [
859
- buildSchemaSync({
860
- resolvers: allResolvers,
861
- validate: false,
862
- scalarsMap: [{ type: Date, scalar: GraphQLTimestamp }],
863
- emitSchemaFile: websiteRunFromPackage !== 1,
864
- pubSub,
865
- globalMiddlewares: [variablesLoggingMiddleware],
866
- }),
867
- ],
920
+ schemas: [builtSchema],
868
921
  typeDefs: [requireSystemUserDirective.typeDefs, publicDirective.typeDefs],
869
922
  });
923
+ const mergeMs = performance.now() - tMerge;
870
924
 
871
925
  // Verbose-mode-only diagnostic: name custom-resolver args that aren't metadata-bound
872
926
  // and aren't @NoLog-marked. No-op in default config (logVariables=false).
873
927
  auditResolversForUndecoratedArgs();
928
+
929
+ const tTransform = performance.now();
874
930
  schema = requireSystemUserDirective.transformer(schema);
875
931
  schema = publicDirective.transformer(schema);
876
932
 
@@ -878,6 +934,17 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
878
934
  for (const transformer of mwSchemaTransformers) {
879
935
  schema = transformer(schema);
880
936
  }
937
+ const transformMs = performance.now() - tTransform;
938
+
939
+ if (schemaProfile) {
940
+ const typeMapSize = Object.keys(schema.getTypeMap()).length;
941
+ // eslint-disable-next-line no-console
942
+ console.log(
943
+ `[SCHEMA-PROFILE] resolvers=${allResolvers.length} objectTypes=${objectTypeCount} fields=${fieldCount} ` +
944
+ `typeMap=${typeMapSize} | buildSchemaSync=${buildMs.toFixed(0)}ms mergeSchemas=${mergeMs.toFixed(0)}ms ` +
945
+ `transformers=${transformMs.toFixed(0)}ms`
946
+ );
947
+ }
881
948
 
882
949
  lap('Schema Build', tServe);
883
950
  tServe = startupLog.BeginPhase('Starting HTTP server');
@@ -1341,6 +1408,29 @@ export const serve = async (resolverPaths: Array<string>, app: Application = cre
1341
1408
  .catch(err => console.warn(`[SessionJanitor] Startup failed: ${err}`));
1342
1409
  }
1343
1410
 
1411
+ // Launch the durable task-graph dispatcher: claim Pending tasks, execute them with a fresh
1412
+ // provider each, and reconcile claims orphaned by a crash. Without this nothing ever picks up a
1413
+ // submitted graph — submission would be durable and inert, which is strictly worse than the old
1414
+ // client-driven path it replaced. Gated on SQL Server because the provider factory mints
1415
+ // SQLServerDataProvider; the PG branch lands with PG parity. Self-registers with ShutdownRegistry.
1416
+ //
1417
+ // `MJ_DISABLE_TASK_GRAPH_DISPATCHER=1` suppresses it, for the one case where a second dispatcher
1418
+ // is actively harmful: the integration suite's task-graph bundle drives its OWN dispatcher against
1419
+ // a stub runner and asserts exactly-once execution. A dispatcher claims from the whole table, not
1420
+ // from "its own" graphs, so a server sharing that database races the suite for every claim and
1421
+ // executes the suite's tasks with the real agent runner. The bundle then reports tasks that never
1422
+ // ran and graphs that settled to the wrong status — symptoms that read as engine defects and cost
1423
+ // a release cycle to trace back to here. The suite still needs MJAPI up for its client-transport
1424
+ // members, so "stop the server" is not the remedy; this is.
1425
+ const taskGraphPool = dataSources[0]?.dataSource;
1426
+ const taskGraphDispatcherDisabled = process.env.MJ_DISABLE_TASK_GRAPH_DISPATCHER === '1';
1427
+ if (taskGraphDispatcherDisabled) {
1428
+ LogStatus('[TaskGraphDispatcher] Disabled by MJ_DISABLE_TASK_GRAPH_DISPATCHER=1 — submitted graphs will not be executed by this process.');
1429
+ } else if (resumeUser && taskGraphPool instanceof sql.ConnectionPool) {
1430
+ StartTaskGraphDispatcher(taskGraphPool, resumeUser)
1431
+ .catch(err => console.warn(`[TaskGraphDispatcher] Startup failed: ${err}`));
1432
+ }
1433
+
1344
1434
  // Launch the calendar / scheduled-bridge loop (M2): poll agent calendars for meeting invites and
1345
1435
  // start due meeting bridges. Mirrors the SessionJanitor lifecycle (run-once + interval, timer
1346
1436
  // unref'd). Gated on Teams meetings being enabled (the provider whose scheduled-join is wired) and
@@ -1730,31 +1820,6 @@ function createMSSQLCompatPool(pgPool: import('pg').Pool): sql.ConnectionPool {
1730
1820
  return wrapper as unknown as sql.ConnectionPool;
1731
1821
  }
1732
1822
 
1733
- /**
1734
- * Refreshes the UserCache using PostgreSQL queries instead of MSSQL.
1735
- * This mirrors the logic in UserCache.Refresh() but uses pg.Pool.
1736
- */
1737
- async function refreshUserCacheFromPG(pgPool: import('pg').Pool, coreSchema: string): Promise<void> {
1738
- const { UserInfo } = await import('@memberjunction/core');
1739
- const uResult = await pgPool.query(`SELECT * FROM ${coreSchema}."vwUsers"`);
1740
- const rResult = await pgPool.query(`SELECT * FROM ${coreSchema}."vwUserRoles"`);
1741
- const users = uResult.rows;
1742
- const roles = rResult.rows;
1743
-
1744
- if (users) {
1745
- const userInfos = users.map((user: Record<string, unknown>) => {
1746
- const userWithRoles = {
1747
- ...user,
1748
- UserRoles: roles.filter((role: Record<string, unknown>) => UUIDsEqual(role.UserID as string, user.ID as string)),
1749
- };
1750
- return new UserInfo(Metadata.Provider, userWithRoles); // global-provider-ok: bootstrap (UserCache initialization)
1751
- });
1752
- // Access the UserCache internals to set users
1753
- const cache = UserCache.Instance;
1754
- (cache as unknown as Record<string, unknown>)['_users'] = userInfos;
1755
- }
1756
- }
1757
-
1758
1823
  /**
1759
1824
  * Translates SQL Server bracket-quoted identifiers to PostgreSQL double-quoted identifiers.
1760
1825
  * Converts [schema].[table] to "schema"."table" and handles common T-SQL patterns.
@@ -51,6 +51,20 @@ export function redactArg(ctx: RedactionContext): unknown {
51
51
  const entity = ctx.provider.Entities.find((e) => e.ClassName === match.groups!.name);
52
52
  if (entity) {
53
53
  encryptedFieldNames = new Set(entity.EncryptedFields.map((f) => f.Name));
54
+ } else {
55
+ // The name matched the CRUD convention but resolves to no entity — so the
56
+ // metadata half of the redactor contributes nothing and, absent any @NoLog
57
+ // field, this arg falls through to shortenForLog with its values intact.
58
+ //
59
+ // This is the silent case: the boot audit tests only the NAME pattern, so an
60
+ // input like `CreateConnectionInput` is classified as metadata-bound and never
61
+ // warned about, while the lookup here quietly fails. Every hand-written
62
+ // `Create*Input` in the resolvers hits this — codegen inputs embed the entity
63
+ // ClassName (`CreateMJCredentialInput`), hand-written ones do not.
64
+ //
65
+ // Report it once per input type so the operator who enabled variables logging
66
+ // learns which args are unprotected, rather than discovering it in a log file.
67
+ WarnUnboundCrudInput(ctx.inputTypeName);
54
68
  }
55
69
  }
56
70
 
@@ -80,3 +94,32 @@ export function redactArg(ctx: RedactionContext): unknown {
80
94
  }
81
95
 
82
96
  const EMPTY_SET: ReadonlySet<string> = new Set<string>();
97
+
98
+ /**
99
+ * Input type names already reported by {@link WarnUnboundCrudInput}. Warning once
100
+ * per type keeps a hot mutation from flooding the log with the same diagnostic.
101
+ */
102
+ const warnedUnboundInputTypes = new Set<string>();
103
+
104
+ /**
105
+ * Reports, once per input type, that an arg named like a CRUD input resolves to no
106
+ * entity and is therefore logged unredacted unless its fields carry `@NoLog`.
107
+ *
108
+ * Exported for tests; not part of the redaction path's contract.
109
+ */
110
+ export function WarnUnboundCrudInput(inputTypeName: string): void {
111
+ if (warnedUnboundInputTypes.has(inputTypeName)) {
112
+ return;
113
+ }
114
+ warnedUnboundInputTypes.add(inputTypeName);
115
+ console.warn(
116
+ `[MJServer] Variables logging: '${inputTypeName}' matches the Create/Update/Delete input ` +
117
+ `naming convention but maps to no entity, so encrypted-field redaction cannot apply to it. ` +
118
+ `Its values are being logged. Mark any sensitive @Field() on this input with @NoLog.`,
119
+ );
120
+ }
121
+
122
+ /** Test seam — clears the once-per-type warning state. */
123
+ export function ResetUnboundCrudInputWarnings(): void {
124
+ warnedUnboundInputTypes.clear();
125
+ }
@@ -126,6 +126,11 @@ export abstract class BaseServerMiddleware {
126
126
  /**
127
127
  * Hook that runs after a RunView operation completes. Can modify the result
128
128
  * (e.g., filtering or augmenting data) before it is returned to the caller.
129
+ *
130
+ * ⚠️ `results.Results` and its rows may be FROZEN shared cache state (LocalCacheManager
131
+ * freeze-on-write) — writing into a row throws `TypeError`. To modify, build copies and
132
+ * either reassign `results.Results = results.Results.map(r => ({ ...r, ... }))` or return
133
+ * a new result; never mutate rows in place.
129
134
  */
130
135
  PostRunView(params: RunViewParams, results: RunViewResult, contextUser: UserInfo | undefined): RunViewResult | Promise<RunViewResult> {
131
136
  return results;
@@ -17,7 +17,7 @@
17
17
  import { Metadata, RunView, UserInfo, LogError, LogStatus } from '@memberjunction/core';
18
18
  import { MJLruCache, UUIDsEqual } from '@memberjunction/global';
19
19
  import type { MJConversationWidgetInstanceEntity, MJConversationEntity } from '@memberjunction/core-entities';
20
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
20
+ import { UserCache } from '@memberjunction/generic-database-provider';
21
21
  import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
22
22
  import { generateSessionId } from '../auth/magicLink/magicLinkCore.js';
23
23
  import { MagicLinkService } from '../auth/magicLink/MagicLinkService.js';
@@ -15,7 +15,7 @@
15
15
 
16
16
  import { RunView, LogError, type UserInfo, type DatabaseProviderBase } from '@memberjunction/core';
17
17
  import type { MJConversationWidgetInstanceEntity } from '@memberjunction/core-entities';
18
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
18
+ import { UserCache } from '@memberjunction/generic-database-provider';
19
19
  import type { UserPayload } from '../types.js';
20
20
 
21
21
  const WIDGET_ENTITY = 'MJ: Conversation Widget Instances';
@@ -366,6 +366,19 @@ export class ActionResolver extends ResolverBase {
366
366
  // Run the entity action
367
367
  const result = await EntityActionEngineServer.Instance.RunEntityAction(params);
368
368
 
369
+ if (!result) {
370
+ // The binding is scoped (ScopeEntityID/ScopeRecordID) and this record falls outside it, so
371
+ // the action never ran. That is a legitimate answer, not a failure — dereferencing the null
372
+ // here turned "this binding does not apply to that record" into a server error for the
373
+ // caller. Reported as a successful no-op with an explicit message, so a client can tell it
374
+ // apart from an action that ran and did nothing.
375
+ return {
376
+ Success: true,
377
+ Message: 'The entity action did not run: its binding is scoped to different records.',
378
+ ResultData: JSON.stringify({ Ran: false, Reason: 'OutOfScope' })
379
+ };
380
+ }
381
+
369
382
  // Return the result
370
383
  return {
371
384
  Success: result.Success,
@@ -1,7 +1,7 @@
1
1
  import { Arg, Ctx, Field, InputType, ObjectType, Query, Mutation, Resolver } from 'type-graphql';
2
2
  import { UserInfo, LogError, LogStatus } from '@memberjunction/core';
3
3
  import { UUIDsEqual, MJLruCache } from '@memberjunction/global';
4
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
+ import { UserCache } from '@memberjunction/generic-database-provider';
5
5
  import { MJComponentRegistryEntity, ComponentMetadataEngine } from '@memberjunction/core-entities';
6
6
  import { CredentialEngine } from '@memberjunction/credentials';
7
7
  import { ComponentSpec } from '@memberjunction/interactive-component-types';
@@ -3,7 +3,7 @@ import { LogError } from '@memberjunction/core';
3
3
  import { AppContext } from '../types.js';
4
4
  import { ResolverBase } from '../generic/ResolverBase.js';
5
5
  import { GetReadOnlyProvider } from '../util.js';
6
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
6
+ import { UserCache } from '@memberjunction/generic-database-provider';
7
7
 
8
8
  @ObjectType()
9
9
  class EntityPermissionResult {
@@ -1,6 +1,7 @@
1
1
  import { Resolver, Query, Mutation, Arg, Ctx, ObjectType, Field, InputType, Int, Float } from "type-graphql";
2
2
  import { CompositeKey, DatabaseProviderBase, EntityInfo, LocalCacheManager, Metadata, RunView, UserInfo, LogError, LogStatus, IMetadataProvider, TransactionGroupBase } from "@memberjunction/core";
3
3
  import { GetReadOnlyProvider, GetReadWriteProvider } from "../util.js";
4
+ import { NoLog } from "../logging/NoLog.js";
4
5
  import { UUIDsEqual } from "@memberjunction/global";
5
6
  import { CronExpressionHelper } from "@memberjunction/scheduling-engine";
6
7
  import {
@@ -56,7 +57,7 @@ import { IntegrationCustomColumnPromoter } from "../integration/CustomColumnProm
56
57
  import { ComputeCascadeRemovalSet, ComputeRemovedDependencyWarnings, DisableUnselectedEntityMaps, ReenableFieldMapsForEntityMap, ResetPullWatermarks, SetEntityMapEnabled } from "../integration/EntityMapLifecycle.js";
57
58
  import { AppContext } from "../types.js";
58
59
  import { RequireSystemUser } from "../directives/RequireSystemUser.js";
59
- import { UserCache } from "@memberjunction/sqlserver-dataprovider";
60
+ import { UserCache } from "@memberjunction/generic-database-provider";
60
61
 
61
62
  // ─── RSU Pipeline Output Types ──────────────────────────────────────────────
62
63
 
@@ -551,7 +552,16 @@ class CreateConnectionInput {
551
552
  @Field() CompanyID: string;
552
553
  @Field() CredentialTypeID: string;
553
554
  @Field() CredentialName: string;
554
- @Field() CredentialValues: string;
555
+ /**
556
+ * The credential payload being connected, as a JSON string.
557
+ *
558
+ * `@NoLog` is load-bearing here, not decorative. This field does NOT map to an
559
+ * entity column — the resolver assigns it onto `MJ: Credentials`.`Values`
560
+ * (which IS `Encrypt=true`) in procedural code further down. The metadata-driven
561
+ * half of the redactor keys off entity columns, so it cannot see this field, and
562
+ * without the mark the variables log emits the credential in plaintext.
563
+ */
564
+ @Field() @NoLog CredentialValues: string;
555
565
  @Field({ nullable: true }) ExternalSystemID?: string;
556
566
  @Field({ nullable: true }) Configuration?: string;
557
567
  }
@@ -8,7 +8,7 @@ import {
8
8
  PotentialDuplicateResult,
9
9
  } from '@memberjunction/core';
10
10
  import { AppContext } from '../types.js';
11
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
11
+ import { UserCache } from '@memberjunction/generic-database-provider';
12
12
 
13
13
  import { CompositeKeyInputType, CompositeKeyOutputType, KeyValuePairOutputType } from '../generic/KeyInputOutputTypes.js';
14
14
  import { GetReadOnlyProvider } from '../util.js';
@@ -227,6 +227,18 @@ export class StartRealtimeClientSessionResult {
227
227
  @Field(() => String, { nullable: true })
228
228
  ModelName?: string;
229
229
 
230
+ /**
231
+ * `DriverClass` of the vendor that actually runs the session (e.g. `OpenAIRealtime`,
232
+ * `ElevenLabsRealtime`). Null when unknown.
233
+ *
234
+ * Surfaced to the BROWSER because on the default-model path the framework picks the vendor itself:
235
+ * without this the caller cannot tell which vendor spoke, and so cannot diagnose a voice that did
236
+ * not land (issue #3530). `ModelName` alone is not enough — one model can resolve to different
237
+ * vendors depending on which API keys a deployment configures.
238
+ */
239
+ @Field(() => String, { nullable: true })
240
+ DriverClass?: string;
241
+
230
242
  /**
231
243
  * DB-driven progress-narration instruction template (contains a `{{ progressMessage }}`
232
244
  * placeholder). Null when the narration prompt is not present in this deployment's metadata —
@@ -1677,6 +1689,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1677
1689
  ExpiresAt: cfg.ExpiresAt,
1678
1690
  SessionConfigJson: JSON.stringify(cfg.SessionConfig),
1679
1691
  ModelName: prep.ModelName,
1692
+ DriverClass: prep.DriverClass,
1680
1693
  NarrationInstructionsTemplate: prep.NarrationInstructionsTemplate,
1681
1694
  NarrationPaceMs: prep.NarrationPaceMs,
1682
1695
  EffectiveConfigJson: prep.EffectiveConfig ? JSON.stringify(prep.EffectiveConfig) : undefined,
@@ -1,7 +1,7 @@
1
1
  import { Arg, Ctx, Field, Float, InputType, Int, ObjectType, Query, Resolver } from 'type-graphql';
2
2
  import { EntitySearchResult, SearchEntityParams } from '@memberjunction/core';
3
3
  import { AppContext } from '../types.js';
4
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
+ import { UserCache } from '@memberjunction/generic-database-provider';
5
5
  import { GetReadOnlyProvider } from '../util.js';
6
6
 
7
7
  /**
@@ -2,14 +2,13 @@ import { Arg, Ctx, Field, InputType, Mutation, ObjectType, Query, Int, Resolver
2
2
  import { AppContext } from '../types.js';
3
3
  import { Metadata, UserInfo } from '@memberjunction/core';
4
4
  import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
5
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
6
5
  import { v4 as uuidv4 } from 'uuid';
7
6
  import * as path from 'path';
8
7
  import * as fs from 'fs/promises';
9
8
  import { loadConfig } from '../config.js';
10
9
  import { ResolverBase } from '../generic/ResolverBase.js';
11
10
  import { GetReadOnlyProvider } from '../util.js';
12
- import { SqlLoggingOptions as ProviderSqlLoggingOptions } from '@memberjunction/generic-database-provider';
11
+ import { UserCache, SqlLoggingOptions as ProviderSqlLoggingOptions } from '@memberjunction/generic-database-provider';
13
12
 
14
13
  /**
15
14
  * Configuration options for SQL logging sessions.
@@ -4,7 +4,7 @@ import { DatabaseProviderBase, EntityDeleteOptions, EntitySaveOptions, IMetadata
4
4
  import { UUIDsEqual } from '@memberjunction/global';
5
5
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
6
6
  import { MJRoleEntity, MJUserEntity, MJUserRoleEntity } from '@memberjunction/core-entities';
7
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
7
+ import { UserCache } from '@memberjunction/generic-database-provider';
8
8
  import { GetReadWriteProvider } from '../util.js';
9
9
 
10
10
  @ObjectType()
@@ -112,7 +112,7 @@ export class SyncRolesAndUsersResolver {
112
112
  await provider.CommitTransaction();
113
113
 
114
114
  // refresh the user cache — one-time, since the normal auto-refresh is already scheduled
115
- await UserCache.Instance.Refresh(context.dataSource);
115
+ await UserCache.Instance.Refresh(provider);
116
116
  return usersResult;
117
117
  } catch (txErr) {
118
118
  await provider.RollbackTransaction();