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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/README.md +27 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +3 -3
  4. package/dist/agentSessions/SessionJanitor.js +3 -3
  5. package/dist/agentSessions/SessionManager.d.ts +15 -5
  6. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionManager.js +34 -12
  8. package/dist/agentSessions/SessionManager.js.map +1 -1
  9. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  10. package/dist/auth/AuthProviderCatalogRouter.js +2 -0
  11. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +51 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/initializeProviders.d.ts +10 -0
  16. package/dist/auth/initializeProviders.d.ts.map +1 -1
  17. package/dist/auth/initializeProviders.js +37 -1
  18. package/dist/auth/initializeProviders.js.map +1 -1
  19. package/dist/auth/localDevAuth.d.ts +79 -0
  20. package/dist/auth/localDevAuth.d.ts.map +1 -0
  21. package/dist/auth/localDevAuth.js +439 -0
  22. package/dist/auth/localDevAuth.js.map +1 -0
  23. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  24. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  26. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  27. package/dist/auth/magicLink/types.d.ts +12 -0
  28. package/dist/auth/magicLink/types.d.ts.map +1 -1
  29. package/dist/auth/sessionUserClone.d.ts +1 -1
  30. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  31. package/dist/auth/sessionUserClone.js +3 -2
  32. package/dist/auth/sessionUserClone.js.map +1 -1
  33. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  34. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  35. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  36. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  37. package/dist/broadcast/index.d.ts +6 -0
  38. package/dist/broadcast/index.d.ts.map +1 -0
  39. package/dist/broadcast/index.js +6 -0
  40. package/dist/broadcast/index.js.map +1 -0
  41. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  42. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  43. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  44. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  45. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  46. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  47. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  48. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  49. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  50. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  51. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  52. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  53. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  54. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  55. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  56. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  57. package/dist/cache.d.ts +3 -2
  58. package/dist/cache.d.ts.map +1 -1
  59. package/dist/cache.js.map +1 -1
  60. package/dist/config.d.ts +496 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +61 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/context.d.ts +3 -1
  65. package/dist/context.d.ts.map +1 -1
  66. package/dist/context.js +29 -8
  67. package/dist/context.js.map +1 -1
  68. package/dist/coreRoutes.d.ts.map +1 -1
  69. package/dist/coreRoutes.js +14 -0
  70. package/dist/coreRoutes.js.map +1 -1
  71. package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +3366 -418
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +154 -79
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  80. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  81. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  82. package/dist/generic/PubSubManager.d.ts +10 -0
  83. package/dist/generic/PubSubManager.d.ts.map +1 -1
  84. package/dist/generic/PubSubManager.js +44 -1
  85. package/dist/generic/PubSubManager.js.map +1 -1
  86. package/dist/generic/PushStatusResolver.d.ts +39 -6
  87. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  88. package/dist/generic/PushStatusResolver.js +93 -11
  89. package/dist/generic/PushStatusResolver.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +65 -25
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +201 -101
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +11 -3
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +79 -13
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  99. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  100. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  101. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  102. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  103. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  104. package/dist/generic/fieldSecurityTransport.js +30 -0
  105. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  106. package/dist/index.d.ts +3 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +122 -66
  109. package/dist/index.js.map +1 -1
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +6 -1
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  114. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  115. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  116. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  117. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  118. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  119. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  120. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
  121. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  122. package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
  123. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  124. package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
  125. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  126. package/dist/realtimeSessions/sessionAccess.js +125 -22
  127. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  128. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  129. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  130. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  131. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  132. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  133. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  134. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  135. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  136. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  137. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  138. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  139. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  140. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  141. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  142. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  143. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  144. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  145. package/dist/realtimeWidget/host-identity.js +1 -1
  146. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  147. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  148. package/dist/realtimeWidget/widgetCore.js +17 -1
  149. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  150. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  151. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  152. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  153. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  154. package/dist/resolvers/ActionResolver.d.ts +19 -1
  155. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  156. package/dist/resolvers/ActionResolver.js +63 -12
  157. package/dist/resolvers/ActionResolver.js.map +1 -1
  158. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  159. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  160. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  161. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  162. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  163. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  164. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  165. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  166. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  167. package/dist/resolvers/AgentSessionResolver.js +40 -17
  168. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  169. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  170. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  171. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  172. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  173. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
  174. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  175. package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
  176. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  177. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  178. package/dist/resolvers/FileCategoryResolver.js +1 -0
  179. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  180. package/dist/resolvers/FileResolver.d.ts +5 -0
  181. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  182. package/dist/resolvers/FileResolver.js +13 -4
  183. package/dist/resolvers/FileResolver.js.map +1 -1
  184. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
  186. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  187. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  188. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  189. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  190. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  191. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  192. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  193. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  194. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  195. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  196. package/dist/resolvers/QueryResolver.js +20 -6
  197. package/dist/resolvers/QueryResolver.js.map +1 -1
  198. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  199. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  200. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  201. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  202. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  203. package/dist/resolvers/RSUResolver.js +12 -0
  204. package/dist/resolvers/RSUResolver.js.map +1 -1
  205. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
  206. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  207. package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
  208. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  209. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  211. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  212. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  213. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  214. package/dist/resolvers/RunAIAgentResolver.js +209 -64
  215. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  217. package/dist/resolvers/RunTemplateResolver.js +4 -4
  218. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  219. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  220. package/dist/resolvers/RunTestResolver.js +3 -1
  221. package/dist/resolvers/RunTestResolver.js.map +1 -1
  222. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  223. package/dist/resolvers/SyncDataResolver.js +4 -0
  224. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  225. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  226. package/dist/resolvers/TestQuerySQLResolver.js +1 -0
  227. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  228. package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
  229. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  230. package/dist/resolvers/TransactionGroupResolver.js +60 -15
  231. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  232. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  233. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  234. package/dist/resolvers/UserViewResolver.js +2 -1
  235. package/dist/resolvers/UserViewResolver.js.map +1 -1
  236. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  237. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  238. package/dist/rest/EntityCRUDHandler.js +19 -5
  239. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  240. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  241. package/dist/rest/RESTEndpointHandler.js +8 -5
  242. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  243. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  244. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  245. package/dist/rest/RealtimeRecordingHandler.js +7 -7
  246. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  247. package/dist/rest/ViewOperationsHandler.d.ts +6 -0
  248. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  249. package/dist/rest/ViewOperationsHandler.js +22 -5
  250. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  251. package/dist/serverRoleConfigUnits.d.ts +29 -0
  252. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  253. package/dist/serverRoleConfigUnits.js +33 -0
  254. package/dist/serverRoleConfigUnits.js.map +1 -0
  255. package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
  256. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  257. package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
  258. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  259. package/dist/types.d.ts +23 -1
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +98 -98
  263. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  264. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  265. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  267. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  268. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  269. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  270. package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
  271. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  272. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  273. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  274. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  275. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  276. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  277. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  278. package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
  279. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  280. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  281. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  282. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  283. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  284. package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
  285. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  286. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  287. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
  288. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
  289. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  290. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  291. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  292. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  293. package/src/__tests__/SessionManager.test.ts +95 -0
  294. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
  295. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  296. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  297. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  298. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  299. package/src/__tests__/apiRootBoot.test.ts +1 -1
  300. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  301. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  302. package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
  303. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  304. package/src/__tests__/globalCors.test.ts +3 -3
  305. package/src/__tests__/localDevAuth.test.ts +196 -0
  306. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  307. package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
  308. package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
  309. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  310. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  311. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  312. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  313. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  314. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  315. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  316. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  317. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  318. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  319. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  320. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  321. package/src/__tests__/runViewProperties.test.ts +114 -0
  322. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  323. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  324. package/src/__tests__/sessionUserClone.test.ts +5 -1
  325. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  326. package/src/__tests__/unifiedAuth.test.ts +28 -0
  327. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  328. package/src/agentSessions/SessionJanitor.ts +3 -3
  329. package/src/agentSessions/SessionManager.ts +35 -12
  330. package/src/auth/AuthProviderCatalogRouter.ts +3 -0
  331. package/src/auth/index.ts +56 -12
  332. package/src/auth/initializeProviders.ts +43 -1
  333. package/src/auth/localDevAuth.ts +528 -0
  334. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  335. package/src/auth/magicLink/types.ts +10 -0
  336. package/src/auth/sessionUserClone.ts +3 -2
  337. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  338. package/src/broadcast/index.ts +5 -0
  339. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  340. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  341. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  342. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  343. package/src/cache.ts +2 -1
  344. package/src/config.ts +69 -0
  345. package/src/context.ts +32 -8
  346. package/src/coreRoutes.ts +15 -0
  347. package/src/generated/graphql-schemas/__mj.ts +2239 -102
  348. package/src/generic/CacheInvalidationResolver.ts +135 -78
  349. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  350. package/src/generic/PubSubManager.ts +54 -1
  351. package/src/generic/PushStatusResolver.ts +108 -11
  352. package/src/generic/ResolverBase.ts +237 -107
  353. package/src/generic/RunViewResolver.ts +80 -15
  354. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  355. package/src/generic/fieldSecurityTransport.ts +62 -0
  356. package/src/index.ts +127 -68
  357. package/src/integration/CustomColumnPromoter.ts +6 -1
  358. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  359. package/src/realtimeSessions/README.md +7 -3
  360. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  361. package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
  362. package/src/realtimeSessions/sessionAccess.ts +181 -25
  363. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  364. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  365. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  366. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  367. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  368. package/src/realtimeWidget/host-identity.ts +1 -1
  369. package/src/realtimeWidget/widgetCore.ts +30 -1
  370. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  371. package/src/resolvers/ActionResolver.ts +78 -15
  372. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  373. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  374. package/src/resolvers/AgentSessionResolver.ts +46 -18
  375. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  376. package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
  377. package/src/resolvers/FileCategoryResolver.ts +1 -0
  378. package/src/resolvers/FileResolver.ts +14 -4
  379. package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
  380. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  381. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  382. package/src/resolvers/QueryResolver.ts +20 -6
  383. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  384. package/src/resolvers/RSUResolver.ts +12 -0
  385. package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
  386. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  387. package/src/resolvers/RunAIAgentResolver.ts +258 -82
  388. package/src/resolvers/RunTemplateResolver.ts +6 -4
  389. package/src/resolvers/RunTestResolver.ts +3 -1
  390. package/src/resolvers/SyncDataResolver.ts +4 -0
  391. package/src/resolvers/TestQuerySQLResolver.ts +1 -0
  392. package/src/resolvers/TransactionGroupResolver.ts +66 -15
  393. package/src/resolvers/UserViewResolver.ts +2 -1
  394. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  395. package/src/rest/EntityCRUDHandler.ts +22 -5
  396. package/src/rest/RESTEndpointHandler.ts +8 -5
  397. package/src/rest/RealtimeRecordingHandler.ts +13 -10
  398. package/src/rest/ViewOperationsHandler.ts +24 -6
  399. package/src/serverRoleConfigUnits.ts +48 -0
  400. package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
  401. package/src/types.ts +23 -1
