@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,285 @@
1
+ import 'reflect-metadata';
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+ import { UserInfo, type DatabaseProviderBase, type EntityInfo, type RunViewParams } from '@memberjunction/core';
4
+ import { MJGlobal } from '@memberjunction/global';
5
+ import type { VectorDBBase } from '@memberjunction/ai-vectordb';
6
+
7
+ type ViewResult = { Success: boolean; Results: unknown[]; ErrorMessage?: string };
8
+ type RunViewHandler = (params: RunViewParams, contextUser: UserInfo | undefined) => ViewResult;
9
+
10
+ // Vitest's transform emits no decorator metadata, so type-graphql can't infer field types —
11
+ // stub the decorators; these tests exercise the resolver method, not the schema.
12
+ vi.mock('type-graphql', () => {
13
+ const decoratorFactory = (..._args: unknown[]) => (..._decorated: unknown[]) => undefined;
14
+ const exportNames = [
15
+ 'Resolver', 'Query', 'Mutation', 'Subscription', 'Arg', 'Args', 'ArgsType', 'Ctx', 'Root',
16
+ 'Info', 'Field', 'FieldResolver', 'ObjectType', 'InputType', 'InterfaceType', 'Authorized',
17
+ 'UseMiddleware', 'Extensions', 'Directive', 'ID', 'Int', 'Float', 'GraphQLISODateTime',
18
+ 'GraphQLTimestamp', 'registerEnumType', 'createMethodDecorator', 'createParamDecorator',
19
+ 'buildSchema', 'buildSchemaSync', 'PubSub',
20
+ ];
21
+ return Object.fromEntries(exportNames.map((name) => [name, decoratorFactory]));
22
+ });
23
+
24
+ const runViewCalls: Array<{ params: RunViewParams; contextUser: UserInfo | undefined }> = [];
25
+ let runViewHandler: RunViewHandler = () => ({ Success: true, Results: [] });
26
+
27
+ vi.mock('@memberjunction/core', async (importOriginal) => {
28
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
29
+ class MockRunView {
30
+ async RunView(params: RunViewParams, contextUser?: UserInfo): Promise<ViewResult> {
31
+ runViewCalls.push({ params, contextUser });
32
+ return runViewHandler(params, contextUser);
33
+ }
34
+ }
35
+ return { ...actual, RunView: MockRunView, LogStatus: () => undefined, LogError: () => undefined };
36
+ });
37
+
38
+ // GetAIAPIKey resolves through the ClassFactory too; keep it off the CreateInstance spy below.
39
+ vi.mock('@memberjunction/ai', async (importOriginal) => {
40
+ const actual = await importOriginal<typeof import('@memberjunction/ai')>();
41
+ return { ...actual, GetAIAPIKey: () => 'test-key' };
42
+ });
43
+
44
+ vi.mock('@memberjunction/aiengine', async (importOriginal) => {
45
+ const actual = await importOriginal<typeof import('@memberjunction/aiengine')>();
46
+ return { ...actual, AIEngine: { Instance: { GetProviderIndexName: () => 'provider-index-name' } } };
47
+ });
48
+
49
+ import { FetchEntityVectorsResolver } from '../resolvers/FetchEntityVectorsResolver.js';
50
+ import type { AppContext } from '../types.js';
51
+
52
+ /* ───── fixtures ───── */
53
+
54
+ const DOC_ID = 'DOC-1';
55
+ const contextUser = new UserInfo();
56
+
57
+ const membersEntity = {
58
+ Name: 'Members',
59
+ PrimaryKeys: [{ Name: 'ID' }],
60
+ Fields: [
61
+ { Name: 'ID', IsNameField: false },
62
+ { Name: 'FirstName', IsNameField: true },
63
+ { Name: 'LastName', IsNameField: true },
64
+ { Name: 'Email', IsNameField: false },
65
+ ],
66
+ NameField: undefined,
67
+ } as unknown as EntityInfo;
68
+
69
+ const orderLinesEntity = {
70
+ Name: 'Order Lines',
71
+ PrimaryKeys: [{ Name: 'OrderID' }, { Name: 'LineNo' }],
72
+ Fields: [
73
+ { Name: 'OrderID', IsNameField: false },
74
+ { Name: 'LineNo', IsNameField: false },
75
+ { Name: 'Description', IsNameField: false },
76
+ ],
77
+ NameField: { Name: 'Description' },
78
+ } as unknown as EntityInfo;
79
+
80
+ function fakeVectorDB(queryKeyIsEntityDocumentID: boolean) {
81
+ const queryIndex = vi.fn().mockResolvedValue({
82
+ success: true,
83
+ data: { matches: [{ id: 'pc-1', values: [0.5, 0.5], metadata: { Entity: 'Members', RecordID: 'a' } }] },
84
+ });
85
+ const db = { QueryKeyIsEntityDocumentID: queryKeyIsEntityDocumentID, QueryIndex: queryIndex } as unknown as VectorDBBase;
86
+ return { db, queryIndex };
87
+ }
88
+
89
+ function appContext(entityInfo: EntityInfo): AppContext {
90
+ const provider = { EntityByName: (name: string) => (name === entityInfo.Name ? entityInfo : undefined) } as unknown as DatabaseProviderBase;
91
+ return {
92
+ userPayload: { email: 'test@example.com', sessionId: 's', userRecord: contextUser },
93
+ providers: [{ type: 'Read-Only', provider }],
94
+ } as unknown as AppContext;
95
+ }
96
+
97
+ /** Route RunView by entity: the doc → index → database lookups, then the case-specific views. */
98
+ function routeViews(entityName: string, views: Record<string, (params: RunViewParams) => ViewResult>): void {
99
+ runViewHandler = (params) => {
100
+ switch (params.EntityName) {
101
+ case 'MJ: Entity Documents':
102
+ return { Success: true, Results: [{ ID: DOC_ID, Name: 'Doc', Entity: entityName, VectorIndexID: 'VI-1' }] };
103
+ case 'MJ: Vector Indexes':
104
+ return { Success: true, Results: [{ ID: 'VI-1', Name: 'Index', VectorDatabaseID: 'VDB-1', Dimensions: 2 }] };
105
+ case 'MJ: Vector Databases':
106
+ return { Success: true, Results: [{ ID: 'VDB-1', ClassKey: 'TestVectorDB' }] };
107
+ default: {
108
+ const view = views[params.EntityName ?? ''];
109
+ return view ? view(params) : { Success: false, Results: [], ErrorMessage: `unexpected view ${params.EntityName}` };
110
+ }
111
+ }
112
+ };
113
+ }
114
+
115
+ const storedRows = [
116
+ { ID: 'ERD-1', RecordID: 'AAA', VectorJSON: '[0.1,0.2]' },
117
+ { ID: 'ERD-2', RecordID: 'bbb', VectorJSON: '[0.3,0.4]' },
118
+ { ID: 'ERD-3', RecordID: 'ccc', VectorJSON: '[0.5,0.6]' },
119
+ ];
120
+ const memberRows = [
121
+ { ID: 'aaa', FirstName: 'Ada', LastName: 'Lovelace' },
122
+ { ID: 'bbb', FirstName: 'Grace', LastName: 'Hopper' },
123
+ { ID: 'ccc', FirstName: 'Alan', LastName: null },
124
+ ];
125
+
126
+ function callsFor(entityName: string) {
127
+ return runViewCalls.filter((c) => c.params.EntityName === entityName);
128
+ }
129
+
130
+ /* ───── tests ───── */
131
+
132
+ describe('FetchEntityVectorsResolver', () => {
133
+ let createInstanceSpy: ReturnType<typeof vi.spyOn>;
134
+
135
+ beforeEach(() => {
136
+ runViewCalls.length = 0;
137
+ });
138
+
139
+ afterEach(() => {
140
+ createInstanceSpy?.mockRestore();
141
+ });
142
+
143
+ function useVectorDB(queryKeyIsEntityDocumentID: boolean) {
144
+ const fake = fakeVectorDB(queryKeyIsEntityDocumentID);
145
+ createInstanceSpy = vi.spyOn(MJGlobal.Instance.ClassFactory, 'CreateInstance').mockReturnValue(fake.db);
146
+ return fake;
147
+ }
148
+
149
+ describe('local vector store (QueryKeyIsEntityDocumentID)', () => {
150
+ it('reads VectorJSON for the document with the context user instead of calling QueryIndex', async () => {
151
+ const { queryIndex } = useVectorDB(true);
152
+ routeViews('Members', {
153
+ 'MJ: Entity Record Documents': () => ({ Success: true, Results: storedRows }),
154
+ Members: () => ({ Success: true, Results: memberRows }),
155
+ });
156
+
157
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, undefined, undefined, appContext(membersEntity));
158
+
159
+ expect(result.ErrorMessage).toBeUndefined();
160
+ expect(result.Success).toBe(true);
161
+ expect(queryIndex).not.toHaveBeenCalled();
162
+ expect(result.TotalCount).toBe(3);
163
+ expect(result.Results.map((r) => r.ID)).toEqual(['ERD-1', 'ERD-2', 'ERD-3']);
164
+ expect(result.Results.map((r) => r.Values)).toEqual([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]);
165
+
166
+ const [erdCall] = callsFor('MJ: Entity Record Documents');
167
+ expect(erdCall.contextUser).toBe(contextUser);
168
+ expect(erdCall.params.ExtraFilter).toBe(`EntityDocumentID='${DOC_ID}' AND VectorJSON IS NOT NULL`);
169
+ expect(erdCall.params.Fields).toEqual(['ID', 'RecordID', 'VectorJSON']);
170
+ expect(erdCall.params.ResultType).toBe('simple');
171
+ expect(erdCall.params.IgnoreMaxRows).toBe(true);
172
+ });
173
+
174
+ it('labels each item with Entity, RecordID and the record name fields (RecordID matched case-insensitively)', async () => {
175
+ useVectorDB(true);
176
+ routeViews('Members', {
177
+ 'MJ: Entity Record Documents': () => ({ Success: true, Results: storedRows }),
178
+ Members: () => ({ Success: true, Results: memberRows }),
179
+ });
180
+
181
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, undefined, undefined, appContext(membersEntity));
182
+
183
+ expect(result.Results.map((r) => JSON.parse(r.Metadata))).toEqual([
184
+ { Entity: 'Members', RecordID: 'AAA', FirstName: 'Ada', LastName: 'Lovelace' },
185
+ { Entity: 'Members', RecordID: 'bbb', FirstName: 'Grace', LastName: 'Hopper' },
186
+ { Entity: 'Members', RecordID: 'ccc', FirstName: 'Alan' },
187
+ ]);
188
+ const [membersCall] = callsFor('Members');
189
+ expect(membersCall.contextUser).toBe(contextUser);
190
+ expect(membersCall.params.Fields).toEqual(['ID', 'FirstName', 'LastName']);
191
+ expect(membersCall.params.ResultType).toBe('simple');
192
+ expect(membersCall.params.ExtraFilter).toBeUndefined();
193
+ });
194
+
195
+ it('honours the filter: passes it to the entity view and drops vectors whose record did not pass', async () => {
196
+ useVectorDB(true);
197
+ const filter = "LastName = 'Hopper'";
198
+ routeViews('Members', {
199
+ 'MJ: Entity Record Documents': () => ({ Success: true, Results: storedRows }),
200
+ Members: (params) => ({
201
+ Success: true,
202
+ Results: params.ExtraFilter === filter ? memberRows.filter((r) => r.LastName === 'Hopper') : memberRows,
203
+ }),
204
+ });
205
+
206
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, undefined, filter, appContext(membersEntity));
207
+
208
+ expect(result.Success).toBe(true);
209
+ expect(callsFor('Members')[0].params.ExtraFilter).toBe(filter);
210
+ expect(result.TotalCount).toBe(1);
211
+ expect(result.Results[0].ID).toBe('ERD-2');
212
+ expect(JSON.parse(result.Results[0].Metadata)).toEqual({ Entity: 'Members', RecordID: 'bbb', FirstName: 'Grace', LastName: 'Hopper' });
213
+ });
214
+
215
+ it('stops at maxRecords', async () => {
216
+ useVectorDB(true);
217
+ routeViews('Members', {
218
+ 'MJ: Entity Record Documents': () => ({ Success: true, Results: storedRows }),
219
+ Members: () => ({ Success: true, Results: memberRows }),
220
+ });
221
+
222
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, 2, undefined, appContext(membersEntity));
223
+
224
+ expect(result.Results.map((r) => r.ID)).toEqual(['ERD-1', 'ERD-2']);
225
+ expect(result.TotalCount).toBe(2);
226
+ });
227
+
228
+ it('matches composite-key records by the compact RecordID segment and falls back to NameField for labels', async () => {
229
+ useVectorDB(true);
230
+ routeViews('Order Lines', {
231
+ 'MJ: Entity Record Documents': () => ({
232
+ Success: true,
233
+ Results: [{ ID: 'ERD-9', RecordID: 'OrderID|o-1||LineNo|2', VectorJSON: '[1,0]' }],
234
+ }),
235
+ 'Order Lines': () => ({ Success: true, Results: [{ OrderID: 'o-1', LineNo: 2, Description: 'Brie wheel' }] }),
236
+ });
237
+
238
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, undefined, undefined, appContext(orderLinesEntity));
239
+
240
+ expect(result.Success).toBe(true);
241
+ expect(result.Results).toHaveLength(1);
242
+ expect(JSON.parse(result.Results[0].Metadata)).toEqual({
243
+ Entity: 'Order Lines',
244
+ RecordID: 'OrderID|o-1||LineNo|2',
245
+ Description: 'Brie wheel',
246
+ });
247
+ expect(callsFor('Order Lines')[0].params.Fields).toEqual(['OrderID', 'LineNo', 'Description']);
248
+ });
249
+
250
+ it('returns an error result when the entity records cannot be loaded (e.g. an invalid filter)', async () => {
251
+ useVectorDB(true);
252
+ routeViews('Members', {
253
+ 'MJ: Entity Record Documents': () => ({ Success: true, Results: storedRows }),
254
+ Members: () => ({ Success: false, Results: [], ErrorMessage: 'bad filter' }),
255
+ });
256
+
257
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, undefined, 'nonsense', appContext(membersEntity));
258
+
259
+ expect(result.Success).toBe(false);
260
+ expect(result.Results).toEqual([]);
261
+ expect(result.ErrorMessage).toBe('Failed to load "Members" records: bad filter');
262
+ });
263
+ });
264
+
265
+ describe('index-keyed providers (Pinecone path, unchanged)', () => {
266
+ it('queries the provider index by name and never reads Entity Record Documents', async () => {
267
+ const { queryIndex } = useVectorDB(false);
268
+ routeViews('Members', {});
269
+
270
+ const result = await new FetchEntityVectorsResolver().FetchEntityVectors(DOC_ID, 50, undefined, appContext(membersEntity));
271
+
272
+ expect(result.Success).toBe(true);
273
+ expect(queryIndex).toHaveBeenCalledTimes(1);
274
+ const query = queryIndex.mock.calls[0][0];
275
+ expect(query.id).toBe('provider-index-name');
276
+ expect(query.topK).toBe(50);
277
+ expect(query.includeValues).toBe(true);
278
+ expect(query.filter).toEqual({ Entity: { $eq: 'Members' } });
279
+ expect(query.vector).toHaveLength(2);
280
+ expect(result.Results).toEqual([{ ID: 'pc-1', Values: [0.5, 0.5], Metadata: JSON.stringify({ Entity: 'Members', RecordID: 'a' }) }]);
281
+ expect(callsFor('MJ: Entity Record Documents')).toHaveLength(0);
282
+ expect(callsFor('Members')).toHaveLength(0);
283
+ });
284
+ });
285
+ });
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { hostname } from 'os';
3
+
4
+ const { logErrorMock } = vi.hoisted(() => ({ logErrorMock: vi.fn() }));
5
+ vi.mock('@memberjunction/core', async (importOriginal) => {
6
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
7
+ return { ...actual, LogError: logErrorMock };
8
+ });
9
+
10
+ import { HostInstanceIdentity } from '../agentSessions/HostInstance.js';
11
+
12
+ /** A fresh copy of the module: no port set, no identity read yet. */
13
+ async function freshHostInstanceModule(): Promise<typeof import('../agentSessions/HostInstance.js')> {
14
+ vi.resetModules();
15
+ return import('../agentSessions/HostInstance.js');
16
+ }
17
+
18
+ beforeEach(() => {
19
+ logErrorMock.mockReset();
20
+ });
21
+
22
+ describe('HostInstanceIdentity', () => {
23
+ const ours = new HostInstanceIdentity('build-01', '4000', 101, 'boot-ours');
24
+
25
+ it('stamps hostname:port:pid:bootId, and every boot of the instance shares hostname:port:', () => {
26
+ expect(ours.GetHostInstanceID()).toBe('build-01:4000:101:boot-ours');
27
+ expect(ours.GetInstancePrefix()).toBe('build-01:4000:');
28
+ });
29
+
30
+ describe('IsPriorBoot', () => {
31
+ it('is true for an earlier boot of the same instance, including one whose pid the OS reused', () => {
32
+ expect(ours.IsPriorBoot('build-01:4000:99:boot-before')).toBe(true);
33
+ expect(ours.IsPriorBoot('build-01:4000:101:boot-before')).toBe(true);
34
+ });
35
+
36
+ it.each([
37
+ ['this boot', 'build-01:4000:101:boot-ours'],
38
+ ['another MJAPI on the same host (another port)', 'build-01:4100:202:boot-other'],
39
+ ['an MJAPI on another host', 'build-02:4000:101:boot-other'],
40
+ ['the format before the port was added, with a pid equal to the port', 'build-01:4000:boot-before'],
41
+ ['no id', null],
42
+ ['an empty id', ''],
43
+ ])('is false for %s', (_case, hostInstanceID) => {
44
+ expect(ours.IsPriorBoot(hostInstanceID)).toBe(false);
45
+ });
46
+
47
+ it("matches a host name exactly, though a SQL LIKE reads its '_' as any character", () => {
48
+ const underscored = new HostInstanceIdentity('web_1', '4000', 7, 'boot-ours');
49
+ expect(underscored.IsPriorBoot('web_1:4000:8:boot-before')).toBe(true);
50
+ expect(underscored.IsPriorBoot('web-1:4000:8:boot-before')).toBe(false);
51
+ });
52
+ });
53
+ });
54
+
55
+ describe('the current host instance', () => {
56
+ it('carries the port SetHostInstancePort set before it was first read', async () => {
57
+ const hostInstance = await freshHostInstanceModule();
58
+ hostInstance.SetHostInstancePort(4100);
59
+
60
+ const current = hostInstance.GetCurrentHostInstance();
61
+ expect(current.GetHostInstanceID()).toBe(`${hostname()}:4100:${process.pid}:${hostInstance.GetBootID()}`);
62
+ expect(current.GetInstancePrefix()).toBe(`${hostname()}:4100:`);
63
+ expect(hostInstance.GetHostInstanceID()).toBe(current.GetHostInstanceID());
64
+ });
65
+
66
+ it('is this process alone (pid-<pid>) when no port was set', async () => {
67
+ const hostInstance = await freshHostInstanceModule();
68
+
69
+ expect(hostInstance.GetCurrentHostInstance().InstanceKey).toBe(`pid-${process.pid}`);
70
+ expect(hostInstance.GetHostInstanceID()).toBe(`${hostname()}:pid-${process.pid}:${process.pid}:${hostInstance.GetBootID()}`);
71
+ });
72
+
73
+ it('keeps its identity, and logs, when another port is set after it was read', async () => {
74
+ const hostInstance = await freshHostInstanceModule();
75
+ hostInstance.SetHostInstancePort(4100);
76
+ const before = hostInstance.GetHostInstanceID();
77
+
78
+ hostInstance.SetHostInstancePort(4200);
79
+
80
+ expect(hostInstance.GetHostInstanceID()).toBe(before);
81
+ expect(logErrorMock).toHaveBeenCalledTimes(1);
82
+ expect(String(logErrorMock.mock.calls[0][0])).toContain('Port 4200 was set after');
83
+ });
84
+ });
@@ -0,0 +1,250 @@
1
+ // type-graphql decorators on the resolver call Reflect.getMetadata, which only exists when this polyfill
2
+ // is loaded first. Vitest does not bring it in automatically — this MUST precede the resolver import.
3
+ import 'reflect-metadata';
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
7
+
8
+ const h = vi.hoisted(() => ({
9
+ configure: vi.fn(),
10
+ registryConfigure: vi.fn(),
11
+ getSystemUser: vi.fn(),
12
+ listOffersForUser: vi.fn(),
13
+ acceptOffer: vi.fn(),
14
+ declineOffer: vi.fn(),
15
+ notificationConfig: vi.fn(async () => undefined),
16
+ sendNotification: vi.fn(async () => ({ success: true, deliveryChannels: { inApp: true, email: false, sms: false } })),
17
+ }));
18
+
19
+ vi.mock('@memberjunction/livekit-room-server', () => ({
20
+ RoomHandoffEngine: {
21
+ Instance: {
22
+ Configure: h.configure,
23
+ ListOffersForUser: h.listOffersForUser,
24
+ AcceptOffer: h.acceptOffer,
25
+ DeclineOffer: h.declineOffer,
26
+ },
27
+ },
28
+ HandoffOfferRegistry: { Instance: { Configure: h.registryConfigure } },
29
+ LiveKitSipService: class {},
30
+ }));
31
+ vi.mock('@memberjunction/generic-database-provider', () => ({
32
+ UserCache: { Instance: { GetSystemUser: h.getSystemUser } },
33
+ }));
34
+ vi.mock('@memberjunction/notifications', () => ({
35
+ NotificationEngine: { Instance: { Config: h.notificationConfig, SendNotification: h.sendNotification } },
36
+ }));
37
+
38
+ import {
39
+ HumanHandoffResolver,
40
+ HandoffOfferChangeFilter,
41
+ NotificationHandoffNotifier,
42
+ PubSubHandoffPublisher,
43
+ SetHandoffOfferPublishHook,
44
+ ParseReplicatedHandoffOfferUpdate,
45
+ HANDOFF_OFFER_TOPIC,
46
+ } from '../resolvers/HumanHandoffResolver';
47
+ import { PubSubManager } from '../generic/PubSubManager.js';
48
+ import type { AppContext } from '../types.js';
49
+
50
+ /** Captured at module load: the resolver binds the engine's server-only collaborators then. */
51
+ const wiredAtLoad = h.configure.mock.calls[0]?.[0] as { Presence: unknown; Publisher: unknown; Notifier: unknown };
52
+
53
+ const registryWiredAtLoad = h.registryConfigure.mock.calls[0]?.[0] as { ResolveContextUser: () => UserInfo | undefined };
54
+
55
+ describe('offer registry server identity wiring', () => {
56
+ it('configures the registry with a lazy system-user resolver that reads the user cache on every call', () => {
57
+ const systemUser = { ID: 'sys' } as unknown as UserInfo;
58
+ h.getSystemUser.mockReturnValueOnce(undefined).mockReturnValueOnce(systemUser);
59
+ expect(registryWiredAtLoad.ResolveContextUser()).toBeUndefined();
60
+ expect(registryWiredAtLoad.ResolveContextUser()).toBe(systemUser);
61
+ });
62
+ });
63
+
64
+ class TestableResolver extends HumanHandoffResolver {
65
+ public user: UserInfo | undefined = { ID: 'U1', Name: 'Dana', Email: 'dana@x.com' } as unknown as UserInfo;
66
+ protected override GetUserFromPayload(): UserInfo | undefined {
67
+ return this.user;
68
+ }
69
+ }
70
+
71
+ const ctx = {} as AppContext;
72
+
73
+ const VIEW = {
74
+ OfferID: 'offer-1',
75
+ RoomName: 'call-1',
76
+ Mode: 'warm',
77
+ Summary: 'Wants a refund',
78
+ CallerLabel: 'Phone caller ****0123',
79
+ AgentName: 'Sage',
80
+ Status: 'Pending',
81
+ CreatedAt: '2026-10-03T10:00:00.000Z',
82
+ ExpiresAt: '2026-10-03T10:00:45.000Z',
83
+ InteractionID: 'int-123',
84
+ };
85
+
86
+ describe('HumanHandoffResolver', () => {
87
+ let resolver: TestableResolver;
88
+
89
+ beforeEach(() => {
90
+ resolver = new TestableResolver();
91
+ h.listOffersForUser.mockReset();
92
+ h.acceptOffer.mockReset();
93
+ h.declineOffer.mockReset();
94
+ h.sendNotification.mockClear();
95
+ h.notificationConfig.mockClear();
96
+ });
97
+
98
+ it('gives the handoff engine its server-only collaborators when the module loads', () => {
99
+ expect(wiredAtLoad.Presence).toBeDefined();
100
+ expect(wiredAtLoad.Publisher).toBeInstanceOf(PubSubHandoffPublisher);
101
+ expect(wiredAtLoad.Notifier).toBeInstanceOf(NotificationHandoffNotifier);
102
+ });
103
+
104
+ describe('MyHandoffOffers', () => {
105
+ it('lists the signed-in user\'s offers, and only asks the engine about that user', async () => {
106
+ h.listOffersForUser.mockReturnValue([VIEW]);
107
+ const offers = await resolver.MyHandoffOffers(ctx);
108
+ expect(h.listOffersForUser).toHaveBeenCalledWith('U1', resolver.user);
109
+ expect(offers).toEqual([VIEW]);
110
+ });
111
+
112
+ it('returns nothing when there is no signed-in user', async () => {
113
+ resolver.user = undefined;
114
+ expect(await resolver.MyHandoffOffers(ctx)).toEqual([]);
115
+ expect(h.listOffersForUser).not.toHaveBeenCalled();
116
+ });
117
+ });
118
+
119
+ describe('AcceptHandoffOffer', () => {
120
+ it('accepts as the signed-in user (never a user id from the client) and returns the room to join', async () => {
121
+ h.acceptOffer.mockReturnValue({ Ok: true, Offer: { ...VIEW, Status: 'Accepted' } });
122
+ const result = await resolver.AcceptHandoffOffer('offer-1', ctx);
123
+ expect(h.acceptOffer).toHaveBeenCalledWith('offer-1', 'U1', resolver.user);
124
+ expect(result).toMatchObject({ Success: true, RoomName: 'call-1', Offer: { Status: 'Accepted' } });
125
+ });
126
+
127
+ it('passes the engine\'s refusal back without a room', async () => {
128
+ h.acceptOffer.mockReturnValue({ Ok: false, Reason: 'This conversation offer is no longer available.' });
129
+ expect(await resolver.AcceptHandoffOffer('offer-1', ctx)).toEqual({ Success: false, ErrorMessage: 'This conversation offer is no longer available.', RoomName: '' });
130
+ });
131
+
132
+ it('requires a signed-in user', async () => {
133
+ resolver.user = undefined;
134
+ const result = await resolver.AcceptHandoffOffer('offer-1', ctx);
135
+ expect(result.Success).toBe(false);
136
+ expect(h.acceptOffer).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it('turns an engine failure into a plain failure', async () => {
140
+ h.acceptOffer.mockImplementation(() => {
141
+ throw new Error('boom');
142
+ });
143
+ expect(await resolver.AcceptHandoffOffer('offer-1', ctx)).toMatchObject({ Success: false, RoomName: '' });
144
+ });
145
+ });
146
+
147
+ describe('DeclineHandoffOffer', () => {
148
+ it('declines as the signed-in user', async () => {
149
+ h.declineOffer.mockReturnValue({ Ok: true });
150
+ expect(await resolver.DeclineHandoffOffer('offer-1', ctx)).toEqual({ Success: true });
151
+ expect(h.declineOffer).toHaveBeenCalledWith('offer-1', 'U1', resolver.user);
152
+ });
153
+
154
+ it('passes a refusal back, and requires a user', async () => {
155
+ h.declineOffer.mockReturnValue({ Ok: false, Reason: 'gone' });
156
+ expect(await resolver.DeclineHandoffOffer('offer-1', ctx)).toEqual({ Success: false, ErrorMessage: 'gone' });
157
+ resolver.user = undefined;
158
+ expect((await resolver.DeclineHandoffOffer('offer-1', ctx)).Success).toBe(false);
159
+ });
160
+ });
161
+
162
+ describe('HandoffOfferChanges (subscription)', () => {
163
+ it('shapes the payload for the client and drops the server-side delivery key', () => {
164
+ const change = resolver.HandoffOfferChanges({ UserID: 'U1', Kind: 'offered', Offer: VIEW as never });
165
+ expect(change).toEqual({ Kind: 'offered', Offer: VIEW });
166
+ expect(JSON.stringify(change)).not.toContain('U1');
167
+ });
168
+ });
169
+ });
170
+
171
+ describe('HandoffOfferChangeFilter', () => {
172
+ const payload = { UserID: 'AAAAAAAA-0000-0000-0000-000000000001', Kind: 'offered' as const, Offer: VIEW as never };
173
+ const context = (id?: string) => ({ userPayload: { userRecord: { ID: id } } });
174
+
175
+ it('delivers a change only to the person it belongs to, comparing ids case-insensitively', () => {
176
+ expect(HandoffOfferChangeFilter({ payload, context: context('aaaaaaaa-0000-0000-0000-000000000001') })).toBe(true);
177
+ expect(HandoffOfferChangeFilter({ payload, context: context('BBBBBBBB-0000-0000-0000-000000000002') })).toBe(false);
178
+ });
179
+
180
+ it('fails closed when either identity is missing', () => {
181
+ expect(HandoffOfferChangeFilter({ payload, context: undefined })).toBe(false);
182
+ expect(HandoffOfferChangeFilter({ payload, context: context(undefined) })).toBe(false);
183
+ expect(HandoffOfferChangeFilter({ payload: { ...payload, UserID: '' }, context: context('x') })).toBe(false);
184
+ });
185
+ });
186
+
187
+ describe('PubSubHandoffPublisher', () => {
188
+ it('publishes the change on the offer topic, keyed by the person it belongs to', () => {
189
+ const publish = vi.spyOn(PubSubManager.Instance, 'Publish').mockImplementation(() => undefined);
190
+ new PubSubHandoffPublisher().Publish({ UserID: 'U1', Kind: 'updated', Offer: VIEW as never });
191
+ expect(publish).toHaveBeenCalledWith(HANDOFF_OFFER_TOPIC, { UserID: 'U1', Kind: 'updated', Offer: VIEW });
192
+ publish.mockRestore();
193
+ });
194
+
195
+ it('forwards the change to the cluster hook if set', () => {
196
+ const hook = vi.fn();
197
+ SetHandoffOfferPublishHook(hook);
198
+ new PubSubHandoffPublisher().Publish({ UserID: 'U1', Kind: 'updated', Offer: VIEW as never });
199
+ expect(hook).toHaveBeenCalledWith(expect.objectContaining({ UserID: 'U1', Kind: 'updated', Offer: VIEW }));
200
+ SetHandoffOfferPublishHook(null);
201
+ });
202
+ });
203
+
204
+ describe('ParseReplicatedHandoffOfferUpdate', () => {
205
+ it('parses valid replicated offer payload', () => {
206
+ const raw = JSON.stringify({
207
+ UserID: 'U1',
208
+ Kind: 'updated',
209
+ Offer: VIEW,
210
+ });
211
+ const parsed = ParseReplicatedHandoffOfferUpdate(raw);
212
+ expect(parsed).toMatchObject({
213
+ UserID: 'U1',
214
+ Kind: 'updated',
215
+ Offer: { OfferID: 'offer-1' },
216
+ });
217
+ });
218
+
219
+ it('rejects invalid payloads cleanly', () => {
220
+ expect(ParseReplicatedHandoffOfferUpdate('invalid json')).toBeNull();
221
+ expect(ParseReplicatedHandoffOfferUpdate(JSON.stringify({ Kind: 'unknown' }))).toBeNull();
222
+ expect(ParseReplicatedHandoffOfferUpdate(JSON.stringify({ Kind: 'updated' }))).toBeNull();
223
+ });
224
+ });
225
+
226
+ describe('NotificationHandoffNotifier', () => {
227
+ const user = { ID: 'run-as' } as unknown as UserInfo;
228
+ const provider = {} as unknown as IMetadataProvider;
229
+
230
+ it('sends a notification to the person, routed to the handoff console, through the dedicated handoff offer type', async () => {
231
+ await new NotificationHandoffNotifier().NotifyOffer(VIEW as never, 'U1', user, provider);
232
+ expect(h.notificationConfig).toHaveBeenCalledWith(false, user, provider);
233
+ expect(h.sendNotification).toHaveBeenCalledWith(
234
+ expect.objectContaining({
235
+ userId: 'U1',
236
+ typeNameOrId: 'Conversation Handoff Offer',
237
+ title: expect.stringContaining('Phone caller ****0123'),
238
+ message: expect.stringContaining('Wants a refund'),
239
+ resourceConfiguration: { type: 'handoff-offer', offerId: 'offer-1', room: 'call-1' },
240
+ }),
241
+ user,
242
+ );
243
+ });
244
+
245
+ it('throws (so the engine logs it) when the notification engine reports a failure', async () => {
246
+ h.sendNotification.mockResolvedValueOnce({ success: false, errors: ['type not found'], deliveryChannels: { inApp: false, email: false, sms: false } } as never);
247
+ await expect(new NotificationHandoffNotifier().NotifyOffer(VIEW as never, 'U1', user, provider)).rejects.toThrow('type not found');
248
+ });
249
+ });
250
+