@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  3. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  4. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  5. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  6. package/dist/agentSessions/HostInstance.d.ts +75 -11
  7. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  8. package/dist/agentSessions/HostInstance.js +99 -28
  9. package/dist/agentSessions/HostInstance.js.map +1 -1
  10. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +452 -54
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +43 -2
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +99 -8
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  25. package/dist/agentSessions/index.d.ts +2 -0
  26. package/dist/agentSessions/index.d.ts.map +1 -1
  27. package/dist/agentSessions/index.js +10 -3
  28. package/dist/agentSessions/index.js.map +1 -1
  29. package/dist/auth/actionAuthorization.d.ts +40 -0
  30. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  31. package/dist/auth/actionAuthorization.js +94 -0
  32. package/dist/auth/actionAuthorization.js.map +1 -0
  33. package/dist/auth/index.d.ts.map +1 -1
  34. package/dist/auth/index.js +3 -10
  35. package/dist/auth/index.js.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  38. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  39. package/dist/auth/newUsers.d.ts +10 -0
  40. package/dist/auth/newUsers.d.ts.map +1 -1
  41. package/dist/auth/newUsers.js +80 -11
  42. package/dist/auth/newUsers.js.map +1 -1
  43. package/dist/config.d.ts +2378 -0
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +123 -0
  46. package/dist/config.js.map +1 -1
  47. package/dist/context.d.ts +8 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +14 -11
  50. package/dist/context.js.map +1 -1
  51. package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
  52. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  53. package/dist/generated/graphql-schemas/__mj.js +706 -0
  54. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  55. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  56. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  57. package/dist/generic/ResolverBase.d.ts +20 -4
  58. package/dist/generic/ResolverBase.d.ts.map +1 -1
  59. package/dist/generic/ResolverBase.js +39 -10
  60. package/dist/generic/ResolverBase.js.map +1 -1
  61. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  62. package/dist/generic/RunViewResolver.js +4 -0
  63. package/dist/generic/RunViewResolver.js.map +1 -1
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +120 -131
  67. package/dist/index.js.map +1 -1
  68. package/dist/isolatedProvider.d.ts +20 -0
  69. package/dist/isolatedProvider.d.ts.map +1 -0
  70. package/dist/isolatedProvider.js +22 -0
  71. package/dist/isolatedProvider.js.map +1 -0
  72. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  73. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  74. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  75. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  76. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  77. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  78. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  79. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  80. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  81. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  82. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  83. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  84. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  85. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  86. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  87. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  88. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  89. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  90. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  91. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  92. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  93. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  94. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  95. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  96. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  97. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  98. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  99. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  100. package/dist/realtimeSessions/index.d.ts +17 -0
  101. package/dist/realtimeSessions/index.d.ts.map +1 -0
  102. package/dist/realtimeSessions/index.js +17 -0
  103. package/dist/realtimeSessions/index.js.map +1 -0
  104. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  105. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  106. package/dist/realtimeSessions/sessionAccess.js +90 -0
  107. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  108. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  109. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  110. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  111. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  112. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  113. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  114. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  115. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  116. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  117. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  118. package/dist/realtimeSessions/verificationCore.js +356 -0
  119. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  120. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  121. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  122. package/dist/realtimeSessions/verificationPages.js +110 -0
  123. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  124. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  125. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  126. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  127. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  128. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  129. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  130. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  131. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  132. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  133. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  134. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  135. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  136. package/dist/resolvers/ActionResolver.d.ts +31 -5
  137. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  138. package/dist/resolvers/ActionResolver.js +75 -14
  139. package/dist/resolvers/ActionResolver.js.map +1 -1
  140. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  141. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  142. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  143. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  144. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  145. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  146. package/dist/resolvers/CacheStatsResolver.js +113 -2
  147. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  148. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  149. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  150. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  151. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  152. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  153. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  154. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  155. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  156. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  157. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  158. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +9 -4
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  163. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  164. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  165. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  166. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
  167. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  168. package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
  169. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  170. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  171. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  172. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  173. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  174. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  175. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  176. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  177. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  178. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  179. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  180. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  181. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  182. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  183. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  184. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  185. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  186. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTemplateResolver.js +12 -11
  188. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  189. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  190. package/dist/resolvers/SyncDataResolver.js +6 -2
  191. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  192. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  193. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  194. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  195. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  196. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  197. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  198. package/dist/resolvers/UserAvatarResolver.js +124 -0
  199. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  200. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  201. package/dist/resolvers/UserResolver.js +2 -17
  202. package/dist/resolvers/UserResolver.js.map +1 -1
  203. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  204. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  205. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  206. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  207. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  208. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  209. package/dist/resolvers/avatarInputValidation.js +97 -0
  210. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  211. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  212. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  213. package/dist/resolvers/currentUserRoles.js +30 -0
  214. package/dist/resolvers/currentUserRoles.js.map +1 -1
  215. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  216. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  217. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  218. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  219. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  220. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  221. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  222. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  223. package/dist/sharedCache.d.ts +105 -0
  224. package/dist/sharedCache.d.ts.map +1 -0
  225. package/dist/sharedCache.js +276 -0
  226. package/dist/sharedCache.js.map +1 -0
  227. package/dist/types.d.ts +6 -0
  228. package/dist/types.d.ts.map +1 -1
  229. package/dist/types.js.map +1 -1
  230. package/package.json +97 -95
  231. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  232. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  233. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  234. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  235. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  236. package/src/__tests__/HostInstance.test.ts +84 -0
  237. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  238. package/src/__tests__/MeetingResolver.test.ts +75 -1
  239. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  240. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  241. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  242. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  243. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  244. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  245. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  246. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  247. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  248. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  249. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  250. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  251. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  252. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  253. package/src/__tests__/SessionJanitor.test.ts +845 -18
  254. package/src/__tests__/SessionManager.test.ts +213 -17
  255. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  256. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  257. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  258. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  259. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  260. package/src/__tests__/actionAuthorization.test.ts +130 -0
  261. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  262. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  263. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  264. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  265. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  266. package/src/__tests__/newUsers.template.test.ts +151 -0
  267. package/src/__tests__/newUsers.test.ts +74 -5
  268. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  269. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  270. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  271. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  272. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  273. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  274. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  275. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  276. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  277. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  278. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  279. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  280. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  281. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  282. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  283. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  284. package/src/__tests__/sharedCache.test.ts +310 -0
  285. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  286. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  287. package/src/agentSessions/HostInstance.ts +124 -28
  288. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  289. package/src/agentSessions/SessionJanitor.ts +543 -57
  290. package/src/agentSessions/SessionManager.ts +125 -25
  291. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  292. package/src/agentSessions/index.ts +10 -3
  293. package/src/auth/actionAuthorization.ts +116 -0
  294. package/src/auth/index.ts +3 -8
  295. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  296. package/src/auth/newUsers.ts +99 -11
  297. package/src/config.ts +125 -0
  298. package/src/context.ts +15 -10
  299. package/src/generated/graphql-schemas/__mj.ts +496 -1
  300. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  301. package/src/generic/ResolverBase.ts +39 -9
  302. package/src/generic/RunViewResolver.ts +4 -0
  303. package/src/index.ts +132 -143
  304. package/src/isolatedProvider.ts +22 -0
  305. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  306. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  307. package/src/realtimeSessions/README.md +179 -0
  308. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  309. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  310. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  311. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  312. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  313. package/src/realtimeSessions/index.ts +16 -0
  314. package/src/realtimeSessions/sessionAccess.ts +141 -0
  315. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  316. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  317. package/src/realtimeSessions/verificationCore.ts +502 -0
  318. package/src/realtimeSessions/verificationPages.ts +149 -0
  319. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  320. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  321. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  322. package/src/resolvers/ActionResolver.ts +84 -21
  323. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  324. package/src/resolvers/CacheStatsResolver.ts +89 -2
  325. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  326. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  327. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  328. package/src/resolvers/MCPResolver.ts +8 -5
  329. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  330. package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
  331. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  332. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  333. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  334. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  335. package/src/resolvers/RunTemplateResolver.ts +13 -12
  336. package/src/resolvers/SyncDataResolver.ts +6 -2
  337. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  338. package/src/resolvers/UserAvatarResolver.ts +111 -0
  339. package/src/resolvers/UserResolver.ts +2 -16
  340. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  341. package/src/resolvers/avatarInputValidation.ts +123 -0
  342. package/src/resolvers/currentUserRoles.ts +32 -1
  343. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  344. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  345. package/src/sharedCache.ts +302 -0
  346. package/src/types.ts +7 -0
