@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4

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 (435) hide show
  1. package/README.md +30 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  5. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  7. package/dist/agentSessions/HostInstance.d.ts +75 -11
  8. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  9. package/dist/agentSessions/HostInstance.js +99 -28
  10. package/dist/agentSessions/HostInstance.js.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  12. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  13. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  14. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +452 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +43 -2
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +99 -8
  21. package/dist/agentSessions/SessionManager.js.map +1 -1
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  25. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  26. package/dist/agentSessions/index.d.ts +2 -0
  27. package/dist/agentSessions/index.d.ts.map +1 -1
  28. package/dist/agentSessions/index.js +10 -3
  29. package/dist/agentSessions/index.js.map +1 -1
  30. package/dist/auth/actionAuthorization.d.ts +40 -0
  31. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  32. package/dist/auth/actionAuthorization.js +94 -0
  33. package/dist/auth/actionAuthorization.js.map +1 -0
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +3 -10
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  39. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  40. package/dist/auth/newUsers.d.ts +10 -0
  41. package/dist/auth/newUsers.d.ts.map +1 -1
  42. package/dist/auth/newUsers.js +80 -11
  43. package/dist/auth/newUsers.js.map +1 -1
  44. package/dist/config.d.ts +4567 -71
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +246 -3
  47. package/dist/config.js.map +1 -1
  48. package/dist/context.d.ts +8 -0
  49. package/dist/context.d.ts.map +1 -1
  50. package/dist/context.js +25 -34
  51. package/dist/context.js.map +1 -1
  52. package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
  53. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  54. package/dist/generated/graphql-schemas/__mj.js +9188 -3649
  55. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  56. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  57. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  58. package/dist/generic/ResolverBase.d.ts +62 -6
  59. package/dist/generic/ResolverBase.d.ts.map +1 -1
  60. package/dist/generic/ResolverBase.js +179 -35
  61. package/dist/generic/ResolverBase.js.map +1 -1
  62. package/dist/generic/RunViewResolver.d.ts +4 -0
  63. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  64. package/dist/generic/RunViewResolver.js +37 -1
  65. package/dist/generic/RunViewResolver.js.map +1 -1
  66. package/dist/index.d.ts +7 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +214 -159
  69. package/dist/index.js.map +1 -1
  70. package/dist/isolatedProvider.d.ts +20 -0
  71. package/dist/isolatedProvider.d.ts.map +1 -0
  72. package/dist/isolatedProvider.js +22 -0
  73. package/dist/isolatedProvider.js.map +1 -0
  74. package/dist/postgresqlCompat.d.ts +8 -0
  75. package/dist/postgresqlCompat.d.ts.map +1 -0
  76. package/dist/postgresqlCompat.js +19 -0
  77. package/dist/postgresqlCompat.js.map +1 -0
  78. package/dist/postgresqlPoolSettings.d.ts +108 -0
  79. package/dist/postgresqlPoolSettings.d.ts.map +1 -0
  80. package/dist/postgresqlPoolSettings.js +134 -0
  81. package/dist/postgresqlPoolSettings.js.map +1 -0
  82. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  83. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  84. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  85. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  86. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  87. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  88. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  89. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  90. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  91. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  92. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  93. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  94. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  95. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  96. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  97. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  98. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  99. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  100. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  101. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  102. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  103. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  104. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  105. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  106. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  107. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  108. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  109. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  110. package/dist/realtimeSessions/index.d.ts +17 -0
  111. package/dist/realtimeSessions/index.d.ts.map +1 -0
  112. package/dist/realtimeSessions/index.js +17 -0
  113. package/dist/realtimeSessions/index.js.map +1 -0
  114. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  115. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  116. package/dist/realtimeSessions/sessionAccess.js +90 -0
  117. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  118. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  119. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  120. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  121. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  122. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  123. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  124. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  125. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  126. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  127. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  128. package/dist/realtimeSessions/verificationCore.js +356 -0
  129. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  130. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  131. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  132. package/dist/realtimeSessions/verificationPages.js +110 -0
  133. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  134. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  135. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  136. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  137. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  138. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  139. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  140. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  141. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  142. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  143. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  144. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  145. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  146. package/dist/resolvers/ActionResolver.d.ts +31 -5
  147. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  148. package/dist/resolvers/ActionResolver.js +75 -14
  149. package/dist/resolvers/ActionResolver.js.map +1 -1
  150. package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
  151. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  152. package/dist/resolvers/AdhocQueryResolver.js +92 -166
  153. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  154. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  155. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  156. package/dist/resolvers/CacheStatsResolver.js +113 -2
  157. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  158. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  159. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  160. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  161. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  162. package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
  163. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  164. package/dist/resolvers/EntityRecordNameResolver.js +9 -47
  165. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  166. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  167. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  168. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  169. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  170. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  171. package/dist/resolvers/GetDataResolver.js +9 -0
  172. package/dist/resolvers/GetDataResolver.js.map +1 -1
  173. package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
  174. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
  175. package/dist/resolvers/HumanHandoffResolver.js +330 -0
  176. package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
  177. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  178. package/dist/resolvers/MCPResolver.js +9 -4
  179. package/dist/resolvers/MCPResolver.js.map +1 -1
  180. package/dist/resolvers/MeetingResolver.d.ts +69 -0
  181. package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
  182. package/dist/resolvers/MeetingResolver.js +381 -0
  183. package/dist/resolvers/MeetingResolver.js.map +1 -0
  184. package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
  185. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  186. package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
  187. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  188. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
  189. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  190. package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
  191. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  192. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  193. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  194. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  195. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  196. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  197. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  198. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  199. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  200. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  201. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  202. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  204. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  205. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  206. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  207. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  208. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunTemplateResolver.js +12 -11
  210. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  211. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  212. package/dist/resolvers/SyncDataResolver.js +6 -2
  213. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  214. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  215. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  216. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  217. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  218. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  219. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  220. package/dist/resolvers/UserAvatarResolver.js +124 -0
  221. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  222. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  223. package/dist/resolvers/UserResolver.js +2 -17
  224. package/dist/resolvers/UserResolver.js.map +1 -1
  225. package/dist/resolvers/adhocTimeout.d.ts +7 -0
  226. package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
  227. package/dist/resolvers/adhocTimeout.js +12 -0
  228. package/dist/resolvers/adhocTimeout.js.map +1 -0
  229. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  230. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  231. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  232. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  233. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  234. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  235. package/dist/resolvers/avatarInputValidation.js +97 -0
  236. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  237. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  238. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  239. package/dist/resolvers/currentUserRoles.js +30 -0
  240. package/dist/resolvers/currentUserRoles.js.map +1 -1
  241. package/dist/resolvers/meetings/index.d.ts +5 -0
  242. package/dist/resolvers/meetings/index.d.ts.map +1 -0
  243. package/dist/resolvers/meetings/index.js +5 -0
  244. package/dist/resolvers/meetings/index.js.map +1 -0
  245. package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
  246. package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
  247. package/dist/resolvers/meetings/meetingHelpers.js +212 -0
  248. package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
  249. package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
  250. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
  251. package/dist/resolvers/meetings/meetingMutations.js +439 -0
  252. package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
  253. package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
  254. package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
  255. package/dist/resolvers/meetings/meetingQueries.js +113 -0
  256. package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
  257. package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
  258. package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
  259. package/dist/resolvers/meetings/meetingTypes.js +345 -0
  260. package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
  261. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  262. package/dist/rest/ViewOperationsHandler.js +8 -5
  263. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  264. package/dist/services/TaskGraphActionRunner.d.ts +8 -0
  265. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  266. package/dist/services/TaskGraphActionRunner.js +27 -1
  267. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  268. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  269. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  270. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  271. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  272. package/dist/services/WorkQueueHostService.d.ts +36 -0
  273. package/dist/services/WorkQueueHostService.d.ts.map +1 -0
  274. package/dist/services/WorkQueueHostService.js +97 -0
  275. package/dist/services/WorkQueueHostService.js.map +1 -0
  276. package/dist/services/workQueueConfig.d.ts +103 -0
  277. package/dist/services/workQueueConfig.d.ts.map +1 -0
  278. package/dist/services/workQueueConfig.js +48 -0
  279. package/dist/services/workQueueConfig.js.map +1 -0
  280. package/dist/sharedCache.d.ts +105 -0
  281. package/dist/sharedCache.d.ts.map +1 -0
  282. package/dist/sharedCache.js +276 -0
  283. package/dist/sharedCache.js.map +1 -0
  284. package/dist/types.d.ts +11 -0
  285. package/dist/types.d.ts.map +1 -1
  286. package/dist/types.js.map +1 -1
  287. package/package.json +97 -94
  288. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  289. package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
  290. package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
  291. package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
  292. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  293. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  294. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  295. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
  296. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  297. package/src/__tests__/HostInstance.test.ts +84 -0
  298. package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
  299. package/src/__tests__/MeetingResolver.test.ts +448 -0
  300. package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
  301. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  302. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  303. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  304. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  305. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  306. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
  307. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  308. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  309. package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
  310. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
  311. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  312. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  313. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  314. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  315. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  316. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  317. package/src/__tests__/SessionJanitor.test.ts +845 -18
  318. package/src/__tests__/SessionManager.test.ts +213 -17
  319. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  320. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  321. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
  322. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  323. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  324. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  325. package/src/__tests__/WorkQueueHostService.test.ts +133 -0
  326. package/src/__tests__/actionAuthorization.test.ts +130 -0
  327. package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
  328. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  329. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  330. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  331. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  332. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  333. package/src/__tests__/newUsers.template.test.ts +151 -0
  334. package/src/__tests__/newUsers.test.ts +74 -5
  335. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  336. package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
  337. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  338. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  339. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  340. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  341. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  342. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  343. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  344. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  345. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  346. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  347. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  348. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  349. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  350. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  351. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  352. package/src/__tests__/sharedCache.test.ts +310 -0
  353. package/src/__tests__/translateBracketsToPG.test.ts +20 -0
  354. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  355. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  356. package/src/agentSessions/HostInstance.ts +124 -28
  357. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  358. package/src/agentSessions/SessionJanitor.ts +543 -57
  359. package/src/agentSessions/SessionManager.ts +125 -25
  360. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  361. package/src/agentSessions/index.ts +10 -3
  362. package/src/auth/actionAuthorization.ts +116 -0
  363. package/src/auth/index.ts +3 -8
  364. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  365. package/src/auth/newUsers.ts +99 -11
  366. package/src/config.ts +255 -3
  367. package/src/context.ts +26 -34
  368. package/src/generated/graphql-schemas/__mj.ts +22701 -18838
  369. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  370. package/src/generic/ResolverBase.ts +210 -36
  371. package/src/generic/RunViewResolver.ts +37 -2
  372. package/src/index.ts +236 -170
  373. package/src/isolatedProvider.ts +22 -0
  374. package/src/postgresqlCompat.ts +21 -0
  375. package/src/postgresqlPoolSettings.ts +213 -0
  376. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  377. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  378. package/src/realtimeSessions/README.md +179 -0
  379. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  380. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  381. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  382. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  383. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  384. package/src/realtimeSessions/index.ts +16 -0
  385. package/src/realtimeSessions/sessionAccess.ts +141 -0
  386. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  387. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  388. package/src/realtimeSessions/verificationCore.ts +502 -0
  389. package/src/realtimeSessions/verificationPages.ts +149 -0
  390. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  391. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  392. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  393. package/src/resolvers/ActionResolver.ts +84 -21
  394. package/src/resolvers/AdhocQueryResolver.ts +105 -187
  395. package/src/resolvers/CacheStatsResolver.ts +89 -2
  396. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  397. package/src/resolvers/EntityRecordNameResolver.ts +9 -51
  398. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  399. package/src/resolvers/GetDataResolver.ts +10 -0
  400. package/src/resolvers/HumanHandoffResolver.ts +312 -0
  401. package/src/resolvers/MCPResolver.ts +8 -5
  402. package/src/resolvers/MeetingResolver.ts +438 -0
  403. package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
  404. package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
  405. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  406. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  407. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  408. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  409. package/src/resolvers/RunTemplateResolver.ts +13 -12
  410. package/src/resolvers/SyncDataResolver.ts +6 -2
  411. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  412. package/src/resolvers/UserAvatarResolver.ts +111 -0
  413. package/src/resolvers/UserResolver.ts +2 -16
  414. package/src/resolvers/adhocTimeout.ts +12 -0
  415. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  416. package/src/resolvers/avatarInputValidation.ts +123 -0
  417. package/src/resolvers/currentUserRoles.ts +32 -1
  418. package/src/resolvers/meetings/index.ts +4 -0
  419. package/src/resolvers/meetings/meetingHelpers.ts +307 -0
  420. package/src/resolvers/meetings/meetingMutations.ts +556 -0
  421. package/src/resolvers/meetings/meetingQueries.ts +172 -0
  422. package/src/resolvers/meetings/meetingTypes.ts +238 -0
  423. package/src/rest/ViewOperationsHandler.ts +8 -5
  424. package/src/services/TaskGraphActionRunner.ts +29 -1
  425. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  426. package/src/services/WorkQueueHostService.ts +129 -0
  427. package/src/services/workQueueConfig.ts +52 -0
  428. package/src/sharedCache.ts +302 -0
  429. package/src/types.ts +12 -0
  430. package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
  431. package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
  432. package/dist/resolvers/adhoc-query-helpers.js +0 -50
  433. package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
  434. package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
  435. package/src/resolvers/adhoc-query-helpers.ts +0 -67
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Tests for userHandling.newUserTemplate: NewUserBase.createNewUser builds the new user as a copy of a
3
+ * template user through the record-cloning engine, and falls back to a plain user whenever the template
4
+ * can't be used (missing, not flagged IsTemplate, or the copy fails), so sign-in never breaks.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+
8
+ const {
9
+ mockConfig,
10
+ mockClone,
11
+ mockRunView,
12
+ mockLogError,
13
+ mockProvider,
14
+ savedUsers,
15
+ } = vi.hoisted(() => {
16
+ const savedUsers: Array<Record<string, unknown>> = [];
17
+ const makeUser = () => {
18
+ const user: Record<string, unknown> & { NewRecord: () => void; Load: (id: string) => Promise<boolean>; Save: () => Promise<boolean> } = {
19
+ NewRecord: vi.fn(() => {
20
+ user.ID = 'plain-user-id';
21
+ user.Created = 'plain';
22
+ }),
23
+ Load: vi.fn(async (id: string) => {
24
+ user.ID = id;
25
+ user.Created = 'template';
26
+ return true;
27
+ }),
28
+ Save: vi.fn(async () => {
29
+ savedUsers.push({ ...user });
30
+ return true;
31
+ }),
32
+ LatestResult: null,
33
+ };
34
+ return user;
35
+ };
36
+ return {
37
+ mockConfig: {
38
+ userHandling: {
39
+ newUserRoles: [] as string[],
40
+ CreateUserApplicationRecords: false,
41
+ UserApplications: [] as string[],
42
+ contextUserForNewUserCreation: '',
43
+ newUserTemplate: { email: 'demo@example.org', preset: 'template' } as { email: string; preset: string } | undefined,
44
+ },
45
+ },
46
+ mockClone: vi.fn(),
47
+ mockRunView: vi.fn(),
48
+ mockLogError: vi.fn(),
49
+ mockProvider: {
50
+ GetEntityObject: vi.fn(async () => makeUser()),
51
+ BeginTransaction: vi.fn(),
52
+ CommitTransaction: vi.fn(),
53
+ RollbackTransaction: vi.fn(),
54
+ Roles: [],
55
+ Applications: [],
56
+ },
57
+ savedUsers,
58
+ };
59
+ });
60
+
61
+ vi.mock('../config.js', () => ({ configInfo: mockConfig }));
62
+ vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: vi.fn(async () => mockProvider) }));
63
+ vi.mock('../auth/principals.js', () => ({ ResolveConfiguredPrincipal: vi.fn(() => ({ ID: 'system-id', Type: 'Owner' })) }));
64
+ vi.mock('@memberjunction/record-cloning', () => ({
65
+ RecordCloneEngine: class {
66
+ Clone = mockClone;
67
+ },
68
+ }));
69
+ vi.mock('@memberjunction/core', async (importOriginal) => {
70
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
71
+ return {
72
+ ...actual,
73
+ RunView: { FromMetadataProvider: () => ({ RunView: mockRunView }) },
74
+ LogError: mockLogError,
75
+ LogStatus: vi.fn(),
76
+ };
77
+ });
78
+ vi.mock('@memberjunction/core-entities', () => ({ UserInfoEngine: { GetDefaultApplicationsForNewUser: () => [] } }));
79
+
80
+ import { NewUserBase } from '../auth/newUsers.js';
81
+
82
+ const templateRow = (isTemplate: boolean) => ({ Success: true, Results: [{ ID: 'tpl-id', IsTemplate: isTemplate }] });
83
+ const cloned = { Success: true, ResultCode: 'SUCCESS', Warnings: [], RecordsCloned: 120, Roots: [{ EntityName: 'MJ: Users', SourceKey: { KeyValuePairs: [] }, TargetKey: { KeyValuePairs: [{ FieldName: 'ID', Value: 'new-user-id' }] } }] };
84
+
85
+ describe('NewUserBase.createNewUser with newUserTemplate', () => {
86
+ beforeEach(() => {
87
+ vi.clearAllMocks();
88
+ savedUsers.length = 0;
89
+ mockConfig.userHandling.newUserTemplate = { email: 'demo@example.org', preset: 'template' };
90
+ });
91
+
92
+ it('creates the user as an active copy of the template', async () => {
93
+ mockRunView.mockResolvedValue(templateRow(true));
94
+ mockClone.mockResolvedValue(cloned);
95
+
96
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
97
+
98
+ expect(mockClone).toHaveBeenCalledWith(
99
+ expect.objectContaining({
100
+ EntityName: 'MJ: Users',
101
+ SourceRecordKey: expect.objectContaining({ KeyValuePairs: [expect.objectContaining({ FieldName: 'ID', Value: 'tpl-id' })] }),
102
+ Preset: 'template',
103
+ PromptedValues: { Email: 'jane@example.com', FirstName: 'Jane', LastName: 'Doe' },
104
+ }),
105
+ expect.objectContaining({ ID: 'system-id' })
106
+ );
107
+ expect(user?.ID).toBe('new-user-id');
108
+ expect(savedUsers).toEqual([expect.objectContaining({ ID: 'new-user-id', Created: 'template', IsActive: true, LinkedRecordType: 'None' })]);
109
+ expect(mockProvider.BeginTransaction).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('falls back to a plain user when the template user is not flagged IsTemplate', async () => {
113
+ mockRunView.mockResolvedValue(templateRow(false));
114
+
115
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
116
+
117
+ expect(mockClone).not.toHaveBeenCalled();
118
+ expect(mockLogError.mock.calls.flat().join(' ')).toContain('not marked as a template user');
119
+ expect(user?.ID).toBe('plain-user-id');
120
+ expect(savedUsers[0]).toEqual(expect.objectContaining({ Created: 'plain', Email: 'jane@example.com', IsActive: true }));
121
+ });
122
+
123
+ it('falls back to a plain user when no user has the template email', async () => {
124
+ mockRunView.mockResolvedValue({ Success: true, Results: [] });
125
+
126
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
127
+
128
+ expect(mockClone).not.toHaveBeenCalled();
129
+ expect(user?.ID).toBe('plain-user-id');
130
+ });
131
+
132
+ it('falls back to a plain user when the copy is blocked', async () => {
133
+ mockRunView.mockResolvedValue(templateRow(true));
134
+ mockClone.mockResolvedValue({ Success: false, ResultCode: 'BLOCKED', Warnings: [{ Severity: 'Error', Code: 'FORBIDDEN', Message: 'needs Clone Records' }] });
135
+
136
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
137
+
138
+ expect(mockLogError.mock.calls.flat().join(' ')).toContain('needs Clone Records');
139
+ expect(user?.ID).toBe('plain-user-id');
140
+ });
141
+
142
+ it('creates a plain user, as before, when no template is configured', async () => {
143
+ mockConfig.userHandling.newUserTemplate = undefined;
144
+
145
+ const user = await new NewUserBase().createNewUser('Jane', 'Doe', 'jane@example.com');
146
+
147
+ expect(mockRunView).not.toHaveBeenCalled();
148
+ expect(mockClone).not.toHaveBeenCalled();
149
+ expect(user?.ID).toBe('plain-user-id');
150
+ });
151
+ });
@@ -30,6 +30,8 @@ const {
30
30
  mockBeginTransaction,
31
31
  mockCommitTransaction,
32
32
  mockRollbackTransaction,
33
+ mockGlobalProviderCalls,
34
+ mockCreateIsolatedProvider,
33
35
  mockGetDefaultApplicationsForNewUser,
34
36
  mockRolesArray,
35
37
  mockApplicationsArray,
@@ -69,6 +71,10 @@ const {
69
71
  mockBeginTransaction: vi.fn(),
70
72
  mockCommitTransaction: vi.fn(),
71
73
  mockRollbackTransaction: vi.fn(),
74
+ // Every call new-user creation makes on the SHARED Metadata.Provider. Must stay empty: a
75
+ // transaction there nests into every concurrent request's (see CreateIsolatedProvider).
76
+ mockGlobalProviderCalls: [] as string[],
77
+ mockCreateIsolatedProvider: vi.fn(),
72
78
  mockGetDefaultApplicationsForNewUser: vi.fn(),
73
79
  mockRolesArray: [] as Array<{ ID: string; Name: string }>,
74
80
  mockApplicationsArray: [] as Array<{ ID: string; Name: string }>,
@@ -79,6 +85,9 @@ const {
79
85
 
80
86
  vi.mock('../config.js', () => ({ configInfo: mockConfig }));
81
87
 
88
+ // The provider new-user creation must use: its own instance, so its transaction is its own.
89
+ vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: mockCreateIsolatedProvider }));
90
+
82
91
  vi.mock('@memberjunction/generic-database-provider', () => {
83
92
  const instance = {
84
93
  get Users() {
@@ -89,6 +98,18 @@ vi.mock('@memberjunction/generic-database-provider', () => {
89
98
  },
90
99
  Refresh: mockRefresh,
91
100
  GetSystemUser: vi.fn(),
101
+ /**
102
+ * The real cache falls back to a database read on a miss. The stub answers from
103
+ * the same array `Users` exposes, so these tests still describe the cache's behaviour.
104
+ */
105
+ FindUser: vi.fn(async ({ Email }: { Email?: string }) =>
106
+ mockCacheUsers.find((u: UserInfo) => {
107
+ if (!u.Email || u.Email.trim() === '') {
108
+ console.error(`SYSTEM METADATA ISSUE: User ${u.ID} has no email address`);
109
+ return false;
110
+ }
111
+ return u.Email.toLowerCase().trim() === (Email ?? '').toLowerCase().trim();
112
+ })),
92
113
  };
93
114
  return {
94
115
  UserCache: class MockUserCache {
@@ -104,10 +125,11 @@ vi.mock('@memberjunction/generic-database-provider', () => {
104
125
 
105
126
  vi.mock('@memberjunction/core', async (importOriginal) => {
106
127
  const actual = await importOriginal<typeof import('@memberjunction/core')>();
107
- const provider = {
108
- BeginTransaction: mockBeginTransaction,
109
- CommitTransaction: mockCommitTransaction,
110
- RollbackTransaction: mockRollbackTransaction,
128
+ // The shared global provider: records any use instead of doing anything.
129
+ const globalProvider = {
130
+ BeginTransaction: async () => { mockGlobalProviderCalls.push('BeginTransaction'); },
131
+ CommitTransaction: async () => { mockGlobalProviderCalls.push('CommitTransaction'); },
132
+ RollbackTransaction: async () => { mockGlobalProviderCalls.push('RollbackTransaction'); },
111
133
  Dialect: { BooleanLiteral: (value: boolean) => (value ? '1' : '0') },
112
134
  };
113
135
  class MockMetadata {
@@ -118,9 +140,10 @@ vi.mock('@memberjunction/core', async (importOriginal) => {
118
140
  return mockApplicationsArray;
119
141
  }
120
142
  public GetEntityObject(entityName: string, contextUser?: UserInfo): Promise<unknown> {
143
+ mockGlobalProviderCalls.push(`GetEntityObject:${entityName}`);
121
144
  return mockGetEntityObject(entityName, contextUser);
122
145
  }
123
- static Provider = provider;
146
+ static Provider = globalProvider;
124
147
  }
125
148
  class MockUserInfo {
126
149
  constructor(_provider: unknown, initData: Record<string, unknown>) {
@@ -128,6 +151,9 @@ vi.mock('@memberjunction/core', async (importOriginal) => {
128
151
  }
129
152
  }
130
153
  class MockRunView {
154
+ public static FromMetadataProvider(_provider: unknown): MockRunView {
155
+ return new MockRunView();
156
+ }
131
157
  public RunView(params: unknown, contextUser?: UserInfo): Promise<unknown> {
132
158
  return mockRunViewFn(params, contextUser);
133
159
  }
@@ -295,6 +321,21 @@ beforeEach(() => {
295
321
  });
296
322
  mockRunViewFn.mockResolvedValue({ Success: true, Results: [] });
297
323
  mockGetDefaultApplicationsForNewUser.mockReturnValue([]);
324
+
325
+ mockGlobalProviderCalls.length = 0;
326
+ mockCreateIsolatedProvider.mockImplementation(async () => ({
327
+ BeginTransaction: mockBeginTransaction,
328
+ CommitTransaction: mockCommitTransaction,
329
+ RollbackTransaction: mockRollbackTransaction,
330
+ Dialect: { BooleanLiteral: (value: boolean) => (value ? '1' : '0') },
331
+ get Roles() {
332
+ return mockRolesArray;
333
+ },
334
+ get Applications() {
335
+ return mockApplicationsArray;
336
+ },
337
+ GetEntityObject: (entityName: string, contextUser?: UserInfo) => mockGetEntityObject(entityName, contextUser),
338
+ }));
298
339
  });
299
340
 
300
341
  // ─── NewUserBase.createNewUser ──────────────────────────────────────────────
@@ -303,6 +344,34 @@ describe('NewUserBase.createNewUser', () => {
303
344
  const create = (first = 'Ada', last = 'Lovelace', email = 'ada@example.com') =>
304
345
  new NewUserBase().createNewUser(first, last, email);
305
346
 
347
+ describe('provider isolation', () => {
348
+ it('runs the whole provisioning on its own provider, never the shared Metadata.Provider', async () => {
349
+ mockConfig.userHandling.CreateUserApplicationRecords = true;
350
+ mockConfig.userHandling.UserApplications = ['CRM'];
351
+ mockRunViewFn.mockResolvedValue({ Success: true, Results: [{ EntityID: 'ent-1', Entity: 'Accounts' }] });
352
+
353
+ const user = await create();
354
+
355
+ expect(user).toBeTruthy();
356
+ expect(mockCreateIsolatedProvider).toHaveBeenCalledTimes(1);
357
+ expect(mockBeginTransaction).toHaveBeenCalledTimes(1);
358
+ expect(mockCommitTransaction).toHaveBeenCalledTimes(1);
359
+ // User, role, application and application-entity records all came from the isolated one.
360
+ expect(getEntityObjectCalls.map((c) => c.entityName)).toEqual([
361
+ 'MJ: Users', 'MJ: User Roles', 'MJ: User Applications', 'MJ: User Application Entities',
362
+ ]);
363
+ expect(mockGlobalProviderCalls).toEqual([]);
364
+ });
365
+
366
+ it('rolls back on the isolated provider too, never the shared one', async () => {
367
+ saveBehavior['MJ: Users'] = () => false;
368
+
369
+ expect(await create()).toBeNull();
370
+ expect(mockRollbackTransaction).toHaveBeenCalledTimes(1);
371
+ expect(mockGlobalProviderCalls).toEqual([]);
372
+ });
373
+ });
374
+
306
375
  describe('context-user resolution', () => {
307
376
  it('uses the configured contextUserForNewUserCreation for every entity it creates', async () => {
308
377
  const user = await create();
@@ -20,8 +20,10 @@ const { mockGetEntityObject, writableStore } = vi.hoisted(() => ({
20
20
  writableStore: { last: null as Record<string, unknown> | null },
21
21
  }));
22
22
 
23
- vi.mock('@memberjunction/global', () => ({
24
- UUIDsEqual: (a: string, b: string) => String(a).toLowerCase() === String(b).toLowerCase(),
23
+ // The real UUID helpers: the reconciler indexes users with IndexByUUID, so a stub that only
24
+ // provides UUIDsEqual would make the owner lookup throw (and the pass silently do nothing).
25
+ vi.mock('@memberjunction/global', async (importOriginal) => ({
26
+ ...(await importOriginal<typeof import('@memberjunction/global')>()),
25
27
  }));
26
28
 
27
29
  vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
@@ -230,6 +232,19 @@ describe('ReconcileOrphanedConversationDetails', () => {
230
232
  );
231
233
  });
232
234
 
235
+ it('resolves the owner when the conversation spells the user ID in a different case', async () => {
236
+ // The cached user is 'OWNER-1'; SQL Server and PostgreSQL disagree on UUID case, so the
237
+ // conversation row may carry it lower-cased. The owner lookup must still find the user.
238
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], 'owner-1');
239
+
240
+ await ReconcileOrphanedConversationDetails(PROVIDER, USER);
241
+
242
+ expect(mockGetEntityObject).toHaveBeenCalledWith(
243
+ 'MJ: Conversation Details',
244
+ expect.objectContaining({ ID: 'OWNER-1' })
245
+ );
246
+ });
247
+
233
248
  it('leaves the detail alone when no owner can be resolved', async () => {
234
249
  script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], null);
235
250
 
@@ -0,0 +1,131 @@
1
+ /**
2
+ * PostgreSQL pools read the same databaseSettings values as SQL Server, with the same defaults.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import {
6
+ BuildPostgreSQLConnectionConfig,
7
+ CODEGEN_STATEMENT_TIMEOUT_MS,
8
+ ResolvePostgreSQLEndpoint,
9
+ ResolvePostgreSQLReadOnlyCredentials,
10
+ ToPGPoolConfig
11
+ } from '../postgresqlPoolSettings.js';
12
+ import type { DatabaseSettingsInfo } from '../config.js';
13
+ import { DescribeReadOnlyLoginOverreach } from '../postgresqlPoolSettings.js';
14
+ import type { ReadOnlyLoginCheckClient, ReadOnlyLoginGrants } from '../postgresqlPoolSettings.js';
15
+
16
+ const defaults: DatabaseSettingsInfo = {
17
+ connectionTimeout: 45000,
18
+ requestTimeout: 30000,
19
+ metadataCacheRefreshInterval: 180000,
20
+ connectionPool: { max: 50, min: 5, idleTimeoutMillis: 30000, acquireTimeoutMillis: 30000 }
21
+ };
22
+ const endpoint = { Host: 'db', Port: 5432, Database: 'mj', User: 'api', Password: 'secret' };
23
+
24
+ describe('BuildPostgreSQLConnectionConfig', () => {
25
+ it('gives API pools requestTimeout as the statement timeout and the pool settings SQL Server uses', () => {
26
+ const config = BuildPostgreSQLConnectionConfig(endpoint, defaults, 'api');
27
+ expect(config).toMatchObject({
28
+ ...endpoint,
29
+ MaxConnections: 50,
30
+ MinConnections: 5,
31
+ IdleTimeoutMillis: 30000,
32
+ ConnectionTimeoutMillis: 30000,
33
+ StatementTimeoutMs: 30000,
34
+ IdleInTransactionSessionTimeoutMs: 60000
35
+ });
36
+ });
37
+
38
+ it('follows a configured requestTimeout and pool', () => {
39
+ const config = BuildPostgreSQLConnectionConfig(endpoint, {
40
+ ...defaults,
41
+ requestTimeout: 5000,
42
+ connectionPool: { max: 12, min: 1, idleTimeoutMillis: 7000, acquireTimeoutMillis: 9000 }
43
+ }, 'api');
44
+ expect(config).toMatchObject({ MaxConnections: 12, MinConnections: 1, IdleTimeoutMillis: 7000, ConnectionTimeoutMillis: 9000, StatementTimeoutMs: 5000, IdleInTransactionSessionTimeoutMs: 10000 });
45
+ });
46
+
47
+ it('sets no timeouts when requestTimeout is 0', () => {
48
+ const config = BuildPostgreSQLConnectionConfig(endpoint, { ...defaults, requestTimeout: 0 }, 'api');
49
+ expect(config.StatementTimeoutMs).toBe(0);
50
+ expect(config.IdleInTransactionSessionTimeoutMs).toBe(0);
51
+ });
52
+
53
+ it('keeps the read-only pool small but gives it the API timeouts', () => {
54
+ const config = BuildPostgreSQLConnectionConfig(endpoint, defaults, 'read-only');
55
+ expect(config).toMatchObject({ MaxConnections: 10, MinConnections: 0, StatementTimeoutMs: 30000, IdleInTransactionSessionTimeoutMs: 60000 });
56
+ expect(BuildPostgreSQLConnectionConfig(endpoint, { ...defaults, connectionPool: { max: 4, min: 1, idleTimeoutMillis: 1, acquireTimeoutMillis: 1 } }, 'read-only').MaxConnections).toBe(4);
57
+ });
58
+
59
+ it('gives the CodeGen pool the long timeout the SQL Server CodeGen pool has, and no idle-in-transaction limit', () => {
60
+ const config = BuildPostgreSQLConnectionConfig(endpoint, defaults, 'codegen');
61
+ expect(config.StatementTimeoutMs).toBe(CODEGEN_STATEMENT_TIMEOUT_MS);
62
+ expect(CODEGEN_STATEMENT_TIMEOUT_MS).toBe(600000);
63
+ expect(config.IdleInTransactionSessionTimeoutMs).toBe(0);
64
+ expect(config.MaxConnections).toBe(10);
65
+ });
66
+ });
67
+
68
+ describe('ToPGPoolConfig', () => {
69
+ it('maps the connection config onto pg.Pool option names', () => {
70
+ const pool = ToPGPoolConfig(BuildPostgreSQLConnectionConfig(endpoint, defaults, 'api'));
71
+ expect(pool).toEqual({
72
+ host: 'db', port: 5432, database: 'mj', user: 'api', password: 'secret',
73
+ max: 50, min: 5, idleTimeoutMillis: 30000, connectionTimeoutMillis: 30000,
74
+ statement_timeout: 30000, idle_in_transaction_session_timeout: 60000
75
+ });
76
+ });
77
+
78
+ it('leaves the timeouts out when they are 0', () => {
79
+ const pool = ToPGPoolConfig(BuildPostgreSQLConnectionConfig(endpoint, { ...defaults, requestTimeout: 0 }, 'api'));
80
+ expect(pool).not.toHaveProperty('statement_timeout');
81
+ expect(pool).not.toHaveProperty('idle_in_transaction_session_timeout');
82
+ });
83
+ });
84
+
85
+ describe('ResolvePostgreSQLEndpoint', () => {
86
+ it('prefers PG_* variables over DB_* ones', () => {
87
+ expect(ResolvePostgreSQLEndpoint({ PG_HOST: 'pg', DB_HOST: 'db', DB_PORT: '6000', DB_DATABASE: 'd', DB_USERNAME: 'u', DB_PASSWORD: 'p' }))
88
+ .toEqual({ Host: 'pg', Port: 6000, Database: 'd', User: 'u', Password: 'p' });
89
+ });
90
+
91
+ it('falls back to localhost:5432 and the postgres user', () => {
92
+ expect(ResolvePostgreSQLEndpoint({})).toEqual({ Host: 'localhost', Port: 5432, Database: '', User: 'postgres', Password: '' });
93
+ });
94
+ });
95
+
96
+ describe('ResolvePostgreSQLReadOnlyCredentials', () => {
97
+ it('uses the dbReadOnlyUsername / dbReadOnlyPassword settings SQL Server uses', () => {
98
+ expect(ResolvePostgreSQLReadOnlyCredentials({ dbReadOnlyUsername: 'ro', dbReadOnlyPassword: 'pw' }, {}))
99
+ .toEqual({ User: 'ro', Password: 'pw' });
100
+ });
101
+
102
+ it('falls back to PG_READ_ONLY_* when the settings are absent', () => {
103
+ expect(ResolvePostgreSQLReadOnlyCredentials({}, { PG_READ_ONLY_USERNAME: 'pgro', PG_READ_ONLY_PASSWORD: 'x' }))
104
+ .toEqual({ User: 'pgro', Password: 'x' });
105
+ });
106
+
107
+ it('returns null when no complete pair is configured', () => {
108
+ expect(ResolvePostgreSQLReadOnlyCredentials({ dbReadOnlyUsername: 'ro' }, { PG_READ_ONLY_USERNAME: 'pgro' })).toBeNull();
109
+ });
110
+ });
111
+
112
+ describe('DescribeReadOnlyLoginOverreach', () => {
113
+ const clientAnswering = (row: ReadOnlyLoginGrants): ReadOnlyLoginCheckClient => ({
114
+ query: async () => ({ rows: [row] }),
115
+ });
116
+
117
+ it('says nothing for a login that reads only views', async () => {
118
+ expect(await DescribeReadOnlyLoginOverreach(clientAnswering({ ReadsServerFiles: false, ReadableBaseTables: 0 }), '__mj')).toEqual([]);
119
+ });
120
+
121
+ it('warns when the login can read base tables', async () => {
122
+ const warnings = await DescribeReadOnlyLoginOverreach(clientAnswering({ ReadsServerFiles: false, ReadableBaseTables: 412 }), '__mj');
123
+ expect(warnings).toHaveLength(1);
124
+ expect(warnings[0]).toMatch(/can read 412 base table\(s\) in schema "__mj"/);
125
+ });
126
+
127
+ it('warns when the login can read server files', async () => {
128
+ const warnings = await DescribeReadOnlyLoginOverreach(clientAnswering({ ReadsServerFiles: true, ReadableBaseTables: 0 }), '__mj');
129
+ expect(warnings[0]).toMatch(/pg_read_server_files/);
130
+ });
131
+ });
@@ -0,0 +1,201 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
+ import { MJGlobal } from '@memberjunction/global';
4
+ import type { PubSubEngine } from 'type-graphql';
5
+ import { PubSubManager } from '../../generic/PubSubManager.js';
6
+ import {
7
+ ParseReplicatedRealtimeSessionEvent,
8
+ REALTIME_SESSION_EVENTS_TOPIC,
9
+ RealtimeSessionEventService,
10
+ type RealtimeSessionEventTopicPayload,
11
+ } from '../../realtimeSessions/RealtimeSessionEventService.js';
12
+
13
+ const OWNER = 'AAAAAAAA-0000-4000-8000-000000000001';
14
+ const SESSION_ID = 'BBBBBBBB-0000-4000-8000-000000000001';
15
+ const CONVERSATION_ID = 'CCCCCCCC-0000-4000-8000-000000000001';
16
+ const USER = { ID: OWNER } as UserInfo;
17
+
18
+ const VERIFIED = { VerifiedEmail: 'pat@example.com', VerifiedName: 'Pat', VerifiedAt: '2026-10-02T12:00:00.000Z', Method: 'link' as const };
19
+
20
+ /** A provider that serves one session (+ conversation) — or nothing. */
21
+ function provider(found = true, externalId: string | null = 'scope-1'): IMetadataProvider {
22
+ return {
23
+ GetEntityObject: vi.fn(async (name: string) => {
24
+ if (name === 'MJ: AI Agent Sessions') {
25
+ return found
26
+ ? { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID, Load: vi.fn(async () => true) }
27
+ : { Load: vi.fn(async () => false) };
28
+ }
29
+ return { ExternalID: externalId, Load: vi.fn(async () => true) };
30
+ }),
31
+ } as unknown as IMetadataProvider;
32
+ }
33
+
34
+ describe('RealtimeSessionEventService', () => {
35
+ const published: Array<{ topic: string; payload: RealtimeSessionEventTopicPayload }> = [];
36
+
37
+ beforeEach(() => {
38
+ published.length = 0;
39
+ const engine = { publish: vi.fn(async (topic: string, payload: RealtimeSessionEventTopicPayload) => void published.push({ topic, payload })) };
40
+ PubSubManager.Instance.SetPubSubEngine(engine as unknown as PubSubEngine);
41
+ RealtimeSessionEventService.Instance.SetReplicationHook(undefined);
42
+ });
43
+
44
+ describe('Publish', () => {
45
+ it('publishes on the shared topic with the session routing snapshot', async () => {
46
+ const result = await RealtimeSessionEventService.Instance.Publish({
47
+ AgentSessionID: SESSION_ID,
48
+ Type: 'identity.verified',
49
+ Payload: VERIFIED,
50
+ ContextUser: USER,
51
+ Provider: provider(),
52
+ });
53
+ expect(result).toEqual({ Success: true });
54
+ expect(published).toHaveLength(1);
55
+ expect(published[0].topic).toBe(REALTIME_SESSION_EVENTS_TOPIC);
56
+ expect(published[0].payload).toMatchObject({
57
+ Type: 'identity.verified',
58
+ AgentSessionID: SESSION_ID,
59
+ OwnerUserID: OWNER,
60
+ ScopeKey: 'scope-1',
61
+ SourceServerId: MJGlobal.Instance.ProcessUUID,
62
+ });
63
+ expect(JSON.parse(published[0].payload.PayloadJson)).toEqual(VERIFIED);
64
+ expect(Date.parse(published[0].payload.OccurredAt)).not.toBeNaN();
65
+ });
66
+
67
+ it('snapshots a null scope for a session whose conversation carries none', async () => {
68
+ await RealtimeSessionEventService.Instance.Publish({
69
+ AgentSessionID: SESSION_ID,
70
+ Type: 'identity.verified',
71
+ Payload: VERIFIED,
72
+ ContextUser: USER,
73
+ Provider: provider(true, null),
74
+ });
75
+ expect(published[0].payload.ScopeKey).toBeNull();
76
+ });
77
+
78
+ it('fails (and publishes nothing) when the session cannot be found', async () => {
79
+ const result = await RealtimeSessionEventService.Instance.Publish({
80
+ AgentSessionID: SESSION_ID,
81
+ Type: 'identity.verified',
82
+ Payload: VERIFIED,
83
+ ContextUser: USER,
84
+ Provider: provider(false),
85
+ });
86
+ expect(result.Success).toBe(false);
87
+ expect(result.ErrorMessage).toContain('not found');
88
+ expect(published).toHaveLength(0);
89
+ });
90
+
91
+ it('returns a failure instead of throwing when reading the session throws', async () => {
92
+ const broken = { GetEntityObject: vi.fn(async () => { throw new Error('db down'); }) } as unknown as IMetadataProvider;
93
+ const result = await RealtimeSessionEventService.Instance.Publish({
94
+ AgentSessionID: SESSION_ID,
95
+ Type: 'identity.verified',
96
+ Payload: VERIFIED,
97
+ ContextUser: USER,
98
+ Provider: broken,
99
+ });
100
+ expect(result).toMatchObject({ Success: false });
101
+ expect(result.ErrorMessage).toContain('db down');
102
+ });
103
+ });
104
+
105
+ describe('PublishRouted', () => {
106
+ it('fails loudly when PubSub is not configured', () => {
107
+ PubSubManager.Instance.SetPubSubEngine(null as unknown as PubSubEngine);
108
+ const result = RealtimeSessionEventService.Instance.PublishRouted({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null }, 'identity.verified', VERIFIED);
109
+ expect(result).toMatchObject({ Success: false });
110
+ expect(result.ErrorMessage).toContain('PubSub is not configured');
111
+ });
112
+
113
+ it('hands every locally published event to the replication hook', () => {
114
+ const hook = vi.fn();
115
+ RealtimeSessionEventService.Instance.SetReplicationHook(hook);
116
+ RealtimeSessionEventService.Instance.PublishRouted({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null }, 'identity.verified', VERIFIED);
117
+ expect(hook).toHaveBeenCalledTimes(1);
118
+ expect(hook.mock.calls[0][0]).toMatchObject({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, Type: 'identity.verified' });
119
+ });
120
+
121
+ it('never lets a failing replication hook break local delivery', () => {
122
+ RealtimeSessionEventService.Instance.SetReplicationHook(() => {
123
+ throw new Error('redis down');
124
+ });
125
+ const result = RealtimeSessionEventService.Instance.PublishRouted({ AgentSessionID: SESSION_ID, OwnerUserID: OWNER, ScopeKey: null }, 'identity.verified', VERIFIED);
126
+ expect(result).toEqual({ Success: true });
127
+ expect(published).toHaveLength(1);
128
+ });
129
+ });
130
+
131
+ describe('PublishReplicated', () => {
132
+ it('republishes locally and never back through the replication hook (no loop)', () => {
133
+ const hook = vi.fn();
134
+ RealtimeSessionEventService.Instance.SetReplicationHook(hook);
135
+ const payload: RealtimeSessionEventTopicPayload = {
136
+ Type: 'identity.verified',
137
+ AgentSessionID: SESSION_ID,
138
+ OccurredAt: '2026-10-02T12:00:00.000Z',
139
+ PayloadJson: JSON.stringify(VERIFIED),
140
+ OwnerUserID: OWNER,
141
+ ScopeKey: null,
142
+ SourceServerId: 'other-instance',
143
+ };
144
+ RealtimeSessionEventService.Instance.PublishReplicated(payload);
145
+ expect(published).toEqual([{ topic: REALTIME_SESSION_EVENTS_TOPIC, payload }]);
146
+ expect(hook).not.toHaveBeenCalled();
147
+ });
148
+ });
149
+ });
150
+
151
+ describe('ParseReplicatedRealtimeSessionEvent', () => {
152
+ const wire = (over: Record<string, unknown> = {}) =>
153
+ JSON.stringify({
154
+ Type: 'identity.verified',
155
+ AgentSessionID: SESSION_ID,
156
+ OccurredAt: '2026-10-02T12:00:00.000Z',
157
+ PayloadJson: '{}',
158
+ OwnerUserID: OWNER,
159
+ ScopeKey: 'scope-1',
160
+ SourceServerId: 'other-instance',
161
+ ...over,
162
+ });
163
+
164
+ it('accepts a well-formed event from another instance', () => {
165
+ expect(ParseReplicatedRealtimeSessionEvent(wire(), 'this-instance')).toEqual({
166
+ Type: 'identity.verified',
167
+ AgentSessionID: SESSION_ID,
168
+ OccurredAt: '2026-10-02T12:00:00.000Z',
169
+ PayloadJson: '{}',
170
+ OwnerUserID: OWNER,
171
+ ScopeKey: 'scope-1',
172
+ SourceServerId: 'other-instance',
173
+ });
174
+ });
175
+
176
+ it('drops our own echo', () => {
177
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ SourceServerId: 'this-instance' }), 'this-instance')).toBeNull();
178
+ });
179
+
180
+ it('fails closed when the routing identity is missing or malformed', () => {
181
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ OwnerUserID: undefined }), 'x')).toBeNull();
182
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ OwnerUserID: '' }), 'x')).toBeNull();
183
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ OwnerUserID: 5 }), 'x')).toBeNull();
184
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ ScopeKey: 5 }), 'x')).toBeNull();
185
+ });
186
+
187
+ it('normalises an absent scope to null and tolerates an absent source', () => {
188
+ const parsed = ParseReplicatedRealtimeSessionEvent(wire({ ScopeKey: undefined, SourceServerId: undefined }), 'x');
189
+ expect(parsed?.ScopeKey).toBeNull();
190
+ expect(parsed?.SourceServerId).toBeUndefined();
191
+ });
192
+
193
+ it('drops non-JSON, non-objects and events missing required strings', () => {
194
+ expect(ParseReplicatedRealtimeSessionEvent('{nope', 'x')).toBeNull();
195
+ expect(ParseReplicatedRealtimeSessionEvent('"text"', 'x')).toBeNull();
196
+ expect(ParseReplicatedRealtimeSessionEvent('null', 'x')).toBeNull();
197
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ Type: undefined }), 'x')).toBeNull();
198
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ PayloadJson: undefined }), 'x')).toBeNull();
199
+ expect(ParseReplicatedRealtimeSessionEvent(wire({ SourceServerId: 7 }), 'x')).toBeNull();
200
+ });
201
+ });