@memberjunction/server 6.2.0-edge.4 → 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 (560) hide show
  1. package/README.md +47 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +91 -30
  4. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  5. package/dist/agentSessions/SessionJanitor.js +311 -81
  6. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  7. package/dist/agentSessions/SessionManager.d.ts +17 -3
  8. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  9. package/dist/agentSessions/SessionManager.js +44 -10
  10. package/dist/agentSessions/SessionManager.js.map +1 -1
  11. package/dist/apiRoot.d.ts +32 -0
  12. package/dist/apiRoot.d.ts.map +1 -0
  13. package/dist/apiRoot.js +67 -0
  14. package/dist/apiRoot.js.map +1 -0
  15. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  16. package/dist/auth/AuthProviderCatalogRouter.js +27 -0
  17. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  18. package/dist/auth/LoginScreenEngine.d.ts +22 -0
  19. package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
  20. package/dist/auth/LoginScreenEngine.js +57 -0
  21. package/dist/auth/LoginScreenEngine.js.map +1 -0
  22. package/dist/auth/index.d.ts +1 -0
  23. package/dist/auth/index.d.ts.map +1 -1
  24. package/dist/auth/index.js +52 -10
  25. package/dist/auth/index.js.map +1 -1
  26. package/dist/auth/initializeProviders.d.ts +10 -0
  27. package/dist/auth/initializeProviders.d.ts.map +1 -1
  28. package/dist/auth/initializeProviders.js +37 -1
  29. package/dist/auth/initializeProviders.js.map +1 -1
  30. package/dist/auth/localDevAuth.d.ts +79 -0
  31. package/dist/auth/localDevAuth.d.ts.map +1 -0
  32. package/dist/auth/localDevAuth.js +439 -0
  33. package/dist/auth/localDevAuth.js.map +1 -0
  34. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  35. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  37. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  39. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  40. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  41. package/dist/auth/magicLink/types.d.ts +12 -0
  42. package/dist/auth/magicLink/types.d.ts.map +1 -1
  43. package/dist/auth/sessionUserClone.d.ts +1 -1
  44. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  45. package/dist/auth/sessionUserClone.js +3 -2
  46. package/dist/auth/sessionUserClone.js.map +1 -1
  47. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  48. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  49. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  50. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  51. package/dist/broadcast/index.d.ts +6 -0
  52. package/dist/broadcast/index.d.ts.map +1 -0
  53. package/dist/broadcast/index.js +6 -0
  54. package/dist/broadcast/index.js.map +1 -0
  55. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  56. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  57. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  58. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  59. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  60. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  61. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  62. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  63. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  64. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  65. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  66. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  67. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  68. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  69. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  70. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  71. package/dist/cache.d.ts +3 -2
  72. package/dist/cache.d.ts.map +1 -1
  73. package/dist/cache.js.map +1 -1
  74. package/dist/config.d.ts +632 -22
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +104 -4
  77. package/dist/config.js.map +1 -1
  78. package/dist/context.d.ts +3 -1
  79. package/dist/context.d.ts.map +1 -1
  80. package/dist/context.js +29 -8
  81. package/dist/context.js.map +1 -1
  82. package/dist/coreRoutes.d.ts +29 -0
  83. package/dist/coreRoutes.d.ts.map +1 -0
  84. package/dist/coreRoutes.js +156 -0
  85. package/dist/coreRoutes.js.map +1 -0
  86. package/dist/generated/graphql-schemas/__mj.d.ts +1016 -29
  87. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  88. package/dist/generated/graphql-schemas/__mj.js +5125 -434
  89. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  90. package/dist/generic/CacheInvalidationResolver.d.ts +85 -30
  91. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  92. package/dist/generic/CacheInvalidationResolver.js +175 -60
  93. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  94. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  95. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  96. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  97. package/dist/generic/PubSubManager.d.ts +10 -0
  98. package/dist/generic/PubSubManager.d.ts.map +1 -1
  99. package/dist/generic/PubSubManager.js +44 -1
  100. package/dist/generic/PubSubManager.js.map +1 -1
  101. package/dist/generic/PushStatusResolver.d.ts +39 -6
  102. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  103. package/dist/generic/PushStatusResolver.js +94 -12
  104. package/dist/generic/PushStatusResolver.js.map +1 -1
  105. package/dist/generic/ResolverBase.d.ts +168 -69
  106. package/dist/generic/ResolverBase.d.ts.map +1 -1
  107. package/dist/generic/ResolverBase.js +464 -405
  108. package/dist/generic/ResolverBase.js.map +1 -1
  109. package/dist/generic/RunViewResolver.d.ts +11 -3
  110. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  111. package/dist/generic/RunViewResolver.js +91 -13
  112. package/dist/generic/RunViewResolver.js.map +1 -1
  113. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  114. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  115. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  116. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  117. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  118. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  119. package/dist/generic/fieldSecurityTransport.js +30 -0
  120. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  121. package/dist/globalCors.d.ts +33 -0
  122. package/dist/globalCors.d.ts.map +1 -0
  123. package/dist/globalCors.js +58 -0
  124. package/dist/globalCors.js.map +1 -0
  125. package/dist/index.d.ts +14 -0
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +230 -222
  128. package/dist/index.js.map +1 -1
  129. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  130. package/dist/integration/CustomColumnPromoter.js +6 -1
  131. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  132. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  133. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  134. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  135. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  136. package/dist/multiTenancy/index.d.ts.map +1 -1
  137. package/dist/multiTenancy/index.js +22 -2
  138. package/dist/multiTenancy/index.js.map +1 -1
  139. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
  140. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
  141. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
  142. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
  143. package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
  144. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
  145. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  146. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  147. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  148. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  149. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  150. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  151. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  152. package/dist/realtimeSessions/index.d.ts +2 -0
  153. package/dist/realtimeSessions/index.d.ts.map +1 -1
  154. package/dist/realtimeSessions/index.js +2 -0
  155. package/dist/realtimeSessions/index.js.map +1 -1
  156. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +32 -0
  157. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  158. package/dist/realtimeSessions/ownedRealtimeSession.js +56 -0
  159. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  160. package/dist/realtimeSessions/sessionAccess.d.ts +91 -8
  161. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  162. package/dist/realtimeSessions/sessionAccess.js +127 -20
  163. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  164. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  165. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  166. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  167. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  168. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  169. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  170. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  171. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  172. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  173. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  174. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  175. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  176. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  177. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  178. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  179. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  180. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  181. package/dist/realtimeWidget/host-identity.js +1 -1
  182. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  183. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  184. package/dist/realtimeWidget/widgetCore.js +17 -1
  185. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  186. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  187. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  188. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  189. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  190. package/dist/resolvers/ActionResolver.d.ts +19 -1
  191. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  192. package/dist/resolvers/ActionResolver.js +63 -12
  193. package/dist/resolvers/ActionResolver.js.map +1 -1
  194. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  195. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  196. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  197. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  198. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  199. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  200. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  201. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  202. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  203. package/dist/resolvers/AgentSessionResolver.js +40 -17
  204. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  205. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  206. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  207. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  208. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  209. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  210. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  211. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  212. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  213. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  214. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  215. package/dist/resolvers/DatasetResolver.js +57 -8
  216. package/dist/resolvers/DatasetResolver.js.map +1 -1
  217. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  218. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  219. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  220. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  221. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  222. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  223. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  224. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  225. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +46 -3
  226. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  227. package/dist/resolvers/FetchEntityVectorsResolver.js +211 -50
  228. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  229. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  230. package/dist/resolvers/FileCategoryResolver.js +1 -0
  231. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  232. package/dist/resolvers/FileResolver.d.ts +87 -11
  233. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  234. package/dist/resolvers/FileResolver.js +292 -105
  235. package/dist/resolvers/FileResolver.js.map +1 -1
  236. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  237. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  238. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  239. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  240. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  241. package/dist/resolvers/GetDataResolver.js +6 -3
  242. package/dist/resolvers/GetDataResolver.js.map +1 -1
  243. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  244. package/dist/resolvers/IntegrationDiscoveryResolver.js +49 -9
  245. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  246. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  247. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  248. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  249. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  250. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  251. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  252. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  253. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  254. package/dist/resolvers/QueryResolver.d.ts +2 -0
  255. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  256. package/dist/resolvers/QueryResolver.js +33 -6
  257. package/dist/resolvers/QueryResolver.js.map +1 -1
  258. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  259. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  260. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  261. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  262. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  263. package/dist/resolvers/RSUResolver.js +12 -0
  264. package/dist/resolvers/RSUResolver.js.map +1 -1
  265. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +18 -10
  266. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  267. package/dist/resolvers/RealtimeClientSessionResolver.js +92 -54
  268. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  269. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  270. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  271. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  272. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  273. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  274. package/dist/resolvers/RunAIAgentResolver.js +241 -67
  275. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  276. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  277. package/dist/resolvers/RunTemplateResolver.js +4 -4
  278. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  279. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  280. package/dist/resolvers/RunTestResolver.js +3 -1
  281. package/dist/resolvers/RunTestResolver.js.map +1 -1
  282. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  283. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  284. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  285. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  286. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  287. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  288. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  289. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  290. package/dist/resolvers/SyncDataResolver.js +4 -0
  291. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  292. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  293. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  294. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  295. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  296. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  297. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  298. package/dist/resolvers/TestQuerySQLResolver.js +66 -13
  299. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  300. package/dist/resolvers/TransactionGroupResolver.d.ts +34 -2
  301. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  302. package/dist/resolvers/TransactionGroupResolver.js +150 -27
  303. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  304. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  305. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  306. package/dist/resolvers/UserViewResolver.js +2 -1
  307. package/dist/resolvers/UserViewResolver.js.map +1 -1
  308. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  309. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  310. package/dist/resolvers/agent-run-data-guard.js +72 -0
  311. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  312. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  313. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  314. package/dist/rest/EntityCRUDHandler.js +19 -5
  315. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  316. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  317. package/dist/rest/RESTEndpointHandler.js +8 -5
  318. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  319. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  320. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  321. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  322. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  323. package/dist/rest/ViewOperationsHandler.d.ts +9 -1
  324. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  325. package/dist/rest/ViewOperationsHandler.js +43 -17
  326. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  327. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  328. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  329. package/dist/rest/setupRESTEndpoints.js +6 -0
  330. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  331. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  332. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  333. package/dist/serverExtensionReservedRoots.js +14 -17
  334. package/dist/serverExtensionReservedRoots.js.map +1 -1
  335. package/dist/serverRoleConfigUnits.d.ts +29 -0
  336. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  337. package/dist/serverRoleConfigUnits.js +33 -0
  338. package/dist/serverRoleConfigUnits.js.map +1 -0
  339. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  340. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  341. package/dist/services/MJStorageBlobStore.js +25 -19
  342. package/dist/services/MJStorageBlobStore.js.map +1 -1
  343. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  344. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  345. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  346. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  347. package/dist/services/TaskGraphActionRunner.js +27 -5
  348. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  349. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  350. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  351. package/dist/services/TaskGraphAgentRunner.js +157 -9
  352. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  353. package/dist/services/TaskGraphContinuationDeliverer.d.ts +59 -2
  354. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  355. package/dist/services/TaskGraphContinuationDeliverer.js +626 -16
  356. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  357. package/dist/sharedCache.d.ts.map +1 -1
  358. package/dist/sharedCache.js +3 -0
  359. package/dist/sharedCache.js.map +1 -1
  360. package/dist/types.d.ts +23 -1
  361. package/dist/types.d.ts.map +1 -1
  362. package/dist/types.js.map +1 -1
  363. package/package.json +99 -98
  364. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  365. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  366. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  367. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  368. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  369. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  370. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  371. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  372. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  373. package/src/__tests__/FetchEntityVectorsResolver.test.ts +369 -18
  374. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  375. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  376. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  377. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  378. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  379. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  380. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  381. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  382. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  383. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  384. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  385. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  386. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  387. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  388. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  389. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  390. package/src/__tests__/ResolverBase.clientOldValues.test.ts +243 -7
  391. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  392. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  393. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  394. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  395. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  396. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  397. package/src/__tests__/RunAIAgentResolver.branch.test.ts +69 -13
  398. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  399. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  400. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +20 -28
  401. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +264 -0
  402. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  403. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  404. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  405. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  406. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  407. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  408. package/src/__tests__/SessionJanitor.test.ts +365 -10
  409. package/src/__tests__/SessionManager.test.ts +122 -0
  410. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  411. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  412. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +468 -23
  413. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  414. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  415. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  416. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  417. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  418. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  419. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  420. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  421. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  422. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  423. package/src/__tests__/apiRootBoot.test.ts +202 -0
  424. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  425. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  426. package/src/__tests__/cacheInvalidationRecordData.test.ts +109 -0
  427. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  428. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  429. package/src/__tests__/globalCors.test.ts +217 -0
  430. package/src/__tests__/localDevAuth.test.ts +196 -0
  431. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  432. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  433. package/src/__tests__/multiTenancy.test.ts +1 -1
  434. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  435. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  436. package/src/__tests__/pushStatusResolver.filter.test.ts +64 -1
  437. package/src/__tests__/realtimeRecordingHandler.test.ts +669 -0
  438. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  439. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  440. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  441. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  442. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  443. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  444. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  445. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  446. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  447. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  448. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  449. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  450. package/src/__tests__/runViewProperties.test.ts +114 -0
  451. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  452. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  453. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  454. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
  455. package/src/__tests__/sessionUserClone.test.ts +5 -1
  456. package/src/__tests__/sharedCache.test.ts +5 -3
  457. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  458. package/src/__tests__/unifiedAuth.test.ts +28 -0
  459. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  460. package/src/agentSessions/SessionJanitor.ts +369 -93
  461. package/src/agentSessions/SessionManager.ts +45 -10
  462. package/src/apiRoot.ts +78 -0
  463. package/src/auth/AuthProviderCatalogRouter.ts +29 -0
  464. package/src/auth/LoginScreenEngine.ts +50 -0
  465. package/src/auth/index.ts +57 -12
  466. package/src/auth/initializeProviders.ts +43 -1
  467. package/src/auth/localDevAuth.ts +528 -0
  468. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  469. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  470. package/src/auth/magicLink/types.ts +10 -0
  471. package/src/auth/sessionUserClone.ts +3 -2
  472. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  473. package/src/broadcast/index.ts +5 -0
  474. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  475. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  476. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  477. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  478. package/src/cache.ts +2 -1
  479. package/src/config.ts +115 -5
  480. package/src/context.ts +32 -8
  481. package/src/coreRoutes.ts +181 -0
  482. package/src/generated/graphql-schemas/__mj.ts +3684 -284
  483. package/src/generic/CacheInvalidationResolver.ts +172 -60
  484. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  485. package/src/generic/PubSubManager.ts +54 -1
  486. package/src/generic/PushStatusResolver.ts +109 -12
  487. package/src/generic/ResolverBase.ts +540 -431
  488. package/src/generic/RunViewResolver.ts +97 -15
  489. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  490. package/src/generic/fieldSecurityTransport.ts +62 -0
  491. package/src/globalCors.ts +71 -0
  492. package/src/index.ts +239 -238
  493. package/src/integration/CustomColumnPromoter.ts +6 -1
  494. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  495. package/src/multiTenancy/index.ts +24 -2
  496. package/src/realtimeSessions/README.md +7 -3
  497. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
  498. package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
  499. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  500. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  501. package/src/realtimeSessions/index.ts +2 -0
  502. package/src/realtimeSessions/ownedRealtimeSession.ts +69 -0
  503. package/src/realtimeSessions/sessionAccess.ts +183 -23
  504. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  505. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  506. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  507. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  508. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  509. package/src/realtimeWidget/host-identity.ts +1 -1
  510. package/src/realtimeWidget/widgetCore.ts +30 -1
  511. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  512. package/src/resolvers/ActionResolver.ts +78 -15
  513. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  514. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  515. package/src/resolvers/AgentSessionResolver.ts +46 -18
  516. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  517. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  518. package/src/resolvers/DatasetResolver.ts +70 -10
  519. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  520. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  521. package/src/resolvers/FetchEntityVectorsResolver.ts +248 -55
  522. package/src/resolvers/FileCategoryResolver.ts +1 -0
  523. package/src/resolvers/FileResolver.ts +334 -109
  524. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  525. package/src/resolvers/GetDataResolver.ts +6 -3
  526. package/src/resolvers/IntegrationDiscoveryResolver.ts +53 -9
  527. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  528. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  529. package/src/resolvers/QueryResolver.ts +31 -6
  530. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  531. package/src/resolvers/RSUResolver.ts +12 -0
  532. package/src/resolvers/RealtimeClientSessionResolver.ts +114 -66
  533. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  534. package/src/resolvers/RunAIAgentResolver.ts +296 -85
  535. package/src/resolvers/RunTemplateResolver.ts +6 -4
  536. package/src/resolvers/RunTestResolver.ts +3 -1
  537. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  538. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  539. package/src/resolvers/SyncDataResolver.ts +4 -0
  540. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  541. package/src/resolvers/TestQuerySQLResolver.ts +69 -13
  542. package/src/resolvers/TransactionGroupResolver.ts +185 -30
  543. package/src/resolvers/UserViewResolver.ts +2 -1
  544. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  545. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  546. package/src/resolvers/agent-run-data-guard.ts +89 -0
  547. package/src/rest/EntityCRUDHandler.ts +22 -5
  548. package/src/rest/RESTEndpointHandler.ts +8 -5
  549. package/src/rest/RealtimeRecordingHandler.ts +467 -0
  550. package/src/rest/ViewOperationsHandler.ts +52 -18
  551. package/src/rest/setupRESTEndpoints.ts +6 -0
  552. package/src/serverExtensionReservedRoots.ts +14 -17
  553. package/src/serverRoleConfigUnits.ts +48 -0
  554. package/src/services/MJStorageBlobStore.ts +31 -30
  555. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  556. package/src/services/TaskGraphActionRunner.ts +25 -5
  557. package/src/services/TaskGraphAgentRunner.ts +188 -11
  558. package/src/services/TaskGraphContinuationDeliverer.ts +738 -16
  559. package/src/sharedCache.ts +3 -0
  560. package/src/types.ts +23 -1
