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

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 (463) hide show
  1. package/README.md +69 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
  5. package/dist/agentSessions/BridgeHostInstance.js +14 -4
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
  7. package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
  8. package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
  9. package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
  10. package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
  11. package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +31 -8
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +85 -41
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +14 -4
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +28 -4
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
  22. package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
  23. package/dist/agentSessions/conversationBrowsers.js +118 -0
  24. package/dist/agentSessions/conversationBrowsers.js.map +1 -0
  25. package/dist/agentSessions/sessionDeadline.d.ts +43 -0
  26. package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
  27. package/dist/agentSessions/sessionDeadline.js +68 -0
  28. package/dist/agentSessions/sessionDeadline.js.map +1 -0
  29. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  30. package/dist/auth/APIKeyScopeAuth.js +5 -4
  31. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  32. package/dist/auth/loginEmailResolution.d.ts +41 -0
  33. package/dist/auth/loginEmailResolution.d.ts.map +1 -0
  34. package/dist/auth/loginEmailResolution.js +47 -0
  35. package/dist/auth/loginEmailResolution.js.map +1 -0
  36. package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
  37. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
  39. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  40. package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
  41. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  42. package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
  43. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  44. package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
  45. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  46. package/dist/auth/magicLink/MagicLinkService.js +221 -9
  47. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  48. package/dist/auth/magicLink/index.d.ts +1 -1
  49. package/dist/auth/magicLink/index.d.ts.map +1 -1
  50. package/dist/auth/magicLink/index.js +1 -1
  51. package/dist/auth/magicLink/index.js.map +1 -1
  52. package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
  53. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  54. package/dist/auth/magicLink/redeemLanding.js +7 -4
  55. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  56. package/dist/auth/magicLink/types.d.ts +27 -3
  57. package/dist/auth/magicLink/types.d.ts.map +1 -1
  58. package/dist/auth/magicLink/types.js +0 -4
  59. package/dist/auth/magicLink/types.js.map +1 -1
  60. package/dist/auth/newUsers.d.ts.map +1 -1
  61. package/dist/auth/newUsers.js +1 -0
  62. package/dist/auth/newUsers.js.map +1 -1
  63. package/dist/backgroundSweepConfigUnits.d.ts +231 -0
  64. package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
  65. package/dist/backgroundSweepConfigUnits.js +180 -0
  66. package/dist/backgroundSweepConfigUnits.js.map +1 -0
  67. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
  68. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
  69. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
  70. package/dist/config.d.ts +906 -8
  71. package/dist/config.d.ts.map +1 -1
  72. package/dist/config.js +59 -0
  73. package/dist/config.js.map +1 -1
  74. package/dist/context.d.ts +2 -0
  75. package/dist/context.d.ts.map +1 -1
  76. package/dist/context.js +41 -12
  77. package/dist/context.js.map +1 -1
  78. package/dist/coreRoutes.d.ts +3 -1
  79. package/dist/coreRoutes.d.ts.map +1 -1
  80. package/dist/coreRoutes.js +6 -4
  81. package/dist/coreRoutes.js.map +1 -1
  82. package/dist/generated/graphql-schemas/__mj.d.ts +1072 -5
  83. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  84. package/dist/generated/graphql-schemas/__mj.js +10174 -4618
  85. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  87. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  88. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  89. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +50 -5
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +329 -132
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +4 -0
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +47 -7
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/globalCors.d.ts.map +1 -1
  99. package/dist/globalCors.js +1 -0
  100. package/dist/globalCors.js.map +1 -1
  101. package/dist/index.d.ts +3 -0
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +201 -43
  104. package/dist/index.js.map +1 -1
  105. package/dist/integration/ConnectionPause.d.ts +121 -0
  106. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  107. package/dist/integration/ConnectionPause.js +183 -0
  108. package/dist/integration/ConnectionPause.js.map +1 -0
  109. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +87 -46
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  114. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  115. package/dist/integration/EntityMapLifecycle.js +199 -5
  116. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  117. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  118. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  119. package/dist/integration/InactiveRowWarnings.js +29 -0
  120. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  121. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  122. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  123. package/dist/integration/ObjectNamesByID.js +87 -0
  124. package/dist/integration/ObjectNamesByID.js.map +1 -0
  125. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  126. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  127. package/dist/integration/PostRestartIntrospect.js +27 -0
  128. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  129. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  130. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  131. package/dist/integration/RSUPostRestartProgress.js +323 -0
  132. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  133. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  134. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  135. package/dist/integration/RSUProgressBridge.js +84 -1
  136. package/dist/integration/RSUProgressBridge.js.map +1 -1
  137. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  138. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  139. package/dist/integration/RunArtifactAuthorization.js +45 -0
  140. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  141. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  142. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  143. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  144. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  145. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  146. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  147. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  148. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  149. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  150. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  151. package/dist/integration/TargetConfigGuard.js +40 -0
  152. package/dist/integration/TargetConfigGuard.js.map +1 -0
  153. package/dist/logging/StartupLogger.d.ts +50 -0
  154. package/dist/logging/StartupLogger.d.ts.map +1 -1
  155. package/dist/logging/StartupLogger.js +15 -22
  156. package/dist/logging/StartupLogger.js.map +1 -1
  157. package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
  158. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
  159. package/dist/middleware/BaseServerMiddleware.js +4 -2
  160. package/dist/middleware/BaseServerMiddleware.js.map +1 -1
  161. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  162. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  163. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  164. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  165. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  166. package/dist/realtimeSessions/sessionAccess.js +22 -0
  167. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  168. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  169. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  170. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  171. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  172. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  173. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  174. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  175. package/dist/realtimeWidget/widgetCore.js +4 -7
  176. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  177. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  178. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  179. package/dist/requestOrigin.d.ts +22 -0
  180. package/dist/requestOrigin.d.ts.map +1 -0
  181. package/dist/requestOrigin.js +60 -0
  182. package/dist/requestOrigin.js.map +1 -0
  183. package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
  184. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  185. package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
  186. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  187. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  188. package/dist/resolvers/AgentSessionResolver.js +6 -2
  189. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  190. package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
  191. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  192. package/dist/resolvers/AutotagPipelineResolver.js +61 -16
  193. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  194. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  195. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  196. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  197. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  198. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  199. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  200. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  201. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  202. package/dist/resolvers/GetDataResolver.js +34 -13
  203. package/dist/resolvers/GetDataResolver.js.map +1 -1
  204. package/dist/resolvers/InfoResolver.js +1 -1
  205. package/dist/resolvers/InfoResolver.js.map +1 -1
  206. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  207. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  208. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  209. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  210. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  211. package/dist/resolvers/MCPResolver.js +13 -2
  212. package/dist/resolvers/MCPResolver.js.map +1 -1
  213. package/dist/resolvers/QuerySystemUserResolver.d.ts +38 -1
  214. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  215. package/dist/resolvers/QuerySystemUserResolver.js +181 -88
  216. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  217. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  218. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  219. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  220. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  221. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  222. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  223. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  224. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  225. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  226. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  227. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  228. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  229. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  230. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  231. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  232. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  233. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  234. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  235. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  236. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  237. package/dist/resolvers/RunTemplateResolver.js +3 -1
  238. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  239. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  240. package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
  241. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  242. package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
  243. package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
  244. package/dist/resolvers/TagGovernanceResolver.js +84 -0
  245. package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
  246. package/dist/resolvers/TelemetryResolver.d.ts +13 -8
  247. package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
  248. package/dist/resolvers/TelemetryResolver.js +40 -9
  249. package/dist/resolvers/TelemetryResolver.js.map +1 -1
  250. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  251. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  252. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  253. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  254. package/dist/resolvers/meetings/meetingMutations.js +34 -4
  255. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  256. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  257. package/dist/rest/MediaAccessKeys.js +6 -2
  258. package/dist/rest/MediaAccessKeys.js.map +1 -1
  259. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  260. package/dist/rest/OAuthCallbackHandler.js +11 -5
  261. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  262. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  263. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  264. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  265. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  266. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  267. package/dist/rest/ViewOperationsHandler.js +2 -1
  268. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  269. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  270. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  271. package/dist/rest/setupRESTEndpoints.js +11 -2
  272. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  273. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  274. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  275. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  276. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  277. package/dist/services/TaskGraphActionRunner.js +2 -26
  278. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  279. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  280. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  281. package/dist/services/TaskGraphAgentRunner.js +38 -0
  282. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  283. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  284. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  285. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  286. package/dist/services/WorkQueueHostService.d.ts +2 -1
  287. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  288. package/dist/services/WorkQueueHostService.js +19 -4
  289. package/dist/services/WorkQueueHostService.js.map +1 -1
  290. package/dist/services/workQueueConfig.d.ts +2 -2
  291. package/dist/services/workQueueConfig.js +5 -5
  292. package/dist/services/workQueueConfig.js.map +1 -1
  293. package/dist/sharedCache.js +1 -1
  294. package/dist/sharedCache.js.map +1 -1
  295. package/dist/types.d.ts +17 -0
  296. package/dist/types.d.ts.map +1 -1
  297. package/dist/types.js.map +1 -1
  298. package/dist/util.d.ts.map +1 -1
  299. package/dist/util.js +55 -2
  300. package/dist/util.js.map +1 -1
  301. package/package.json +107 -100
  302. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  303. package/src/__tests__/ActiveOperations.test.ts +68 -0
  304. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  305. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  306. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  307. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  308. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  309. package/src/__tests__/ConnectionPause.test.ts +192 -0
  310. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  311. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  312. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  313. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  314. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  315. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  316. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  317. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  318. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  319. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  320. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  321. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  322. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  323. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  324. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  325. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  326. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  327. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  328. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  329. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  330. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  331. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  332. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  333. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  334. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  335. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  336. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  337. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  338. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  339. package/src/__tests__/ResolverBase.clientOldValues.test.ts +327 -26
  340. package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
  341. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  342. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  343. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  344. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  345. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  346. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  347. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  348. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  349. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  350. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
  351. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  352. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  353. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  354. package/src/__tests__/SessionJanitor.test.ts +195 -4
  355. package/src/__tests__/SessionManager.test.ts +69 -0
  356. package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
  357. package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
  358. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  359. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  360. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  361. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  362. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  363. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  364. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  365. package/src/__tests__/globalCors.test.ts +12 -0
  366. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  367. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  368. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  369. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  370. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  371. package/src/__tests__/requestOrigin.test.ts +104 -0
  372. package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
  373. package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
  374. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  375. package/src/__tests__/sessionDeadline.test.ts +51 -0
  376. package/src/__tests__/sharedCache.test.ts +11 -0
  377. package/src/__tests__/unifiedAuth.test.ts +73 -1
  378. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  379. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  380. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  381. package/src/agentSessions/SessionJanitor.ts +92 -42
  382. package/src/agentSessions/SessionManager.ts +38 -8
  383. package/src/agentSessions/conversationBrowsers.ts +143 -0
  384. package/src/agentSessions/sessionDeadline.ts +81 -0
  385. package/src/auth/APIKeyScopeAuth.ts +5 -4
  386. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  387. package/src/auth/loginEmailResolution.ts +67 -0
  388. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  389. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  390. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  391. package/src/auth/magicLink/index.ts +1 -1
  392. package/src/auth/magicLink/redeemLanding.ts +7 -4
  393. package/src/auth/magicLink/types.ts +28 -3
  394. package/src/auth/newUsers.ts +1 -0
  395. package/src/backgroundSweepConfigUnits.ts +213 -0
  396. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
  397. package/src/config.ts +69 -0
  398. package/src/context.ts +46 -15
  399. package/src/coreRoutes.ts +14 -7
  400. package/src/generated/graphql-schemas/__mj.ts +10360 -6479
  401. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  402. package/src/generic/ResolverBase.ts +422 -143
  403. package/src/generic/RunViewResolver.ts +96 -49
  404. package/src/globalCors.ts +1 -0
  405. package/src/index.ts +246 -44
  406. package/src/integration/ConnectionPause.ts +222 -0
  407. package/src/integration/CustomColumnPromoter.ts +123 -53
  408. package/src/integration/EntityMapLifecycle.ts +229 -6
  409. package/src/integration/InactiveRowWarnings.ts +50 -0
  410. package/src/integration/ObjectNamesByID.ts +97 -0
  411. package/src/integration/PostRestartIntrospect.ts +49 -0
  412. package/src/integration/RSUPostRestartProgress.ts +359 -0
  413. package/src/integration/RSUProgressBridge.ts +85 -2
  414. package/src/integration/RunArtifactAuthorization.ts +65 -0
  415. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  416. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  417. package/src/integration/TargetConfigGuard.ts +71 -0
  418. package/src/logging/StartupLogger.ts +24 -0
  419. package/src/middleware/BaseServerMiddleware.ts +20 -3
  420. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  421. package/src/realtimeSessions/sessionAccess.ts +30 -0
  422. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  423. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  424. package/src/realtimeWidget/widgetCore.ts +4 -8
  425. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  426. package/src/requestOrigin.ts +64 -0
  427. package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
  428. package/src/resolvers/AgentSessionResolver.ts +6 -2
  429. package/src/resolvers/AutotagPipelineResolver.ts +59 -14
  430. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  431. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  432. package/src/resolvers/GetDataResolver.ts +35 -13
  433. package/src/resolvers/InfoResolver.ts +1 -1
  434. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  435. package/src/resolvers/MCPResolver.ts +15 -2
  436. package/src/resolvers/QuerySystemUserResolver.ts +211 -100
  437. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  438. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  439. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  440. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  441. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  442. package/src/resolvers/RunTemplateResolver.ts +3 -1
  443. package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
  444. package/src/resolvers/TagGovernanceResolver.ts +65 -0
  445. package/src/resolvers/TelemetryResolver.ts +44 -9
  446. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  447. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  448. package/src/resolvers/meetings/meetingMutations.ts +37 -7
  449. package/src/rest/MediaAccessKeys.ts +6 -2
  450. package/src/rest/OAuthCallbackHandler.ts +13 -5
  451. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  452. package/src/rest/ViewOperationsHandler.ts +2 -1
  453. package/src/rest/setupRESTEndpoints.ts +11 -2
  454. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  455. package/src/services/TaskGraphActionRunner.ts +2 -23
  456. package/src/services/TaskGraphAgentRunner.ts +43 -0
  457. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  458. package/src/services/WorkQueueHostService.ts +26 -4
  459. package/src/services/workQueueConfig.ts +5 -5
  460. package/src/sharedCache.ts +1 -1
  461. package/src/types.ts +18 -0
  462. package/src/util.ts +55 -3
  463. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -23,6 +23,19 @@ import { PublishStatusUpdate } from '../generic/PushStatusResolver.js';
