@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
@@ -11,7 +11,7 @@
11
11
  * load-truth-from-DB branch so denied fields hold real values a stripped SetMany never
12
12
  * touches.
13
13
  */
14
- import { describe, it, expect, vi, beforeEach } from 'vitest';
14
+ import { describe, it, expect, vi, beforeEach, beforeAll, afterAll } from 'vitest';
15
15
 
16
16
  // ─── Hoisted mocks ────────────────────────────────────────────────────────
17
17
  const { mockUserCacheUsers } = vi.hoisted(() => ({
@@ -116,7 +116,7 @@ vi.mock('@memberjunction/core-entities', () => ({}));
116
116
 
117
117
  // ─── Import after mocks ──────────────────────────────────────────────────
118
118
  import { ResolverBase } from '../generic/ResolverBase';
119
- import { EntityInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
119
+ import { EntityInfo, Metadata, RowLevelSecurityFilterInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
120
120
 
121
121
  // ─── Metadata builders (mirrors MJCore's fieldSecurity.enforcement tests) ──
122
122
 
@@ -192,8 +192,9 @@ class TestResolver extends ResolverBase {
192
192
  return this.StripDeniedReadFieldsFromClientInput(entityInfo, userInfo, input, clientNewValues);
193
193
  }
194
194
 
195
- public TestMustLoadTruth(entityInfo: EntityInfo, input: ClientInput, hasDeniedReadFields: boolean, hasNarrowedAuditPayload: boolean): boolean {
196
- return this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload);
195
+ /** The user defaults to HR, whose permission rows carry no row filter; pass null for a missing user. */
196
+ public TestMustLoadTruth(entityInfo: EntityInfo, input: ClientInput, hasDeniedReadFields: boolean, hasNarrowedAuditPayload: boolean, user: UserInfo | null = buildUser([HR_ROLE_ID])): boolean {
197
+ return this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload, user ?? undefined);
197
198
  }
198
199
  }
199
200
 
@@ -481,3 +482,85 @@ describe('ResolverBase.MustLoadTruthFromDatabase', () => {
481
482
  expect(resolver.TestMustLoadTruth(new EntityInfo(tracked), withOldValues(), false, false)).toBe(true);
482
483
  });
483
484
  });
485
+
486
+ /**
487
+ * #4919: a caller under an Update row filter must not be hydrated from its own OldValues.
488
+ *
489
+ * The save's post-image RLS check skips its query when no column the filter reads is dirty. Hydrated
490
+ * from OldValues, "dirty" is whatever the client claims: an Intern bound to `Status = 'Draft'` sends
491
+ * Status = 'Published' and claims 'Published' as the old value too, so Status is clean, the check is
492
+ * skipped, and the update moves the row out of the Intern's scope.
493
+ */
494
+ describe('ResolverBase.MustLoadTruthFromDatabase: Update row filters (#4919)', () => {
495
+ const DRAFTS_ONLY = 'F0000000-0000-0000-0000-000000004919';
496
+ let resolver: TestResolver;
497
+ let savedProvider: typeof Metadata.Provider;
498
+
499
+ beforeAll(() => {
500
+ savedProvider = Metadata.Provider;
501
+ Metadata.Provider = {
502
+ Entities: [],
503
+ RowLevelSecurityFilters: [new RowLevelSecurityFilterInfo({ ID: DRAFTS_ONLY, Name: 'Drafts only', FilterText: "Status = 'Draft'" })],
504
+ } as unknown as typeof Metadata.Provider;
505
+ });
506
+ afterAll(() => {
507
+ Metadata.Provider = savedProvider;
508
+ });
509
+ beforeEach(() => {
510
+ resolver = new TestResolver();
511
+ });
512
+
513
+ /** Untracked, no field security: the OldValues shortcut is open unless the row filter closes it. */
514
+ function entityWithInternFilter(filter: { UpdateRLSFilterID?: string; ReadRLSFilterID?: string }): EntityInfo {
515
+ const init = employeeEntityInit(false);
516
+ init['TrackRecordChanges'] = false;
517
+ init['Permissions'] = [
518
+ { EntityID: ENTITY_ID, RoleID: HR_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true },
519
+ { EntityID: ENTITY_ID, RoleID: INTERN_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true, ...filter },
520
+ ];
521
+ return new EntityInfo(init);
522
+ }
523
+
524
+ const claimsNewStatusAsOld = (): ClientInput => ({
525
+ ID: '1',
526
+ Status: 'Published',
527
+ OldValues___: [
528
+ { Key: 'ID', Value: '1' },
529
+ { Key: 'Status', Value: 'Published' },
530
+ ],
531
+ });
532
+
533
+ it('loads the stored row for a caller under an Update row filter, whatever OldValues claim', () => {
534
+ const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
535
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID]))).toBe(true);
536
+ });
537
+
538
+ it('keeps the shortcut for a caller with no Update row filter, on the same entity', () => {
539
+ const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
540
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([HR_ROLE_ID]))).toBe(false);
541
+ // A role without the filter exempts the user from it (roles are additive).
542
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID, HR_ROLE_ID]))).toBe(false);
543
+ });
544
+
545
+ it('loads the stored row when the user is missing, instead of throwing or skipping the filter', () => {
546
+ const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
547
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, null)).toBe(true);
548
+ });
549
+
550
+ it('loads the stored row under an API-key Update row filter, even for a role-exempt user', () => {
551
+ const entity = entityWithInternFilter({});
552
+ const keyBound = (permissionType: 'Read' | 'Update') => {
553
+ const user = buildUser([HR_ROLE_ID]); // no role filter: only the key binding narrows this caller
554
+ user.APIKeyRowFilters = [{ EntityID: ENTITY_ID, PermissionType: permissionType, FilterID: DRAFTS_ONLY }];
555
+ return user;
556
+ };
557
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([HR_ROLE_ID]))).toBe(false);
558
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, keyBound('Update'))).toBe(true);
559
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, keyBound('Read'))).toBe(false);
560
+ });
561
+
562
+ it('ignores a Read-only row filter: only the Update filter gates this save', () => {
563
+ const entity = entityWithInternFilter({ ReadRLSFilterID: DRAFTS_ONLY });
564
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID]))).toBe(false);
565
+ });
566
+ });
@@ -90,6 +90,8 @@ describe('coreReservedServerExtensionRoots', () => {
90
90
 
91
91
  const knownConstants: Record<string, string> = {
92
92
  REALTIME_SDP_EXCHANGE_PATH: '/realtime',
93
+ // Public verify-link routes for realtime identity verification; under the reserved '/realtime' root.
94
+ REALTIME_VERIFY_MOUNT_PATH: '/realtime/verify',
93
95
  MAGIC_LINK_MOUNT_PATH: '/magic-link',
94
96
  WIDGET_MOUNT_PATH: '/widget',
95
97
  AUTH_CATALOG_MOUNT_PATH: '/auth',
@@ -0,0 +1,310 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Routing of shared-cache events in MJAPI: metadata notices trigger a metadata check,
4
+ * everything reaches LocalCacheManager, and only RunView slot changes reach browsers.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import type { CacheChangedEvent, ProviderBase } from '@memberjunction/core';
8
+
9
+ const dispatch = vi.fn();
10
+ const publish = vi.fn();
11
+ const startSweeper = vi.fn();
12
+
13
+ /** Intervals StartUserCacheChecks handed to the user cache. */
14
+ const userCacheInterval: number[] = [];
15
+
16
+ vi.mock('@memberjunction/core', () => ({
17
+ CacheCategory: { RunViewCache: 'RunViewCache', RunQueryCache: 'RunQueryCache', DatasetCache: 'DatasetCache', Metadata: 'Metadata', Default: 'default' },
18
+ LocalCacheManager: { Instance: {
19
+ DispatchCacheChange: (e: CacheChangedEvent) => dispatch(e),
20
+ // The metadata sweep takes a fleet lease before doing any work; grant it in tests.
21
+ TryAcquireSharedLease: vi.fn(async () => true),
22
+ } },
23
+ LogStatusEx: vi.fn(),
24
+ LogError: vi.fn(),
25
+ BaseEngineSweeper: { Instance: { Start: (ms: number) => startSweeper(ms) } },
26
+ }));
27
+ vi.mock('@memberjunction/redis-provider', () => ({ RedisLocalStorageProvider: vi.fn(function (this: Record<string, unknown>, cfg: unknown) { this.cfg = cfg; }) }));
28
+ vi.mock('../generic/PubSubManager.js', () => ({ PubSubManager: { Instance: { Publish: (...a: unknown[]) => publish(...a) } } }));
29
+ vi.mock('../generic/CacheInvalidationResolver.js', () => ({ CACHE_INVALIDATION_TOPIC: 'CACHE_INVALIDATION' }));
30
+ vi.mock('@memberjunction/generic-database-provider', () => ({
31
+ UserCache: { Instance: { StartStalenessChecks: (ms: number) => userCacheInterval.push(ms) } },
32
+ }));
33
+
34
+ import {
35
+ CacheManagerConfigFromSettings, CreateSharedCacheFromEnvironment, MJAPI_PUBLISH_MODES, ResolveSharedCacheTTLSeconds, RouteSharedCacheEvent,
36
+ StartEngineSweeper,
37
+ StartUserCacheChecks,
38
+ StartMetadataSweep,
39
+ WirePushStatusFanOut,
40
+ } from '../sharedCache.js';
41
+ import { MJGlobal } from '@memberjunction/global';
42
+ import type { PubSubEngine } from 'type-graphql';
43
+ import { PublishStatusUpdate, SetPushStatusPublishHook } from '../generic/PushStatusResolver.js';
44
+ import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
45
+ import type { CacheSettingsConfig } from '../config.js';
46
+
47
+ function settings(overrides: Partial<CacheSettingsConfig>): CacheSettingsConfig {
48
+ return { maxMemoryMB: 150, maxPercentOfCachePerEntity: 50, defaultTTLSeconds: 0, evictionSweepIntervalSeconds: 300, verboseLogging: false, engineSweepIntervalSeconds: 300, ...overrides };
49
+ }
50
+
51
+ function event(overrides: Partial<CacheChangedEvent>): CacheChangedEvent {
52
+ return { CacheKey: 'k', Category: 'RunViewCache', Action: 'set', Timestamp: 1, SourceServerId: 'peer-server', ...overrides };
53
+ }
54
+
55
+ function provider(isNotice: boolean): ProviderBase {
56
+ return { HandlePeerMetadataNotice: vi.fn(() => isNotice) } as unknown as ProviderBase;
57
+ }
58
+
59
+ describe('shared cache event routing', () => {
60
+ beforeEach(() => {
61
+ dispatch.mockReset();
62
+ publish.mockReset();
63
+ });
64
+
65
+ it('publishes RunView rows in full, metadata snapshots as notices, and nothing else', () => {
66
+ expect(MJAPI_PUBLISH_MODES).toEqual({ RunViewCache: 'full', default: 'notice' });
67
+ });
68
+
69
+ it('hands a metadata notice to the provider and stops there', () => {
70
+ const p = provider(true);
71
+ RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_Timestamps' }), p);
72
+ expect(p.HandlePeerMetadataNotice).toHaveBeenCalledOnce();
73
+ expect(dispatch).not.toHaveBeenCalled();
74
+ expect(publish).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it('dispatches a RunView change and relays it to browsers as an entity invalidation', () => {
78
+ RouteSharedCacheEvent(event({ CacheKey: 'MJ: AI Models|_|_|-1|0|_|_|imr:1' }), provider(false));
79
+ expect(dispatch).toHaveBeenCalledOnce();
80
+ expect(publish).toHaveBeenCalledWith('CACHE_INVALIDATION', expect.objectContaining({ entityName: 'MJ: AI Models', action: 'set', primaryKeyValues: null }));
81
+ });
82
+
83
+ it('does not relay other categories or category clears to browsers', () => {
84
+ RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_AllMetadata' }), provider(false));
85
+ RouteSharedCacheEvent(event({ Category: 'DatasetCache', CacheKey: 'ds' }), provider(false));
86
+ RouteSharedCacheEvent(event({ Category: 'RunViewCache', CacheKey: 'RunViewCache', Action: 'category_cleared' }), provider(false));
87
+ expect(dispatch).toHaveBeenCalledTimes(3);
88
+ expect(publish).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('ignores notices that arrive before the metadata provider exists', () => {
92
+ RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_Timestamps' }), undefined);
93
+ expect(dispatch).toHaveBeenCalledOnce();
94
+ expect(publish).not.toHaveBeenCalled();
95
+ });
96
+ });
97
+
98
+ describe('shared cache configuration', () => {
99
+ it('builds the provider only when REDIS_URL is set, with MJAPI publish modes', () => {
100
+ expect(CreateSharedCacheFromEnvironment(undefined, {})).toBeNull();
101
+ const provider = CreateSharedCacheFromEnvironment(settings({ verboseLogging: true, sharedCacheTTLSeconds: 120 }),
102
+ { REDIS_URL: 'redis://h:1', REDIS_KEY_PREFIX: 'fleet' }) as unknown as { cfg: Record<string, unknown> };
103
+ expect(provider).toBeInstanceOf(RedisLocalStorageProvider);
104
+ expect(provider.cfg).toMatchObject({
105
+ url: 'redis://h:1', keyPrefix: 'fleet', defaultTTLSeconds: 120, enablePubSub: true, enableLogging: true,
106
+ publishModes: { RunViewCache: 'full', default: 'notice' }, defaultPublishMode: 'none',
107
+ });
108
+ });
109
+
110
+ it('lets REDIS_TTL_SECONDS override the configured TTL, and ignores junk', () => {
111
+ expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: '60' })).toBe(60);
112
+ expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: '0' })).toBe(0);
113
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
114
+ expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: 'soon' })).toBe(120);
115
+ warn.mockRestore();
116
+ expect(ResolveSharedCacheTTLSeconds(undefined, {})).toBeUndefined();
117
+ });
118
+
119
+ it('maps cacheSettings onto LocalCacheManager settings', () => {
120
+ expect(CacheManagerConfigFromSettings(settings({ maxMemoryMB: 10, maxPercentOfCachePerEntity: 20, defaultTTLSeconds: 5, evictionSweepIntervalSeconds: 7, verboseLogging: true })))
121
+ .toEqual({ maxSizeBytes: 10 * 1024 * 1024, maxPercentOfCachePerEntity: 20, defaultTTLMs: 5000, evictionSweepIntervalMs: 7000, verboseLogging: true });
122
+ expect(CacheManagerConfigFromSettings(undefined).maxSizeBytes).toBe(150 * 1024 * 1024);
123
+ });
124
+
125
+ it('starts the engine sweep at the configured interval, off when 0', () => {
126
+ startSweeper.mockReset();
127
+ expect(StartEngineSweeper(settings({ engineSweepIntervalSeconds: 60 }))).toBe(60_000);
128
+ expect(startSweeper).toHaveBeenLastCalledWith(60_000);
129
+ expect(StartEngineSweeper(undefined)).toBe(300_000);
130
+ expect(StartEngineSweeper(settings({ engineSweepIntervalSeconds: 0 }))).toBe(0);
131
+ expect(startSweeper).toHaveBeenLastCalledWith(0);
132
+ });
133
+
134
+ it('starts the user-cache staleness check at the configured interval, off when 0', () => {
135
+ userCacheInterval.length = 0;
136
+ expect(StartUserCacheChecks(settings({ userCacheCheckIntervalSeconds: 60 }))).toBe(60_000);
137
+ expect(StartUserCacheChecks(undefined)).toBe(300_000);
138
+ expect(StartUserCacheChecks(settings({ userCacheCheckIntervalSeconds: 0 }))).toBe(0);
139
+ expect(userCacheInterval).toEqual([60_000, 300_000, 0]);
140
+ });
141
+ });
142
+
143
+ /**
144
+ * The metadata sweep is the backstop for metadata written outside MJ — and nothing else.
145
+ *
146
+ * Metadata staleness is otherwise event-driven: a `BaseEntity` write to a member entity schedules a
147
+ * refresh, a peer's snapshot write publishes a notice, a CLI write clears the cache. None of those
148
+ * fire for raw SQL, so metadata edited directly in the database is never noticed by a running
149
+ * process.
150
+ *
151
+ * It must not become an unconditional poll. A recurring query prevents a serverless database from
152
+ * pausing, and `TrustServerCacheCompletely = true` (every MJ entity, by default) states that the
153
+ * event paths already cover the entity — so there is nothing a query could find.
154
+ */
155
+ describe('StartMetadataSweep', () => {
156
+ afterEach(() => {
157
+ StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 0 }), () => undefined);
158
+ vi.useRealTimers();
159
+ });
160
+
161
+ it('returns the configured interval, and 0 disables it', () => {
162
+ expect(StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 45 }), () => undefined)).toBe(45_000);
163
+ expect(StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 0 }), () => undefined)).toBe(0);
164
+ });
165
+
166
+ it('asks the provider NOTHING when no metadata entity declares drift', async () => {
167
+ vi.useFakeTimers();
168
+ const sweep = vi.fn();
169
+ const provider = {
170
+ MetadataMembersDeclaringDrift: () => [],
171
+ SweepMetadataAgainstDatabase: sweep,
172
+ } as unknown as ProviderBase;
173
+
174
+ StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 10 }), () => provider);
175
+ await vi.advanceTimersByTimeAsync(10_000 * 3 + 100);
176
+
177
+ expect(sweep).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it('sweeps when a metadata entity declares drift', async () => {
181
+ vi.useFakeTimers();
182
+ const sweep = vi.fn().mockResolvedValue({ Declared: ['MJ: Entities'], Checked: true, Refreshed: true });
183
+ const provider = {
184
+ MetadataMembersDeclaringDrift: () => ['MJ: Entities'],
185
+ SweepMetadataAgainstDatabase: sweep,
186
+ } as unknown as ProviderBase;
187
+
188
+ StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 10 }), () => provider);
189
+ await vi.advanceTimersByTimeAsync(10_000 + 100);
190
+
191
+ expect(sweep).toHaveBeenCalled();
192
+ });
193
+ });
194
+
195
+ /**
196
+ * The push-status fan-out's WIRING (MJ #4222).
197
+ *
198
+ * `pushStatusFanOut.test.ts` covers the pieces — what is worth replicating, that every publish is
199
+ * stamped with the publishing instance, and every branch of `ParseReplicatedStatusUpdate`. What
200
+ * nothing covered is the wiring that joins them to Redis, which is the code that moved into this
201
+ * module: that a channel is actually subscribed, that inbound traffic is parsed against THIS
202
+ * process's id and handed to `PubSubManager`, that a local publish is forwarded on the same
203
+ * channel, and that a Redis failure degrades the feature instead of stopping the server.
204
+ *
205
+ * The parser and the publisher here are the real ones; mocking them would make these tests of the
206
+ * mock rather than of the wiring.
207
+ */
208
+ describe('push-status fan-out wiring', () => {
209
+ /** A stand-in for the shared cache provider, capturing what the fan-out does to it. */
210
+ class FakeSharedCache {
211
+ public SubscribedChannel: string | undefined;
212
+ public Published: Array<{ channel: string; payload: string }> = [];
213
+ private inboundHandler: ((raw: string) => void) | undefined;
214
+ constructor(private subscribeFails = false) {}
215
+
216
+ public SubscribeToChannel = vi.fn(async (channel: string, handler: (raw: string) => void) => {
217
+ if (this.subscribeFails) {
218
+ throw new Error('redis is down');
219
+ }
220
+ this.SubscribedChannel = channel;
221
+ this.inboundHandler = handler;
222
+ return () => undefined;
223
+ });
224
+
225
+ public PublishMessage = vi.fn((channel: string, payload: string) => {
226
+ this.Published.push({ channel, payload });
227
+ });
228
+
229
+ /** Simulates Redis delivering a message on the subscribed channel. */
230
+ public Deliver(raw: string): void {
231
+ this.inboundHandler?.(raw);
232
+ }
233
+
234
+ public AsProvider(): RedisLocalStorageProvider {
235
+ return this as unknown as RedisLocalStorageProvider;
236
+ }
237
+ }
238
+
239
+ /** A PubSubEngine the real publisher can publish locally on, which these tests ignore. */
240
+ function localTopic(): PubSubEngine {
241
+ return { publish: vi.fn(async () => undefined) } as unknown as PubSubEngine;
242
+ }
243
+
244
+ beforeEach(() => {
245
+ publish.mockReset();
246
+ SetPushStatusPublishHook(undefined);
247
+ });
248
+ afterEach(() => {
249
+ SetPushStatusPublishHook(undefined);
250
+ });
251
+
252
+ it('subscribes to the push-status channel on the shared connection', async () => {
253
+ const redis = new FakeSharedCache();
254
+ await WirePushStatusFanOut(redis.AsProvider());
255
+
256
+ expect(redis.SubscribeToChannel).toHaveBeenCalledTimes(1);
257
+ expect(redis.SubscribedChannel).toBe('push-status-updates');
258
+ });
259
+
260
+ it("hands a PEER's update to this process's local topic, identity fields intact", async () => {
261
+ const redis = new FakeSharedCache();
262
+ await WirePushStatusFanOut(redis.AsProvider());
263
+
264
+ redis.Deliver(JSON.stringify({
265
+ sessionId: 'session-1', ownerUserId: 'user-1', message: 'done', SourceServerId: 'some-other-replica',
266
+ }));
267
+
268
+ expect(publish).toHaveBeenCalledTimes(1);
269
+ expect(publish).toHaveBeenCalledWith('PUSH_STATUS_UPDATES', {
270
+ sessionId: 'session-1', ownerUserId: 'user-1', message: 'done', SourceServerId: 'some-other-replica',
271
+ });
272
+ });
273
+
274
+ it("drops this process's own update echoed back, so a push is not delivered twice", async () => {
275
+ // The parser owns the comparison; this pins that the wiring gives it THIS process's id.
276
+ const redis = new FakeSharedCache();
277
+ await WirePushStatusFanOut(redis.AsProvider());
278
+
279
+ redis.Deliver(JSON.stringify({
280
+ sessionId: 'session-1', ownerUserId: 'user-1', message: 'done',
281
+ SourceServerId: MJGlobal.Instance.ProcessUUID,
282
+ }));
283
+
284
+ expect(publish).not.toHaveBeenCalled();
285
+ });
286
+
287
+ it('forwards a real local publish onto the same channel', async () => {
288
+ const redis = new FakeSharedCache();
289
+ await WirePushStatusFanOut(redis.AsProvider());
290
+
291
+ PublishStatusUpdate(localTopic(), { sessionId: 'session-9', ownerUserId: 'user-9', message: 'agent finished' });
292
+
293
+ expect(redis.Published).toHaveLength(1);
294
+ expect(redis.Published[0].channel).toBe('push-status-updates');
295
+ expect(JSON.parse(redis.Published[0].payload)).toEqual({
296
+ sessionId: 'session-9', ownerUserId: 'user-9', message: 'agent finished',
297
+ SourceServerId: MJGlobal.Instance.ProcessUUID,
298
+ });
299
+ });
300
+
301
+ it('degrades instead of stopping the server when Redis cannot subscribe', async () => {
302
+ const redis = new FakeSharedCache(true);
303
+
304
+ await expect(WirePushStatusFanOut(redis.AsProvider())).resolves.toBeUndefined();
305
+
306
+ // No hook may be left pointing at a connection it could not subscribe on.
307
+ PublishStatusUpdate(localTopic(), { sessionId: 's', ownerUserId: 'u', message: 'x' });
308
+ expect(redis.PublishMessage).not.toHaveBeenCalled();
309
+ });
310
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Generated resolvers emit SQL Server bracket-quoted identifiers; on PostgreSQL they become
3
+ * double-quoted. Only identifiers change: brackets inside a string literal are data.
4
+ */
5
+ import { describe, it, expect } from 'vitest';
6
+ import { TranslateBracketsToPG } from '../postgresqlCompat.js';
7
+
8
+ describe('TranslateBracketsToPG', () => {
9
+ it('turns bracket-quoted identifiers into double-quoted ones', () => {
10
+ expect(TranslateBracketsToPG('SELECT [ID], [Name] FROM [__mj].[vwUsers]')).toBe('SELECT "ID", "Name" FROM "__mj"."vwUsers"');
11
+ });
12
+
13
+ it('leaves brackets inside a string literal alone', () => {
14
+ expect(TranslateBracketsToPG("SELECT [ID] FROM [t] WHERE [Note] = '[draft] copy'")).toBe(`SELECT "ID" FROM "t" WHERE "Note" = '[draft] copy'`);
15
+ });
16
+
17
+ it('unescapes ]] and escapes a double quote in the name', () => {
18
+ expect(TranslateBracketsToPG('SELECT [a]]b], [say "hi"] FROM t')).toBe('SELECT "a]b", "say ""hi""" FROM t');
19
+ });
20
+ });
@@ -16,6 +16,7 @@ import {
16
16
  ResolveWidgetGuestRunContext,
17
17
  ElevateUserPayload,
18
18
  ResolveScopedAnonymousRunUser,
19
+ ResolveRecordingStoreUser,
19
20
  } from '../realtimeWidget/widgetGuestElevation.js';
20
21
  import type { UserPayload } from '../types.js';
21
22
 
@@ -99,6 +100,49 @@ describe('widgetGuestElevation — ResolveScopedAnonymousRunUser (issue #3371)',
99
100
  });
100
101
  });