@@ -0,0 +1,127 @@
1
+ // ResolverBase pulls in type-graphql decorators, which need the Reflect.metadata polyfill first.
2
+ import 'reflect-metadata';
3
+ import { describe, it, expect } from 'vitest';
4
+ import type { DatabaseProviderBase, RunViewParams, RunViewResult, UserInfo } from '@memberjunction/core';
5
+ import type { PubSubEngine } from 'type-graphql';
6
+ import { ResolverBase } from '../generic/ResolverBase.js';
7
+ import type { RunDynamicViewInput } from '../generic/RunViewResolver.js';
8
+ import type { UserPayload } from '../types.js';
9
+
10
+ /**
11
+ * `RunDynamicView` is the GraphQL entry point for a client RunView over one entity. A subquery in
12
+ * its ExtraFilter reads an entity's base view directly, so RunView applies neither that entity's
13
+ * row-level security nor its denied-field projection to the subquery. These tests pin that the
14
+ * boundary screen refuses such a subquery when the caller's read of that entity is narrowed (the
15
+ * rule the ad-hoc SQL screen applies) and still passes it when the read is not. Inputs are harmless.
16
+ */
17
+
18
+ /** How the caller's read of an entity is narrowed: a row filter, denied fields, or neither. */
19
+ interface ReadScope {
20
+ RowFilter?: string;
21
+ DeniedFields?: string[];
22
+ }
23
+
24
+ /** What the resolver handed to RunView; null when the request never got there. */
25
+ type Captured = { params: RunViewParams | null };
26
+
27
+ const USER = { ID: 'U1', Email: 'reader@example.com' } as UserInfo;
28
+ const OWN_ROWS = "(OwnerID = 'U1')";
29
+ const ORDERS_SUBQUERY = `ID IN (SELECT AccountID FROM [crm].[vwOrders] WHERE [Total] > 100)`;
30
+
31
+ /** Provider double: two entities the caller can read, each under `scopes[Name]`, and a RunView that records its params. */
32
+ function fakeProvider(captured: Captured, scopes: Record<string, ReadScope> = {}): DatabaseProviderBase {
33
+ const entity = (id: string, name: string, baseView: string) => ({
34
+ ID: id,
35
+ Name: name,
36
+ SchemaName: 'crm',
37
+ BaseView: baseView,
38
+ GetUserPermisions: () => ({ CanRead: true }),
39
+ GetEffectiveRowFilterWhereClause: () => scopes[name]?.RowFilter ?? '',
40
+ GetDeniedReadFields: () => new Set(scopes[name]?.DeniedFields ?? []),
41
+ });
42
+ return {
43
+ Entities: [entity('E1', 'Accounts', 'vwAccounts'), entity('E2', 'Orders', 'vwOrders')],
44
+ RunView: async (params: RunViewParams): Promise<RunViewResult> => {
45
+ captured.params = params;
46
+ return { Success: true, Results: [], RowCount: 0, TotalRowCount: 0, ErrorMessage: '' } as unknown as RunViewResult;
47
+ },
48
+ } as unknown as DatabaseProviderBase;
49
+ }
50
+
51
+ /** Runs `RunDynamicViewGeneric` over Accounts as USER. */
52
+ class Probe extends ResolverBase {
53
+ public RunDynamic(input: Partial<RunDynamicViewInput>, provider: DatabaseProviderBase) {
54
+ const payload = { email: USER.Email, userRecord: USER } as UserPayload;
55
+ const view = { EntityName: 'Accounts', ...input } as RunDynamicViewInput;
56
+ return this.RunDynamicViewGeneric(view, provider, payload, undefined as unknown as PubSubEngine);
57
+ }
58
+ }
59
+
60
+ describe('RunDynamicViewGeneric — a client subquery reads an entity only when the caller may read it unscoped', () => {
61
+ it('refuses a subquery over an entity that is row-level-security filtered for the caller, before RunView', async () => {
62
+ const captured: Captured = { params: null };
63
+
64
+ await expect(
65
+ new Probe().RunDynamic({ ExtraFilter: ORDERS_SUBQUERY }, fakeProvider(captured, { Orders: { RowFilter: OWN_ROWS } })),
66
+ ).rejects.toThrow(/'Orders' is row-level-security filtered for you/);
67
+ expect(captured.params).toBeNull();
68
+ });
69
+
70
+ it('refuses a subquery over an entity with a field the caller may not read, before RunView', async () => {
71
+ const captured: Captured = { params: null };
72
+
73
+ await expect(
74
+ new Probe().RunDynamic({ ExtraFilter: ORDERS_SUBQUERY }, fakeProvider(captured, { Orders: { DeniedFields: ['total'] } })),
75
+ ).rejects.toThrow(/'Orders' has fields you are not permitted to read/);
76
+ expect(captured.params).toBeNull();
77
+ });
78
+
79
+ it('passes the same subquery through when the caller reads that entity unscoped', async () => {
80
+ const captured: Captured = { params: null };
81
+
82
+ await new Probe().RunDynamic({ ExtraFilter: ORDERS_SUBQUERY }, fakeProvider(captured));
83
+
84
+ expect(captured.params?.ExtraFilter).toBe(ORDERS_SUBQUERY);
85
+ });
86
+
87
+ it("refuses a subquery over the queried entity's own base view when the caller's read of it is row-filtered", async () => {
88
+ const captured: Captured = { params: null };
89
+ const ownView = `ID IN (SELECT ID FROM [crm].[vwAccounts] WHERE [Name] LIKE 'A%')`;
90
+
91
+ await expect(
92
+ new Probe().RunDynamic({ ExtraFilter: ownView }, fakeProvider(captured, { Accounts: { RowFilter: OWN_ROWS } })),
93
+ ).rejects.toThrow(/'Accounts' is row-level-security filtered for you/);
94
+ expect(captured.params).toBeNull();
95
+ });
96
+
97
+ it('refuses a CASE-WHEN subquery over a row-filtered entity, before RunView', async () => {
98
+ const captured: Captured = { params: null };
99
+ const caseWhen = `CASE WHEN (SELECT COUNT(*) FROM [crm].[vwOrders] WHERE [Total] > 100) > 0 THEN 1 ELSE 0 END = 1`;
100
+
101
+ await expect(
102
+ new Probe().RunDynamic({ ExtraFilter: caseWhen }, fakeProvider(captured, { Orders: { RowFilter: OWN_ROWS } })),
103
+ ).rejects.toThrow(/'Orders' is row-level-security filtered for you/);
104
+ expect(captured.params).toBeNull();
105
+ });
106
+
107
+ it("refuses a subquery over a row-filtered entity in a window's ORDER BY, before RunView", async () => {
108
+ const captured: Captured = { params: null };
109
+ const window = `ID IN (SELECT ID FROM (SELECT ID, ROW_NUMBER() OVER (ORDER BY (SELECT COUNT(*) FROM [crm].[vwOrders])) AS rn FROM [crm].[vwAccounts]) x)`;
110
+
111
+ await expect(
112
+ new Probe().RunDynamic({ ExtraFilter: window }, fakeProvider(captured, { Orders: { RowFilter: OWN_ROWS } })),
113
+ ).rejects.toThrow(/'Orders' is row-level-security filtered for you/);
114
+ expect(captured.params).toBeNull();
115
+ });
116
+
117
+ it('still passes an ordinary filter and sort on an entity the caller reads under a row filter', async () => {
118
+ const captured: Captured = { params: null };
119
+
120
+ await new Probe().RunDynamic(
121
+ { ExtraFilter: `[Name] = 'Acme'`, OrderBy: '[Name] DESC' },
122
+ fakeProvider(captured, { Accounts: { RowFilter: OWN_ROWS } }),
123
+ );
124
+
125
+ expect(captured.params?.ExtraFilter).toBe(`[Name] = 'Acme'`);
126
+ });
127
+ });
@@ -1,7 +1,7 @@
1
1
  // Reflect.metadata polyfill at import time.
