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

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 (407) 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/config.d.ts +906 -8
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +59 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +2 -0
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +35 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/coreRoutes.d.ts +3 -1
  76. package/dist/coreRoutes.d.ts.map +1 -1
  77. package/dist/coreRoutes.js +6 -4
  78. package/dist/coreRoutes.js.map +1 -1
  79. package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
  80. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  81. package/dist/generated/graphql-schemas/__mj.js +1163 -6
  82. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  83. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  85. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  87. package/dist/generic/ResolverBase.d.ts +50 -5
  88. package/dist/generic/ResolverBase.d.ts.map +1 -1
  89. package/dist/generic/ResolverBase.js +321 -132
  90. package/dist/generic/ResolverBase.js.map +1 -1
  91. package/dist/generic/RunViewResolver.d.ts +4 -0
  92. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  93. package/dist/generic/RunViewResolver.js +47 -7
  94. package/dist/generic/RunViewResolver.js.map +1 -1
  95. package/dist/index.d.ts +1 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +157 -35
  98. package/dist/index.js.map +1 -1
  99. package/dist/integration/ConnectionPause.d.ts +121 -0
  100. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  101. package/dist/integration/ConnectionPause.js +183 -0
  102. package/dist/integration/ConnectionPause.js.map +1 -0
  103. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  104. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  105. package/dist/integration/CustomColumnPromoter.js +87 -46
  106. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  107. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  108. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  109. package/dist/integration/EntityMapLifecycle.js +199 -5
  110. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  111. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  112. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  113. package/dist/integration/InactiveRowWarnings.js +29 -0
  114. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  115. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  116. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  117. package/dist/integration/ObjectNamesByID.js +87 -0
  118. package/dist/integration/ObjectNamesByID.js.map +1 -0
  119. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  120. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  121. package/dist/integration/PostRestartIntrospect.js +27 -0
  122. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  123. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  124. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  125. package/dist/integration/RSUPostRestartProgress.js +323 -0
  126. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  127. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  128. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  129. package/dist/integration/RSUProgressBridge.js +84 -1
  130. package/dist/integration/RSUProgressBridge.js.map +1 -1
  131. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  132. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  133. package/dist/integration/RunArtifactAuthorization.js +45 -0
  134. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  135. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  136. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  137. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  138. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  139. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  140. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  141. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  142. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  143. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  144. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  145. package/dist/integration/TargetConfigGuard.js +40 -0
  146. package/dist/integration/TargetConfigGuard.js.map +1 -0
  147. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  148. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  149. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  150. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  151. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  152. package/dist/realtimeSessions/sessionAccess.js +22 -0
  153. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  154. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  155. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  156. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  157. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  158. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  159. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  160. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  161. package/dist/realtimeWidget/widgetCore.js +4 -7
  162. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  164. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  165. package/dist/requestOrigin.d.ts +22 -0
  166. package/dist/requestOrigin.d.ts.map +1 -0
  167. package/dist/requestOrigin.js +60 -0
  168. package/dist/requestOrigin.js.map +1 -0
  169. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  170. package/dist/resolvers/AgentSessionResolver.js +6 -2
  171. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  172. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  173. package/dist/resolvers/AutotagPipelineResolver.js +2 -1
  174. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  175. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  176. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  178. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  179. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  180. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  181. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  182. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  183. package/dist/resolvers/GetDataResolver.js +34 -13
  184. package/dist/resolvers/GetDataResolver.js.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  186. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  187. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  188. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  189. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  190. package/dist/resolvers/MCPResolver.js +13 -2
  191. package/dist/resolvers/MCPResolver.js.map +1 -1
  192. package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
  193. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  194. package/dist/resolvers/QuerySystemUserResolver.js +174 -87
  195. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  196. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  197. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  198. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  199. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  200. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  201. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  202. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  204. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  205. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  206. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  207. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  208. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  210. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  211. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  212. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  213. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  214. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  215. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.js +3 -1
  217. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  218. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  219. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  220. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  221. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  222. package/dist/rest/MediaAccessKeys.js +6 -2
  223. package/dist/rest/MediaAccessKeys.js.map +1 -1
  224. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.js +11 -5
  226. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  227. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  228. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  229. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  230. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  231. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  232. package/dist/rest/ViewOperationsHandler.js +2 -1
  233. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  234. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  235. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  236. package/dist/rest/setupRESTEndpoints.js +11 -2
  237. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  238. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  239. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  240. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  241. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  242. package/dist/services/TaskGraphActionRunner.js +2 -26
  243. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  244. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  245. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  246. package/dist/services/TaskGraphAgentRunner.js +38 -0
  247. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  248. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  249. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  250. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  251. package/dist/services/WorkQueueHostService.d.ts +2 -1
  252. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  253. package/dist/services/WorkQueueHostService.js +19 -4
  254. package/dist/services/WorkQueueHostService.js.map +1 -1
  255. package/dist/services/workQueueConfig.d.ts +2 -2
  256. package/dist/services/workQueueConfig.js +5 -5
  257. package/dist/services/workQueueConfig.js.map +1 -1
  258. package/dist/sharedCache.js +1 -1
  259. package/dist/sharedCache.js.map +1 -1
  260. package/dist/types.d.ts +17 -0
  261. package/dist/types.d.ts.map +1 -1
  262. package/dist/types.js.map +1 -1
  263. package/package.json +98 -98
  264. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  265. package/src/__tests__/ActiveOperations.test.ts +68 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  267. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  268. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  269. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  270. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  271. package/src/__tests__/ConnectionPause.test.ts +192 -0
  272. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  273. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  274. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  275. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  276. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  277. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  278. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  279. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  280. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  281. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  282. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  283. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  284. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  285. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  286. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  287. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  288. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  289. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  290. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  291. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  292. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  293. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  294. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  295. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  296. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  297. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  298. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  299. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  300. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  301. package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
  302. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  303. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  304. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  305. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  306. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  307. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  308. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  309. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  310. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  311. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  312. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  313. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  314. package/src/__tests__/SessionJanitor.test.ts +195 -4
  315. package/src/__tests__/SessionManager.test.ts +69 -0
  316. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  317. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  318. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  319. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  320. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  321. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  322. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  323. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  324. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  325. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  326. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  327. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  328. package/src/__tests__/requestOrigin.test.ts +104 -0
  329. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  330. package/src/__tests__/sessionDeadline.test.ts +51 -0
  331. package/src/__tests__/sharedCache.test.ts +11 -0
  332. package/src/__tests__/unifiedAuth.test.ts +73 -1
  333. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  334. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  335. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  336. package/src/agentSessions/SessionJanitor.ts +92 -42
  337. package/src/agentSessions/SessionManager.ts +38 -8
  338. package/src/agentSessions/conversationBrowsers.ts +143 -0
  339. package/src/agentSessions/sessionDeadline.ts +81 -0
  340. package/src/auth/APIKeyScopeAuth.ts +5 -4
  341. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  342. package/src/auth/loginEmailResolution.ts +67 -0
  343. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  344. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  345. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  346. package/src/auth/magicLink/index.ts +1 -1
  347. package/src/auth/magicLink/redeemLanding.ts +7 -4
  348. package/src/auth/magicLink/types.ts +28 -3
  349. package/src/auth/newUsers.ts +1 -0
  350. package/src/backgroundSweepConfigUnits.ts +213 -0
  351. package/src/config.ts +69 -0
  352. package/src/context.ts +39 -6
  353. package/src/coreRoutes.ts +14 -7
  354. package/src/generated/graphql-schemas/__mj.ts +822 -7
  355. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  356. package/src/generic/ResolverBase.ts +414 -143
  357. package/src/generic/RunViewResolver.ts +96 -49
  358. package/src/index.ts +201 -35
  359. package/src/integration/ConnectionPause.ts +222 -0
  360. package/src/integration/CustomColumnPromoter.ts +123 -53
  361. package/src/integration/EntityMapLifecycle.ts +229 -6
  362. package/src/integration/InactiveRowWarnings.ts +50 -0
  363. package/src/integration/ObjectNamesByID.ts +97 -0
  364. package/src/integration/PostRestartIntrospect.ts +49 -0
  365. package/src/integration/RSUPostRestartProgress.ts +359 -0
  366. package/src/integration/RSUProgressBridge.ts +85 -2
  367. package/src/integration/RunArtifactAuthorization.ts +65 -0
  368. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  369. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  370. package/src/integration/TargetConfigGuard.ts +71 -0
  371. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  372. package/src/realtimeSessions/sessionAccess.ts +30 -0
  373. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  374. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  375. package/src/realtimeWidget/widgetCore.ts +4 -8
  376. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  377. package/src/requestOrigin.ts +64 -0
  378. package/src/resolvers/AgentSessionResolver.ts +6 -2
  379. package/src/resolvers/AutotagPipelineResolver.ts +2 -1
  380. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  381. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  382. package/src/resolvers/GetDataResolver.ts +35 -13
  383. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  384. package/src/resolvers/MCPResolver.ts +15 -2
  385. package/src/resolvers/QuerySystemUserResolver.ts +197 -98
  386. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  387. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  388. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  389. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  390. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  391. package/src/resolvers/RunTemplateResolver.ts +3 -1
  392. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  393. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  394. package/src/rest/MediaAccessKeys.ts +6 -2
  395. package/src/rest/OAuthCallbackHandler.ts +13 -5
  396. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  397. package/src/rest/ViewOperationsHandler.ts +2 -1
  398. package/src/rest/setupRESTEndpoints.ts +11 -2
  399. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  400. package/src/services/TaskGraphActionRunner.ts +2 -23
  401. package/src/services/TaskGraphAgentRunner.ts +43 -0
  402. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  403. package/src/services/WorkQueueHostService.ts +26 -4
  404. package/src/services/workQueueConfig.ts +5 -5
  405. package/src/sharedCache.ts +1 -1
  406. package/src/types.ts +18 -0
  407. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Completing a promotion after the restart.
