@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,674 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { BaseSingleton, MJGlobal } from '@memberjunction/global';
3
+ import { EntityInfo, Metadata, UserInfo } from '@memberjunction/core';
4
+ import type {
5
+ CacheInvalidationNotification,
6
+ CacheInvalidationPayload,
7
+ ReadPermissionRow,
8
+ RoleHolder,
9
+ } from '../generic/CacheInvalidationResolver.js';
10
+
11
+ /**
12
+ * Supported cache invalidation broadcast topologies.
13
+ */
14
+ export type CacheInvalidationBroadcastTopology =
15
+ | 'SequencedRoleBucket'
16
+ | 'AzureWebPubSub'
17
+ | 'AWSIoTCore'
18
+ | 'GCPPubSub';
19
+
20
+ /**
21
+ * Configuration options for the cache invalidation broadcast subsystem.
22
+ * Configured under `cacheSettings.broadcast` in `mj.config.cjs` or via `MJ_CACHE_BROADCAST_*` env vars.
23
+ */
24
+ export interface CacheBroadcastProviderConfig {
25
+ /**
26
+ * Driver key registered with `@RegisterClass(BaseCacheInvalidationBroadcastProvider, key)`.
27
+ * Defaults to `'SequencedRoleBucket'`.
28
+ */
29
+ provider?: CacheInvalidationBroadcastTopology | string;
30
+ /**
31
+ * Maximum number of sequenced invalidation events retained in the in-memory ring buffer
32
+ * for catch-up delta queries (`GetCacheInvalidationsSince`). Defaults to 5000.
33
+ */
34
+ ringBufferSize?: number;
35
+ /**
36
+ * Optional list of entity names whose cache invalidation broadcasts should be suppressed
37
+ * (e.g., high-churn telemetry/audit tables never cached on the client).
38
+ */
39
+ excludedEntities?: string[];
40
+ /**
41
+ * Azure Web PubSub configuration (used when `provider === 'AzureWebPubSub'`).
42
+ */
43
+ azureWebPubSub?: {
44
+ endpoint?: string;
45
+ hubName?: string;
46
+ accessKey?: string;
47
+ tokenTtlMinutes?: number;
48
+ };
49
+ /**
50
+ * AWS IoT Core MQTT over WebSockets configuration (used when `provider === 'AWSIoTCore'`).
51
+ */
52
+ awsIoTCore?: {
53
+ endpoint?: string;
54
+ region?: string;
55
+ topicPrefix?: string;
56
+ accessKeyId?: string;
57
+ secretAccessKey?: string;
58
+ tokenTtlSeconds?: number;
59
+ };
60
+ /**
61
+ * Google Cloud Pub/Sub / Cloud Run WebSockets configuration (used when `provider === 'GCPPubSub'`).
62
+ */
63
+ gcpPubSub?: {
64
+ projectId?: string;
65
+ topicPrefix?: string;
66
+ gatewayUrl?: string;
67
+ signingSecret?: string;
68
+ tokenTtlSeconds?: number;
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Catch-up response returned by `GetChangesSince` when a client detects a sequence gap
74
+ * via `X-MJ-Cache-Seq` / `X-MJ-Server-ID` HTTP headers or WebSocket reconnection.
75
+ */
76
+ export interface CacheInvalidationCatchUpResponse {
77
+ /**
78
+ * True when the requested `sinceSequence` has already been evicted from the ring buffer,
79
+ * belongs to a future sequence, or the server restarted. Instructs the client to evict
80
+ * its local dataset cache and re-fetch cleanly.
81
+ */
82
+ RequiresFullRefresh: boolean;
83
+ /**
84
+ * Latest monotonic sequence number assigned by this server process.
85
+ */
86
+ CurrentSequence: number;
87
+ /**
88
+ * Unique process identifier for this MJServer instance (`X-MJ-Server-ID`).
89
+ */
90
+ ServerProcessID: string;
91
+ /**
92
+ * Ordered delta of permission-filtered, RLS-shaped invalidation events strictly after `sinceSequence`.
93
+ */
94
+ Events: CacheInvalidationNotification[];
95
+ }
96
+
97
+ /**
98
+ * Connection token and subscription metadata returned by `NegotiateClientConnection`
99
+ * for Topology 3 external cloud WebSocket multiplexers (or in-process GraphQL WS fallback).
100
+ */
101
+ export interface CacheBroadcastNegotiationResult {
102
+ /**
103
+ * Active provider driver key (e.g. `'SequencedRoleBucket'`, `'AzureWebPubSub'`, `'AWSIoTCore'`, `'GCPPubSub'`).
104
+ */
105
+ Provider: string;
106
+ /**
107
+ * WebSocket endpoint URL for the client to connect to.
108
+ */
109
+ Url: string;
110
+ /**
111
+ * Signed token / credentials authorizing the client for its role-scoped broadcast groups.
112
+ */
113
+ Token?: string;
114
+ /**
115
+ * Role-scoped group/topic names the client is authorized to subscribe to.
116
+ */
117
+ Groups: string[];
118
+ /**
119
+ * ISO-8601 expiration timestamp for the negotiated token.
120
+ */
121
+ ExpiresAt: string;
122
+ /**
123
+ * Current server process ID for sequence continuity tracking.
124
+ */
125
+ ServerProcessID: string;
126
+ /**
127
+ * Current server invalidation sequence at the moment of negotiation.
128
+ */
129
+ CurrentSequence: number;
130
+ }
131
+
132
+ /**
133
+ * Result of evaluating an invalidation event against a single role fingerprint bucket.
134
+ */
135
+ export interface RoleBucketEvaluationResult {
136
+ Allow: boolean;
137
+ WithholdKey: boolean;
138
+ Shaped: CacheInvalidationNotification;
139
+ }
140
+
141
+ interface RingBufferEntry {
142
+ Sequence: number;
143
+ Payload: CacheInvalidationPayload;
144
+ }
145
+
146
+ /**
147
+ * High-churn system telemetry/logging entities that can optionally be excluded from browser
148
+ * invalidation fan-out because browsers do not maintain live dataset caches for them.
149
+ */
150
+ export const DEFAULT_HIGH_CHURN_SYSTEM_ENTITIES: readonly string[] = [
151
+ 'MJ: Audit Logs',
152
+ 'MJ: Error Logs',
153
+ 'MJ: AI Prompt Runs',
154
+ 'MJ: AI Agent Runs',
155
+ 'MJ: AI Agent Run Steps',
156
+ 'MJ: Query Execution Histories',
157
+ ];
158
+
159
+ /**
160
+ * Abstract base class for MemberJunction's pluggable Cache Invalidation Broadcast Hierarchy.
161
+ *
162
+ * Provides the shared primitives used by all broadcast topologies:
163
+ * 1. **Monotonic Sequence Ring Buffer (Topology 1)**: Every invalidation event is stamped with a
164
+ * monotonically increasing `Sequence` integer and stored in a bounded circular buffer (`MaxRingBufferSize`),
165
+ * enabling HTTP `X-MJ-Cache-Seq` piggybacking and `GetCacheInvalidationsSince` delta catch-up.
166
+ * 2. **Role-Fingerprint $O(\text{Roles})$ Evaluation & Shaping Memoizer (Topology 2)**: Computes a canonical
167
+ * fingerprint of a user's sorted `RoleID`s (cached per `UserInfo` in a `WeakMap`) and memoizes
168
+ * entity read-permission checks (`CanRead`) and row-level security key-withholding (`ReadIsRowFiltered`)
169
+ * once per `(payload, entity, roleFingerprint)` tuple instead of once per connected WebSocket client.
170
+ * 3. **Cloud Multiplexer Extension Hooks (Topology 3)**: Subclasses (`AzureWebPubSubBroadcastProvider`,
171
+ * `AWSIoTCoreBroadcastProvider`, `GCPPubSubBroadcastProvider`) override `PublishInvalidation` and
172
+ * `NegotiateClientConnection` to offload WebSocket fan-out to managed cloud pub/sub infrastructure.
173
+ */
174
+ export abstract class BaseCacheInvalidationBroadcastProvider {
175
+ private static readonly _serverProcessId: string = randomUUID();
176
+
177
+ private _currentSequence = 0;
178
+ private _maxRingBufferSize = 5000;
179
+ private _ringBuffer: Array<RingBufferEntry | undefined> = [];
180
+ private _ringHead = 0;
181
+ private _ringCount = 0;
182
+ private _excludedEntities: Set<string> = new Set<string>();
183
+ protected _config: CacheBroadcastProviderConfig = {};
184
+
185
+ /**
186
+ * Memoizes canonical role fingerprints per `RoleHolder` / `UserInfo` object reference.
187
+ */
188
+ private readonly _roleFingerprintCache = new WeakMap<object, { roleCount: number; fingerprint: string }>();
189
+
190
+ /**
191
+ * Memoizes `Metadata.EntityByName` lookup per `CacheInvalidationPayload` object reference
192
+ * so `CacheInvalidationFilter` and `CacheInvalidationResolver` only look up the entity once per event.
193
+ */
194
+ private readonly _payloadEntityCache = new WeakMap<CacheInvalidationPayload, EntityInfo | null>();
195
+
196
+ /**
197
+ * Memoizes entity-level read permission (`CanRead`) per `(payload, roleFingerprint)`.
198
+ */
199
+ private readonly _payloadFilterMemo = new WeakMap<CacheInvalidationPayload, Map<string, boolean>>();
200
+
201
+ /**
202
+ * Memoizes row-level security key-withholding and shaped `CacheInvalidationNotification`
203
+ * per `(payload, entity, roleFingerprint)`.
204
+ */
205
+ private readonly _payloadShapeMemo = new WeakMap<
206
+ CacheInvalidationPayload,
207
+ WeakMap<object, Map<string, CacheInvalidationNotification>>
208
+ >();
209
+
210
+ /**
211
+ * Unique identifier of the running MJServer process, emitted in `X-MJ-Server-ID`.
212
+ */
213
+ public get ServerProcessID(): string {
214
+ return BaseCacheInvalidationBroadcastProvider._serverProcessId;
215
+ }
216
+
217
+ /**
218
+ * Identifier for the active broadcast topology.
219
+ */
220
+ public abstract get Topology(): CacheInvalidationBroadcastTopology;
221
+
222
+ /**
223
+ * Latest monotonic sequence number assigned to a cache invalidation event in this process.
224
+ */
225
+ public get CurrentSequence(): number {
226
+ return this._currentSequence;
227
+ }
228
+
229
+ /**
230
+ * Maximum number of invalidation events held in the circular ring buffer.
231
+ */
232
+ public get MaxRingBufferSize(): number {
233
+ return this._maxRingBufferSize;
234
+ }
235
+
236
+ /**
237
+ * Number of invalidation events currently retained in the circular ring buffer.
238
+ */
239
+ public get BufferedEventCount(): number {
240
+ return this._ringCount;
241
+ }
242
+
243
+ /**
244
+ * Configures the provider instance with buffer sizing, entity exclusions, and cloud settings.
245
+ */
246
+ public Configure(config?: CacheBroadcastProviderConfig): void {
247
+ this._config = config ?? {};
248
+ const desiredSize = config?.ringBufferSize && config.ringBufferSize > 0 ? Math.floor(config.ringBufferSize) : 5000;
249
+ if (desiredSize !== this._maxRingBufferSize || this._ringBuffer.length === 0) {
250
+ this._maxRingBufferSize = desiredSize;
251
+ this._ringBuffer = new Array<RingBufferEntry | undefined>(this._maxRingBufferSize);
252
+ this._ringHead = 0;
253
+ this._ringCount = 0;
254
+ }
255
+ this._excludedEntities = new Set(
256
+ (config?.excludedEntities ?? [])
257
+ .map((name) => name.trim().toLowerCase())
258
+ .filter((name) => name.length > 0)
259
+ );
260
+ }
261
+
262
+ /**
263
+ * Returns whether invalidation events for `entityName` should be broadcast to clients.
264
+ * Returns `false` for blank names or entities explicitly listed in `excludedEntities`.
265
+ */
266
+ public ShouldBroadcastEntity(entityName: string | undefined | null): boolean {
267
+ if (!entityName) {
268
+ return false;
269
+ }
270
+ const normalized = entityName.trim().toLowerCase();
271
+ if (normalized.length === 0) {
272
+ return false;
273
+ }
274
+ return !this._excludedEntities.has(normalized);
275
+ }
276
+
277
+ /**
278
+ * Computes a canonical, order-independent role fingerprint for a user (`RoleHolder`).
279
+ * Memoized per user object in a `WeakMap` so repeated evaluations across thousands of events are $O(1)$.
280
+ */
281
+ public GetRoleFingerprint(user: RoleHolder | undefined | null): string {
282
+ if (!user || typeof user !== 'object') {
283
+ return 'anonymous';
284
+ }
285
+ const roles = user.UserRoles;
286
+ if (!Array.isArray(roles)) {
287
+ return 'no-roles';
288
+ }
289
+ const cached = this._roleFingerprintCache.get(user);
290
+ if (cached && cached.roleCount === roles.length) {
291
+ return cached.fingerprint;
292
+ }
293
+
294
+ let fingerprint: string;
295
+ if (roles.length === 0) {
296
+ fingerprint = 'roles:empty';
297
+ } else if (roles.length === 1) {
298
+ fingerprint = `roles:${(roles[0]?.RoleID ?? '').trim().toLowerCase()}`;
299
+ } else {
300
+ const normalizedIds: string[] = new Array<string>(roles.length);
301
+ for (let i = 0; i < roles.length; i++) {
302
+ normalizedIds[i] = (roles[i]?.RoleID ?? '').trim().toLowerCase();
303
+ }
304
+ normalizedIds.sort();
305
+ fingerprint = `roles:${normalizedIds.join('|')}`;
306
+ }
307
+
308
+ this._roleFingerprintCache.set(user, { roleCount: roles.length, fingerprint });
309
+ return fingerprint;
310
+ }
311
+
312
+ /**
313
+ * Resolves and caches the `EntityInfo` for a `CacheInvalidationPayload` so that
314
+ * `CacheInvalidationFilter` and `CacheInvalidationResolver` do not repeat metadata lookups.
315
+ */
316
+ public ResolveEntityForPayload(
317
+ payload: CacheInvalidationPayload,
318
+ entityLookup?: (entityName: string) => EntityInfo | undefined | null
319
+ ): EntityInfo | null {
320
+ if (this._payloadEntityCache.has(payload)) {
321
+ return this._payloadEntityCache.get(payload) ?? null;
322
+ }
323
+ const entityName = payload?.entityName;
324
+ if (!entityName) {
325
+ this._payloadEntityCache.set(payload, null);
326
+ return null;
327
+ }
328
+ const entity = entityLookup
329
+ ? (entityLookup(entityName) ?? null)
330
+ : (new Metadata().EntityByName(entityName) ?? null); // global-provider-ok: subscription broadcast provider resolves server-wide entity metadata once per event
331
+ this._payloadEntityCache.set(payload, entity);
332
+ return entity;
333
+ }
334
+
335
+ /**
336
+ * Evaluates whether `user` has entity-level read permission for `payload`, memoized by
337
+ * `(payload, entityName, roleFingerprint)` so $N$ subscribers sharing $R$ role combinations
338
+ * only run `GetUserPermisions` $R$ times per event.
339
+ */
340
+ public EvaluateEntityReadAccess(
341
+ payload: CacheInvalidationPayload,
342
+ user: UserInfo | undefined,
343
+ entityLookup?: (entityName: string) => EntityInfo | undefined | null
344
+ ): boolean {
345
+ if (!user) {
346
+ return false;
347
+ }
348
+ const entityName = payload?.entityName;
349
+ if (!this.ShouldBroadcastEntity(entityName)) {
350
+ return false;
351
+ }
352
+
353
+ const entity = this.ResolveEntityForPayload(payload, entityLookup);
354
+ if (!entity) {
355
+ // Unknown entity: deliver rather than leaving client cache silently stale
356
+ return true;
357
+ }
358
+
359
+ // Only coalesce across users when UserRoles is an array; mock users without UserRoles
360
+ // in isolated unit tests are evaluated directly.
361
+ if (!Array.isArray(user.UserRoles)) {
362
+ return entity.GetUserPermisions(user).CanRead;
363
+ }
364
+
365
+ let bucketMap = this._payloadFilterMemo.get(payload);
366
+ if (!bucketMap) {
367
+ bucketMap = new Map<string, boolean>();
368
+ this._payloadFilterMemo.set(payload, bucketMap);
369
+ }
370
+
371
+ const bucketKey = `${entity.Name.toLowerCase()}::${this.GetRoleFingerprint(user)}`;
372
+ const cached = bucketMap.get(bucketKey);
373
+ if (cached !== undefined) {
374
+ return cached;
375
+ }
376
+
377
+ const canRead = entity.GetUserPermisions(user).CanRead;
378
+ bucketMap.set(bucketKey, canRead);
379
+ return canRead;
380
+ }
381
+
382
+ /**
383
+ * Evaluates whether every read permission the user holds on `entity` is gated by a
384
+ * Row-Level Security filter (`ReadRLSFilterID`).
385
+ */
386
+ public ReadIsRowFiltered(
387
+ entity: { Permissions: ReadonlyArray<ReadPermissionRow> },
388
+ user: RoleHolder
389
+ ): boolean {
390
+ const roles = new Set((user.UserRoles ?? []).map((role) => (role.RoleID ?? '').trim().toLowerCase()));
391
+ let filteredReads = 0;
392
+ for (const row of entity.Permissions) {
393
+ if (!row.CanRead) continue;
394
+ const isDeny = row.IsDeny ?? (row.Type ?? 'Allow').trim().toLowerCase() === 'deny';
395
+ if (isDeny) continue;
396
+ if (!roles.has((row.RoleID ?? '').trim().toLowerCase())) continue;
397
+ if (!row.ReadRLSFilterID) return false;
398
+ filteredReads += 1;
399
+ }
400
+ return filteredReads > 0;
401
+ }
402
+
403
+ /**
404
+ * Shapes a `CacheInvalidationPayload` into a `CacheInvalidationNotification` for `user` and `entity`.
405
+ * When `user` and `entity` are present and `user.UserRoles` is populated, the shaped object is
406
+ * memoized per `(payload, entity, roleFingerprint)` so $N$ subscribers sharing $R$ role buckets
407
+ * only allocate and evaluate `ReadIsRowFiltered` $R$ times per event.
408
+ */
409
+ public ShapeEventForSubscriber(
410
+ payload: CacheInvalidationPayload,
411
+ user: RoleHolder | undefined,
412
+ entity: { Permissions: ReadonlyArray<ReadPermissionRow> } | null | undefined
413
+ ): CacheInvalidationNotification {
414
+ if (!user || !entity || !Array.isArray(user.UserRoles)) {
415
+ const withholdKey = !!user && !!entity && this.ReadIsRowFiltered(entity, user);
416
+ return this.buildNotification(payload, withholdKey);
417
+ }
418
+
419
+ let entityMap = this._payloadShapeMemo.get(payload);
420
+ if (!entityMap) {
421
+ entityMap = new WeakMap<object, Map<string, CacheInvalidationNotification>>();
422
+ this._payloadShapeMemo.set(payload, entityMap);
423
+ }
424
+
425
+ let roleMap = entityMap.get(entity);
426
+ if (!roleMap) {
427
+ roleMap = new Map<string, CacheInvalidationNotification>();
428
+ entityMap.set(entity, roleMap);
429
+ }
430
+
431
+ const fingerprint = this.GetRoleFingerprint(user);
432
+ const existing = roleMap.get(fingerprint);
433
+ if (existing) {
434
+ return existing;
435
+ }
436
+
437
+ const withholdKey = this.ReadIsRowFiltered(entity, user);
438
+ const shaped = this.buildNotification(payload, withholdKey);
439
+ roleMap.set(fingerprint, shaped);
440
+ return shaped;
441
+ }
442
+
443
+ /**
444
+ * Assigns the next monotonic sequence number to `payload` (if not already stamped on this server),
445
+ * appends it to the bounded circular ring buffer, and invokes `OnInvalidationRecorded` for
446
+ * provider-specific fan-out.
447
+ */
448
+ public RecordInvalidation(payload: CacheInvalidationPayload): CacheInvalidationPayload {
449
+ if (!this.ShouldBroadcastEntity(payload.entityName)) {
450
+ return payload;
451
+ }
452
+ if (this._ringBuffer.length === 0) {
453
+ this.Configure(this._config);
454
+ }
455
+
456
+ const seq = ++this._currentSequence;
457
+ payload.sequence = seq;
458
+
459
+ const writeIndex = (this._ringHead + this._ringCount) % this._maxRingBufferSize;
460
+ if (this._ringCount < this._maxRingBufferSize) {
461
+ this._ringBuffer[writeIndex] = { Sequence: seq, Payload: payload };
462
+ this._ringCount++;
463
+ } else {
464
+ // Buffer full: overwrite oldest at _ringHead and advance head
465
+ this._ringBuffer[this._ringHead] = { Sequence: seq, Payload: payload };
466
+ this._ringHead = (this._ringHead + 1) % this._maxRingBufferSize;
467
+ }
468
+
469
+ void this.OnInvalidationRecorded(payload);
470
+ return payload;
471
+ }
472
+
473
+ /**
474
+ * Returns all permission-allowed, RLS-shaped invalidation events strictly after `sinceSequence`
475
+ * for `user`. If `sinceSequence` has already been evicted from the ring buffer (or lies in the
476
+ * future due to a server restart), sets `RequiresFullRefresh: true` and returns an empty `Events` list.
477
+ */
478
+ public GetChangesSince(
479
+ sinceSequence: number,
480
+ user: UserInfo | undefined
481
+ ): CacheInvalidationCatchUpResponse {
482
+ if (!user) {
483
+ return {
484
+ RequiresFullRefresh: true,
485
+ CurrentSequence: this._currentSequence,
486
+ ServerProcessID: this.ServerProcessID,
487
+ Events: [],
488
+ };
489
+ }
490
+
491
+ if (sinceSequence < 0 || sinceSequence > this._currentSequence) {
492
+ return {
493
+ RequiresFullRefresh: true,
494
+ CurrentSequence: this._currentSequence,
495
+ ServerProcessID: this.ServerProcessID,
496
+ Events: [],
497
+ };
498
+ }
499
+
500
+ if (sinceSequence === this._currentSequence) {
501
+ return {
502
+ RequiresFullRefresh: false,
503
+ CurrentSequence: this._currentSequence,
504
+ ServerProcessID: this.ServerProcessID,
505
+ Events: [],
506
+ };
507
+ }
508
+
509
+ if (this._ringCount === 0) {
510
+ return {
511
+ RequiresFullRefresh: true,
512
+ CurrentSequence: this._currentSequence,
513
+ ServerProcessID: this.ServerProcessID,
514
+ Events: [],
515
+ };
516
+ }
517
+
518
+ const oldestEntry = this._ringBuffer[this._ringHead];
519
+ if (!oldestEntry || sinceSequence < oldestEntry.Sequence - 1) {
520
+ return {
521
+ RequiresFullRefresh: true,
522
+ CurrentSequence: this._currentSequence,
523
+ ServerProcessID: this.ServerProcessID,
524
+ Events: [],
525
+ };
526
+ }
527
+
528
+ // Coalesce duplicate entity+PK events within the catch-up window (latest event wins)
529
+ const coalescedPayloads = new Map<string, CacheInvalidationPayload>();
530
+ for (let i = 0; i < this._ringCount; i++) {
531
+ const idx = (this._ringHead + i) % this._maxRingBufferSize;
532
+ const entry = this._ringBuffer[idx];
533
+ if (!entry || entry.Sequence <= sinceSequence) {
534
+ continue;
535
+ }
536
+ const payload = entry.Payload;
537
+ if (!this.EvaluateEntityReadAccess(payload, user)) {
538
+ continue;
539
+ }
540
+ const entity = this.ResolveEntityForPayload(payload);
541
+ const shaped = this.ShapeEventForSubscriber(payload, user, entity);
542
+ // If key is withheld (RLS-filtered), coalesce at whole-entity granularity; otherwise entity + PK
543
+ const dedupKey = shaped.PrimaryKeyValues
544
+ ? `${shaped.EntityName.toLowerCase()}::pk::${shaped.PrimaryKeyValues}`
545
+ : `${shaped.EntityName.toLowerCase()}::all`;
546
+ coalescedPayloads.set(dedupKey, payload);
547
+ }
548
+
549
+ const events: CacheInvalidationNotification[] = [];
550
+ for (const payload of coalescedPayloads.values()) {
551
+ const entity = this.ResolveEntityForPayload(payload);
552
+ events.push(this.ShapeEventForSubscriber(payload, user, entity));
553
+ }
554
+
555
+ return {
556
+ RequiresFullRefresh: false,
557
+ CurrentSequence: this._currentSequence,
558
+ ServerProcessID: this.ServerProcessID,
559
+ Events: events,
560
+ };
561
+ }
562
+
563
+ /**
564
+ * Resets the sequence counter and ring buffer. Intended for unit tests.
565
+ */
566
+ public ResetStateForTesting(): void {
567
+ this._currentSequence = 0;
568
+ this._ringHead = 0;
569
+ this._ringCount = 0;
570
+ this._ringBuffer = new Array<RingBufferEntry | undefined>(this._maxRingBufferSize);
571
+ }
572
+
573
+ /**
574
+ * Hook invoked after an invalidation event is sequenced and stored in the ring buffer.
575
+ * Cloud multiplexer providers override this to forward role-bucketed payloads to their
576
+ * external pub/sub infrastructure.
577
+ */
578
+ protected abstract OnInvalidationRecorded(payload: CacheInvalidationPayload): Promise<void>;
579
+
580
+ /**
581
+ * Negotiates a client broadcast connection (URL, token, and authorized role-bucket groups)
582
+ * for the given authenticated user.
583
+ */
584
+ public abstract NegotiateClientConnection(
585
+ user: UserInfo,
586
+ defaultGraphQLWsUrl?: string
587
+ ): Promise<CacheBroadcastNegotiationResult>;
588
+
589
+ private buildNotification(
590
+ payload: CacheInvalidationPayload,
591
+ withholdKey: boolean
592
+ ): CacheInvalidationNotification {
593
+ const notification: CacheInvalidationNotification = {
594
+ EntityName: payload.entityName,
595
+ PrimaryKeyValues: withholdKey ? undefined : (payload.primaryKeyValues ?? undefined),
596
+ Action: payload.action,
597
+ SourceServerID: payload.sourceServerId,
598
+ Timestamp: payload.timestamp,
599
+ OriginSessionID: payload.originSessionId ?? undefined,
600
+ RecordData: withholdKey ? undefined : (payload.recordData ?? undefined),
601
+ };
602
+ if (payload.sequence !== undefined) {
603
+ notification.Sequence = payload.sequence;
604
+ }
605
+ return notification;
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Process-wide singleton managing the active {@link BaseCacheInvalidationBroadcastProvider}.
611
+ *
612
+ * Uses `MJGlobal.Instance.ClassFactory` to instantiate the configured provider class
613
+ * (defaulting to `'SequencedRoleBucket'`).
614
+ */
615
+ export class CacheInvalidationBroadcastManager extends BaseSingleton<CacheInvalidationBroadcastManager> {
616
+ private _provider: BaseCacheInvalidationBroadcastProvider | null = null;
617
+ private _fallbackFactory: (() => BaseCacheInvalidationBroadcastProvider) | null = null;
618
+
619
+ protected constructor() {
620
+ super();
621
+ }
622
+
623
+ public static get Instance(): CacheInvalidationBroadcastManager {
624
+ return super.getInstance<CacheInvalidationBroadcastManager>();
625
+ }
626
+
627
+ /**
628
+ * Registers the default fallback provider factory so `Provider` is always available even
629
+ * before `Configure()` is explicitly called.
630
+ */
631
+ public RegisterDefaultProviderFactory(factory: () => BaseCacheInvalidationBroadcastProvider): void {
632
+ this._fallbackFactory = factory;
633
+ }
634
+
635
+ /**
636
+ * Returns the active broadcast provider, lazily instantiating the default
637
+ * `SequencedRoleBucketBroadcastProvider` if not yet configured.
638
+ */
639
+ public get Provider(): BaseCacheInvalidationBroadcastProvider {
640
+ if (!this._provider) {
641
+ this.Configure();
642
+ }
643
+ return this._provider!;
644
+ }
645
+
646
+ /**
647
+ * Configures and activates a broadcast provider via `MJGlobal.Instance.ClassFactory`.
648
+ */
649
+ public Configure(config?: CacheBroadcastProviderConfig): BaseCacheInvalidationBroadcastProvider {
650
+ const driverKey = config?.provider?.trim() || 'SequencedRoleBucket';
651
+ let instance = MJGlobal.Instance.ClassFactory.CreateInstance<BaseCacheInvalidationBroadcastProvider>(
652
+ BaseCacheInvalidationBroadcastProvider,
653
+ driverKey
654
+ );
655
+ if (!instance && this._fallbackFactory) {
656
+ instance = this._fallbackFactory();
657
+ }
658
+ if (!instance) {
659
+ throw new Error(
660
+ `Failed to instantiate BaseCacheInvalidationBroadcastProvider for driver '${driverKey}'.`
661
+ );
662
+ }
663
+ instance.Configure(config);
664
+ this._provider = instance;
665
+ return instance;
666
+ }
667
+
668
+ /**
669
+ * Explicitly sets the active provider instance (useful for testing or custom programmatic wiring).
670
+ */
671
+ public SetProvider(provider: BaseCacheInvalidationBroadcastProvider): void {
672
+ this._provider = provider;
673
+ }
674
+ }
@@ -0,0 +1,5 @@
1
+ export * from './BaseCacheInvalidationBroadcastProvider.js';
2
+ export * from './providers/SequencedRoleBucketBroadcastProvider.js';
3
+ export * from './providers/AzureWebPubSubBroadcastProvider.js';
4
+ export * from './providers/AWSIoTCoreBroadcastProvider.js';
5
+ export * from './providers/GCPPubSubBroadcastProvider.js';