@@ -0,0 +1,98 @@
1
+ import 'reflect-metadata';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
4
+ import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
5
+
6
+ vi.mock('type-graphql', () => {
7
+ const noopDecorator = () => () => undefined;
8
+ return {
9
+ Resolver: noopDecorator,
10
+ Mutation: noopDecorator,
11
+ Query: noopDecorator,
12
+ Subscription: noopDecorator,
13
+ ObjectType: noopDecorator,
14
+ InputType: noopDecorator,
15
+ Field: noopDecorator,
16
+ Arg: noopDecorator,
17
+ Args: noopDecorator,
18
+ Ctx: noopDecorator,
19
+ PubSub: noopDecorator,
20
+ Root: noopDecorator,
21
+ Float: class {},
22
+ Int: class {},
23
+ ID: class {},
24
+ };
25
+ });
26
+
27
+ vi.mock('@memberjunction/remote-browser-server', () => ({
28
+ NormalizeInstanceKey: (key?: string) => key,
29
+ RemoteBrowserEngine: { Instance: {} },
30
+ }));
31
+ vi.mock('@memberjunction/aiengine', () => ({ AIEngine: { Instance: { Config: vi.fn(), Prompts: [] } } }));
32
+ vi.mock('@memberjunction/ai-prompts', async (importOriginal) => ({
33
+ ...(await importOriginal<typeof import('@memberjunction/ai-prompts')>()),
34
+ AIPromptRunner: class {},
35
+ }));
36
+ vi.mock('@memberjunction/ai-core-plus', async (importOriginal) => ({
37
+ ...(await importOriginal<typeof import('@memberjunction/ai-core-plus')>()),
38
+ AIPromptParams: class {},
39
+ }));
40
+
41
+ import { RemoteBrowserActionResolver } from '../resolvers/RemoteBrowserActionResolver.js';
42
+
43
+ const uuid = (a: string, b: string) =>
44
+ [a.repeat(8), b.repeat(4), '4' + b.repeat(3), '8' + b.repeat(3), b.repeat(12)].join('-');
45
+ const ANON = uuid('A', '0');
46
+ const SESSION = uuid('1', '1');
47
+ const CONVERSATION = uuid('2', '2');
48
+
49
+ const guest = (visitorId: string) =>
50
+ ({
51
+ ID: ANON,
52
+ IsMagicLinkAnonymous: true,
53
+ // An unscoped multi-use invite: redemptions are told apart by mj_sid (a resource scope would be shared).
54
+ AnonymousVisitorID: visitorId,
55
+ }) as unknown as UserInfo;
56
+
57
+ function makeProvider(sessionFound = true, externalId = 'visitor-1'): IMetadataProvider {
58
+ return {
59
+ GetEntityObject: vi.fn(async (entityName: string) =>
60
+ entityName === 'MJ: AI Agent Sessions'
61
+ ? { ID: SESSION, UserID: ANON, ConversationID: CONVERSATION, Load: vi.fn(async () => sessionFound) }
62
+ : { ExternalID: externalId, Load: vi.fn(async () => true) },
63
+ ),
64
+ } as unknown as IMetadataProvider;
65
+ }
66
+
67
+ class TestableRemoteBrowserResolver extends RemoteBrowserActionResolver {
68
+ public async LoadOwned(
69
+ agentSessionID: string,
70
+ contextUser: UserInfo,
71
+ provider: IMetadataProvider,
72
+ ): Promise<MJAIAgentSessionEntity> {
73
+ return this.loadOwnedSession(agentSessionID, contextUser, provider);
74
+ }
75
+ }
76
+
77
+ describe('RemoteBrowserActionResolver.loadOwnedSession anonymous visitor ownership', () => {
78
+ it('allows the matching anonymous visitor and refuses a second anonymous visitor', async () => {
79
+ const resolver = new TestableRemoteBrowserResolver();
80
+ const provider = makeProvider(true, 'visitor-1');
81
+
82
+ const loaded = await resolver.LoadOwned(SESSION, guest('visitor-1'), provider);
83
+ expect(loaded.ID).toBe(SESSION);
84
+
85
+ await expect(resolver.LoadOwned(SESSION, guest('visitor-2'), provider)).rejects.toThrow(
86
+ `Not authorized: remote-browser session ${SESSION} is not owned by you.`,
87
+ );
88
+ });
89
+
90
+ it('throws the expected not-found message when the session does not exist', async () => {
91
+ const resolver = new TestableRemoteBrowserResolver();
92
+ const provider = makeProvider(false);
93
+
94
+ await expect(resolver.LoadOwned(SESSION, guest('visitor-1'), provider)).rejects.toThrow(
95
+ `Remote-browser session ${SESSION} not found.`,
96
+ );
97
+ });
98
+ });
@@ -1,6 +1,6 @@
1
1
  // Reflect.metadata polyfill at import time.