101
102
 
103
+ describe('widgetGuestElevation — ResolveRecordingStoreUser (#5195: UI-role users lost every recording)', () => {
104
+ const systemUser = { ID: 'system-1', Email: 'system@system.org' } as UserInfo;
105
+
106
+ function userWith(flags: Partial<UserInfo>): UserInfo {
107
+ return { ID: 'user-1', Email: 'someone@example.com', ...flags } as UserInfo;
108
+ }
109
+
110
+ beforeEach(() => {
111
+ getSystemUserMock.mockReset();
112
+ getSystemUserMock.mockReturnValue(systemUser);
113
+ });
114
+
115
+ it('runs a signed-in owner’s recording store as the SYSTEM user (the stock UI role cannot create MJ: Files)', () => {
116
+ expect(ResolveRecordingStoreUser(userWith({ IsMagicLinkAnonymous: false }))).toBe(systemUser);
117
+ });
118
+
119
+ it('runs a scoped anonymous magic-link session as the SYSTEM user, exactly as before', () => {
120
+ const scopedAnon = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'res-1' } });
121
+ expect(ResolveRecordingStoreUser(scopedAnon)).toBe(systemUser);
122
+ });
123
+
124
+ it('keeps an UNSCOPED anonymous session on the caller, exactly as before', () => {
125
+ const unscoped = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: undefined });
126
+ expect(ResolveRecordingStoreUser(unscoped)).toBe(unscoped);
127
+ });
128
+
129
+ it('keeps a PUBLIC WEB-WIDGET guest on the caller (their RLS read filter depends on it)', () => {
130
+ const widgetGuest = userWith({
131
+ IsMagicLinkAnonymous: true,
132
+ MagicLinkScope: { ResourceID: 'res-1' },
133
+ WidgetGuestContext: { WidgetID: 'widget-1' },
134
+ });
135
+ expect(ResolveRecordingStoreUser(widgetGuest)).toBe(widgetGuest);
136
+ expect(getSystemUserMock).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it('FAILS CLOSED to the caller when no system user is available', () => {
140
+ getSystemUserMock.mockReturnValue(undefined);
141
+ const named = userWith({ IsMagicLinkAnonymous: false });
142
+ expect(ResolveRecordingStoreUser(named)).toBe(named);
143
+ });
144
+ });
145
+
102
146
  describe('widgetGuestElevation — elevateUserPayload', () => {
103
147
  it('swaps in the elevated principal while preserving the guest sessionId for PubSub routing', () => {
104
148
  const guestPayload = payloadWith({ IsMagicLinkAnonymous: true });
@@ -0,0 +1,32 @@
1
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
2
+ import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
3
+ import { GetCurrentHostInstance } from './HostInstance.js';
4
+
5
+ /**
6
+ * Gives the realtime bridge engine (`AIBridgeEngine`) this MJAPI instance's identity, the one its agent-session rows
7
+ * carry: `hostname:port:pid:bootId` ({@link GetCurrentHostInstance}). The engine stamps it into the `HostInstanceID`
8
+ * of each `MJ: AI Agent Session Bridges` row it creates, and its orphan reconcile closes only the bridges an earlier
9
+ * boot of this instance left, never another MJAPI's (#5310). Without it the engine's fallback identity applies, which
10
+ * names this process alone, so the reconcile would never find what an earlier boot left.
11
+ *
12
+ * `Serve` calls it right after `SetHostInstancePort`, before anything can start a bridge: a server extension may start
13
+ * a scheduled meeting bridge before the server listens.
14
+ */
15
+ export function BindBridgeEngineHostInstance(): void {
16
+ AIBridgeEngine.Instance.SetHostInstanceIdentity(GetCurrentHostInstance());
17
+ }
18
+
19
+ /**
20
+ * Starts the realtime bridge engine's orphan reconcile: now, then every 10 minutes, it closes the bridges an earlier
21
+ * boot of this instance left `Connecting` or `Connected` (reason `Janitor`) and finalizes their co-agent runs.
22
+ *
23
+ * `Serve` calls it once the server listens on its port, where it starts the session janitor. Holding the port is what
24
+ * makes another boot of this instance a process that has ended; before that, an MJAPI started on a port another MJAPI
25
+ * still serves would take that one's live bridges for its own orphans.
26
+ *
27
+ * @param provider The server's metadata provider, for the reads and writes.
28
+ * @param systemUser The user the writes run as.
29
+ */
30
+ export function StartBridgeOrphanReconciliation(provider: IMetadataProvider, systemUser: UserInfo): void {
31
+ AIBridgeEngine.Instance.StartOrphanReconciliation(systemUser, provider);
32
+ }