@@ -1,7 +1,9 @@
1
- import { ApplicationInfo, DatabaseProviderBase, EntitySaveOptions, LogError, LogStatus, Metadata, RunView, RunViewResult, UserInfo } from "@memberjunction/core";
1
+ import { ApplicationInfo, CompositeKey, EntitySaveOptions, IMetadataProvider, LogError, LogStatus, RunView, RunViewResult, UserInfo } from "@memberjunction/core";
2
+ import { RecordCloneEngine } from "@memberjunction/record-cloning";
2
3
  import { RegisterClass } from "@memberjunction/global";
3
4
  import { ResolveConfiguredPrincipal } from "./principals.js";
4
5
  import { configInfo } from "../config.js";
6
+ import { CreateIsolatedProvider } from "../isolatedProvider.js";
5
7
  import { MJUserEntity, MJUserRoleEntity, MJUserApplicationEntity, MJUserApplicationEntityEntity, MJApplicationEntityType, MJApplicationEntityEntityType, UserInfoEngine } from "@memberjunction/core-entities";
6
8
 
7
9
  export class NewUserBase {
@@ -16,8 +18,21 @@ export class NewUserBase {
16
18
  return null;
17
19
  }
18
20
 
19
- const md: Metadata = new Metadata(); // global-provider-ok: new-user creation runs in the JWT auth flow, before AppContext.providers is built
20
- const user = await md.GetEntityObject<MJUserEntity>('MJ: Users', contextUser) // To-Do - change this to be a different defined user for the user creation process
21
+ // Everything here, metadata lookups, entity objects and the transaction, goes through ONE
22
+ // isolated provider. This runs in the JWT auth flow, before AppContext.providers exists, and
23
+ // the global Metadata.Provider is shared by every concurrent request: a transaction opened
24
+ // there nests into anyone else's and swallows their writes (see CreateIsolatedProvider).
25
+ const provider = await CreateIsolatedProvider();
26
+
27
+ // A configured template user is copied whole; a plain user is created only if it can't be.
28
+ const template = configInfo?.userHandling?.newUserTemplate;
29
+ if (template?.email) {
30
+ const fromTemplate = await this.createFromTemplate(provider, contextUser, template.email, template.preset, firstName, lastName, email, linkedRecordType, linkedEntityId, linkedEntityRecordId);
31
+ if (fromTemplate) return fromTemplate;
32
+ LogError(`Could not create ${email} from template user ${template.email}; creating a plain user instead`);
33
+ }
34
+
35
+ const user = await provider.GetEntityObject<MJUserEntity>('MJ: Users', contextUser) // To-Do - change this to be a different defined user for the user creation process
21
36
  user.NewRecord();
22
37
  user.Name = email;
23
38
  user.IsActive = true;
@@ -38,7 +53,6 @@ export class NewUserBase {
38
53
  // Create the user and all of its role/application/app-entity records atomically.
39
54
  // If any Save fails partway through, the whole provisioning rolls back so we never
40
55
  // leave a half-created user with partial roles/applications behind.
41
- const provider = Metadata.Provider as DatabaseProviderBase; // global-provider-ok: new-user creation runs in the JWT auth flow, before AppContext.providers is built
42
56
  await provider.BeginTransaction();
43
57
  try {
44
58
  if (!await user.Save()) {
@@ -48,13 +62,13 @@ export class NewUserBase {
48
62
  if(configInfo.userHandling && configInfo.userHandling.newUserRoles){
49
63
  LogStatus(`User ${user.Email} created, assigning roles`);
50
64
  for (const role of configInfo.userHandling.newUserRoles) {
51
- const userRole = md.Roles.find(r => r.Name === role);
65
+ const userRole = provider.Roles.find(r => r.Name === role);
52
66
  if (!userRole) {
53
67
  LogError(`Role ${role} not found in the database, cannot assign to new user ${user.Name}`);
54
68
  continue;
55
69
  }
56
70
 
57
- const userRoleEntity: MJUserRoleEntity = await md.GetEntityObject<MJUserRoleEntity>('MJ: User Roles', contextUser);
71
+ const userRoleEntity: MJUserRoleEntity = await provider.GetEntityObject<MJUserRoleEntity>('MJ: User Roles', contextUser);
58
72
  userRoleEntity.NewRecord();
59
73
  userRoleEntity.UserID = user.ID;
60
74
  userRoleEntity.RoleID = userRole.ID;
@@ -73,7 +87,7 @@ export class NewUserBase {
73
87
  if (configInfo.userHandling.UserApplications && configInfo.userHandling.UserApplications.length > 0) {
74
88
  for (const appName of configInfo.userHandling.UserApplications) {
75
89
  const toLowerCase: string = appName.trim().toLocaleLowerCase();
76
- const application: ApplicationInfo | undefined = md.Applications.find(a => a.Name.trim().toLocaleLowerCase() === toLowerCase);
90
+ const application: ApplicationInfo | undefined = provider.Applications.find(a => a.Name.trim().toLocaleLowerCase() === toLowerCase);
77
91
  if (application) {
78
92
  applicationsToCreate.push(application);
79
93
  } else {
@@ -85,12 +99,12 @@ export class NewUserBase {
85
99
  // Bug F2: use the shared selector, which filters to Active apps flagged
86
100
  // DefaultForNewUser (in DefaultSequence order). This path previously omitted the
87
101
  // Status = 'Active' check, so an inactive default app could be provisioned here.
88
- applicationsToCreate = UserInfoEngine.GetDefaultApplicationsForNewUser(md);
102
+ applicationsToCreate = UserInfoEngine.GetDefaultApplicationsForNewUser(provider);
89
103
  LogStatus(`Found ${applicationsToCreate.length} active applications with DefaultForNewUser=true`);
90
104
  }
91
105
 
92
106
  for (const [appIndex, application] of applicationsToCreate.entries()) {
93
- const userApplication: MJUserApplicationEntity = await md.GetEntityObject<MJUserApplicationEntity>('MJ: User Applications', contextUser);
107
+ const userApplication: MJUserApplicationEntity = await provider.GetEntityObject<MJUserApplicationEntity>('MJ: User Applications', contextUser);
94
108
  userApplication.NewRecord();
95
109
  userApplication.UserID = user.ID;
96
110
  userApplication.ApplicationID = application.ID;
@@ -102,7 +116,7 @@ export class NewUserBase {
102
116
  }
103
117
  LogStatus(`Created User Application ${application.Name} for new user ${user.Name}`);
104
118
 
105
- const rv: RunView = new RunView();
119
+ const rv: RunView = RunView.FromMetadataProvider(provider);
106
120
  // Boolean literal goes through the active provider's dialect:
107
121
  // SQL Server emits `= 1`, PostgreSQL emits `= TRUE`.
108
122
  // This keeps the filter server-side (no client-side `.filter()`
@@ -125,7 +139,7 @@ export class NewUserBase {
125
139
  LogStatus(`Creating ${defaultForNewUserEntities.length} User Application Entities for User Application ${application.Name} for new user ${user.Name}`);
126
140
 
127
141
  for(const [index, appEntity] of defaultForNewUserEntities.entries()){
128
- const userAppEntity: MJUserApplicationEntityEntity = await md.GetEntityObject<MJUserApplicationEntityEntity>('MJ: User Application Entities', contextUser);
142
+ const userAppEntity: MJUserApplicationEntityEntity = await provider.GetEntityObject<MJUserApplicationEntityEntity>('MJ: User Application Entities', contextUser);
129
143
  userAppEntity.NewRecord();
130
144
  userAppEntity.UserApplicationID = userApplication.ID;
131
145
  userAppEntity.EntityID = appEntity.EntityID;
@@ -153,4 +167,78 @@ export class NewUserBase {
153
167
  return undefined;
154
168
  }
155
169
  }
170
+
171
+ /**
172
+ * Creates the new user as a copy of the template user `templateEmail`, through the `MJ: Users`
173
+ * clone `preset`: its roles, apps, views, settings, dashboards and conversations come with it, owned
174
+ * by the new user. The clone engine creates users inactive for review; a user signing in is active.
175
+ *
176
+ * @returns the new user, or null when the template is missing, isn't flagged IsTemplate, or the copy
177
+ * fails (the reason is logged and the caller falls back to a plain user).
178
+ */
179
+ protected async createFromTemplate(
180
+ provider: IMetadataProvider,
181
+ contextUser: UserInfo,
182
+ templateEmail: string,
183
+ preset: string,
184
+ firstName: string,
185
+ lastName: string,
186
+ email: string,
187
+ linkedRecordType: string,
188
+ linkedEntityId?: string,
189
+ linkedEntityRecordId?: string
190
+ ): Promise<MJUserEntity | null> {
191
+ const lookup = await RunView.FromMetadataProvider(provider).RunView<{ ID: string; IsTemplate: boolean }>(
192
+ {
193
+ EntityName: 'MJ: Users',
194
+ ExtraFilter: `Email = '${templateEmail.replace(/'/g, "''")}'`,
195
+ Fields: ['ID', 'IsTemplate'],
196
+ ResultType: 'simple',
197
+ MaxRows: 1,
198
+ },
199
+ contextUser
200
+ );
201
+ const templateUser = lookup.Success ? lookup.Results[0] : undefined;
202
+ if (!templateUser) {
203
+ LogError(`userHandling.newUserTemplate: no user has the email ${templateEmail}`);
204
+ return null;
205
+ }
206
+ if (!templateUser.IsTemplate) {
207
+ LogError(`userHandling.newUserTemplate: ${templateEmail} is not marked as a template user (IsTemplate)`);
208
+ return null;
209
+ }
210
+
211
+ const result = await new RecordCloneEngine(provider).Clone(
212
+ {
213
+ EntityName: 'MJ: Users',
214
+ SourceRecordKey: CompositeKey.FromID(templateUser.ID),
215
+ Preset: preset,
216
+ PromptedValues: { Email: email, FirstName: firstName, LastName: lastName },
217
+ Options: { Reason: `New user ${email} created at first sign-in from template ${templateEmail}` },
218
+ },
219
+ contextUser
220
+ );
221
+ const newKey = result.Roots?.[0]?.TargetKey.KeyValuePairs.find((kvp) => kvp.FieldName === 'ID')?.Value;
222
+ if (!result.Success || !newKey) {
223
+ const reasons = result.Warnings.filter((w) => w.Severity === 'Error').map((w) => w.Message);
224
+ LogError(`userHandling.newUserTemplate: copying ${templateEmail} failed: ${result.ErrorMessage || reasons.join('; ') || result.ResultCode}`);
225
+ return null;
226
+ }
227
+
228
+ const user = await provider.GetEntityObject<MJUserEntity>('MJ: Users', contextUser);
229
+ if (!(await user.Load(String(newKey)))) {
230
+ LogError(`userHandling.newUserTemplate: the copy of ${templateEmail} for ${email} could not be loaded`);
231
+ return null;
232
+ }
233
+ user.IsActive = true;
234
+ user.LinkedRecordType = linkedRecordType;
235
+ if (linkedEntityId) user.LinkedEntityID = linkedEntityId;
236
+ if (linkedEntityRecordId) user.LinkedEntityRecordID = linkedEntityRecordId;
237
+ if (!(await user.Save())) {
238
+ LogError(`userHandling.newUserTemplate: could not activate ${email}: ${user.LatestResult?.CompleteMessage ?? 'unknown error'}`);
239
+ return null;
240
+ }
241
+ LogStatus(`Created user ${email} from template ${templateEmail} (${result.RecordsCloned ?? result.Created?.length ?? 0} records copied)`);
242
+ return user;
243
+ }
156
244
  }
package/src/config.ts CHANGED
@@ -38,6 +38,19 @@ const userHandlingInfoSchema = z.object({
38
38
  contextUserForNewUserCreation: z.string().optional().default(''),
39
39
  CreateUserApplicationRecords: z.boolean().optional().default(false),
40
40
  UserApplications: z.array(z.string()).optional().default([]),
41
+ /**
42
+ * Build each auto-created user as a copy of a template user (`User.IsTemplate`), so new people start
43
+ * with its pinned apps, views, settings, dashboards and conversations. `email` names the template;
44
+ * `preset` is the `MJ: Users` clone preset to apply (default `template`). The copy takes the roles
45
+ * and apps of the template, so `newUserRoles`, `CreateUserApplicationRecords` and `UserApplications`
46
+ * apply only when the template can't be used, in which case a plain user is created as before.
47
+ */
48
+ newUserTemplate: z
49
+ .object({
50
+ email: z.string(),
51
+ preset: z.string().optional().default('template'),
52
+ })
53
+ .optional(),
41
54
  });
42
55
 
43
56
  const databaseSettingsInfoSchema = z.object({
@@ -254,6 +267,38 @@ const cacheSettingsSchema = z.object({
254
267
  * every signed-in user may read every row of every entity.
255
268
  */
256
269
  recordDataBroadcastEntities: z.array(z.string()).optional().default([]),
270
+ /**
271
+ * Expiry, in seconds, of entries in the shared Redis cache (used when REDIS_URL is set). The
272
+ * REDIS_TTL_SECONDS environment variable overrides it. 0 stores entries without expiry. When
273
+ * neither is set, the Redis provider's default applies (one hour).
274
+ */
275
+ sharedCacheTTLSeconds: z.number().int().nonnegative().optional(),
276
+ /**
277
+ * How often, in seconds, loaded engines compare their rows with the database and reload what
278
+ * changed without an MJ event (direct SQL, other applications). With a shared cache one server
279
+ * does it per interval. 0 = disabled. Default: 300 (5 minutes).
280
+ */
281
+ engineSweepIntervalSeconds: z.number().int().nonnegative().optional().default(300),
282
+ /**
283
+ * How often, in seconds, the user cache compares two row counts and the newest update timestamp
284
+ * with the database and reloads only when they differ. The cache already refreshes on MJ writes
285
+ * (locally and, with a shared cache, on other servers); this covers changes made outside MJ.
286
+ * 0 = disabled. Default: 300 (5 minutes).
287
+ */
288
+ userCacheCheckIntervalSeconds: z.number().int().nonnegative().optional().default(300),
289
+ /**
290
+ * How often to compare this process's metadata with the database, in seconds (0 disables).
291
+ * Costs nothing unless an entity the metadata is built from declares
292
+ * `TrustServerCacheCompletely = false`; see `ProviderBase.SweepMetadataAgainstDatabase`.
293
+ */
294
+ metadataSweepIntervalSeconds: z.number().int().nonnegative().optional().default(300),
295
+ /**
296
+ * How long, in seconds, a server holds the shared warm-up turn while it loads its engines, so
297
+ * servers starting together load one at a time and the rest find the cache warm. The lease is
298
+ * renewed while the load runs; this value is both its expiry and how long another server waits
299
+ * for it. 0 disables the turn-taking. Default: 30.
300
+ */
301
+ startupWarmupLeaseSeconds: z.number().int().nonnegative().optional().default(30),
257
302
  });
258
303
 
259
304
  const loggingSettingsSchema = z.object({
@@ -684,9 +729,87 @@ const telephonySchema = z.object({
684
729
  teams: teamsMeetingsSchema.optional(),
685
730
  }).passthrough();
686
731
 
732
+ /**
733
+ * Mid-session identity verification for realtime sessions (see `src/realtimeSessions/README.md`).
734
+ *
735
+ * A person talking to an agent proves they control an email address WITHOUT leaving the conversation:
736
+ * the server emails a one-time link plus a short code, records the verification on the session, extends
737
+ * the session's limits, and tells the live session through a session event. The session's principal is
738
+ * never changed.
739
+ *
740
+ * Off by default: nothing is emailed and `/realtime/verify` is not mounted until `enabled` is true AND a
741
+ * `communicationProvider` is configured. Policy set here is the BASE layer; the config cascade
742
+ * (`channels.config.IdentityVerification`) snapshotted onto each session at mint overrides it per knob.
743
+ */
744
+ const identityVerificationSchema = z.object({
745
+ /** Master switch. When false the verify routes are not mounted and the GraphQL operations refuse. */
746
+ enabled: zodBooleanWithTransforms().default(false),
747
+ /** CommunicationEngine provider that delivers the verification email (e.g. 'SendGrid', 'Microsoft Graph'). Required to send. */
748
+ communicationProvider: z.string().optional(),
749
+ /** From address of the verification email. */
750
+ fromAddress: z.string().optional(),
751
+ /**
752
+ * Server-only secret the typed verification code is HMAC'd under before it is stored on the session
753
+ * (at least 16 characters; 32+ random characters recommended). REQUIRED: without it verification is
754
+ * unavailable, because a session's owner can read their own session row and a bare hash of a six-digit
755
+ * code can be reversed offline. Never exposed to clients. Rotating it voids pending verifications only.
756
+ */
757
+ hmacSecret: z.string().optional().default(process.env.MJ_REALTIME_VERIFICATION_SECRET || ''),
758
+ /**
759
+ * `send` (default) delivers the email. `dry-run` builds and logs the message through the communication
760
+ * provider WITHOUT delivering it — for rehearsal and integration environments, where a test reads the
761
+ * emailed link and code from the `MJ: Communication Logs` row. Never use `dry-run` in production: the
762
+ * Communication Log then holds live credentials and no one is emailed.
763
+ */
764
+ deliveryMode: z.enum(['send', 'dry-run']).optional().default('send'),
765
+ /**
766
+ * Base URL the emailed link is built from (`<publicBaseUrl>/realtime/verify/<token>`). Defaults to the
767
+ * server's `publicUrl`, or `baseUrl:graphqlPort` when that is unset — the same derivation magic links use.
768
+ */
769
+ publicBaseUrl: z.string().optional(),
770
+ /**
771
+ * The internal user the server acts as to read/write the session, send the email and write the audit
772
+ * row, matched against `User.Name` then `User.Email` (falls back to the system user, then the lowest-ID
773
+ * active Owner). The verify link is public, so it has no caller identity of its own.
774
+ */
775
+ contextUserForVerification: z.string().optional(),
776
+ /** Base policy layer; each knob here is overridden by the per-session cascade layer. */
777
+ policy: z.object({
778
+ requireBusinessDomain: z.boolean().optional(),
779
+ blockedDomains: z.array(z.string()).optional(),
780
+ consumerDomains: z.array(z.string()).optional(),
781
+ linkTtlMinutes: z.coerce.number().optional(),
782
+ maxSendsPerSession: z.coerce.number().optional(),
783
+ maxCodeAttempts: z.coerce.number().optional(),
784
+ unverifiedMaxSeconds: z.coerce.number().optional(),
785
+ verifiedMaxSeconds: z.coerce.number().optional(),
786
+ }).optional().default({}),
787
+ /** Send-side and attempt-side limits (per instance, in memory; per-session limits are persisted and exact). */
788
+ rateLimits: z.object({
789
+ /** Window (ms) for the three send limits. Default 1 hour. */
790
+ sendWindowMs: z.coerce.number().optional().default(3_600_000),
791
+ /** Verification emails per client IP per window. Default 10. 0 disables. */
792
+ perIpSends: z.coerce.number().optional().default(10),
793
+ /** Verification emails per recipient domain per window. Default 30. 0 disables. */
794
+ perEmailDomainSends: z.coerce.number().optional().default(30),
795
+ /** Verification emails per recipient address per window — stops the feature being used to mail-bomb one inbox. Default 3. 0 disables. */
796
+ perEmailSends: z.coerce.number().optional().default(3),
797
+ /** Window (ms) for code attempts. Default 10 minutes. */
798
+ codeAttemptWindowMs: z.coerce.number().optional().default(600_000),
799
+ /** Code submissions per session per window. Default 10. 0 disables. */
800
+ perSessionCodeAttempts: z.coerce.number().optional().default(10),
801
+ /** Window (ms) of the public `/realtime/verify` route limiter. Default 60s. */
802
+ redeemWindowMs: z.coerce.number().optional().default(60_000),
803
+ /** `/realtime/verify` requests per IP per window. Default 30. */
804
+ redeemPerIp: z.coerce.number().optional().default(30),
805
+ }).optional().default({}),
806
+ }).passthrough();
807
+
687
808
  const realtimeSchema = z.object({
688
809
  /** Master switch. When false, the WebRTC SDP broker router is not mounted. Defaults to true. */
689
810
  enabled: zodBooleanWithTransforms().default(true),
811
+ /** Mid-session identity verification (off by default). */
812
+ identityVerification: identityVerificationSchema.optional().default({}),
690
813
  }).passthrough();
691
814
 
692
815
  const configInfoSchema = z.object({
@@ -751,6 +874,7 @@ export type MagicLinkConfig = z.infer<typeof magicLinkSchema>;
751
874
  export type WidgetConfig = z.infer<typeof widgetSchema>;
752
875
  export type TelephonyConfig = z.infer<typeof telephonySchema>;
753
876
  export type RealtimeConfig = z.infer<typeof realtimeSchema>;
877
+ export type IdentityVerificationConfig = z.infer<typeof identityVerificationSchema>;
754
878
  export type TwilioTelephonyConfig = z.infer<typeof twilioTelephonySchema>;
755
879
  export type VonageTelephonyConfig = z.infer<typeof vonageTelephonySchema>;
756
880
  export type RingCentralTelephonyConfig = z.infer<typeof ringcentralTelephonySchema>;
@@ -926,6 +1050,7 @@ export const DEFAULT_SERVER_CONFIG: Partial<ConfigInfo> = {
926
1050
  defaultTTLSeconds: 0,
927
1051
  evictionSweepIntervalSeconds: 300,
928
1052
  verboseLogging: false,
1053
+ engineSweepIntervalSeconds: 300,
929
1054
  },
930
1055
 
931
1056
  // Logging settings defaults — variables logging is always off unless the operator
package/src/context.ts CHANGED
@@ -781,6 +781,7 @@ export const ContextFunction =
781
781
  dataSources,
782
782
  userPayload,
783
783
  providers,
784
+ ClientIp: expressReq.ip,
784
785
  };
785
786
  };
786
787
 
@@ -811,21 +812,25 @@ async function createPerRequestProviders(
811
812
  { provider: p, type: 'Read-Write' }
812
813
  ];
813
814
 
814
- if (isPostgres) {
815
- const rp = await tryCreateReadOnlyPostgresProvider();
816
- if (rp) {
817
- providers.push({ provider: rp, type: 'Read-Only' });
818
- }
819
- } else {
820
- const rp = await tryCreateReadOnlyProvider(dataSources);
821
- if (rp) {
822
- providers.push({ provider: rp, type: 'Read-Only' });
823
- }
815
+ const rp = await CreateReadOnlyProvider(dataSources);
816
+ if (rp) {
817
+ providers.push({ provider: rp, type: 'Read-Only' });
824
818
  }
825
819
 
826
820
  return providers;
827
821
  }
828
822
 
823
+ /**
824
+ * Opens a provider on the read-only database login, for the current platform. Resolves null when no
825
+ * read-only login is configured; it never falls back to the read-write pool.
826
+ * @param dataSources the data sources MJAPI opened at startup
827
+ */
828
+ export async function CreateReadOnlyProvider(dataSources: DataSourceInfo[]): Promise<DatabaseProviderBase | null> {
829
+ return resolveDbPlatformFromEnv() === 'postgresql'
830
+ ? tryCreateReadOnlyPostgresProvider()
831
+ : tryCreateReadOnlyProvider(dataSources);
832
+ }
833
+
829
834
  /**
830
835
  * Creates a PostgreSQL per-request provider, sharing the connection pool
831
836
  * from the primary provider to avoid pool exhaustion.