2
2
  import 'reflect-metadata';
3
- import { describe, it, expect } from 'vitest';
4
- import { EntityFieldTSType, EntityInfo, UserInfo, type EntityFieldInfo } from '@memberjunction/core';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import { BaseEntity, EntityFieldTSType, EntityInfo, UserInfo, type EntityFieldInfo, type IMetadataProvider } from '@memberjunction/core';
5
5
  import { ResolverBase } from '../generic/ResolverBase.js';
6
6
 
7
7
  /**
@@ -13,11 +13,42 @@ class Probe extends ResolverBase {
13
13
  public Convert(field: Partial<EntityFieldInfo>, raw: unknown) {
14
14
  return this.ClientOldValueToFieldValue(field as EntityFieldInfo, raw);
15
15
  }
16
- public MustLoad(entity: Record<string, unknown>, oldValues: boolean) {
16
+ public MustLoad(
17
+ entity: Record<string, unknown>,
18
+ oldValues: boolean | Array<{ Key: string; Value: unknown }>,
19
+ entityFields: Array<Record<string, unknown>> = []
20
+ ) {
17
21
  // A real entity with no permission rows and a user with no roles: no row filter applies.
18
22
  // The Update RLS term itself is covered in resolverBase.fls.test.ts.
19
23
  const user = new UserInfo(null, { ID: 'u-1', Name: 'Probe', Email: 'probe@example.test', IsActive: true, UserRoles: [] });
20
- return this.MustLoadTruthFromDatabase(new EntityInfo({ Fields: [], ...entity }), { OldValues___: oldValues ? [{ Key: 'Comments', Value: null }] : undefined }, false, false, user);
24
+ const resolvedOldValues = Array.isArray(oldValues)
25
+ ? oldValues
26
+ : oldValues
27
+ ? [{ Key: 'Comments', Value: null }]
28
+ : undefined;
29
+ return this.MustLoadTruthFromDatabase(
30
+ new EntityInfo({ EntityFields: entityFields, ...entity }),
31
+ { OldValues___: resolvedOldValues },
32
+ false,
33
+ false,
34
+ user
35
+ );
36
+ }
37
+ public HasComplete(entityInfo: EntityInfo, oldValues?: Array<{ Key: string; Value: unknown }> | null): boolean {
38
+ return this.hasCompleteOldValues(entityInfo, oldValues);
39
+ }
40
+ public async TestAndSet(
41
+ entityObject: BaseEntity,
42
+ input: { OldValues___?: Array<{ Key: string; Value: unknown }> } & Record<string, unknown>,
43
+ contextUser: UserInfo
44
+ ): Promise<void> {
45
+ const clientNewValues: Record<string, unknown> = {};
46
+ for (const [k, v] of Object.entries(input)) {
47
+ if (k !== 'OldValues___') {
48
+ clientNewValues[k] = v;
49
+ }
50
+ }
51
+ await this.TestAndSetClientOldValuesToDBValues(input, clientNewValues, entityObject, contextUser);
21
52
  }
22
53
  }
23
54
  const probe = new Probe();
@@ -44,12 +75,217 @@ describe('ResolverBase.ClientOldValueToFieldValue', () => {
44
75
  });
45
76
  });
46
77
 
47
- describe('ResolverBase.MustLoadTruthFromDatabase', () => {
78
+ function buildTagEntityFields(): Array<Record<string, unknown>> {
79
+ const entityId = 'E0000000-0000-4000-8000-000000000001';
80
+ return [
81
+ { ID: 'F-1', EntityID: entityId, Name: 'ID', Type: 'uniqueidentifier', IsPrimaryKey: true, AllowsNull: false, AllowUpdateAPI: false, IsVirtual: false, Sequence: 1, Status: 'Active' },
82
+ { ID: 'F-2', EntityID: entityId, Name: 'Name', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: false, AllowUpdateAPI: true, IsVirtual: false, Sequence: 2, Status: 'Active' },
83
+ { ID: 'F-3', EntityID: entityId, Name: 'Display Name', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: false, AllowUpdateAPI: true, IsVirtual: false, Sequence: 3, Status: 'Active' },
84
+ { ID: 'F-4', EntityID: entityId, Name: 'Description', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: true, IsVirtual: false, Sequence: 4, Status: 'Active' },
85
+ { ID: 'F-5', EntityID: entityId, Name: '__mj_UpdatedAt', Type: 'datetimeoffset', IsPrimaryKey: false, AllowsNull: false, AllowUpdateAPI: false, IsVirtual: false, Sequence: 5, Status: 'Active' },
86
+ { ID: 'F-6', EntityID: entityId, Name: 'ParentName', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: false, IsVirtual: true, Sequence: 6, Status: 'Active' },
87
+ ];
88
+ }
89
+
90
+ describe('ResolverBase.MustLoadTruthFromDatabase & hasCompleteOldValues', () => {
48
91
  it('always loads a Record Change from the database, so client OldValues cannot pin forged audit columns', () => {
49
92
  expect(probe.MustLoad({ Name: 'MJ: Record Changes', TrackRecordChanges: false }, true)).toBe(true);
50
93
  });
51
94
 
52
- it('still trusts OldValues on other untracked entities without field security', () => {
53
- expect(probe.MustLoad({ Name: 'MJ: Tags', TrackRecordChanges: false, EnableFieldLevelSecurity: false }, true)).toBe(false);
95
+ it("always loads a conversation message, so client OldValues cannot claim a message's author or role", () => {
96
+ expect(probe.MustLoad({ Name: 'MJ: Conversation Details', TrackRecordChanges: false }, true)).toBe(true);
97
+ expect(probe.MustLoad({ Name: ' mj: conversation details ', TrackRecordChanges: false }, true)).toBe(true);
98
+ });
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
+
105
+ it('still trusts OldValues on other untracked entities without field security when OldValues___ covers all writable fields', () => {
106
+ const fields = buildTagEntityFields();
107
+ const completeOldValues = [
108
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
109
+ { Key: 'Name', Value: 'Original' },
110
+ { Key: 'Display_Name', Value: 'Original Display' },
111
+ { Key: 'Description', Value: 'Desc' },
112
+ ];
113
+ expect(
114
+ probe.MustLoad(
115
+ { ID: 'E0000000-0000-4000-8000-000000000001', Name: 'MJ: Tags', TrackRecordChanges: false, EnableFieldLevelSecurity: false },
116
+ completeOldValues,
117
+ fields
118
+ )
119
+ ).toBe(false);
120
+ });
121
+
122
+ it('requires loading truth from the database when OldValues___ is a partial delta (even when TrackRecordChanges === false)', () => {
123
+ const fields = buildTagEntityFields();
124
+ const deltaOldValues = [
125
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
126
+ { Key: 'Name', Value: 'Original' },
127
+ // Display_Name and Description omitted because they were not dirty on the client
128
+ ];
129
+ expect(
130
+ probe.MustLoad(
131
+ { ID: 'E0000000-0000-4000-8000-000000000001', Name: 'MJ: Tags', TrackRecordChanges: false, EnableFieldLevelSecurity: false },
132
+ deltaOldValues,
133
+ fields
134
+ )
135
+ ).toBe(true);
136
+ });
137
+
138
+ it('requires loading truth from the database when OldValues___ is empty or undefined', () => {
139
+ const fields = buildTagEntityFields();
140
+ const entityMeta = { ID: 'E0000000-0000-4000-8000-000000000001', Name: 'MJ: Tags', TrackRecordChanges: false, EnableFieldLevelSecurity: false };
141
+ expect(probe.MustLoad(entityMeta, [], fields)).toBe(true);
142
+ expect(probe.MustLoad(entityMeta, false, fields)).toBe(true);
143
+ });
144
+ });
145
+
146
+ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Update', () => {
147
+ const tagEntityInfo = new EntityInfo({
148
+ ID: 'E0000000-0000-4000-8000-000000000001',
149
+ Name: 'MJ: Tags',
150
+ BaseTable: 'Tag',
151
+ BaseView: 'vwTags',
152
+ SchemaName: '__mj',
153
+ Status: 'Active',
154
+ EntityFields: buildTagEntityFields(),
155
+ });
156
+
157
+ function createLoadedTagEntity(errorLogSaves: Array<Record<string, unknown>> = [], failSaveWith?: string): { entity: BaseEntity; user: UserInfo } {
158
+ const fakeProvider = {
159
+ Entities: [tagEntityInfo],
160
+ GetEntityObject: async () => {
161
+ const errEntity: Record<string, unknown> = {
162
+ Save: async () => {
163
+ errorLogSaves.push({ ...errEntity });
164
+ return failSaveWith === undefined;
165
+ },
166
+ LatestResult: failSaveWith === undefined ? null : { CompleteMessage: failSaveWith },
167
+ };
168
+ return errEntity;
169
+ },
170
+ } as unknown as IMetadataProvider;
171
+
172
+ const entity = new BaseEntity(tagEntityInfo, fakeProvider);
173
+ entity.LoadFromData({
174
+ ID: '11111111-2222-3333-4444-555555555555',
175
+ Name: 'OriginalTag',
176
+ 'Display Name': 'Original Display',
177
+ Description: 'Untouched DB Description',
178
+ __mj_UpdatedAt: new Date('2026-03-01T00:00:00.000Z'),
179
+ ParentName: 'RootTag',
180
+ });
181
+ const user = new UserInfo(null, { ID: 'u-1', Name: 'Probe', Email: 'probe@example.test', IsActive: true, UserRoles: [] });
182
+ return { entity, user };
183
+ }
184
+
185
+ it('preserves untouched DB fields and applies only the dirty field from a partial delta', async () => {
186
+ const errorLogSaves: Array<Record<string, unknown>> = [];
187
+ const { entity, user } = createLoadedTagEntity(errorLogSaves);
188
+
189
+ await probe.TestAndSet(
190
+ entity,
191
+ {
192
+ ID: '11111111-2222-3333-4444-555555555555',
193
+ Display_Name: 'Updated Display',
194
+ OldValues___: [
195
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
196
+ { Key: 'Display_Name', Value: 'Original Display' },
197
+ ],
198
+ },
199
+ user
200
+ );
201
+
202
+ // Updated field is applied
203
+ expect(entity.Get('Display Name')).toBe('Updated Display');
204
+ // Untouched DB fields remain intact (never overwritten with null/defaults)
205
+ expect(entity.Get('Name')).toBe('OriginalTag');
206
+ expect(entity.Get('Description')).toBe('Untouched DB Description');
207
+ // No concurrency conflict warning logged
208
+ expect(errorLogSaves).toHaveLength(0);
209
+ });
210
+
211
+ it('detects a concurrent modification when the dirty field DB value differs from OldValues___', async () => {
212
+ const errorLogSaves: Array<Record<string, unknown>> = [];
213
+ const { entity, user } = createLoadedTagEntity(errorLogSaves);
214
+
215
+ // Client thinks Display_Name was 'Stale Client Display', but DB currently has 'Original Display'
216
+ await probe.TestAndSet(
217
+ entity,
218
+ {
219
+ ID: '11111111-2222-3333-4444-555555555555',
220
+ Display_Name: 'Client New Display',
221
+ OldValues___: [
222
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
223
+ { Key: 'Display_Name', Value: 'Stale Client Display' },
224
+ ],
225
+ },
226
+ user
227
+ );
228
+
229
+ // Warning is logged to ErrorLog, and save still applies the new value
230
+ expect(errorLogSaves).toHaveLength(1);
231
+ expect(errorLogSaves[0].Code).toBe('SAVE_INCONSISTENCY');
232
+ // ErrorLog.Code is NCHAR(20)
233
+ expect(String(errorLogSaves[0].Code).length).toBeLessThanOrEqual(20);
234
+ expect(String(errorLogSaves[0].Message)).toContain('Display_Name');
235
+ expect(entity.Get('Display Name')).toBe('Client New Display');
236
+ expect(entity.Get('Description')).toBe('Untouched DB Description');
237
+ });
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
+
264
+ it('does not false-positive when a non-overlapping field differs in the DB', async () => {
265
+ const errorLogSaves: Array<Record<string, unknown>> = [];
266
+ const { entity, user } = createLoadedTagEntity(errorLogSaves);
267
+
268
+ // Suppose client sent full OldValues___ (e.g. IgnoreDirtyState: true) where Description
269
+ // changed in DB ('Untouched DB Description' vs 'Old Desc'), while the client only changed Name
270
+ await probe.TestAndSet(
271
+ entity,
272
+ {
273
+ ID: '11111111-2222-3333-4444-555555555555',
274
+ Name: 'RenamedTag',
275
+ Description: 'Old Desc',
276
+ OldValues___: [
277
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
278
+ { Key: 'Name', Value: 'OriginalTag' },
279
+ { Key: 'Description', Value: 'Old Desc' },
280
+ ],
281
+ },
282
+ user
283
+ );
284
+
285
+ // Description had a DB difference ('Untouched DB Description' !== 'Old Desc'),
286
+ // but client was NOT changing Description ('Old Desc' === 'Old Desc'), so overlap is empty!
287
+ expect(errorLogSaves).toHaveLength(0);
288
+ expect(entity.Get('Name')).toBe('RenamedTag');
54
289
  });
55
290
  });
291
+
@@ -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
+ });
@@ -36,9 +36,12 @@ type Captured = { params: RunViewParams | null };
36
36
  function fakeProvider(captured: Captured, rows: Record<string, unknown>[] = [], unreadableEntities: string[] = []): DatabaseProviderBase {
37
37
  const withPerms = (e: Record<string, unknown>) => ({
38
38
  ...e,
39
- // Real EntityInfo aggregates per-user permissions; the boundary screen now consults
40
- // CanRead for every subquery target, so the mock must answer it.
39
+ // Real EntityInfo aggregates per-user permissions; the boundary screen consults CanRead,
40
+ // the row filter and the denied fields for every subquery target, so the mock answers
41
+ // them (an unscoped read).
41
42
  GetUserPermisions: () => ({ CanRead: !unreadableEntities.includes(e.Name as string) }),
43
+ GetEffectiveRowFilterWhereClause: () => '',
44
+ GetDeniedReadFields: () => new Set<string>(),
42
45
  });
43
46
  return {
44
47
  Entities: [