3
+ *
4
+ * The post-restart consumer calls CompletePromotion with no catalog scope. Unscoped, a connection
5
+ * with per-connection rows reads as having none, so building its catalog writer threw
6
+ * PER_CONNECTION_CATALOG_MISSING. CompletePromotion caught that per entity and returned normally,
7
+ * and the consumer marked the pending work Completed: the columns existed in the database and were
8
+ * never mapped, with nothing failed anywhere.
9
+ *
10
+ * So: the catalog writes run in the connection's own scope, entered per entry; and a failed entry
11
+ * no longer returns normally — the others still run, then the failure is thrown, so the consumer
12
+ * fails the pending row instead of completing it.
13
+ */
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+
16
+ const h = vi.hoisted(() => ({
17
+ scopeAtBuild: [] as Array<string | undefined>,
18
+ savedFields: [] as string[],
19
+ }));
20
+
21
+ vi.mock('@memberjunction/schema-engine', () => ({
22
+ RuntimeSchemaManager: { get Instance() { return {}; } },
23
+ }));
24
+ vi.mock('@memberjunction/integration-schema-builder', () => ({ DDLGenerator: class {} }));
25
+
26
+ // The real scope; only the writer is replaced. It behaves as the real one does for a connection
27
+ // with per-connection rows: outside that connection's scope it cannot tell, and refuses.
28
+ vi.mock('@memberjunction/integration-engine', async (orig) => {
29
+ const actual = await orig<typeof import('@memberjunction/integration-engine')>();
30
+ return {
31
+ ...actual,
32
+ BuildCatalogWriter: () => {
33
+ const ci = actual.CurrentCatalogCI();
34
+ h.scopeAtBuild.push(ci);
35
+ if (!ci) throw new Error('PER_CONNECTION_CATALOG_MISSING: built outside the connection scope');
36
+ return {
37
+ ObjectsInScope: async () => [{ Name: 'Members', ID: 'obj-members' }],
38
+ FieldsForObject: async () => [],
39
+ LoadField: async () => null,
40
+ NewFieldRow: async () => {
41
+ const row: Record<string, unknown> = {};
42
+ return Object.assign(row, { Save: async () => { h.savedFields.push(String(row.Name)); return true; } });
43
+ },
44
+ StampNewField: () => undefined,
45
+ };
46
+ },
47
+ };
48
+ });
49
+
50
+ import type { UserInfo, IMetadataProvider, EntityInfo } from '@memberjunction/core';
51
+ import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
52
+ import { WithCatalogScope } from '@memberjunction/integration-engine';
53
+ import { IntegrationCustomColumnPromoter } from '../integration/CustomColumnPromoter.js';
54
+
55
+ const USER = { ID: 'u-1' } as UserInfo;
56
+ const CONN = 'CCCCCCCC-0000-0000-0000-000000000001';
57
+ const OTHER_CONN = 'CCCCCCCC-0000-0000-0000-000000000002';
58
+
59
+ type Seams = {
60
+ createIntegrationObjectFields: (integrationID: string, entityMapID: string, objectName: string, named: unknown[]) => Promise<void>;
61
+ activeFieldMaps: unknown; createFieldMaps: unknown; spreadAndRebaseline: unknown;
62
+ };
63
+
64
+ function provider(known: string[]): IMetadataProvider {
65
+ return {
66
+ EntityByName: (n: string) => (known.includes(n) ? ({ Name: n } as unknown as EntityInfo) : undefined),
67
+ PlatformKey: 'sqlserver',
68
+ } as unknown as IMetadataProvider;
69
+ }
70
+
71
+ const region = { sourceKey: 'region', columnName: 'region', candidate: { Coverage: 0.5, Inferred: { SchemaFieldType: 'nvarchar', MaxLength: 50 } } };
72
+
73
+ function entry(entityName: string) {
74
+ return {
75
+ EntityName: entityName, EntityMapID: `em-${entityName}`, ExternalObjectName: 'Members', IntegrationID: 'int-1',
76
+ Columns: [{ SourceKey: 'region', ColumnName: 'region', SchemaFieldType: 'nvarchar', MaxLength: 50, Coverage: 0.5 }],
77
+ };
78
+ }
79
+
80
+ beforeEach(() => {
81
+ h.scopeAtBuild.length = 0;
82
+ h.savedFields.length = 0;
83
+ // No catalog entities registered: entering a scope loads nothing and reads nothing.
84
+ vi.spyOn(IntegrationEngineBase.Instance, 'ProviderToUse', 'get').mockImplementation(() => ({ EntityByName: () => undefined }) as never);
85
+ vi.spyOn(IntegrationEngineBase.Instance, 'EntityMaps', 'get')
86
+ .mockReturnValue([{ ID: 'em-Members', CompanyIntegrationID: CONN }] as never);
87
+ vi.spyOn(IntegrationEngineBase.Instance, 'GetCompanyIntegrationByID')
88
+ .mockImplementation((id: string) => ({ ID: id }) as never);
89
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
90
+ });
91
+ afterEach(() => { vi.restoreAllMocks(); });
92
+
93
+ describe('the catalog writes of a promotion run in the connection\'s own scope', () => {
94
+ it('called with no scope (the post-restart consumer), the writer is built in the connection\'s scope', async () => {
95
+ const promoter = new IntegrationCustomColumnPromoter(USER, provider([])) as unknown as Seams;
96
+ await promoter.createIntegrationObjectFields('int-1', 'em-Members', 'Members', [region]);
97
+ expect(h.scopeAtBuild).toEqual([CONN]);
98
+ expect(h.savedFields).toEqual(['region']);
99
+ });
100
+
101
+ it('called inside another connection\'s scope, the entry\'s own connection wins', async () => {
102
+ const promoter = new IntegrationCustomColumnPromoter(USER, provider([])) as unknown as Seams;
103
+ await WithCatalogScope(OTHER_CONN, () => promoter.createIntegrationObjectFields('int-1', 'em-Members', 'Members', [region]), USER);
104
+ expect(h.scopeAtBuild).toEqual([CONN]);
105
+ });
106
+ });
107
+
108
+ describe('CompletePromotion does not complete over a failed entity', () => {
109
+ function stub(known: string[], failing: string[] = []) {
110
+ const promoter = new IntegrationCustomColumnPromoter(USER, provider(known));
111
+ const p = promoter as unknown as Seams;
112
+ p.activeFieldMaps = vi.fn().mockResolvedValue(new Map<string, string>());
113
+ p.createIntegrationObjectFields = vi.fn(async (_i: string, emID: string) => {
114
+ if (failing.some(f => emID === `em-${f}`)) throw new Error('PER_CONNECTION_CATALOG_MISSING');
115
+ });
116
+ const maps = vi.fn().mockResolvedValue(undefined);
117
+ p.createFieldMaps = maps;
118
+ p.spreadAndRebaseline = vi.fn().mockResolvedValue(undefined);
119
+ return { promoter, maps };
120
+ }
121
+
122
+ it('every entry completes: returns the columns and throws nothing', async () => {
123
+ const { promoter } = stub(['A', 'B']);
124
+ await expect(promoter.CompletePromotion([entry('A'), entry('B')])).resolves.toEqual([
125
+ { EntityName: 'A', ColumnName: 'region' },
126
+ { EntityName: 'B', ColumnName: 'region' },
127
+ ]);
128
+ });
129
+
130
+ it('one entry fails: the others still complete, then it throws naming the failed one', async () => {
131
+ const { promoter, maps } = stub(['A', 'B'], ['A']);
132
+ await expect(promoter.CompletePromotion([entry('A'), entry('B')]))
133
+ .rejects.toThrow(/PROMOTION_INCOMPLETE: 1 of 2 .*A: PER_CONNECTION_CATALOG_MISSING/);
134
+ expect(maps).toHaveBeenCalledTimes(1); // B's field maps were still created
135
+ });
136
+
137
+ it('an entity that is not found after the restart is a failure, not a skip', async () => {
138
+ const { promoter } = stub(['B']);
139
+ await expect(promoter.CompletePromotion([entry('A'), entry('B')]))
140
+ .rejects.toThrow(/PROMOTION_INCOMPLETE: 1 of 2 .*A: entity not found/);
141
+ });
142
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The per-connection catalog rows carry a plain FK to the connection and nothing cascades, so a
3
+ * connection that ever ran a discovery on it could not be deleted at all: the CompanyIntegration
4
+ * delete violated the constraint and the whole cascade rolled back (MJ-CAT-18). The fix deletes
5
+ * the field rows, then the object rows, inside the same transaction group, before the connection.
6
+ *
7
+ * Pinned from source, like PostRestartMapsInCatalogScope: the delete is a transaction over a live
8
+ * provider, and the fact being pinned is ORDER inside one method body.
9
+ */
10
+ import { describe, it, expect } from 'vitest';
11
+ import { readFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+
14
+ const SRC = readFileSync(join(__dirname, '..', 'resolvers', 'IntegrationDiscoveryResolver.ts'), 'utf-8');
15
+ const start = SRC.indexOf('async IntegrationDeleteConnection(');
16
+ const end = SRC.indexOf('// ── SCHEMA EVOLUTION', start);
17
+ const BODY = SRC.slice(start, end);
18
+
19
+ describe('IntegrationDeleteConnection removes the per-connection catalog before the connection', () => {
20
+ it('is scoped to the method body', () => {
21
+ expect(start).toBeGreaterThan(0);
22
+ expect(end).toBeGreaterThan(start);
23
+ });
24
+
25
+ it('deletes per-connection fields, then objects, then the CompanyIntegration — in that order', () => {
26
+ const fields = BODY.indexOf("EntityName: 'MJ: Company Integration Object Fields'");
27
+ const objects = BODY.indexOf("EntityName: 'MJ: Company Integration Objects'");
28
+ const fieldDelete = BODY.indexOf('field.TransactionGroup = tg;');
29
+ const objectDelete = BODY.indexOf('object.TransactionGroup = tg;');
30
+ const ciDelete = BODY.indexOf('ci.TransactionGroup = tg;');
31
+ expect(objects).toBeGreaterThan(0);
32
+ expect(fields).toBeGreaterThan(objects);
33
+ expect(fieldDelete).toBeGreaterThan(fields);
34
+ expect(objectDelete).toBeGreaterThan(fieldDelete);
35
+ expect(ciDelete).toBeGreaterThan(objectDelete);
36
+ });
37
+
38
+ it('queues every catalog delete on the same transaction group as the cascade', () => {
39
+ expect(BODY).toMatch(/field\.TransactionGroup = tg;\s*await field\.Delete\(\);/);
40
+ expect(BODY).toMatch(/object\.TransactionGroup = tg;\s*await object\.Delete\(\);/);
41
+ });
42
+
43
+ it('is guarded on the catalog entities being registered, so an unmigrated workspace is untouched', () => {
44
+ expect(BODY).toMatch(/md\.Entities\.some\(e => e\.Name === 'MJ: Company Integration Objects'\)/);
45
+ });
46
+
47
+ it('selects the fields by the objects of THIS connection only', () => {
48
+ expect(BODY).toMatch(/ExtraFilter: `CompanyIntegrationID='\$\{companyIntegrationID\}'`,\s*ResultType: 'entity_object',\s*\}, sysUser\);\s*const perConnectionObjects/);
49
+ expect(BODY).toMatch(/ExtraFilter: `CompanyIntegrationObjectID IN \(\$\{objectIDs\}\)`/);
50
+ });
51
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * plan.md: "going to a page should NEVER, NEVER trigger such things automatically, it must in a
3
+ * very clean way tell the user to click a button to start discovery of tables".
4
+ *
5
+ * A button needs a call that returns at once. The only discovery entry point that existed was
6
+ * synchronous - it runs the whole pipeline inline and times out at the gateway on a large catalog -
7
+ * so both wizards auto-triggered on page entry and adopted whatever run they happened to find.
8
+ * IntegrationStartSchemaRefresh is the explicit start: it returns a tailable RunID immediately.
9
+ *
10
+ * Source-level, for the same reason the sibling default tests are: these are decorator and
11
+ * argument shapes, and invoking the resolver needs a provider, a connector driver and the RSU
12
+ * pipeline stood up.
13
+ */
14
+ import { describe, it, expect } from 'vitest';
15
+ import { readFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+
18
+ const SRC = readFileSync(
19
+ join(__dirname, '..', 'resolvers', 'IntegrationDiscoveryResolver.ts'), 'utf-8');
20
+
21
+ function methodBody(name: string): string {
22
+ const start = SRC.indexOf(`async ${name}(`);
23
+ expect(start, `${name} not found`).toBeGreaterThan(-1);
24
+ const rest = SRC.slice(start);
25
+ const end = rest.search(/\n @(Query|Mutation)\(/);
26
+ return end === -1 ? rest : rest.slice(0, end);
27
+ }
28
+
29
+ describe('IntegrationStartSchemaRefresh', () => {
30
+ it('exists as a mutation', () => {
31
+ expect(SRC).toMatch(/@Mutation\(\(\) => StartSchemaRefreshOutput\)/);
32
+ expect(SRC).toMatch(/async IntegrationStartSchemaRefresh\(/);
33
+ });
34
+
35
+ it('launches DETACHED rather than awaiting the pipeline', () => {
36
+ const body = methodBody('IntegrationStartSchemaRefresh');
37
+ expect(body).toMatch(/startSchemaRefreshPipelineDetached\(/);
38
+ // an await on the pipeline would reintroduce the gateway timeout this exists to avoid
39
+ expect(body).not.toMatch(/await this\.runSchemaRefreshPipeline\(/);
40
+ });
41
+
42
+ it('refuses a second start with the reason, instead of handing back a doomed RunID', () => {
43
+ const body = methodBody('IntegrationStartSchemaRefresh');
44
+ expect(body).toMatch(/GetMaintenanceLock\(/);
45
+ expect(body).toMatch(/RunID: 'not-started'/);
46
+ expect(body).toMatch(/BlockedBy/);
47
+ });
48
+
49
+ it('probes the lock BEFORE launching, or the refusal is useless', () => {
50
+ const body = methodBody('IntegrationStartSchemaRefresh');
51
+ expect(body.indexOf('GetMaintenanceLock('))
52
+ .toBeLessThan(body.indexOf('startSchemaRefreshPipelineDetached('));
53
+ });
54
+
55
+ it('uses the read-write provider, since the pipeline writes the catalog', () => {
56
+ expect(methodBody('IntegrationStartSchemaRefresh')).toMatch(/GetReadWriteProvider\(/);
57
+ });
58
+
59
+ it('is a separate mutation, so the synchronous one keeps its real-counts contract', () => {
60
+ // the synchronous reply promises counts because it waited; a detached launch has only zeros
61
+ expect(SRC).toMatch(/async IntegrationRefreshConnectorSchema\(/);
62
+ const detached = methodBody('IntegrationStartSchemaRefresh');
63
+ expect(detached).not.toMatch(/ObjectsCreated/);
64
+ });
65
+ });
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
+
4
+ const { submitMock } = vi.hoisted(() => ({
5
+ submitMock: vi.fn(),
6
+ }));
7
+
8
+ vi.mock('@memberjunction/task-graph', () => ({
9
+ TaskGraphService: class {
10
+ public Submit = submitMock;
11
+ },
12
+ }));
13
+
14
+ import { DurableEntityActionTaskSubmitter } from '../services/DurableEntityActionTaskSubmitter.js';
15
+
16
+ describe('DurableEntityActionTaskSubmitter', () => {
17
+ const contextUser = { ID: 'user-1', Email: 'test@example.com' } as UserInfo;
18
+
19
+ beforeEach(() => {
20
+ submitMock.mockReset();
21
+ submitMock.mockResolvedValue({ Success: true, ParentTaskID: 'parent-task-1' });
22
+ });
23
+
24
+ it('enlists in request.Provider when supplied instead of minting an isolated provider connection', async () => {
25
+ const savingProvider = { Name: 'saving-tx-provider', GetEntityObject: vi.fn() } as object as IMetadataProvider;
26
+ const createProvider = vi.fn();
27
+ const submitter = new DurableEntityActionTaskSubmitter({ CreateProvider: createProvider }, 'env-1');
28
+
29
+ const result = await submitter.Submit({
30
+ EntityActionID: 'ea-1',
31
+ ActionID: 'act-1',
32
+ ActionName: 'Send Order Webhook',
33
+ EntityID: 'ent-1',
34
+ EntityName: 'Orders',
35
+ RecordID: 'ID|ord-1',
36
+ InvocationType: 'AfterCreate',
37
+ RedactedParams: { OrderID: 'ord-1' },
38
+ ContextUser: contextUser,
39
+ Provider: savingProvider,
40
+ });
41
+
42
+ expect(result).toEqual({ Success: true, ParentTaskID: 'parent-task-1' });
43
+ expect(createProvider).not.toHaveBeenCalled();
44
+ expect(submitMock).toHaveBeenCalledTimes(1);
45
+ expect(submitMock.mock.calls[0][1]).toMatchObject({
46
+ EnvironmentID: 'env-1',
47
+ ContextUser: contextUser,
48
+ Provider: savingProvider,
49
+ });
50
+ });
51
+
52
+ it('falls back to providerFactory.CreateProvider() when request.Provider is omitted', async () => {
53
+ const factoryProvider = { Name: 'factory-provider' } as IMetadataProvider;
54
+ const createProvider = vi.fn().mockResolvedValue(factoryProvider);
55
+ const submitter = new DurableEntityActionTaskSubmitter({ CreateProvider: createProvider }, 'env-1');
56
+
57
+ const result = await submitter.Submit({
58
+ EntityActionID: 'ea-1',
59
+ ActionID: 'act-1',
60
+ ActionName: 'Send Order Webhook',
61
+ EntityID: 'ent-1',
62
+ EntityName: 'Orders',
63
+ RecordID: 'ID|ord-1',
64
+ InvocationType: 'AfterCreate',
65
+ RedactedParams: {},
66
+ ContextUser: contextUser,
67
+ });
68
+
69
+ expect(result).toEqual({ Success: true, ParentTaskID: 'parent-task-1' });
70
+ expect(createProvider).toHaveBeenCalledTimes(1);
71
+ expect(submitMock.mock.calls[0][1]).toMatchObject({ Provider: factoryProvider });
72
+ });
73
+ });
@@ -0,0 +1,252 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { RunView, type DatabaseProviderBase, type UserInfo } from '@memberjunction/core';
3
+ import { CatalogIdentityKey, ClearRekeyedObjectData, DecideRekeyed, IdentityKeyFields } from '../integration/EntityMapLifecycle.js';
4
+
5
+ /**
6
+ * A connector upgrade can change what identifies a row. PheedLoop 1.4.6 makes Attendees
7
+ * `code + eventCode`, because the object is fetched once per event and an attendee at two events
8
+ * comes back twice carrying the same `code`.
9
+ *
10
+ * `ToExternalRecord` joins every IsPrimaryKey field with '|' in Sequence order, so both the
11
+ * MEMBERS and the ORDER are part of identity. These pin the two pure decisions that drive the
12
+ * evolution's clear-and-rebuild.
13
+ */
14
+ const f = (Name: string, IsPrimaryKey: boolean | null, Sequence: number | null) =>
15
+ ({ Name, IsPrimaryKey, Sequence });
16
+
17
+ describe('IdentityKeyFields', () => {
18
+ it('takes only key fields, in Sequence order — not declaration order', () => {
19
+ expect(IdentityKeyFields([
20
+ f('eventCode', true, 2),
21
+ f('is_checked_in', false, 3),
22
+ f('code', true, 1),
23
+ ])).toEqual(['code', 'eventCode']);
24
+ });
25
+
26
+ it('is empty when nothing is flagged', () => {
27
+ expect(IdentityKeyFields([f('a', false, 1), f('b', null, 2)])).toEqual([]);
28
+ });
29
+
30
+ it('treats a missing Sequence as 0 rather than dropping the field', () => {
31
+ expect(IdentityKeyFields([f('b', true, null), f('a', true, 1)])).toEqual(['b', 'a']);
32
+ });
33
+
34
+ it('does not mutate the caller array', () => {
35
+ const input = [f('z', true, 9), f('a', true, 1)];
36
+ IdentityKeyFields(input);
37
+ expect(input[0].Name).toBe('z');
38
+ });
39
+ });
40
+
41
+ describe('DecideRekeyed', () => {
42
+ it('a column joining the key is a re-key — the PheedLoop case', () => {
43
+ expect(DecideRekeyed(['code'], ['code', 'eventCode'])).toBe(true);
44
+ });
45
+
46
+ it('a column leaving the key is a re-key', () => {
47
+ expect(DecideRekeyed(['code', 'eventCode'], ['code'])).toBe(true);
48
+ });
49
+
50
+ it('the same columns in a different ORDER is a re-key — the identity string differs', () => {
51
+ expect(DecideRekeyed(['code', 'eventCode'], ['eventCode', 'code'])).toBe(true);
52
+ });
53
+
54
+ it('an unchanged key is not', () => {
55
+ expect(DecideRekeyed(['code', 'eventCode'], ['code', 'eventCode'])).toBe(false);
56
+ });
57
+
58
+ it('matches case-insensitively, as every catalog-to-map reconciliation does', () => {
59
+ expect(DecideRekeyed(['Code'], ['code'])).toBe(false);
60
+ });
61
+
62
+ // The guard that stops this from clearing the fleet. An empty BUILT key means the entity's
63
+ // fields could not be read, not that the table never had a key — and treating unknown as
64
+ // empty would re-key (and therefore WIPE) every table on a workspace whose metadata failed
65
+ // to load.
66
+ it('an unreadable built key is never a re-key, however different the catalog looks', () => {
67
+ expect(DecideRekeyed([], ['code', 'eventCode'])).toBe(false);
68
+ expect(DecideRekeyed([], [])).toBe(false);
69
+ });
70
+
71
+ it('a catalog that reports no key against a real built key IS a re-key', () => {
72
+ expect(DecideRekeyed(['code'], [])).toBe(true);
73
+ });
74
+
75
+ // The same guard on the catalog side. A refresh reading a per-connection object whose fields
76
+ // were never warmed got `[]`, and "no key" against a real built key cleared the object's
77
+ // record maps and rows on every refresh.
78
+ it('an unread catalog key is never a re-key', () => {
79
+ expect(DecideRekeyed(['code'], null)).toBe(false);
80
+ expect(DecideRekeyed(['code', 'eventCode'], CatalogIdentityKey([]))).toBe(false);
81
+ });
82
+ });
83
+
84
+ describe('CatalogIdentityKey', () => {
85
+ const row = (Name: string, IsPrimaryKey: boolean, Sequence: number, Status = 'Active') =>
86
+ ({ Name, IsPrimaryKey, Sequence, Status });
87
+
88
+ it('is null when no field row was read — a cold per-connection object answers []', () => {
89
+ expect(CatalogIdentityKey([])).toBeNull();
90
+ });
91
+
92
+ it('is null when no row is Active — nothing describes the object', () => {
93
+ expect(CatalogIdentityKey([row('code', true, 1, 'Inactive')])).toBeNull();
94
+ });
95
+
96
+ it('is the Active key fields in Sequence order', () => {
97
+ expect(CatalogIdentityKey([
98
+ row('eventCode', true, 2),
99
+ row('old_key', true, 0, 'Inactive'),
100
+ row('code', true, 1),
101
+ row('name', false, 3),
102
+ ])).toEqual(['code', 'eventCode']);
103
+ });
104
+
105
+ it('is [] when Active rows declare no key — read, and keyless, so still a re-key', () => {
106
+ const key = CatalogIdentityKey([row('name', false, 1)]);
107
+ expect(key).toEqual([]);
108
+ expect(DecideRekeyed(['code'], key)).toBe(true);
109
+ });
110
+ });
111
+
112
+ /**
113
+ * Two connections to the same vendor share one schema, so they share every entity table. The clear
114
+ * may only touch the rows THIS connection's record maps point at; the other connection's rows in
115
+ * the same table are not its to delete.
116
+ */
117
+ describe('ClearRekeyedObjectData', () => {
118
+ const USER = { ID: 'user-1' } as UserInfo;
119
+ const RECORD_MAPS = { Name: 'MJ: Company Integration Record Maps', SchemaName: '__mj', BaseTable: 'CompanyIntegrationRecordMap' };
120
+ const TARGET = { ExternalObjectName: 'attendees', EntityMapID: 'M1', EntityID: 'E1', SchemaName: 'pheedloop', BaseTable: 'attendees' };
121
+ type MapRow = { ID: string; EntityRecordID: string };
122
+ type RunViewParams = Parameters<RunView['RunView']>[0];
123
+ type RunViewResult = Awaited<ReturnType<RunView['RunView']>>;
124
+
125
+ /** A provider that records every statement and transaction step, in order. */
126
+ function fakeProvider(key: string[], failOn?: string) {
127
+ const log: string[] = [];
128
+ const provider = {
129
+ Dialect: {
130
+ QuoteIdentifier: (s: string) => `[${s}]`,
131
+ QuoteStringLiteral: (s: string) => `'${s.replace(/'/g, "''")}'`,
132
+ },
133
+ Entities: [RECORD_MAPS],
134
+ EntityByName: (name: string) => (name === RECORD_MAPS.Name ? RECORD_MAPS : undefined),
135
+ EntityByID: (id: string) => (id === 'E1' ? { PrimaryKeys: key.map(Name => ({ Name })) } : undefined),
136
+ ExecuteSQL: async (sql: string) => {
137
+ log.push(sql);
138
+ if (failOn && sql.includes(failOn)) throw new Error('deadlock victim');
139
+ return [];
140
+ },
141
+ BeginTransaction: async () => { log.push('BEGIN'); },
142
+ CommitTransaction: async () => { log.push('COMMIT'); },
143
+ RollbackTransaction: async () => { log.push('ROLLBACK'); },
144
+ };
145
+ return { provider: provider as unknown as DatabaseProviderBase, log };
146
+ }
147
+
148
+ let runView: ReturnType<typeof vi.spyOn>;
149
+ /** Answers the record-map reads one page at a time, then empty. */
150
+ function recordMapPages(...pages: MapRow[][]) {
151
+ for (const page of pages) {
152
+ runView.mockResolvedValueOnce({ Success: true, Results: page } as unknown as RunViewResult);
153
+ }
154
+ runView.mockResolvedValue({ Success: true, Results: [] } as unknown as RunViewResult);
155
+ }
156
+ const reads = () => runView.mock.calls.map(c => c[0] as RunViewParams);
157
+
158
+ beforeEach(() => { runView = vi.spyOn(RunView.prototype, 'RunView'); });
159
+ afterEach(() => { vi.restoreAllMocks(); });
160
+
161
+ it('deletes only the rows this connection\'s record maps point at — never the whole table', async () => {
162
+ const { provider, log } = fakeProvider(['ID']);
163
+ recordMapPages([{ ID: 'm1', EntityRecordID: 'r1' }, { ID: 'm2', EntityRecordID: 'r2' }]);
164
+
165
+ const cleared = await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider);
166
+
167
+ expect(cleared).toEqual(['attendees']);
168
+ expect(log).not.toContain('DELETE FROM [pheedloop].[attendees]');
169
+ expect(log).toContain("DELETE FROM [pheedloop].[attendees] WHERE [ID] IN ('r1','r2')");
170
+ expect(reads()[0].EntityName).toBe(RECORD_MAPS.Name);
171
+ expect(reads()[0].ExtraFilter).toBe("CompanyIntegrationID='CI-A' AND EntityID='E1'");
172
+ });
173
+
174
+ it('deletes the rows while their maps still say which rows they are, in one transaction with the maps', async () => {
175
+ const { provider, log } = fakeProvider(['ID']);
176
+ recordMapPages([{ ID: 'm1', EntityRecordID: 'r1' }]);
177
+
178
+ await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider);
179
+
180
+ expect(log).toEqual([
181
+ 'BEGIN',
182
+ "DELETE FROM [pheedloop].[attendees] WHERE [ID] IN ('r1')",
183
+ "DELETE FROM [__mj].[CompanyIntegrationRecordMap] WHERE [CompanyIntegrationID]='CI-A' AND [EntityID]='E1' AND [ID] IN ('m1')",
184
+ 'COMMIT',
185
+ ]);
186
+ });
187
+
188
+ it('matches every column of a composite key, split the way the engine joined it', async () => {
189
+ const { provider, log } = fakeProvider(['code', 'eventCode']);
190
+ recordMapPages([{ ID: 'm1', EntityRecordID: 'a|1' }, { ID: 'm2', EntityRecordID: "b'x|2" }]);
191
+
192
+ await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider);
193
+
194
+ expect(log).toContain(
195
+ "DELETE FROM [pheedloop].[attendees] WHERE ([code]='a' AND [eventCode]='1') OR ([code]='b''x' AND [eventCode]='2')"
196
+ );
197
+ });
198
+
199
+ it('leaves a map whose id cannot name exactly one row, and does not report the object cleared', async () => {
200
+ // 'a|1|z' against a two-column key is a value that itself contained '|': splitting it would
201
+ // name some OTHER row. An empty component is a null key value, which matches nothing safely.
202
+ const { provider, log } = fakeProvider(['code', 'eventCode']);
203
+ recordMapPages([
204
+ { ID: 'm1', EntityRecordID: 'a|1' },
205
+ { ID: 'm2', EntityRecordID: 'a|1|z' },
206
+ { ID: 'm3', EntityRecordID: '|2' },
207
+ ]);
208
+
209
+ const cleared = await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider);
210
+
211
+ expect(cleared).toEqual([]);
212
+ expect(log).toContain("DELETE FROM [pheedloop].[attendees] WHERE ([code]='a' AND [eventCode]='1')");
213
+ expect(log.some(s => s.includes('m2') || s.includes('m3'))).toBe(false);
214
+ });
215
+
216
+ it('pages through the maps by id, so a big table is never read or deleted whole', async () => {
217
+ const { provider, log } = fakeProvider(['ID']);
218
+ recordMapPages(
219
+ [{ ID: 'm1', EntityRecordID: 'r1' }, { ID: 'm2', EntityRecordID: 'r2' }],
220
+ [{ ID: 'm3', EntityRecordID: 'r3' }],
221
+ );
222
+
223
+ const cleared = await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider, 2);
224
+
225
+ expect(cleared).toEqual(['attendees']);
226
+ expect(reads()).toHaveLength(2);
227
+ expect(reads()[0]).toMatchObject({ OrderBy: 'ID', MaxRows: 2, SkipTotalRowCount: true, BypassCache: true });
228
+ expect(reads()[1].ExtraFilter).toBe("CompanyIntegrationID='CI-A' AND EntityID='E1' AND ID > 'm2'");
229
+ expect(log.filter(s => s === 'COMMIT')).toHaveLength(2);
230
+ });
231
+
232
+ it('rolls a failed chunk back and does not report the object cleared', async () => {
233
+ const { provider, log } = fakeProvider(['ID'], 'CompanyIntegrationRecordMap');
234
+ recordMapPages([{ ID: 'm1', EntityRecordID: 'r1' }]);
235
+
236
+ const cleared = await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider);
237
+
238
+ expect(cleared).toEqual([]);
239
+ expect(log).toContain('ROLLBACK');
240
+ expect(log).not.toContain('COMMIT');
241
+ });
242
+
243
+ it('deletes nothing when the record maps cannot be read', async () => {
244
+ const { provider, log } = fakeProvider(['ID']);
245
+ runView.mockResolvedValue({ Success: false, Results: [], ErrorMessage: 'timeout' } as unknown as RunViewResult);
246
+
247
+ const cleared = await ClearRekeyedObjectData([TARGET], 'CI-A', USER, provider);
248
+
249
+ expect(cleared).toEqual([]);
250
+ expect(log).toEqual([]);
251
+ });
252
+ });