2
2
  import 'reflect-metadata';
3
- import { describe, it, expect } from 'vitest';
3
+ import { describe, it, expect, vi } from 'vitest';
4
4
  import { BaseEntity, EntityFieldTSType, EntityInfo, UserInfo, type EntityFieldInfo, type IMetadataProvider } from '@memberjunction/core';
5
5
  import { ResolverBase } from '../generic/ResolverBase.js';
6
6
 
@@ -97,6 +97,11 @@ describe('ResolverBase.MustLoadTruthFromDatabase & hasCompleteOldValues', () =>
97
97
  expect(probe.MustLoad({ Name: ' mj: conversation details ', TrackRecordChanges: false }, true)).toBe(true);
98
98
  });
99
99
 
100
+ it("always loads a conversation and an agent session, so client OldValues cannot hide a change to the anonymous ownership key (#4931)", () => {
101
+ expect(probe.MustLoad({ Name: 'MJ: Conversations', TrackRecordChanges: false }, true)).toBe(true);
102
+ expect(probe.MustLoad({ Name: 'MJ: AI Agent Sessions', TrackRecordChanges: false }, true)).toBe(true);
103
+ });
104
+
100
105
  it('still trusts OldValues on other untracked entities without field security when OldValues___ covers all writable fields', () => {
101
106
  const fields = buildTagEntityFields();
102
107
  const completeOldValues = [
@@ -149,15 +154,16 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
149
154
  EntityFields: buildTagEntityFields(),
150
155
  });
151
156
 
152
- function createLoadedTagEntity(errorLogSaves: Array<Record<string, unknown>> = []): { entity: BaseEntity; user: UserInfo } {
157
+ function createLoadedTagEntity(errorLogSaves: Array<Record<string, unknown>> = [], failSaveWith?: string): { entity: BaseEntity; user: UserInfo } {
153
158
  const fakeProvider = {
154
159
  Entities: [tagEntityInfo],
155
160
  GetEntityObject: async () => {
156
161
  const errEntity: Record<string, unknown> = {
157
162
  Save: async () => {
158
163
  errorLogSaves.push({ ...errEntity });
159
- return true;
164
+ return failSaveWith === undefined;
160
165
  },
166
+ LatestResult: failSaveWith === undefined ? null : { CompleteMessage: failSaveWith },
161
167
  };
162
168
  return errEntity;
163
169
  },
@@ -222,12 +228,39 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
222
228
 
223
229
  // Warning is logged to ErrorLog, and save still applies the new value
224
230
  expect(errorLogSaves).toHaveLength(1);
225
- expect(errorLogSaves[0].Code).toBe('ENTITY_SAVE_INCONSISTENCY');
231
+ expect(errorLogSaves[0].Code).toBe('SAVE_INCONSISTENCY');
232
+ // ErrorLog.Code is NCHAR(20)
233
+ expect(String(errorLogSaves[0].Code).length).toBeLessThanOrEqual(20);
226
234
  expect(String(errorLogSaves[0].Message)).toContain('Display_Name');
227
235
  expect(entity.Get('Display Name')).toBe('Client New Display');
228
236
  expect(entity.Get('Description')).toBe('Untouched DB Description');
229
237
  });
230
238
 
239
+ it('reports the entity name and the reason when the ErrorLog save fails', async () => {
240
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
241
+ const { entity, user } = createLoadedTagEntity([], 'value too long for Code');
242
+ try {
243
+ await probe.TestAndSet(
244
+ entity,
245
+ {
246
+ ID: '11111111-2222-3333-4444-555555555555',
247
+ Display_Name: 'Client New Display',
248
+ OldValues___: [
249
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
250
+ { Key: 'Display_Name', Value: 'Stale Client Display' },
251
+ ],
252
+ },
253
+ user
254
+ );
255
+ const message = errorSpy.mock.calls.map((c) => String(c[0])).join('\n');
256
+ expect(message).toContain('Failed to save ErrorLog record');
257
+ expect(message).toContain('value too long for Code');
258
+ expect(message).toContain(entity.EntityInfo.Name);
259
+ } finally {
260
+ errorSpy.mockRestore();
261
+ }
262
+ });
263
+
231
264
  it('does not false-positive when a non-overlapping field differs in the DB', async () => {
232
265
  const errorLogSaves: Array<Record<string, unknown>> = [];
233
266
  const { entity, user } = createLoadedTagEntity(errorLogSaves);
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The GraphQL delete returns the record's last state. It is projected for the caller, so an
3
+ * entity object that holds a full-width row (loaded as another user, or not narrowed at its
4
+ * source) never hands the caller a value they are denied.
5
+ */
6
+ import 'reflect-metadata';
7
+ import { describe, it, expect } from 'vitest';
8
+ import type { UserInfo } from '@memberjunction/core';
9
+ import { EmployeeEntity, FullWidthEmployeeRow, HRUser, Intern, SECRET_SALARY } from './fieldSecurityFixtures.js';
10
+
11
+ /** Drives the real `ResolverBase.DeleteRecord` with its unrelated collaborators stubbed on the instance. */
12
+ async function deleteAs(user: UserInfo): Promise<Record<string, unknown>> {
13
+ const { ResolverBase } = await import('../generic/ResolverBase.js');
14
+ const entityObject = {
15
+ EntityInfo: EmployeeEntity(true),
16
+ InnerLoad: async () => true,
17
+ GetAll: () => FullWidthEmployeeRow(),
18
+ Delete: async () => true,
19
+ LatestResult: { Message: '' },
20
+ };
21
+ const provider = { GetEntityObject: async () => entityObject, Entities: [] };
22
+ const resolver = Object.create(ResolverBase.prototype) as Record<string, unknown> & {
23
+ DeleteRecord: (...args: unknown[]) => Promise<Record<string, unknown>>;
24
+ };
25
+ resolver.CheckAPIKeyScopeAuthorization = async () => undefined;
26
+ resolver.BeforeDelete = async () => true;
27
+ resolver.AfterDelete = async () => undefined;
28
+ resolver.ListenForEntityMessages = () => undefined;
29
+ resolver.GetUserFromPayload = () => user;
30
+ return resolver.DeleteRecord(
31
+ 'Employees',
32
+ { KeyValuePairs: [{ FieldName: 'ID', Value: 'emp-1' }] },
33
+ {},
34
+ provider,
35
+ { email: 'x@example.test', userRecord: user },
36
+ { publish: async () => undefined },
37
+ );
38
+ }
39
+
40
+ describe('ResolverBase.DeleteRecord: the returned last state is projected for the caller', () => {
41
+ it('REGRESSION: returns no denied value even when the entity object holds the full row', async () => {
42
+ const returned = await deleteAs(Intern());
43
+ expect(returned).not.toHaveProperty('Salary');
44
+ expect(JSON.stringify(returned)).not.toContain(String(SECRET_SALARY));
45
+ expect(returned.ID).toBe('emp-1');
46
+ });
47
+
48
+ it('returns every value to a caller who is denied nothing', async () => {
49
+ const returned = await deleteAs(HRUser());
50
+ expect(returned.Salary).toBe(SECRET_SALARY);
51
+ });
52
+ });
@@ -0,0 +1,176 @@
1
+ /**
2
+ * The entity-message relay in `ResolverBase.ListenForEntityMessages`.
3
+ *
4
+ * There is one listener per entity name per process, and it relays to the session that created
5
+ * it. So it must relay only events raised by an object that belongs to that session's user, and
6
+ * never the record values a payload can carry: a delete's payload is the whole deleted row.
7
+ */
8
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
9
+
10
+ const { statusUpdates } = vi.hoisted(() => ({
11
+ statusUpdates: [] as Array<{ sessionId: string; ownerUserId: string; message: string | undefined }>,
12
+ }));
13
+
14
+ vi.mock('@memberjunction/sqlserver-dataprovider', () => ({ SQLServerDataProvider: class {} }));
15
+ vi.mock('@memberjunction/generic-database-provider', () => ({ UserCache: { Users: [] } }));
16
+ vi.mock('cloudevents', () => ({
17
+ CloudEvent: class {},
18
+ httpTransport: () => () => undefined,
19
+ emitterFor: () => () => undefined,
20
+ }));
21
+ vi.mock('type-graphql', () => {
22
+ const noop = () => () => undefined;
23
+ return {
24
+ Resolver: noop, Mutation: noop, Query: noop, Subscription: noop, Ctx: noop, Arg: noop, PubSub: noop,
25
+ Root: noop, ObjectType: noop, InputType: noop, Field: noop, FieldResolver: noop,
26
+ Int: undefined, Float: undefined, registerEnumType: () => undefined,
27
+ };
28
+ });
29
+ vi.mock('graphql', () => ({ GraphQLError: class extends Error {} }));
30
+ vi.mock('mssql', () => ({}));
31
+ vi.mock('@memberjunction/api-keys', () => ({ GetAPIKeyEngine: vi.fn() }));
32
+ vi.mock('@memberjunction/encryption', () => ({ EncryptionEngine: { Instance: {} } }));
33
+ vi.mock('@memberjunction/graphql-dataprovider', () => ({ FieldMapper: class { MapFieldName(n: string) { return n; } ReverseMapFieldName(n: string) { return n; } } }));
34
+ vi.mock('../generic/PubSubManager.js', () => ({ PubSubManager: { Instance: { Publish: () => undefined } } }));
35
+ vi.mock('../generic/PushStatusResolver.js', () => ({
36
+ PUSH_STATUS_UPDATES_TOPIC: 'test-push-topic',
37
+ PublishStatusUpdate: (_pubSub: unknown, update: { sessionId: string; ownerUserId: string; message: string | undefined }) => {
38
+ statusUpdates.push(update);
39
+ },
40
+ // Mirrors the real StatusUpdateOwnerKey/StatusUpdateOwnerID (#4931): the elevated principal's
41
+ // key when the payload stands in for one, else the request user's. Anonymous visitors key on
42
+ // `id|scope`; these relay cases are all named users, so the ID is the whole key.
43
+ StatusUpdateOwnerKey: (user: { ID?: string } | null | undefined) => user?.ID ?? '',
44
+ StatusUpdateOwnerID: (payload: { ElevatedFrom?: { ID?: string }; userRecord?: { ID?: string } } | undefined) =>
45
+ payload?.ElevatedFrom?.ID ?? payload?.userRecord?.ID ?? '',
46
+ }));
47
+ vi.mock('../generic/RunViewResolver.js', () => ({ RunViewByIDInput: class {}, RunViewByNameInput: class {}, RunDynamicViewInput: class {} }));
48
+ vi.mock('../generic/DeleteOptionsInput.js', () => ({ DeleteOptionsInput: class {} }));
49
+ vi.mock('../types.js', () => ({ RunViewGenericParams: class {} }));
50
+ vi.mock('@memberjunction/core-entities', () => ({}));
51
+ vi.mock('@memberjunction/core', async () => {
52
+ const actual = await vi.importActual<typeof import('@memberjunction/core')>('@memberjunction/core');
53
+ return { ...actual, LogError: vi.fn(), LogStatus: vi.fn(), LogDebug: vi.fn() };
54
+ });
55
+
56
+ import { BaseEntity, BaseEntityEvent, EntityInfo, UserInfo } from '@memberjunction/core';
57
+ import { MJEventType, MJGlobal } from '@memberjunction/global';
58
+ import type { PubSubEngine } from 'type-graphql';
59
+ import { Subscription } from 'rxjs';
60
+ import { ResolverBase } from '../generic/ResolverBase';
61
+ import type { UserPayload } from '../types.js';
62
+ import { BuildUser, EmployeeEntity, FullWidthEmployeeRow, HR_ROLE_ID, SECRET_SALARY } from './fieldSecurityFixtures.js';
63
+
64
+ const SUBSCRIPTIONS_KEY = '___MJServer___ResolverBase___EventSubscriptions';
65
+
66
+ /** An entity object of `entityInfo` loaded by `owner`, holding the full row. */
67
+ function entityOwnedBy(entityInfo: EntityInfo, owner: UserInfo) {
68
+ const row = FullWidthEmployeeRow();
69
+ return {
70
+ EntityInfo: entityInfo,
71
+ ContextCurrentUser: owner,
72
+ GetAll: () => ({ ...row }),
73
+ PrimaryKey: { KeyValuePairs: [{ FieldName: 'ID', Value: row.ID }] },
74
+ Fields: entityInfo.Fields.map(f => ({ Name: f.Name, Dirty: false })),
75
+ Get: (name: string) => (row as Record<string, unknown>)[name],
76
+ GetDirtyFieldNames: (): string[] => [],
77
+ };
78
+ }
79
+ type OwnedEntity = ReturnType<typeof entityOwnedBy>;
80
+
81
+ class TestResolver extends ResolverBase {
82
+ public Listen(entity: OwnedEntity, sessionUser: UserInfo) {
83
+ this.ListenForEntityMessages(entity as unknown as BaseEntity, {} as PubSubEngine, { sessionId: 'session-a', userRecord: sessionUser } as UserPayload);
84
+ }
85
+ }
86
+
87
+ function raise(entity: OwnedEntity, type: BaseEntityEvent['type'], payload: unknown): void {
88
+ const args = new BaseEntityEvent();
89
+ args.type = type;
90
+ args.payload = payload;
91
+ args.baseEntity = entity as unknown as BaseEntity;
92
+ MJGlobal.Instance.RaiseEvent({ component: null, event: MJEventType.ComponentEvent, eventCode: BaseEntity.BaseEventCode, args });
93
+ }
94
+
95
+ /** Lets the async listener run for every event raised so far. */
96
+ async function settle(): Promise<void> {
97
+ await new Promise(resolve => setTimeout(resolve, 10));
98
+ }
99
+
100
+ function resetListeners(): void {
101
+ const store = MJGlobal.Instance.GetGlobalObjectStore();
102
+ const subscriptions = store[SUBSCRIPTIONS_KEY] as Map<string, Subscription> | undefined;
103
+ subscriptions?.forEach(s => s.unsubscribe());
104
+ subscriptions?.clear();
105
+ }
106
+
107
+ let userA: UserInfo;
108
+ let userB: UserInfo;
109
+
110
+ beforeEach(() => {
111
+ statusUpdates.length = 0;
112
+ resetListeners();
113
+ userA = BuildUser([HR_ROLE_ID], 'user-a');
114
+ userB = BuildUser([HR_ROLE_ID], 'user-b');
115
+ });
116
+
117
+ describe('ResolverBase.ListenForEntityMessages: relay to the listening session', () => {
118
+ it("REGRESSION: never relays another user's delete to the session that created the listener", async () => {
119
+ const entityInfo = EmployeeEntity(false);
120
+ new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
121
+
122
+ raise(entityOwnedBy(entityInfo, userB), 'delete', { OldValues: FullWidthEmployeeRow() });
123
+ await settle();
124
+
125
+ expect(statusUpdates).toHaveLength(0);
126
+ });
127
+
128
+ it('REGRESSION: relays the session user\'s own delete with the key and type, never the deleted row', async () => {
129
+ const entityInfo = EmployeeEntity(false);
130
+ new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
131
+
132
+ raise(entityOwnedBy(entityInfo, userA), 'delete', { OldValues: FullWidthEmployeeRow() });
133
+ await settle();
134
+
135
+ expect(statusUpdates).toHaveLength(1);
136
+ const message = JSON.parse(statusUpdates[0].message!) as Record<string, unknown>;
137
+ expect(message.entityName).toBe('Employees');
138
+ expect(message.eventType).toBe('delete');
139
+ expect(message.message).toBeUndefined();
140
+ expect(statusUpdates[0].message).not.toContain(String(SECRET_SALARY));
141
+ });
142
+
143
+ it('REGRESSION: an entity under field security never relays a payload, even to its own user', async () => {
144
+ const entityInfo = EmployeeEntity(true);
145
+ new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
146
+
147
+ raise(entityOwnedBy(entityInfo, userA), 'other', { Salary: SECRET_SALARY });
148
+ await settle();
149
+
150
+ expect(statusUpdates).toHaveLength(1);
151
+ expect(JSON.parse(statusUpdates[0].message!).message).toBeUndefined();
152
+ });
153
+
154
+ it('REGRESSION: MJ: Record Changes never relays a payload', async () => {
155
+ const recordChanges = new EntityInfo({ ID: 'rc', Name: 'MJ: Record Changes', SchemaName: '__mj', BaseTable: 'RecordChange', BaseView: 'vwRecordChanges', Fields: [] });
156
+ new TestResolver().Listen(entityOwnedBy(recordChanges, userA), userA);
157
+
158
+ raise(entityOwnedBy(recordChanges, userA), 'other', { ChangesJSON: `{"Salary":${SECRET_SALARY}}` });
159
+ await settle();
160
+
161
+ expect(statusUpdates).toHaveLength(1);
162
+ expect(statusUpdates[0].message).not.toContain(String(SECRET_SALARY));
163
+ });
164
+
165
+ it("still relays a progress message from the session user's own object", async () => {
166
+ const entityInfo = EmployeeEntity(false);
167
+ new TestResolver().Listen(entityOwnedBy(entityInfo, userA), userA);
168
+
169
+ raise(entityOwnedBy(entityInfo, userA), 'other', 'step 2 of 5');
170
+ await settle();
171
+
172
+ expect(statusUpdates).toHaveLength(1);
173
+ expect(JSON.parse(statusUpdates[0].message!).message).toBe('step 2 of 5');
174
+ expect(statusUpdates[0].ownerUserId).toBe('user-a');
175
+ });
176
+ });
@@ -158,7 +158,7 @@ describe('IncludeBinaryFields forwarding — batch resolver path', () => {
158
158
  it('RunViewsGeneric → RunViewsGenericInternal forwards the per-view flag to provider.RunViews', async () => {
159
159
  const { provider, runViewsCalls } = recordingProvider();
160
160
  const inputs = [
161
- { EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '', IncludeBinaryFields: true },
161
+ { EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '', IncludeBinaryFields: true, AlternateBaseView: 'Active Vectors' },
162
162
  { EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '' },
163
163
  ] as (RunViewByNameInput & RunViewByIDInput & RunDynamicViewInput)[];
164
164
 
@@ -167,7 +167,29 @@ describe('IncludeBinaryFields forwarding — batch resolver path', () => {
167
167
  expect(runViewsCalls).toHaveLength(1);
168
168
  expect(runViewsCalls[0]).toHaveLength(2);
169
169
  expect(runViewsCalls[0][0].IncludeBinaryFields).toBe(true);
170
+ expect(runViewsCalls[0][0].AlternateBaseView).toBe('Active Vectors');
170
171
  expect(runViewsCalls[0][1].IncludeBinaryFields).toBeUndefined();
172
+ expect(runViewsCalls[0][1].AlternateBaseView).toBeUndefined();
173
+ });
174
+
175
+ it('RunDynamicViewGeneric, RunViewByNameGeneric, and RunViewByIDGeneric forward AlternateBaseView to provider.RunView', async () => {
176
+ const { provider, runViewCalls } = recordingProvider();
177
+ const probe = new Probe();
178
+
179
+ await probe.RunDynamicViewGeneric(
180
+ { EntityName: ENTITY_NAME, ExtraFilter: '', OrderBy: '', AlternateBaseView: 'Active Vectors' } as RunDynamicViewInput,
181
+ provider, fakePayload(), undefined as never,
182
+ );
183
+ await probe.RunViewByNameGeneric(
184
+ { ViewName: 'Saved View', ExtraFilter: '', OrderBy: '', AlternateBaseView: '' } as RunViewByNameInput,
185
+ provider, fakePayload(), undefined as never,
186
+ );
187
+ await probe.RunViewByIDGeneric(
188
+ { ViewID: 'view-1', ExtraFilter: '', OrderBy: '', AlternateBaseView: 'Draft Vectors' } as RunViewByIDInput,
189
+ provider, fakePayload(), undefined as never,
190
+ );
191
+
192
+ expect(runViewCalls.map(c => c.AlternateBaseView)).toEqual(['Active Vectors', '', 'Draft Vectors']);
171
193
  });
172
194
  });
173
195
 
@@ -0,0 +1,68 @@
1
+ // ResolverBase transitively pulls in type-graphql decorators, which need the
2
+ // Reflect.metadata polyfill at import time.
3
+ import 'reflect-metadata';
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import type { PubSubEngine } from 'type-graphql';
6
+ import { UserInfo } from '@memberjunction/core';
7
+ import { ResolverBase } from '../generic/ResolverBase.js';
8
+ import { PUSH_STATUS_UPDATES_TOPIC, StatusUpdateOwnerID } from '../generic/PushStatusResolver.js';
9
+ import type { UserPayload } from '../types.js';
10
+
11
+ /**
12
+ * `ResolverBase.PublishStatusUpdate` stamps the owner the caller's socket is authenticated as. For a widget guest
13
+ * whose run is elevated to the system user, that is the guest's owner key: a push stamped with the elevated user's ID
14
+ * never passes the subscription filter on the guest's socket.
15
+ */
16
+
17
+ const ANONYMOUS_USER_ID = 'AAAAAAAA-0000-4000-8000-0000000000A0';
18
+ const SYSTEM_USER_ID = 'BBBBBBBB-0000-4000-8000-0000000000B0';
19
+
20
+ /** Reaches the protected publish wrapper. */
21
+ class Probe extends ResolverBase {
22
+ public Publish(pubSub: PubSubEngine, userPayload: UserPayload): void {
23
+ this.PublishStatusUpdate(pubSub, 'session-1', 'progress', userPayload);
24
+ }
25
+ }
26
+
27
+ function elevatedGuestPayload(): UserPayload {
28
+ const guest = new UserInfo(null, { ID: ANONYMOUS_USER_ID, Email: 'anonymous@magic-link.local' });
29
+ guest.IsMagicLinkAnonymous = true;
30
+ guest.AnonymousVisitorID = 'sid-guest-1';
31
+ return {
32
+ sessionId: 'session-1',
33
+ email: 'system@example.com',
34
+ userRecord: new UserInfo(null, { ID: SYSTEM_USER_ID, Email: 'system@example.com' }),
35
+ ElevatedFrom: guest,
36
+ };
37
+ }
38
+
39
+ describe('ResolverBase.PublishStatusUpdate — the owner it stamps', () => {
40
+ it("stamps an elevated widget guest's own owner key, not the system user it was elevated to", () => {
41
+ const publish = vi.fn().mockResolvedValue(undefined);
42
+ const pubSub: PubSubEngine = { publish, subscribe: vi.fn(), unsubscribe: vi.fn(), asyncIterator: vi.fn() };
43
+ const userPayload = elevatedGuestPayload();
44
+
45
+ new Probe().Publish(pubSub, userPayload);
46
+
47
+ expect(publish).toHaveBeenCalledOnce();
48
+ expect(publish.mock.calls[0][0]).toBe(PUSH_STATUS_UPDATES_TOPIC);
49
+ const payload = publish.mock.calls[0][1] as { sessionId: string; ownerUserId: string; message: string };
50
+ expect(payload).toMatchObject({ sessionId: 'session-1', message: 'progress' });
51
+ expect(payload.ownerUserId).toBe(StatusUpdateOwnerID(userPayload));
52
+ expect(payload.ownerUserId).toBe(`${ANONYMOUS_USER_ID.toLowerCase()}|sid-guest-1`);
53
+ });
54
+
55
+ it('stamps a signed-in user with their user ID', () => {
56
+ const publish = vi.fn().mockResolvedValue(undefined);
57
+ const pubSub: PubSubEngine = { publish, subscribe: vi.fn(), unsubscribe: vi.fn(), asyncIterator: vi.fn() };
58
+ const userPayload: UserPayload = {
59
+ sessionId: 'session-1',
60
+ email: 'user@example.com',
61
+ userRecord: new UserInfo(null, { ID: 'CCCCCCCC-0000-4000-8000-0000000000C0', Email: 'user@example.com' }),
62
+ };
63
+
64
+ new Probe().Publish(pubSub, userPayload);
65
+
66
+ expect((publish.mock.calls[0][1] as { ownerUserId: string }).ownerUserId).toBe('CCCCCCCC-0000-4000-8000-0000000000C0');
67
+ });
68
+ });
@@ -0,0 +1,130 @@
1
+ import 'reflect-metadata';
2
+ import { describe, it, expect, vi, afterEach } from 'vitest';
3
+ import { UserViewEngine } from '@memberjunction/core-entities';
4
+ import type { MJUserViewEntityExtended } from '@memberjunction/core-entities';
5
+ import { RunView } from '@memberjunction/core';
6
+ import type { IMetadataProvider, IRunViewProvider, RunViewParams, UserInfo } from '@memberjunction/core';
7
+ import { ResolverBase } from '../generic/ResolverBase.js';
8
+ import { RunViewResolver, RunViewByNameInput, RunViewByIDInput } from '../generic/RunViewResolver.js';
9
+
10
+ class UserViewProbe extends ResolverBase {
11
+ public async TestRunViewByID(
12
+ viewInput: RunViewByIDInput,
13
+ provider: IMetadataProvider,
14
+ userPayload: { userRecord: UserInfo; email: string }
15
+ ) {
16
+ return this.RunViewByIDGeneric(viewInput, provider, userPayload as never);
17
+ }
18
+
19
+ public async TestRunViewByName(
20
+ viewInput: RunViewByNameInput,
21
+ provider: IMetadataProvider,
22
+ userPayload: { userRecord: UserInfo; email: string }
23
+ ) {
24
+ return this.RunViewByNameGeneric(viewInput, provider, userPayload as never);
25
+ }
26
+ }
27
+
28
+ describe('ResolverBase & RunViewResolver UserViewEngine cache and ViewEntity forwarding (MJ-CORE-9)', () => {
29
+ afterEach(() => {
30
+ vi.restoreAllMocks();
31
+ });
32
+
33
+ it('resolves MJ: User Views from UserViewEngine cache first, forwards ViewEntity into RunViewParams, and reuses _resolvedViewEntity in RunViewResolver', async () => {
34
+ const mockViewEntity = {
35
+ ID: 'view-123',
36
+ Name: 'Active Widgets',
37
+ EntityID: 'entity-widgets',
38
+ Entity: 'Widgets',
39
+ WhereClause: 'Status = 1',
40
+ GridState: null,
41
+ SortState: null,
42
+ } as unknown as MJUserViewEntityExtended;
43
+
44
+ vi.spyOn(UserViewEngine.Instance, 'Loaded', 'get').mockReturnValue(true);
45
+ const getViewByIdSpy = vi.spyOn(UserViewEngine.Instance, 'GetViewById').mockReturnValue(mockViewEntity);
46
+ const getViewByNameSpy = vi.spyOn(UserViewEngine.Instance, 'GetViewByName').mockReturnValue(mockViewEntity);
47
+
48
+ const capturedRunViewParams: RunViewParams[] = [];
49
+ const providerRunViewSpy = vi.fn().mockImplementation(async (params: RunViewParams) => {
50
+ capturedRunViewParams.push(params);
51
+ return {
52
+ Success: true,
53
+ Results: [{ ID: 'w-1', Name: 'Widget A' }],
54
+ RowCount: 1,
55
+ TotalRowCount: 1,
56
+ ExecutionTime: 2,
57
+ ErrorMessage: '',
58
+ UserViewRunID: '',
59
+ };
60
+ });
61
+ const fakeProvider = {
62
+ Entities: [
63
+ {
64
+ ID: 'entity-widgets',
65
+ Name: 'Widgets',
66
+ PrimaryKeys: [{ Name: 'ID', CodeName: 'ID' }],
67
+ Fields: [
68
+ { Name: 'ID', CodeName: 'ID', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
69
+ { Name: 'Name', CodeName: 'Name', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
70
+ ],
71
+ EncryptedFields: [],
72
+ },
73
+ ],
74
+ EntityByName: () => ({
75
+ ID: 'entity-widgets',
76
+ Name: 'Widgets',
77
+ PrimaryKeys: [{ Name: 'ID', CodeName: 'ID' }],
78
+ Fields: [
79
+ { Name: 'ID', CodeName: 'ID', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
80
+ { Name: 'Name', CodeName: 'Name', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
81
+ ],
82
+ EncryptedFields: [],
83
+ }),
84
+ EntityByID: () => ({
85
+ ID: 'entity-widgets',
86
+ Name: 'Widgets',
87
+ PrimaryKeys: [{ Name: 'ID', CodeName: 'ID' }],
88
+ Fields: [
89
+ { Name: 'ID', CodeName: 'ID', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
90
+ { Name: 'Name', CodeName: 'Name', AllowInView: true, DefaultInView: true, IsBinaryFieldType: false },
91
+ ],
92
+ EncryptedFields: [],
93
+ }),
94
+ RunView: providerRunViewSpy,
95
+ } as unknown as IMetadataProvider & IRunViewProvider;
96
+
97
+ const userPayload = {
98
+ email: 'tester@example.com',
99
+ userRecord: { ID: 'u-1', Email: 'tester@example.com' } as unknown as UserInfo,
100
+ };
101
+
102
+ const resolver = new RunViewResolver();
103
+ const findBySpy = vi.spyOn(
104
+ resolver as unknown as { findBy: (...args: unknown[]) => Promise<unknown> },
105
+ 'findBy'
106
+ );
107
+
108
+ const byIdResult = await resolver.RunViewByID(
109
+ { ViewID: 'view-123' } as RunViewByIDInput,
110
+ { providers: [{ provider: fakeProvider, type: 'Read-Only' }], userPayload } as never
111
+ );
112
+
113
+ expect(getViewByIdSpy).toHaveBeenCalledWith('view-123');
114
+ expect(findBySpy).not.toHaveBeenCalled();
115
+ expect(capturedRunViewParams).toHaveLength(1);
116
+ expect(capturedRunViewParams[0].ViewEntity).toBe(mockViewEntity);
117
+ expect(byIdResult?.Results).toHaveLength(1);
118
+
119
+ const byNameResult = await resolver.RunViewByName(
120
+ { ViewName: 'Active Widgets' } as RunViewByNameInput,
121
+ { providers: [{ provider: fakeProvider, type: 'Read-Only' }], userPayload } as never
122
+ );
123
+
124
+ expect(getViewByNameSpy).toHaveBeenCalledWith('Active Widgets');
125
+ expect(findBySpy).not.toHaveBeenCalled();
126
+ expect(capturedRunViewParams).toHaveLength(2);
127
+ expect(capturedRunViewParams[1].ViewEntity).toBe(mockViewEntity);
128
+ expect(byNameResult?.Results).toHaveLength(1);
129
+ });
130
+ });