23
23
  import { GraphQLJSONObject } from 'graphql-type-json';
24
24
  import { configInfo } from '../config.js';
25
25
 
26
+ const UUID_SHAPE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
27
+
28
+ /**
29
+ * Validates that a caller-supplied record id is a UUID before it is interpolated into a SQL
30
+ * filter, and returns it unchanged. A validated UUID cannot carry SQL.
31
+ */
32
+ function sqlUuid(value: string, argName: string): string {
33
+ if (typeof value !== 'string' || !UUID_SHAPE.test(value)) {
34
+ throw new Error(`${argName} must be a UUID`);
35
+ }
36
+ return value;
37
+ }
38
+
26
39
  /**
27
40
  * Input type for syncing MCP tools
28
41
  */
@@ -1289,7 +1302,7 @@ export class MCPResolver extends ResolverBase {
1289
1302
  // First get connection to get server ID
1290
1303
  const connResult = await rv.RunView<{ MCPServerID: string }>({
1291
1304
  EntityName: 'MJ: MCP Server Connections',
1292
- ExtraFilter: `ID='${connectionId}'`,
1305
+ ExtraFilter: `ID='${sqlUuid(connectionId, 'connectionId')}'`,
1293
1306
  Fields: ['MCPServerID'],
1294
1307
  ResultType: 'simple'
1295
1308
  }, contextUser);
@@ -1310,7 +1323,7 @@ export class MCPResolver extends ResolverBase {
1310
1323
  OAuthRequirePKCE: boolean | null;
1311
1324
  }>({
1312
1325
  EntityName: 'MJ: MCP Servers',
1313
- ExtraFilter: `ID='${serverId}'`,
1326
+ ExtraFilter: `ID='${sqlUuid(serverId, 'serverId')}'`,
1314
1327
  Fields: [
1315
1328
  'OAuthIssuerURL',
1316
1329
  'OAuthScopes',
@@ -6,7 +6,7 @@ import { MJQueryResolver, MJQuery_, MJQueryField_, MJQueryParameter_, MJQueryEnt
6
6
  import { GetReadWriteProvider } from '../util.js';
7
7
  import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
8
8
  import { MJQueryEntityServer } from '@memberjunction/core-entities-server';
9
- import { EscapeSQLString } from '@memberjunction/global';
9
+ import { EscapeSQLString, UUIDsEqual } from '@memberjunction/global';
10
10
 
11
11
  /**
12
12
  * Minimal shape of a query row returned by RunView lookups (plain object, not entity instance).
@@ -243,6 +243,14 @@ export class DeleteQueryResultType {
243
243
  Status?: string;
244
244
  }
245
245
 
246
+ /** A query's child rows, read from the database. */
247
+ interface StoredQueryChildren {
248
+ readonly Fields: MJQueryFieldEntity[];
249
+ readonly Parameters: MJQueryParameterEntity[];
250
+ readonly Entities: MJQueryEntityEntity[];
251
+ readonly Permissions: MJQueryPermissionEntity[];
252
+ }
253
+
246
254
  @Resolver()
247
255
  export class MJQueryResolverExtended extends MJQueryResolver {
248
256
  /**
@@ -269,6 +277,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
269
277
  }
270
278
 
271
279
  // Check for existing query with same name in the same category
280
+ const requestedName = input.Name;
272
281
  const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
273
282
 
274
283
  if (existingQuery) {
@@ -285,84 +294,119 @@ export class MJQueryResolverExtended extends MJQueryResolver {
285
294
  }
286
295
  }
287
296
 
288
- // Use MJQueryEntityServer which handles AI processing
289
- const record = await provider.GetEntityObject<MJQueryEntityServer>("MJ: Queries", context.userPayload.userRecord);
290
-
291
- // Destructure out non-database fields, keep only fields to persist
292
- const { Permissions: _permissions, CategoryPath: _categoryPath, ParameterHints: _parameterHints, AutoResolveCollision: _autoResolve, ...fieldsToSet } = {
293
- ...input,
294
- CategoryID: finalCategoryID || input.CategoryID,
295
- Status: input.Status || 'Approved',
296
- QualityRank: input.QualityRank || 0,
297
- UsesTemplate: input.UsesTemplate || false,
298
- AuditQueryRuns: input.AuditQueryRuns || false,
299
- CacheEnabled: input.CacheEnabled || false,
300
- CacheTTLMinutes: input.CacheTTLMinutes || null,
301
- CacheMaxSize: input.CacheMaxSize || null
297
+ return await this.createQueryAsync(provider, input, requestedName, finalCategoryID, context, pubSub);
298
+ }
299
+ catch (err) {
300
+ LogError(err);
301
+ return {
302
+ Success: false,
303
+ ErrorMessage: `MJQueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
302
304
  };
305
+ }
306
+ }
303
307
 
304
- // Field-level security: refuse a key the caller may not read, or that names no field
305
- AssertClientInputWritableForUser(record.EntityInfo, fieldsToSet, context.userPayload.userRecord);
306
- record.SetMany(fieldsToSet, true);
308
+ /** The most times a create with `AutoResolveCollision` picks a new name after losing a same-name race. */
309
+ private static readonly maxCollisionRetries = 25;
307
310
 
308
- // Pass caller-provided parameter sample values to override LLM guesses
309
- if (input.ParameterHints && input.ParameterHints.length > 0) {
310
- record.ParameterHints = new Map(input.ParameterHints.map(h => [h.Name, h.Value]));
311
+ /**
312
+ * Creates the query, handling a same-name race (#6007): when the insert fails and a query with
313
+ * the name now exists (another request created it in between), `AutoResolveCollision` picks a
314
+ * new unique name and tries again, a bounded number of times, and never returns the other
315
+ * request's query. Without it, the query that now exists is returned (create-or-get). Any other
316
+ * failure, including a save-time refusal of the SQL, returns the save's message.
317
+ *
318
+ * @param requestedName - The name the caller asked for; each retry's unique name is built from it.
319
+ */
320
+ private async createQueryAsync(
321
+ provider: DatabaseProviderBase,
322
+ input: CreateQuerySystemUserInput,
323
+ requestedName: string,
324
+ categoryID: string | null | undefined,
325
+ context: AppContext,
326
+ pubSub: PubSubEngine
327
+ ): Promise<QueryMutationResultType> {
328
+ const user = context.userPayload.userRecord;
329
+ for (let attempt = 0; ; attempt++) {
330
+ const record = await this.newQueryRecordAsync(provider, input, categoryID, context, pubSub);
331
+ if (await record.Save()) {
332
+ return this.completeCreateAsync(provider, input, record, user);
311
333
  }
334
+ const existing = await this.findExistingQuery(provider, input.Name, categoryID ?? null, user);
335
+ if (!existing) {
336
+ return { Success: false, ErrorMessage: `Failed to create query: ${record.LatestResult?.CompleteMessage || record.LatestResult?.Message || 'the save reported no reason'}` };
337
+ }
338
+ if (!input.AutoResolveCollision) {
339
+ return this.existingQueryResultAsync(provider, existing, input.Name, user);
340
+ }
341
+ if (attempt >= MJQueryResolverExtended.maxCollisionRetries) {
342
+ return { Success: false, ErrorMessage: `Failed to create query '${input.Name}': another request took each unique name tried, ${attempt + 1} times.` };
343
+ }
344
+ const lost = input.Name;
345
+ input.Name = await this.findUniqueName(provider, requestedName, categoryID ?? null, user);
346
+ LogStatus(`[CreateQuery] Lost a name race for "${lost}"; retrying as "${input.Name}"`);
347
+ }
348
+ }
312
349
 
313
- this.ListenForEntityMessages(record, pubSub, context.userPayload.userRecord);
314
-
315
- // Attempt to save the query
316
- const saveResult = await record.Save();
317
-
318
- if (saveResult) {
319
- // Save succeeded - fire the AfterCreate event and return all the data
320
- await this.AfterCreate(provider, input); // fire event
321
- const queryID = record.ID;
322
-
323
- if (input.Permissions && input.Permissions.length > 0) {
324
- await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
325
-
326
- }
327
-
350
+ /** A new, unsaved query record holding the input's values. */
351
+ private async newQueryRecordAsync(
352
+ provider: DatabaseProviderBase,
353
+ input: CreateQuerySystemUserInput,
354
+ categoryID: string | null | undefined,
355
+ context: AppContext,
356
+ pubSub: PubSubEngine
357
+ ): Promise<MJQueryEntityServer> {
358
+ const record = await provider.GetEntityObject<MJQueryEntityServer>("MJ: Queries", context.userPayload.userRecord);
359
+ // Destructure out non-database fields, keep only fields to persist
360
+ const { Permissions: _permissions, CategoryPath: _categoryPath, ParameterHints: _parameterHints, AutoResolveCollision: _autoResolve, ...fieldsToSet } = {
361
+ ...input,
362
+ CategoryID: categoryID || input.CategoryID,
363
+ Status: input.Status || 'Approved',
364
+ QualityRank: input.QualityRank || 0,
365
+ UsesTemplate: input.UsesTemplate || false,
366
+ AuditQueryRuns: input.AuditQueryRuns || false,
367
+ CacheEnabled: input.CacheEnabled || false,
368
+ CacheTTLMinutes: input.CacheTTLMinutes || null,
369
+ CacheMaxSize: input.CacheMaxSize || null
370
+ };
371
+ // Field-level security: refuse a key the caller may not read, or that names no field
372
+ AssertClientInputWritableForUser(record.EntityInfo, fieldsToSet, context.userPayload.userRecord);
373
+ record.SetMany(fieldsToSet, true);
374
+ // Pass caller-provided parameter sample values (used for new parameters and the described renders)
375
+ if (input.ParameterHints && input.ParameterHints.length > 0) {
376
+ record.ParameterHints = new Map(input.ParameterHints.map(h => [h.Name, h.Value]));
377
+ }
378
+ this.ListenForEntityMessages(record, pubSub, context.userPayload);
379
+ return record;
380
+ }
328
381
 
329
- return await this.buildSuccessResult(record, context.userPayload.userRecord, provider);
382
+ /** After a successful create: the AfterCreate event, the permissions, and the result. */
383
+ private async completeCreateAsync(provider: DatabaseProviderBase, input: CreateQuerySystemUserInput, record: MJQueryEntityServer, user: UserInfo): Promise<QueryMutationResultType> {
384
+ await this.AfterCreate(provider, input); // fire event
385
+ let createdPermissions: MJQueryPermissionEntity[] | undefined;
386
+ if (input.Permissions && input.Permissions.length > 0) {
387
+ try {
388
+ createdPermissions = await this.createPermissions(provider, input.Permissions, record.ID, user);
389
+ } catch (e) {
390
+ return { Success: false, ErrorMessage: `Query '${record.Name}' (ID ${record.ID}) was created, but its permissions were not: ${e instanceof Error ? e.message : String(e)}` };
330
391
  }
331
- else {
332
- // Save failed - check if another request created the same query (race condition)
333
- // Always recheck regardless of error type to handle all duplicate scenarios
334
- const existingQuery = await this.findExistingQuery(provider, input.Name, finalCategoryID, context.userPayload.userRecord);
335
-
336
- if (existingQuery) {
337
- // Found the query that was created by another request — load it as a full entity
338
- // so that related metadata (Fields, Parameters, Entities, Permissions) is populated
339
- LogStatus(`[CreateQuery] Unique constraint detected for query '${input.Name}'. Using existing query (ID: ${existingQuery.ID}) created by concurrent request.`);
340
- const existingEntity = await provider.GetEntityObject<MJQueryEntityServer>('MJ: Queries', context.userPayload.userRecord);
341
- if (await existingEntity.Load(existingQuery.ID)) {
342
- return await this.buildSuccessResult(existingEntity, context.userPayload.userRecord, provider);
343
- }
344
- // Entity load failed after confirming the row exists — extremely rare
345
- return {
346
- Success: false,
347
- ErrorMessage: `Found query '${input.Name}' created by concurrent request (ID: ${existingQuery.ID}) but failed to load it as entity`
348
- };
349
- }
392
+ }
393
+ return this.buildSuccessResult(record, user, provider, undefined, createdPermissions);
394
+ }
350
395
 
351
- // Genuine failure - couldn't find an existing query with the same name
352
- const errorMessage = record.LatestResult?.Message || '';
353
- return {
354
- Success: false,
355
- ErrorMessage: `Failed to create query: ${errorMessage || 'Unknown error'}`
356
- };
357
- }
358
- }
359
- catch (err) {
360
- LogError(err);
361
- return {
362
- Success: false,
363
- ErrorMessage: `MJQueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
364
- };
396
+ /** The create-or-get result: the query another request created with this name. */
397
+ private async existingQueryResultAsync(provider: DatabaseProviderBase, existing: QueryViewRow, name: string, user: UserInfo): Promise<QueryMutationResultType> {
398
+ LogStatus(`[CreateQuery] Unique constraint detected for query '${name}'. Using existing query (ID: ${existing.ID}) created by concurrent request.`);
399
+ const existingEntity = await provider.GetEntityObject<MJQueryEntityServer>('MJ: Queries', user);
400
+ if (await existingEntity.Load(existing.ID)) {
401
+ // The other request may have run on another server, or its rows may not have reached this
402
+ // process's query cache yet, so the child rows are read from the database.
403
+ const children = await this.readStoredChildrenAsync(provider, existing.ID, user);
404
+ return this.buildSuccessResult(existingEntity, user, provider, children);
365
405
  }
406
+ return {
407
+ Success: false,
408
+ ErrorMessage: `Found query '${name}' created by concurrent request (ID: ${existing.ID}) but failed to load it as entity`
409
+ };
366
410
  }
367
411
 
368
412
  /**
@@ -372,17 +416,46 @@ export class MJQueryResolverExtended extends MJQueryResolver {
372
416
  *
373
417
  * @param contextUser The caller the result is for. Field security projects the record for them.
374
418
  */
375
- private async buildSuccessResult(entity: MJQueryEntityServer, contextUser: UserInfo, provider: DatabaseProviderBase): Promise<QueryMutationResultType> {
419
+ private async buildSuccessResult(
420
+ entity: MJQueryEntityServer,
421
+ contextUser: UserInfo,
422
+ provider: DatabaseProviderBase,
423
+ children?: StoredQueryChildren,
424
+ createdPermissions?: MJQueryPermissionEntity[]
425
+ ): Promise<QueryMutationResultType> {
426
+ const permissionsToMap = children?.Permissions ?? createdPermissions ?? entity.QueryPermissions;
376
427
  return {
377
428
  Success: true,
378
429
  // Mapped for the caller, so the field-security projection applies: the record was just
379
430
  // saved, and a save re-reads the whole row.
380
431
  // fls-guard: projected for contextUser by MapFieldNamesToCodeNames
381
432
  Query: await this.MapFieldNamesToCodeNames('MJ: Queries', entity.GetAll(), contextUser, provider) as MJQuery_,
382
- Fields: this.mapFields(entity.QueryFields),
383
- Parameters: this.mapParameters(entity.QueryParameters),
384
- Entities: this.mapEntities(entity.QueryEntities),
385
- Permissions: this.mapPermissions(entity.QueryPermissions),
433
+ Fields: this.mapFields(children?.Fields ?? entity.QueryFields),
434
+ Parameters: this.mapParameters(children?.Parameters ?? entity.QueryParameters),
435
+ Entities: this.mapEntities(children?.Entities ?? entity.QueryEntities),
436
+ Permissions: this.mapPermissions(permissionsToMap),
437
+ };
438
+ }
439
+
440
+ /**
441
+ * A query's child rows read from the database with every cache bypassed. A collection whose
442
+ * read fails comes back empty, as the cache would have given it.
443
+ */
444
+ private async readStoredChildrenAsync(provider: DatabaseProviderBase, queryID: string, user: UserInfo): Promise<StoredQueryChildren> {
445
+ const filter = `QueryID='${EscapeSQLString(queryID)}'`;
446
+ const view = (entityName: string, orderBy?: string) => ({ EntityName: entityName, ExtraFilter: filter, OrderBy: orderBy, ResultType: 'entity_object' as const, BypassCache: true, IgnoreMaxRows: true });
447
+ const [fields, parameters, entities, permissions] = await provider.RunViews([
448
+ view('MJ: Query Fields', 'Sequence'),
449
+ view('MJ: Query Parameters', 'Name'),
450
+ view('MJ: Query Entities'),
451
+ view('MJ: Query Permissions'),
452
+ ], user);
453
+ const rows = <T>(result: { Success: boolean; Results: T[] } | undefined): T[] => (result?.Success ? result.Results : []);
454
+ return {
455
+ Fields: rows<MJQueryFieldEntity>(fields),
456
+ Parameters: rows<MJQueryParameterEntity>(parameters),
457
+ Entities: rows<MJQueryEntityEntity>(entities),
458
+ Permissions: rows<MJQueryPermissionEntity>(permissions),
386
459
  };
387
460
  }
388
461
 
@@ -398,6 +471,10 @@ export class MJQueryResolverExtended extends MJQueryResolver {
398
471
  SourceEntityID: f.SourceEntityID,
399
472
  SourceEntity: f.SourceEntity,
400
473
  SourceFieldName: f.SourceFieldName,
474
+ SourceEntityFieldID: f.SourceEntityFieldID,
475
+ SourceEntityField: f.SourceEntityField,
476
+ SourceObjectName: f.SourceObjectName,
477
+ IsNullable: f.IsNullable,
401
478
  IsComputed: f.IsComputed,
402
479
  ComputationDescription: f.ComputationDescription,
403
480
  IsSummary: f.IsSummary,
@@ -421,6 +498,7 @@ export class MJQueryResolverExtended extends MJQueryResolver {
421
498
  DefaultValue: p.DefaultValue,
422
499
  SampleValue: p.SampleValue,
423
500
  ValidationFilters: p.ValidationFilters,
501
+ IsServerSet: p.IsServerSet,
424
502
  _mj__CreatedAt: p.__mj_CreatedAt,
425
503
  _mj__UpdatedAt: p.__mj_UpdatedAt,
426
504
  DetectionMethod: p.DetectionMethod,
@@ -435,6 +513,8 @@ export class MJQueryResolverExtended extends MJQueryResolver {
435
513
  QueryID: e.QueryID,
436
514
  EntityID: e.EntityID,
437
515
  Entity: e.Entity,
516
+ AlternateBaseViewID: e.AlternateBaseViewID,
517
+ AlternateBaseView: e.AlternateBaseView,
438
518
  _mj__CreatedAt: e.__mj_CreatedAt,
439
519
  _mj__UpdatedAt: e.__mj_UpdatedAt,
440
520
  DetectionMethod: e.DetectionMethod,
@@ -455,15 +535,44 @@ export class MJQueryResolverExtended extends MJQueryResolver {
455
535
  }) as MJQueryPermission_);
456
536
  }
457
537
 
458
- protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<void> {
538
+ /**
539
+ * Saves one `MJ: Query Permissions` row per input role for the query.
540
+ *
541
+ * @throws Error naming the role and the save's own message when a row is not saved
542
+ */
543
+ protected async createPermissions(p: DatabaseProviderBase, permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<MJQueryPermissionEntity[]> {
544
+ const created: MJQueryPermissionEntity[] = [];
459
545
  for (const perm of permissions) {
460
546
  const permissionEntity = await p.GetEntityObject<MJQueryPermissionEntity>('MJ: Query Permissions', contextUser);
461
547
  if (permissionEntity) {
462
548
  permissionEntity.QueryID = queryID;
463
549
  permissionEntity.RoleID = perm.RoleID;
464
- await permissionEntity.Save();
550
+ if (!await permissionEntity.Save()) {
551
+ throw new Error(MJQueryResolverExtended.permissionWriteError('save', perm.RoleID, permissionEntity));
552
+ }
553
+ created.push(permissionEntity);
465
554
  }
466
555
  }
556
+ return created;
557
+ }
558
+
559
+ /**
560
+ * Deletes every `MJ: Query Permissions` row of the query.
561
+ *
562
+ * @throws Error naming the role and the delete's own message when a row is not deleted
563
+ */
564
+ protected async deletePermissions(queryID: string): Promise<void> {
565
+ for (const perm of QueryEngine.Instance.GetQueryPermissions(queryID)) {
566
+ if (!await perm.Delete()) {
567
+ throw new Error(MJQueryResolverExtended.permissionWriteError('delete', perm.RoleID, perm));
568
+ }
569
+ }
570
+ }
571
+
572
+ /** The message for a permission row that was not saved or deleted, with the entity's own reason. */
573
+ private static permissionWriteError(action: 'save' | 'delete', roleID: string, entity: MJQueryPermissionEntity): string {
574
+ const reason = entity.LatestResult?.CompleteMessage || entity.LatestResult?.Message || `the ${action} reported no reason`;
575
+ return `Failed to ${action} the query permission for role ${roleID}: ${reason}`;
467
576
  }
468
577
 
469
578
  /**
@@ -498,16 +607,14 @@ export class MJQueryResolverExtended extends MJQueryResolver {
498
607
  finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, provider, context.userPayload.userRecord);
499
608
  }
500
609
 
501
- // now make sure there is NO existing query by the same name in the specified category
502
- const existingQueryResult = await provider.RunView({
503
- EntityName: 'MJ: Queries',
504
- ExtraFilter: `Name='${EscapeSQLString(input.Name)}' AND CategoryID='${EscapeSQLString(finalCategoryID)}'`
505
- }, context.userPayload.userRecord);
506
- if (existingQueryResult.Success && existingQueryResult.Results?.length > 0) {
507
- // we have a match! Let's return an error
610
+ // A rename, or a move to another category, must not collide with another query's name
611
+ const targetName = input.Name ?? queryEntity.Name;
612
+ const targetCategoryID = finalCategoryID !== undefined ? finalCategoryID : queryEntity.CategoryID;
613
+ const sameName = await this.findExistingQuery(provider, targetName, targetCategoryID ?? null, context.userPayload.userRecord);
614
+ if (sameName && !UUIDsEqual(sameName.ID, queryEntity.ID)) {
508
615
  return {
509
616
  Success: false,
510
- ErrorMessage: `Query with name '${input.Name}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`
617
+ ErrorMessage: `Query with name '${targetName}' already exists in the specified ${input.CategoryID ? 'category' : 'categoryPath'}`
511
618
  };
512
619
  }
513
620
 
@@ -545,25 +652,25 @@ export class MJQueryResolverExtended extends MJQueryResolver {
545
652
  if (!saveResult) {
546
653
  return {
547
654
  Success: false,
548
- ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.Message || 'Unknown error'}`
655
+ ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.CompleteMessage || queryEntity.LatestResult?.Message || 'the save reported no reason'}`
549
656
  };
550
657
  }
551
658
 
552
659
  const queryID = queryEntity.ID;
553
660
 
554
661
  // Handle permissions update if provided
662
+ let createdPermissions: MJQueryPermissionEntity[] | undefined;
555
663
  if (input.Permissions !== undefined) {
556
- // Delete existing permissions (read from QueryEngine cache)
557
- const existingPermissions = QueryEngine.Instance.GetQueryPermissions(queryID);
558
- for (const perm of existingPermissions) {
559
- await perm.Delete();
664
+ // Replace the existing permissions (read from the QueryEngine cache); a failed write stops the update with its reason
665
+ try {
666
+ await this.deletePermissions(queryID);
667
+ createdPermissions = await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
668
+ } catch (e) {
669
+ return { Success: false, ErrorMessage: `Failed to update query permissions: ${e instanceof Error ? e.message : String(e)}` };
560
670
  }
561
-
562
- // Create new permissions
563
- await this.createPermissions(provider, input.Permissions, queryID, context.userPayload.userRecord);
564
671
  }
565
672
 
566
- return await this.buildSuccessResult(queryEntity, context.userPayload.userRecord, provider);
673
+ return await this.buildSuccessResult(queryEntity, context.userPayload.userRecord, provider, undefined, createdPermissions);
567
674
 
568
675
  } catch (err) {
569
676
  LogError(err);
@@ -740,12 +847,13 @@ export class MJQueryResolverExtended extends MJQueryResolver {
740
847
  ): Promise<QueryViewRow | null> {
741
848
  try {
742
849
  // Query database directly to avoid cache staleness issues
743
- const categoryFilter = categoryID ? `CategoryID='${categoryID}'` : 'CategoryID IS NULL';
744
- const nameFilter = `LOWER(Name) = LOWER('${queryName.replace(/'/g, "''")}')`;
850
+ const categoryFilter = categoryID ? `CategoryID='${EscapeSQLString(categoryID)}'` : 'CategoryID IS NULL';
851
+ const nameFilter = `LOWER(Name) = LOWER('${EscapeSQLString(queryName)}')`;
745
852
 
746
853
  const result = await provider.RunView<QueryViewRow>({
747
854
  EntityName: 'MJ: Queries',
748
- ExtraFilter: `${nameFilter} AND ${categoryFilter}`
855
+ ExtraFilter: `${nameFilter} AND ${categoryFilter}`,
856
+ BypassCache: true
749
857
  }, contextUser);
750
858
 
751
859
  if (result.Success && result.Results && result.Results.length > 0) {
@@ -797,13 +905,16 @@ export class MJQueryResolverExtended extends MJQueryResolver {
797
905
  private async findCategoryByNameAndParent(provider: DatabaseProviderBase, categoryName: string, parentID: string | null, contextUser: UserInfo): Promise<MJQueryCategoryEntity | null> {
798
906
  try {
799
907
  // Query database directly to avoid cache staleness issues
800
- const parentFilter = parentID ? `ParentID='${parentID}'` : 'ParentID IS NULL';
801
- const nameFilter = `LOWER(Name) = LOWER('${categoryName.replace(/'/g, "''")}')`; // Escape single quotes
908
+ const parentFilter = parentID ? `ParentID='${EscapeSQLString(parentID)}'` : 'ParentID IS NULL';
909
+ const nameFilter = `LOWER(Name) = LOWER('${EscapeSQLString(categoryName)}')`;
802
910
 
911
+ // Uncached: after losing a race to create the category, the re-check must see the row the
912
+ // other request just wrote.
803
913
  const result = await provider.RunView<MJQueryCategoryEntity>({
804
914
  EntityName: 'MJ: Query Categories',
805
915
  ExtraFilter: `${nameFilter} AND ${parentFilter}`,
806
- ResultType: 'entity_object'
916
+ ResultType: 'entity_object',
917
+ BypassCache: true
807
918
  }, contextUser);
808
919
 
809
920
  if (result.Success && result.Results && result.Results.length > 0) {
@@ -701,6 +701,7 @@ export class RealtimeBridgeResolver extends ResolverBase {
701
701
  Fields: ['ExternalID'],
702
702
  OrderBy: '__mj_CreatedAt DESC',
703
703
  MaxRows: 1,
704
+ SkipTotalRowCount: true,
704
705
  ResultType: 'simple',
705
706
  },
706
707
  user,
@@ -34,7 +34,7 @@
34
34
  import { Resolver, Mutation, Arg, Ctx, Int, Float, ObjectType, Field, PubSub, PubSubEngine } from 'type-graphql';
35
35
  import { AppContext, UserPayload } from '../types.js';
36
36
  import { AuthorizationEvaluator, UserInfo, IMetadataProvider, DatabaseProviderBase, LogError, LogErrorEx, LogStatus, RunView, BaseEntity } from '@memberjunction/core';
37
- import { UUIDsEqual, IsValidUUID } from '@memberjunction/global';
37
+ import { EscapeSQLString, UUIDsEqual, IsValidUUID } from '@memberjunction/global';
38
38
  import {
39
39
  MJAIAgentEntity,
40
40
  MJAIAgentSessionEntity,
@@ -106,6 +106,14 @@ const SIGNIFICANT_PROGRESS_STEPS = ['prompt_execution', 'action_execution', 'sub
106
106
  */
107
107
  const MAX_USAGE_DETAILS_JSON_LENGTH = 4096;
108
108
 
109
+ /**
110
+ * Sessions already logged as having no co-agent prompt run. Usage, transcript and tool-turn relays all read
111
+ * the id, many times per call, so the gap is logged once per session instead of once per relay. Bounded:
112
+ * past {@link MAX_LOGGED_MISSING_PROMPT_RUN_SESSIONS} entries the oldest is dropped (a Set keeps insertion order).
113
+ */
114
+ const sessionsLoggedWithoutPromptRun = new Set<string>();
115
+ const MAX_LOGGED_MISSING_PROMPT_RUN_SESSIONS = 5000;
116
+
109
117
  /** Entity name — centralised so the `MJ:`-prefix convention is applied in exactly one place. */
110
118
  const CO_AGENT_ENTITY = 'MJ: AI Agent Co Agents';
111
119
  const CONVERSATION_DETAIL_ENTITY = 'MJ: Conversation Details';
@@ -1330,10 +1338,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1330
1338
  return null;
1331
1339
  }
1332
1340
  if (!config.promptRunID) {
1333
- LogStatus(
1334
- `RelayRealtimeUsage: session ${session.ID} has no co-agent promptRunID (observability run ` +
1335
- 'creation was skipped or failed) — usage delta dropped.',
1336
- );
1341
+ this.logMissingPromptRunOnce(session.ID);
1337
1342
  return null;
1338
1343
  }
1339
1344
  const verified = await this.runIDVerifier.Verify(
@@ -1349,6 +1354,29 @@ export class RealtimeClientSessionResolver extends ResolverBase {
1349
1354
  return verified.PromptRunID;
1350
1355
  }
1351
1356
 
1357
+ /**
1358
+ * Logs, once per session, that the session has no co-agent prompt run, so its usage, cost, transcript and
1359
+ * tool turns are not recorded on one. The reason (no active co-agent prompt, or a failed run save) was
1360
+ * logged when the session was prepared.
1361
+ */
1362
+ private logMissingPromptRunOnce(sessionID: string): void {
1363
+ if (sessionsLoggedWithoutPromptRun.has(sessionID)) {
1364
+ return;
1365
+ }
1366
+ sessionsLoggedWithoutPromptRun.add(sessionID);
1367
+ if (sessionsLoggedWithoutPromptRun.size > MAX_LOGGED_MISSING_PROMPT_RUN_SESSIONS) {
1368
+ const oldest = sessionsLoggedWithoutPromptRun.values().next().value;
1369
+ if (oldest !== undefined) {
1370
+ sessionsLoggedWithoutPromptRun.delete(oldest);
1371
+ }
1372
+ }
1373
+ LogError(
1374
+ `RealtimeClientSessionResolver: session ${sessionID} has no co-agent promptRunID, so its usage, cost, ` +
1375
+ 'transcript and tool turns are not recorded on a prompt run (the reason was logged when the session ' +
1376
+ 'was prepared). Logged once per session.',
1377
+ );
1378
+ }
1379
+
1352
1380
  /**
1353
1381
  * Persist an interactive channel's state of record (e.g. the live whiteboard's serialized
1354
1382
  * scene) onto the session's `MJ: AI Agent Session Channels` row.
@@ -2032,9 +2060,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2032
2060
  const existing = await rv.RunView<MJAIAgentSessionChannelEntity>(
2033
2061
  {
2034
2062
  EntityName: SESSION_CHANNEL_ENTITY,
2035
- ExtraFilter: `AgentSessionID='${agentSessionID}' AND ChannelID='${channelID}'`,
2063
+ ExtraFilter: `AgentSessionID='${EscapeSQLString(agentSessionID)}' AND ChannelID='${EscapeSQLString(channelID)}'`,
2036
2064
  ResultType: 'entity_object',
2037
2065
  MaxRows: 1,
2066
+ SkipTotalRowCount: true,
2038
2067
  },
2039
2068
  contextUser,
2040
2069
  );
@@ -2423,6 +2452,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2423
2452
  Fields: ['ID', 'IsEnabled'],
2424
2453
  ResultType: 'simple',
2425
2454
  MaxRows: 1,
2455
+ SkipTotalRowCount: true,
2426
2456
  },
2427
2457
  contextUser,
2428
2458
  );
@@ -2585,6 +2615,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2585
2615
  OrderBy: '__mj_CreatedAt DESC',
2586
2616
  ResultType: 'simple',
2587
2617
  MaxRows: 1,
2618
+ SkipTotalRowCount: true,
2588
2619
  },
2589
2620
  contextUser,
2590
2621
  );
@@ -2673,9 +2704,10 @@ export class RealtimeClientSessionResolver extends ResolverBase {
2673
2704
  const existing = await rv.RunView<MJAIAgentSessionChannelEntity>(
2674
2705
  {
2675
2706
  EntityName: SESSION_CHANNEL_ENTITY,
2676
- ExtraFilter: `AgentSessionID='${agentSessionID}' AND ChannelID='${channelID}'`,
2707
+ ExtraFilter: `AgentSessionID='${EscapeSQLString(agentSessionID)}' AND ChannelID='${EscapeSQLString(channelID)}'`,
2677
2708
  ResultType: 'entity_object',
2678
2709
  MaxRows: 1,
2710
+ SkipTotalRowCount: true,
2679
2711
  },
2680
2712
  contextUser,
2681
2713
  );
@@ -3014,6 +3046,7 @@ export class RealtimeClientSessionResolver extends ResolverBase {
3014
3046
  ExtraFilter: `AgentSessionID='${session.ID}' AND Role='${mappedRole}'`,
3015
3047
  OrderBy: '__mj_CreatedAt DESC',
3016
3048
  MaxRows: 1,
3049
+ SkipTotalRowCount: true,
3017
3050
  ResultType: 'entity_object',
3018
3051
  },
3019
3052
  writeUser,