@memberjunction/server 6.2.0-edge.5 → 6.2.0-edge.6

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 (401) hide show
  1. package/README.md +27 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +3 -3
  4. package/dist/agentSessions/SessionJanitor.js +3 -3
  5. package/dist/agentSessions/SessionManager.d.ts +15 -5
  6. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionManager.js +34 -12
  8. package/dist/agentSessions/SessionManager.js.map +1 -1
  9. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  10. package/dist/auth/AuthProviderCatalogRouter.js +2 -0
  11. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +51 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/initializeProviders.d.ts +10 -0
  16. package/dist/auth/initializeProviders.d.ts.map +1 -1
  17. package/dist/auth/initializeProviders.js +37 -1
  18. package/dist/auth/initializeProviders.js.map +1 -1
  19. package/dist/auth/localDevAuth.d.ts +79 -0
  20. package/dist/auth/localDevAuth.d.ts.map +1 -0
  21. package/dist/auth/localDevAuth.js +439 -0
  22. package/dist/auth/localDevAuth.js.map +1 -0
  23. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  24. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  26. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  27. package/dist/auth/magicLink/types.d.ts +12 -0
  28. package/dist/auth/magicLink/types.d.ts.map +1 -1
  29. package/dist/auth/sessionUserClone.d.ts +1 -1
  30. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  31. package/dist/auth/sessionUserClone.js +3 -2
  32. package/dist/auth/sessionUserClone.js.map +1 -1
  33. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  34. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  35. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  36. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  37. package/dist/broadcast/index.d.ts +6 -0
  38. package/dist/broadcast/index.d.ts.map +1 -0
  39. package/dist/broadcast/index.js +6 -0
  40. package/dist/broadcast/index.js.map +1 -0
  41. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  42. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  43. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  44. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  45. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  46. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  47. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  48. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  49. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  50. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  51. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  52. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  53. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  54. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  55. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  56. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  57. package/dist/cache.d.ts +3 -2
  58. package/dist/cache.d.ts.map +1 -1
  59. package/dist/cache.js.map +1 -1
  60. package/dist/config.d.ts +496 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +61 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/context.d.ts +3 -1
  65. package/dist/context.d.ts.map +1 -1
  66. package/dist/context.js +29 -8
  67. package/dist/context.js.map +1 -1
  68. package/dist/coreRoutes.d.ts.map +1 -1
  69. package/dist/coreRoutes.js +14 -0
  70. package/dist/coreRoutes.js.map +1 -1
  71. package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +3366 -418
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +154 -79
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  80. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  81. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  82. package/dist/generic/PubSubManager.d.ts +10 -0
  83. package/dist/generic/PubSubManager.d.ts.map +1 -1
  84. package/dist/generic/PubSubManager.js +44 -1
  85. package/dist/generic/PubSubManager.js.map +1 -1
  86. package/dist/generic/PushStatusResolver.d.ts +39 -6
  87. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  88. package/dist/generic/PushStatusResolver.js +93 -11
  89. package/dist/generic/PushStatusResolver.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +65 -25
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +201 -101
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +11 -3
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +79 -13
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  99. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  100. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  101. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  102. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  103. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  104. package/dist/generic/fieldSecurityTransport.js +30 -0
  105. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  106. package/dist/index.d.ts +3 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +122 -66
  109. package/dist/index.js.map +1 -1
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +6 -1
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  114. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  115. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  116. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  117. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  118. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  119. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  120. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
  121. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  122. package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
  123. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  124. package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
  125. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  126. package/dist/realtimeSessions/sessionAccess.js +125 -22
  127. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  128. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  129. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  130. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  131. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  132. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  133. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  134. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  135. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  136. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  137. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  138. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  139. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  140. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  141. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  142. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  143. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  144. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  145. package/dist/realtimeWidget/host-identity.js +1 -1
  146. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  147. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  148. package/dist/realtimeWidget/widgetCore.js +17 -1
  149. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  150. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  151. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  152. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  153. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  154. package/dist/resolvers/ActionResolver.d.ts +19 -1
  155. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  156. package/dist/resolvers/ActionResolver.js +63 -12
  157. package/dist/resolvers/ActionResolver.js.map +1 -1
  158. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  159. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  160. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  161. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  162. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  163. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  164. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  165. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  166. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  167. package/dist/resolvers/AgentSessionResolver.js +40 -17
  168. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  169. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  170. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  171. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  172. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  173. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
  174. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  175. package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
  176. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  177. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  178. package/dist/resolvers/FileCategoryResolver.js +1 -0
  179. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  180. package/dist/resolvers/FileResolver.d.ts +5 -0
  181. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  182. package/dist/resolvers/FileResolver.js +13 -4
  183. package/dist/resolvers/FileResolver.js.map +1 -1
  184. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
  186. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  187. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  188. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  189. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  190. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  191. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  192. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  193. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  194. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  195. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  196. package/dist/resolvers/QueryResolver.js +20 -6
  197. package/dist/resolvers/QueryResolver.js.map +1 -1
  198. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  199. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  200. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  201. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  202. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  203. package/dist/resolvers/RSUResolver.js +12 -0
  204. package/dist/resolvers/RSUResolver.js.map +1 -1
  205. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
  206. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  207. package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
  208. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  209. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  211. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  212. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  213. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  214. package/dist/resolvers/RunAIAgentResolver.js +209 -64
  215. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  217. package/dist/resolvers/RunTemplateResolver.js +4 -4
  218. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  219. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  220. package/dist/resolvers/RunTestResolver.js +3 -1
  221. package/dist/resolvers/RunTestResolver.js.map +1 -1
  222. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  223. package/dist/resolvers/SyncDataResolver.js +4 -0
  224. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  225. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  226. package/dist/resolvers/TestQuerySQLResolver.js +1 -0
  227. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  228. package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
  229. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  230. package/dist/resolvers/TransactionGroupResolver.js +60 -15
  231. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  232. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  233. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  234. package/dist/resolvers/UserViewResolver.js +2 -1
  235. package/dist/resolvers/UserViewResolver.js.map +1 -1
  236. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  237. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  238. package/dist/rest/EntityCRUDHandler.js +19 -5
  239. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  240. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  241. package/dist/rest/RESTEndpointHandler.js +8 -5
  242. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  243. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  244. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  245. package/dist/rest/RealtimeRecordingHandler.js +7 -7
  246. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  247. package/dist/rest/ViewOperationsHandler.d.ts +6 -0
  248. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  249. package/dist/rest/ViewOperationsHandler.js +22 -5
  250. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  251. package/dist/serverRoleConfigUnits.d.ts +29 -0
  252. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  253. package/dist/serverRoleConfigUnits.js +33 -0
  254. package/dist/serverRoleConfigUnits.js.map +1 -0
  255. package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
  256. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  257. package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
  258. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  259. package/dist/types.d.ts +23 -1
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +98 -98
  263. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  264. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  265. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  267. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  268. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  269. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  270. package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
  271. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  272. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  273. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  274. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  275. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  276. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  277. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  278. package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
  279. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  280. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  281. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  282. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  283. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  284. package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
  285. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  286. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  287. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
  288. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
  289. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  290. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  291. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  292. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  293. package/src/__tests__/SessionManager.test.ts +95 -0
  294. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
  295. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  296. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  297. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  298. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  299. package/src/__tests__/apiRootBoot.test.ts +1 -1
  300. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  301. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  302. package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
  303. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  304. package/src/__tests__/globalCors.test.ts +3 -3
  305. package/src/__tests__/localDevAuth.test.ts +196 -0
  306. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  307. package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
  308. package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
  309. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  310. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  311. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  312. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  313. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  314. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  315. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  316. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  317. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  318. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  319. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  320. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  321. package/src/__tests__/runViewProperties.test.ts +114 -0
  322. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  323. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  324. package/src/__tests__/sessionUserClone.test.ts +5 -1
  325. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  326. package/src/__tests__/unifiedAuth.test.ts +28 -0
  327. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  328. package/src/agentSessions/SessionJanitor.ts +3 -3
  329. package/src/agentSessions/SessionManager.ts +35 -12
  330. package/src/auth/AuthProviderCatalogRouter.ts +3 -0
  331. package/src/auth/index.ts +56 -12
  332. package/src/auth/initializeProviders.ts +43 -1
  333. package/src/auth/localDevAuth.ts +528 -0
  334. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  335. package/src/auth/magicLink/types.ts +10 -0
  336. package/src/auth/sessionUserClone.ts +3 -2
  337. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  338. package/src/broadcast/index.ts +5 -0
  339. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  340. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  341. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  342. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  343. package/src/cache.ts +2 -1
  344. package/src/config.ts +69 -0
  345. package/src/context.ts +32 -8
  346. package/src/coreRoutes.ts +15 -0
  347. package/src/generated/graphql-schemas/__mj.ts +2239 -102
  348. package/src/generic/CacheInvalidationResolver.ts +135 -78
  349. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  350. package/src/generic/PubSubManager.ts +54 -1
  351. package/src/generic/PushStatusResolver.ts +108 -11
  352. package/src/generic/ResolverBase.ts +237 -107
  353. package/src/generic/RunViewResolver.ts +80 -15
  354. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  355. package/src/generic/fieldSecurityTransport.ts +62 -0
  356. package/src/index.ts +127 -68
  357. package/src/integration/CustomColumnPromoter.ts +6 -1
  358. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  359. package/src/realtimeSessions/README.md +7 -3
  360. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  361. package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
  362. package/src/realtimeSessions/sessionAccess.ts +181 -25
  363. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  364. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  365. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  366. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  367. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  368. package/src/realtimeWidget/host-identity.ts +1 -1
  369. package/src/realtimeWidget/widgetCore.ts +30 -1
  370. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  371. package/src/resolvers/ActionResolver.ts +78 -15
  372. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  373. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  374. package/src/resolvers/AgentSessionResolver.ts +46 -18
  375. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  376. package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
  377. package/src/resolvers/FileCategoryResolver.ts +1 -0
  378. package/src/resolvers/FileResolver.ts +14 -4
  379. package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
  380. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  381. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  382. package/src/resolvers/QueryResolver.ts +20 -6
  383. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  384. package/src/resolvers/RSUResolver.ts +12 -0
  385. package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
  386. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  387. package/src/resolvers/RunAIAgentResolver.ts +258 -82
  388. package/src/resolvers/RunTemplateResolver.ts +6 -4
  389. package/src/resolvers/RunTestResolver.ts +3 -1
  390. package/src/resolvers/SyncDataResolver.ts +4 -0
  391. package/src/resolvers/TestQuerySQLResolver.ts +1 -0
  392. package/src/resolvers/TransactionGroupResolver.ts +66 -15
  393. package/src/resolvers/UserViewResolver.ts +2 -1
  394. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  395. package/src/rest/EntityCRUDHandler.ts +22 -5
  396. package/src/rest/RESTEndpointHandler.ts +8 -5
  397. package/src/rest/RealtimeRecordingHandler.ts +13 -10
  398. package/src/rest/ViewOperationsHandler.ts +24 -6
  399. package/src/serverRoleConfigUnits.ts +48 -0
  400. package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
  401. package/src/types.ts +23 -1
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Regression suite for the ad-hoc read-only execution path being SQL-Server-shaped.
3
+ *
4
+ * The bug — `ExecuteAdhocQuery` opened with
5
+ * `GetReadOnlyDataSource(context.dataSources, …)`, which looks for an **mssql
6
+ * `ConnectionPool`**. A PostgreSQL provider never populates `context.dataSources`
7
+ * with one, so every ad-hoc query on PostgreSQL returned
8
+ * "No read-only data source available for ad-hoc query execution".
9
+ *
10
+ * `next` fixed the execution path (R31): the resolver now hands the SQL to the
11
+ * read-only PROVIDER's `RunQuery`, and renders it for that provider's platform.
12
+ * This suite pins the dialect half of that contract — the platform comes from the
13
+ * provider the SQL runs on, never from a literal, and nothing reaches for mssql —
14
+ * and drives the resolver against a PostgreSQL provider with no mssql pool at all.
15
+ * Paging, the timeout clamp and the read authorizer have their own suites.
16
+ */
17
+ import 'reflect-metadata';
18
+
19
+ import { describe, it, expect, vi, afterEach } from 'vitest';
20
+ import { readFileSync } from 'fs';
21
+ import { resolve } from 'path';
22
+
23
+ // `RunQueryResultType` is a type-graphql object type whose fields rely on
24
+ // `emitDecoratorMetadata`. esbuild (what vitest transforms with) cannot emit it,
25
+ // so importing QueryResolver for real fails at decoration time. The resolver only
26
+ // uses the class as the @Query return marker, so a bare stand-in is enough and
27
+ // keeps the REAL AdhocQueryResolver under test.
28
+ vi.mock('../resolvers/QueryResolver.js', () => ({
29
+ RunQueryResultType: class RunQueryResultType {},
30
+ }));
31
+
32
+ // The server config is read from mj.config at import; only the timeout cap is used here.
33
+ vi.mock('../config.js', () => ({ configInfo: { databaseSettings: { requestTimeout: 120_000 } } }));
34
+
35
+ import { AdhocQueryResolver } from '../resolvers/AdhocQueryResolver.js';
36
+ import type { AppContext } from '../types.js';
37
+
38
+ const ADHOC_RESOLVER_PATH = resolve(__dirname, '../resolvers/AdhocQueryResolver.ts');
39
+
40
+ function readResolverSource(): string {
41
+ return readFileSync(ADHOC_RESOLVER_PATH, 'utf8');
42
+ }
43
+
44
+ type RunQueryCall = { SQL: string; MaxRows?: number; StartRow?: number };
45
+
46
+ function postgresContext(calls: RunQueryCall[], rows: Record<string, unknown>[]): AppContext {
47
+ const provider = {
48
+ PlatformKey: 'postgresql',
49
+ RunQuery: async (params: RunQueryCall) => {
50
+ calls.push(params);
51
+ return { Success: true, Results: rows, RowCount: rows.length, TotalRowCount: rows.length };
52
+ },
53
+ };
54
+ return {
55
+ // Deliberately EMPTY: a PostgreSQL tenant has no mssql ConnectionPool here.
56
+ // If the resolver still reaches for one, these tests fail.
57
+ dataSources: [],
58
+ providers: [{ type: 'Read-Only', provider }],
59
+ userPayload: { userRecord: { ID: 'u-1', Name: 'Tester' } },
60
+ } as unknown as AppContext;
61
+ }
62
+
63
+ afterEach(() => {
64
+ vi.restoreAllMocks();
65
+ });
66
+
67
+ describe('AdhocQueryResolver dialect source-shape contract', () => {
68
+ it('must NOT gate execution on a SQL-Server-only read-only data source', () => {
69
+ expect(readResolverSource()).not.toContain('GetReadOnlyDataSource');
70
+ });
71
+
72
+ it('must NOT depend on the mssql driver', () => {
73
+ const src = readResolverSource();
74
+ expect(src).not.toMatch(/^import .* from 'mssql';$/m);
75
+ expect(src).not.toContain('new sql.Request(');
76
+ expect(src).not.toContain('sql.ConnectionPool');
77
+ });
78
+
79
+ it('renders for the platform of the read-only provider the SQL runs on, not a literal', () => {
80
+ const src = readResolverSource();
81
+ expect(src).toContain('GetReadOnlyProvider(context.providers');
82
+ expect(src).toContain('Platform: provider.PlatformKey');
83
+ expect(src).not.toMatch(/platform\s*(:\s*DatabasePlatform\s*)?=\s*'sqlserver'/);
84
+ expect(src).not.toMatch(/Platform:\s*'sqlserver'/);
85
+ });
86
+ });
87
+
88
+ describe('ExecuteAdhocQuery on a PostgreSQL tenant', () => {
89
+ it('runs on the read-only provider instead of refusing for want of an mssql pool', async () => {
90
+ const resolver = new AdhocQueryResolver();
91
+ const authorize = vi.spyOn(resolver, 'AuthorizeRenderedSQL').mockReturnValue(null);
92
+ const calls: RunQueryCall[] = [];
93
+
94
+ const result = await resolver.ExecuteAdhocQuery(
95
+ { SQL: 'SELECT ID FROM __mj.vwUsers' },
96
+ postgresContext(calls, [{ ID: '1' }, { ID: '2' }]),
97
+ );
98
+
99
+ expect(result.ErrorMessage).not.toContain('No read-only data source');
100
+ expect(result.Success).toBe(true);
101
+ expect(calls).toHaveLength(1);
102
+ expect(calls[0].SQL).toBe('SELECT ID FROM __mj.vwUsers');
103
+ expect(JSON.parse(result.Results)).toEqual([{ ID: '1' }, { ID: '2' }]);
104
+ // The SQL is authorized (and so rendered) against the same PostgreSQL provider it runs on.
105
+ expect((authorize.mock.calls[0][1] as { PlatformKey: string }).PlatformKey).toBe('postgresql');
106
+ });
107
+ });
108
+
109
+ describe('ExecuteAdhocQuery refusal path', () => {
110
+ it('still refuses when there is no read-only provider at all', async () => {
111
+ const resolver = new AdhocQueryResolver();
112
+
113
+ const result = await resolver.ExecuteAdhocQuery(
114
+ { SQL: 'SELECT ID FROM __mj.vwUsers' },
115
+ { dataSources: [], providers: [], userPayload: { userRecord: { ID: 'u-1' } } } as unknown as AppContext,
116
+ );
117
+
118
+ expect(result.Success).toBe(false);
119
+ expect(result.ErrorMessage).toContain('No read-only data source available for ad-hoc query execution');
120
+ });
121
+
122
+ it('does NOT fall back to a read-write provider', async () => {
123
+ const resolver = new AdhocQueryResolver();
124
+
125
+ const result = await resolver.ExecuteAdhocQuery(
126
+ { SQL: 'SELECT ID FROM __mj.vwUsers' },
127
+ {
128
+ dataSources: [],
129
+ providers: [{ type: 'Read-Write', provider: { PlatformKey: 'postgresql', RunQuery: async () => ({ Success: true, Results: [] }) } }],
130
+ userPayload: { userRecord: { ID: 'u-1' } },
131
+ } as unknown as AppContext,
132
+ );
133
+
134
+ expect(result.Success).toBe(false);
135
+ expect(result.ErrorMessage).toContain('No read-only data source available');
136
+ });
137
+ });
@@ -29,6 +29,7 @@ vi.mock('@memberjunction/graphql-dataprovider', () => ({
29
29
  import type { ExecuteAgentResult } from '@memberjunction/ai-core-plus';
30
30
  import { ConversationStreaming, type IConversationsRuntimeContext } from '@memberjunction/conversations-runtime';
31
31
  import { AgentRunStatusPublisher } from '../resolvers/AgentRunStatusPublisher.js';
32
+ import { StatusUpdatesFilter, type PushStatusNotificationPayload } from '../generic/PushStatusResolver.js';
32
33
  import type { UserPayload } from '../types.js';
33
34
 
34
35
  const run = {
@@ -56,6 +57,31 @@ function envelopes(publish: ReturnType<typeof vi.fn>): Array<Record<string, unkn
56
57
  }
57
58
 
58
59
  describe('AgentRunStatusPublisher', () => {
60
+ it('stamps the reply row on every message when it has one, so a reconnecting client can find its row', () => {
61
+ const publish = vi.fn().mockResolvedValue(undefined);
62
+ const pubSub = { publish } as unknown as PubSubEngine;
63
+ const userPayload = { sessionId: 'session-1', userRecord: { ID: 'user-1' } } as UserPayload;
64
+ const made = new AgentRunStatusPublisher(pubSub, userPayload, 'session-1', 'reply-7');
65
+ const runWithoutDetail = { ...run, ConversationDetailID: undefined, GetAll: () => ({ ID: 'run-1' }) };
66
+
67
+ made.OnProgress({ step: 'prompt_execution', message: 'Writing', metadata: { agentRun: runWithoutDetail } });
68
+ made.OnStreaming({ content: 'Hel', isComplete: false, stepType: 'prompt', kind: 'final-response' });
69
+ made.PublishFinal({ success: true, agentRun: runWithoutDetail } as unknown as ExecuteAgentResult);
70
+
71
+ const details = envelopes(publish).map((row) => (row.data as { conversationDetailId?: string }).conversationDetailId);
72
+ expect(details).toEqual(['reply-7', 'reply-7', 'reply-7', 'reply-7']);
73
+ });
74
+
75
+ it('leaves progress and streaming messages without a reply row when it has none, as before', () => {
76
+ const { publish, made } = publisher();
77
+
78
+ made.OnProgress({ step: 'prompt_execution', message: 'Writing', metadata: { agentRun: run } });
79
+ made.OnStreaming({ content: 'Hel', isComplete: false, stepType: 'prompt', kind: 'final-response' });
80
+
81
+ const sent = envelopes(publish).map((row) => row.data as Record<string, unknown>);
82
+ expect(sent.every((data) => !('conversationDetailId' in data))).toBe(true);
83
+ });
84
+
59
85
  it('publishes the resolver sequence: a significant step, streamed text, the partial result, then the completion', () => {
60
86
  const { publish, made } = publisher();
61
87
 
@@ -173,3 +199,26 @@ describe('AgentRunStatusPublisher', () => {
173
199
  expect(streaming.GetRecentCompletion('detail-1')?.agentRunId).toBe('run-1');
174
200
  });
175
201
  });
202
+
203
+ describe('AgentRunStatusPublisher — a widget guest elevated to the system user (#4931)', () => {
204
+ const GUEST = { ID: 'anonymous-user', IsMagicLinkAnonymous: true, AnonymousVisitorID: 'sid-a', MagicLinkScope: { ResourceID: 'sid-a' } };
205
+ const SYSTEM = { ID: 'system-user' };
206
+
207
+ it("stamps the guest (ElevatedFrom), not the elevated user, so the guest's own socket receives the run's events", () => {
208
+ const publish = vi.fn().mockResolvedValue(undefined);
209
+ const elevated = { sessionId: 'session-1', userRecord: SYSTEM, isSystemUser: true, ElevatedFrom: GUEST } as unknown as UserPayload;
210
+ const made = new AgentRunStatusPublisher({ publish } as unknown as PubSubEngine, elevated, 'session-1');
211
+
212
+ made.PublishFinal({ success: true, agentRun: run, payload: { text: 'Hello' } } as ExecuteAgentResult, 'detail-1', '{"text":"Hello"}');
213
+ made.PublishFailure('detail-1', 'boom');
214
+
215
+ const owners = publish.mock.calls.map((call) => (call[1] as { ownerUserId: string }).ownerUserId);
216
+ expect(owners.length).toBeGreaterThan(0);
217
+ // An anonymous visitor's owner key carries its scope identity, so another visitor cannot receive these events.
218
+ expect(owners.every((owner) => owner === 'anonymous-user|sid-a')).toBe(true);
219
+ const guestConnection = { userPayload: { sessionId: 'session-1', userRecord: GUEST } as unknown as UserPayload };
220
+ for (const call of publish.mock.calls) {
221
+ expect(StatusUpdatesFilter({ payload: call[1] as PushStatusNotificationPayload, args: { sessionId: 'session-1' }, context: guestConnection })).toBe(true);
222
+ }
223
+ });
224
+ });
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
+ import type { AppContext } from '../types.js';
4
+
5
+ vi.mock('type-graphql', () => {
6
+ const noopDecorator = () => () => undefined;
7
+ return {
8
+ Resolver: noopDecorator,
9
+ Mutation: noopDecorator,
10
+ Arg: noopDecorator,
11
+ Ctx: noopDecorator,
12
+ ObjectType: noopDecorator,
13
+ Field: noopDecorator,
14
+ };
15
+ });
16
+
17
+ vi.mock('../generic/ResolverBase.js', () => ({
18
+ ResolverBase: class {
19
+ protected GetUserFromPayload(payload?: { userRecord?: UserInfo }): UserInfo | undefined {
20
+ return payload?.userRecord;
21
+ }
22
+ },
23
+ }));
24
+
25
+ const closeSessionMock = vi.fn(async () => true);
26
+ const heartbeatMock = vi.fn(async () => true);
27
+
28
+ vi.mock('../agentSessions/index.js', () => ({
29
+ SessionManager: class {
30
+ CloseSession = closeSessionMock;
31
+ Heartbeat = heartbeatMock;
32
+ },
33
+ SessionAuthorizationError: class extends Error {},
34
+ }));
35
+
36
+ import { AgentSessionResolver } from '../resolvers/AgentSessionResolver.js';
37
+
38
+ const uuid = (a: string, b: string) =>
39
+ [a.repeat(8), b.repeat(4), '4' + b.repeat(3), '8' + b.repeat(3), b.repeat(12)].join('-');
40
+ const ANON = uuid('A', '0');
41
+ const SESSION = uuid('1', '1');
42
+ const CONVERSATION = uuid('2', '2');
43
+
44
+ const guest = (visitorId: string) =>
45
+ ({
46
+ ID: ANON,
47
+ IsMagicLinkAnonymous: true,
48
+ // An unscoped multi-use invite: redemptions are told apart by mj_sid (a resource scope would be shared).
49
+ AnonymousVisitorID: visitorId,
50
+ }) as unknown as UserInfo;
51
+
52
+ function makeProvider(sessionFound = true, externalId = 'visitor-1'): IMetadataProvider {
53
+ return {
54
+ GetEntityObject: vi.fn(async (entityName: string) =>
55
+ entityName === 'MJ: AI Agent Sessions'
56
+ ? { UserID: ANON, ConversationID: CONVERSATION, Load: vi.fn(async () => sessionFound) }
57
+ : { ExternalID: externalId, Load: vi.fn(async () => true) },
58
+ ),
59
+ } as unknown as IMetadataProvider;
60
+ }
61
+
62
+ function makeCtx(user: UserInfo, provider: IMetadataProvider): AppContext {
63
+ return {
64
+ userPayload: { email: 'anonymous@magic-link.local', sessionId: 's', userRecord: user },
65
+ providers: [{ provider, type: 'Read-Write' }],
66
+ } as unknown as AppContext;
67
+ }
68
+
69
+ describe('AgentSessionResolver anonymous visitor ownership', () => {
70
+ beforeEach(() => {
71
+ closeSessionMock.mockClear();
72
+ heartbeatMock.mockClear();
73
+ });
74
+
75
+ it('CloseAgentSession allows the matching anonymous visitor and refuses a second anonymous visitor', async () => {
76
+ const resolver = new AgentSessionResolver();
77
+ const provider = makeProvider(true, 'visitor-1');
78
+
79
+ await expect(resolver.CloseAgentSession(SESSION, makeCtx(guest('visitor-1'), provider))).resolves.toBe(true);
80
+ expect(closeSessionMock).toHaveBeenCalledOnce();
81
+
82
+ await expect(resolver.CloseAgentSession(SESSION, makeCtx(guest('visitor-2'), provider))).rejects.toThrow(
83
+ 'Not authorized: you do not own this agent session',
84
+ );
85
+ expect(closeSessionMock).toHaveBeenCalledOnce();
86
+ });
87
+
88
+ it('AgentSessionHeartbeat allows the matching anonymous visitor and refuses a second anonymous visitor', async () => {
89
+ const resolver = new AgentSessionResolver();
90
+ const provider = makeProvider(true, 'visitor-1');
91
+
92
+ await expect(resolver.AgentSessionHeartbeat(SESSION, makeCtx(guest('visitor-1'), provider))).resolves.toBe(true);
93
+ expect(heartbeatMock).toHaveBeenCalledOnce();
94
+
95
+ await expect(resolver.AgentSessionHeartbeat(SESSION, makeCtx(guest('visitor-2'), provider))).rejects.toThrow(
96
+ 'Not authorized: you do not own this agent session',
97
+ );
98
+ expect(heartbeatMock).toHaveBeenCalledOnce();
99
+ });
100
+
101
+ it('throws the expected not-found message when the session does not exist', async () => {
102
+ const resolver = new AgentSessionResolver();
103
+ const provider = makeProvider(false);
104
+
105
+ await expect(resolver.CloseAgentSession(SESSION, makeCtx(guest('visitor-1'), provider))).rejects.toThrow(
106
+ `Agent session ${SESSION} not found`,
107
+ );
108
+ await expect(resolver.AgentSessionHeartbeat(SESSION, makeCtx(guest('visitor-1'), provider))).rejects.toThrow(
109
+ `Agent session ${SESSION} not found`,
110
+ );
111
+ });
112
+ });
113
+
114
+ describe('CloseAgentSession close reason (MJ#6013)', () => {
115
+ beforeEach(() => closeSessionMock.mockClear());
116
+
117
+ it('passes a client-reportable reason through, so a start that failed is recorded as an Error', async () => {
118
+ const resolver = new AgentSessionResolver();
119
+ const provider = makeProvider(true, 'visitor-1');
120
+ await expect(resolver.CloseAgentSession(SESSION, makeCtx(guest('visitor-1'), provider), 'Error')).resolves.toBe(true);
121
+ expect(closeSessionMock).toHaveBeenCalledWith(SESSION, expect.anything(), provider, 'Error');
122
+ });
123
+
124
+ it('defaults to Explicit when the client sends no reason (older clients)', async () => {
125
+ const resolver = new AgentSessionResolver();
126
+ const provider = makeProvider(true, 'visitor-1');
127
+ await expect(resolver.CloseAgentSession(SESSION, makeCtx(guest('visitor-1'), provider))).resolves.toBe(true);
128
+ expect(closeSessionMock).toHaveBeenCalledWith(SESSION, expect.anything(), provider, 'Explicit');
129
+ });
130
+
131
+ it('refuses a reason only the server may stamp', async () => {
132
+ const resolver = new AgentSessionResolver();
133
+ const provider = makeProvider(true, 'visitor-1');
134
+ await expect(resolver.CloseAgentSession(SESSION, makeCtx(guest('visitor-1'), provider), 'Janitor')).rejects.toThrow(/close reason/i);
135
+ expect(closeSessionMock).not.toHaveBeenCalled();
136
+ });
137
+ });
@@ -14,10 +14,19 @@
14
14
  import { describe, it, expect, beforeEach, vi } from 'vitest';
15
15
 
16
16
  const runPipelineBatchMock = vi.fn();
17
+ /**
18
+ * MJ-APPLY-6 — the BARE, non-retrying entry point. Bound separately so a regression back to it is
19
+ * an assertion failure rather than an invisible loss of the retry. Promotion runs unattended
20
+ * mid-sync; a transient pipeline step failing there drops the whole promotion silently.
21
+ */
22
+ const runPipelineBatchBareMock = vi.fn();
17
23
  vi.mock('@memberjunction/schema-engine', () => ({
18
24
  RuntimeSchemaManager: {
19
25
  get Instance() {
20
- return { RunPipelineBatch: runPipelineBatchMock };
26
+ return {
27
+ RunPipelineBatch: runPipelineBatchBareMock,
28
+ RunPipelineBatchWithRetry: runPipelineBatchMock,
29
+ };
21
30
  },
22
31
  },
23
32
  }));
@@ -104,6 +113,7 @@ function stubPromoter(
104
113
 
105
114
  beforeEach(() => {
106
115
  runPipelineBatchMock.mockReset();
116
+ runPipelineBatchBareMock.mockReset();
107
117
  });
108
118
 
109
119
  describe('PromoteForSync — one batched RSU pass', () => {
@@ -122,6 +132,10 @@ describe('PromoteForSync — one batched RSU pass', () => {
122
132
  const result = await promoter.PromoteForSync('ci-1', ['A', 'B', 'C']);
123
133
 
124
134
  expect(runPipelineBatchMock).toHaveBeenCalledTimes(1);
135
+ // MJ-APPLY-6 — the ONE pass has to be the RETRYING one. Promotion is unattended: a
136
+ // transient ExecuteMigration/RunCodeGen/Compile/Restart failure with no replay drops every
137
+ // accepted column back into overflow and nothing reruns it.
138
+ expect(runPipelineBatchBareMock).not.toHaveBeenCalled();
125
139
  expect(runPipelineBatchMock.mock.calls[0][0]).toHaveLength(3);
126
140
  expect(result.Promoted).toBe(true);
127
141
  expect(result.ColumnsAdded).toEqual([
@@ -0,0 +1,151 @@
1
+ // The resolver module pulls in type-graphql decorators, which need the Reflect.metadata polyfill.
2
+ import 'reflect-metadata';
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import type { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
5
+ import type { EntityCommunicationParams } from '@memberjunction/entity-communications-base';
6
+
7
+ /**
8
+ * `RunEntityCommunicationByViewID` with `previewOnly` + `includeProcessedMessages` returns the rows
9
+ * it read. It must hand the engine the authenticated caller as the acting user, and screen the
10
+ * view's client-supplied clauses the way a RunView request is screened, because this entry point
11
+ * does not pass through the RunView resolvers.
12
+ */
13
+
14
+ const { captured } = vi.hoisted(() => ({ captured: { params: [] as EntityCommunicationParams[] } }));
15
+
16
+ // The resolver's input types declare fields without explicit GraphQL types; the test runner emits no
17
+ // decorator metadata, so the decorators are stubbed out.
18
+ vi.mock('type-graphql', () => {
19
+ const noopDecorator = () => () => undefined;
20
+ return {
21
+ Resolver: noopDecorator, Mutation: noopDecorator, Query: noopDecorator, Subscription: noopDecorator,
22
+ ObjectType: noopDecorator, InputType: noopDecorator, Field: noopDecorator, Arg: noopDecorator,
23
+ Args: noopDecorator, Ctx: noopDecorator, PubSub: noopDecorator, Root: noopDecorator,
24
+ Directive: noopDecorator, Authorized: noopDecorator, UseMiddleware: noopDecorator,
25
+ createUnionType: () => class {},
26
+ AuthorizationError: class AuthorizationError extends Error {},
27
+ Float: class {}, Int: class {}, ID: class {},
28
+ };
29
+ });
30
+
31
+ vi.mock('@memberjunction/entity-communications-server', () => ({
32
+ EntityCommunicationsEngine: {
33
+ Instance: {
34
+ Config: vi.fn(),
35
+ RunEntityCommunication: vi.fn(async (params: EntityCommunicationParams) => {
36
+ captured.params.push(params);
37
+ return { Success: true, Results: [] };
38
+ }),
39
+ },
40
+ },
41
+ }));
42
+
43
+ vi.mock('@memberjunction/templates', () => ({
44
+ TemplateEngineServer: { Instance: { Config: vi.fn(), FindTemplate: vi.fn() } },
45
+ }));
46
+
47
+ import { ReportResolver } from '../resolvers/EntityCommunicationsResolver.js';
48
+ import type { AppContext } from '../types.js';
49
+ import type { RunViewByIDInput } from '../generic/RunViewResolver.js';
50
+ import type { CommunicationMessageInput } from '../resolvers/EntityCommunicationsResolver.js';
51
+
52
+ const SENDER = { ID: 'sender', Email: 'sender@example.com' } as UserInfo;
53
+
54
+ /** Entity metadata the clause screen reads: one entity the sender can read, one it cannot. */
55
+ function fakeProvider(): DatabaseProviderBase {
56
+ const entity = (name: string, view: string, canRead: boolean) => ({
57
+ ID: name,
58
+ Name: name,
59
+ SchemaName: '__mj',
60
+ BaseView: view,
61
+ BaseTable: view.replace(/^vw/, ''),
62
+ Fields: [{ Name: 'ID', NeedsQuotes: true }, { Name: 'Email', NeedsQuotes: true }],
63
+ GetUserPermisions: () => ({ CanRead: canRead }),
64
+ GetEffectiveRowFilterWhereClause: () => '',
65
+ GetDeniedReadFields: () => new Set<string>(),
66
+ });
67
+ return {
68
+ Entities: [entity('Customers', 'vwCustomers', true), entity('Salaries', 'vwSalaries', false)],
69
+ GetEntityObject: async () => fakeView(),
70
+ } as unknown as DatabaseProviderBase;
71
+ }
72
+
73
+ /** Saved views by ID, as the caller can load them: `view-1` is a Customers view, `view-salaries` is not. */
74
+ const VIEWS: Record<string, { ID: string; EntityID: string }> = {
75
+ 'view-1': { ID: 'view-1', EntityID: 'Customers' },
76
+ 'view-salaries': { ID: 'view-salaries', EntityID: 'Salaries' },
77
+ };
78
+
79
+ function fakeView() {
80
+ const view = {
81
+ ID: undefined as string | undefined,
82
+ EntityID: undefined as string | undefined,
83
+ async Load(id: string) {
84
+ const found = VIEWS[id];
85
+ if (!found) return false;
86
+ Object.assign(view, found);
87
+ return true;
88
+ },
89
+ };
90
+ return view;
91
+ }
92
+
93
+ function context(): AppContext {
94
+ return {
95
+ userPayload: { email: SENDER.Email, userRecord: SENDER },
96
+ providers: [{ provider: fakeProvider(), type: 'Read-Write' }],
97
+ } as unknown as AppContext;
98
+ }
99
+
100
+ function run(extraFilter?: string, viewID = 'view-1') {
101
+ const viewInput = { ViewID: viewID, ExtraFilter: extraFilter } as RunViewByIDInput;
102
+ const message = { MessageType: {}, From: 'a@example.com', To: '' } as unknown as CommunicationMessageInput;
103
+ return new ReportResolver().RunEntityCommunicationByViewID('Customers', viewInput, 'SendGrid', 'Email', message, true, true, context());
104
+ }
105
+
106
+ describe('RunEntityCommunicationByViewID — runs as the caller', () => {
107
+ beforeEach(() => {
108
+ captured.params = [];
109
+ });
110
+
111
+ it('passes the authenticated caller to the engine as the acting user', async () => {
112
+ const result = await run();
113
+ expect(result.Success).toBe(true);
114
+ expect(captured.params).toHaveLength(1);
115
+ expect(captured.params[0].ContextUser).toBe(SENDER);
116
+ });
117
+
118
+ it('refuses an ExtraFilter whose subquery reads an entity the caller cannot read, before the engine runs', async () => {
119
+ const result = await run("ID IN (SELECT ID FROM __mj.vwSalaries WHERE Amount > 100000)");
120
+ expect(result.Success).toBe(false);
121
+ expect(result.ErrorMessage).toMatch(/ExtraFilter/);
122
+ expect(captured.params).toHaveLength(0);
123
+ });
124
+
125
+ it('refuses an ExtraFilter that carries a second statement', async () => {
126
+ const result = await run("1=1; SELECT 1");
127
+ expect(result.Success).toBe(false);
128
+ expect(captured.params).toHaveLength(0);
129
+ });
130
+ });
131
+
132
+ describe('RunEntityCommunicationByViewID — the view must belong to the entity', () => {
133
+ beforeEach(() => {
134
+ captured.params = [];
135
+ });
136
+
137
+ it('refuses a view of a different entity, before the engine runs', async () => {
138
+ const result = await run(undefined, 'view-salaries');
139
+ expect(result.Success).toBe(false);
140
+ expect(captured.params).toHaveLength(0);
141
+ });
142
+
143
+ it('gives the same error for a view of another entity as for a view that does not exist', async () => {
144
+ const mismatched = await run(undefined, 'view-salaries');
145
+ const missing = await run(undefined, 'view-missing');
146
+ expect(missing.Success).toBe(false);
147
+ expect(mismatched.ErrorMessage).toBe(missing.ErrorMessage);
148
+ expect(mismatched.ErrorMessage).toMatch(/does not exist or you do not have access/);
149
+ expect(captured.params).toHaveLength(0);
150
+ });
151
+ });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Two ways the schema-evolution path stopped short of acting on what it already knew.
3
+ *
4
+ * - MJ-RUN-37: the persist layer has always recorded an `IncrementalWatermarkField` change and
5
+ * nothing consumed the record. `changedObjects` — the sole input to `ResetPullWatermarks` — was
6
+ * built from a physical column diff and a field-map add/disable, and a cursor swap produces
7
+ * neither. The stale Pull watermark then became a lower bound on a DIFFERENT column, and where
8
+ * the new column sorts later, every row below it was filtered out at the source, permanently and
9
+ * invisibly (an incremental's fetched-vs-expected check agrees, because the source counts
10
+ * against the filter it was handed).
11
+ *
12
+ * - MJ-APPLY-6 residual: `RunPipelineBatchWithRetry` existed and two of the six pipeline call
13
+ * sites used it. `SchemaBuilder.RunSchemaPipeline` states the reason plainly — the middle steps
14
+ * fail transiently and "the callers simply never used it".
15
+ *
16
+ * The evolution mutation needs a provider, a connector, a SchemaBuilder and an RSU pipeline to
17
+ * invoke, and the properties here are wiring, not behaviour worth standing all that up to observe.
18
+ * Read from source, the convention this directory already uses for this method. The DECISION the
19
+ * wiring feeds is unit-tested for real in the integration-engine package
20
+ * (WatermarkFieldChangeIsAChange.test.ts) — these pin that the resolver calls it, feeds it the
21
+ * right input, and does so before the watermark reset.
22
+ */
23
+ import { describe, it, expect } from 'vitest';
24
+ import { readFileSync } from 'node:fs';
25
+ import { join } from 'node:path';
26
+
27
+ const SRC = readFileSync(join(__dirname, '..', 'resolvers', 'IntegrationDiscoveryResolver.ts'), 'utf-8');
28
+ const PROMOTER = readFileSync(join(__dirname, '..', 'integration', 'CustomColumnPromoter.ts'), 'utf-8');
29
+ const RSU_RESOLVER = readFileSync(join(__dirname, '..', 'resolvers', 'RSUResolver.ts'), 'utf-8');
30
+
31
+ /** The body of a named resolver method, up to the next @Query/@Mutation at the same indent. */
32
+ function methodBody(src: string, name: string): string {
33
+ const start = src.indexOf(`async ${name}(`);
34
+ expect(start, `${name} not found`).toBeGreaterThan(-1);
35
+ const rest = src.slice(start);
36
+ const end = rest.search(/\n @(Query|Mutation)\(/);
37
+ return end === -1 ? rest : rest.slice(0, end);
38
+ }
39
+
40
+ describe('MJ-RUN-37 — a watermark-field change reaches ResetPullWatermarks', () => {
41
+ const body = () => methodBody(SRC, 'IntegrationSchemaEvolution');
42
+
43
+ it('consumes the persist layer\'s detection rather than re-deriving it', () => {
44
+ // The whole defect was that this signal existed and had no consumer. It must come from the
45
+ // refresh's OWN PersistResult — re-reading the catalog afterwards cannot tell a changed
46
+ // watermark field from one that was always that way.
47
+ expect(body()).toMatch(/ObjectsWithWatermarkFieldChange\(\s*refresh\.PersistResult\?\.ObjectMergeLog \?\? \[\]/);
48
+ });
49
+
50
+ it('imports it from the engine that produces it', () => {
51
+ expect(SRC).toMatch(/ObjectsWithWatermarkFieldChange[\s\S]{0,200}?from "@memberjunction\/integration-engine"/);
52
+ });
53
+
54
+ it('scopes it to the CONTINUING maps, so it cannot report what nothing can reset', () => {
55
+ expect(body()).toMatch(/continuingMaps\.map\(m => m\.ExternalObjectName \?\? ''\)/);
56
+ });
57
+
58
+ it('folds the result into changedObjects — the only input ResetPullWatermarks has', () => {
59
+ const b = body();
60
+ const push = b.search(/if \(!changedObjects\.some\(n => n\.toLowerCase\(\) === name\.toLowerCase\(\)\)\) changedObjects\.push\(name\)/);
61
+ const reset = b.indexOf('ResetPullWatermarks(');
62
+ expect(push, 'the watermark-changed names are never pushed onto changedObjects').toBeGreaterThan(-1);
63
+ expect(reset).toBeGreaterThan(-1);
64
+ expect(push, 'the push must happen BEFORE the reset, or it changes nothing').toBeLessThan(reset);
65
+ });
66
+
67
+ it('does not push unconditionally — an unchanged run must not re-full-sync the connection', () => {
68
+ // The guard is the expensive half. `changedObjects` drives ResetPullWatermarks AND HasChanges,
69
+ // so a rule that fires when no cursor moved turns every refresh into a full re-fetch of
70
+ // everything. The emptiness of the detection is what has to carry that, never a bare loop.
71
+ const b = body();
72
+ const i = b.indexOf('ObjectsWithWatermarkFieldChange(');
73
+ const after = b.slice(i, i + 600);
74
+ expect(after).toMatch(/for \(const name of watermarkFieldChanged\)/);
75
+ expect(after).not.toMatch(/changedObjects\.push\(\.\.\./);
76
+ });
77
+ });
78
+
79
+ describe('MJ-APPLY-6 residual — the bare pipeline calls that should retry', () => {
80
+ it('IntegrationApplySchemaBatch retries, matching its single-connector sibling', () => {
81
+ // IntegrationApplySchema retries via SchemaBuilder.RunSchemaPipeline. Same install path, same
82
+ // generated DDL, same four transient steps — the batch simply never used the wrapper.
83
+ const b = methodBody(SRC, 'IntegrationApplySchemaBatch');
84
+ expect(b).toMatch(/rsm\.RunPipelineBatchWithRetry\(pipelineInputs\)/);
85
+ expect(b).not.toMatch(/rsm\.RunPipelineBatch\(/);
86
+ });
87
+
88
+ it('IntegrationSchemaEvolution retries', () => {
89
+ const b = methodBody(SRC, 'IntegrationSchemaEvolution');
90
+ expect(b).toMatch(/rsm\.RunPipelineBatchWithRetry\(/);
91
+ expect(b).not.toMatch(/rsm\.RunPipelineBatch\(/);
92
+ });
93
+
94
+ it('the custom-column promoter retries — it runs unattended, mid-sync', () => {
95
+ expect(PROMOTER).toMatch(/RuntimeSchemaManager\.Instance\.RunPipelineBatchWithRetry\(batchInputs\)/);
96
+ expect(PROMOTER).not.toMatch(/RuntimeSchemaManager\.Instance\.RunPipelineBatch\(/);
97
+ });
98
+
99
+ it('no bare RunPipelineBatch survives in the integration resolver', () => {
100
+ // A regression here is invisible at runtime: the pipeline still works, it just stops
101
+ // absorbing the hiccup it was wrapped to absorb.
102
+ expect(SRC).not.toMatch(/RunPipelineBatch\((?!\w)/);
103
+ });
104
+
105
+ it('RunRuntimeSchemaUpdateBatch deliberately does NOT retry, and says why', () => {
106
+ // The one exception, kept explicit so it reads as a decision rather than an oversight: this
107
+ // mutation replays CALLER-AUTHORED SQL, and executeMigration is not transactional.
108
+ const b = methodBody(RSU_RESOLVER, 'RunRuntimeSchemaUpdateBatch');
109
+ expect(b).toMatch(/rsm\.RunPipelineBatch\(pipelineInputs\)/);
110
+ expect(b).toMatch(/deliberately the BARE call/);
111
+ expect(b).toMatch(/NOT transactional/);
112
+ });
113
+ });