@memberjunction/server 6.1.4 → 6.2.0-edge.1

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 (435) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/ReturningVisitorRecap.d.ts +2 -0
  3. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  4. package/dist/agentSessions/ReturningVisitorRecap.js +5 -1
  5. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  6. package/dist/agentSessions/SessionManager.js +2 -2
  7. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +8 -0
  8. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -1
  9. package/dist/agentSessions/remoteBrowserGoalEngine.js +21 -5
  10. package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -1
  11. package/dist/auth/AuthProviderCatalogRouter.d.ts +2 -0
  12. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  13. package/dist/auth/AuthProviderCatalogRouter.js +5 -1
  14. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  15. package/dist/auth/exampleNewUserSubClass.js +2 -1
  16. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  17. package/dist/auth/index.d.ts +20 -1
  18. package/dist/auth/index.d.ts.map +1 -1
  19. package/dist/auth/index.js +20 -10
  20. package/dist/auth/index.js.map +1 -1
  21. package/dist/auth/initializeProviders.d.ts +8 -0
  22. package/dist/auth/initializeProviders.d.ts.map +1 -1
  23. package/dist/auth/initializeProviders.js +21 -5
  24. package/dist/auth/initializeProviders.js.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkRouter.d.ts +9 -0
  26. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  27. package/dist/auth/magicLink/MagicLinkRouter.js +22 -10
  28. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  29. package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
  30. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  31. package/dist/auth/magicLink/MagicLinkService.js +65 -51
  32. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  33. package/dist/auth/magicLink/index.d.ts +2 -2
  34. package/dist/auth/magicLink/index.d.ts.map +1 -1
  35. package/dist/auth/magicLink/index.js +2 -2
  36. package/dist/auth/magicLink/index.js.map +1 -1
  37. package/dist/auth/magicLink/magicLinkCore.d.ts +54 -33
  38. package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
  39. package/dist/auth/magicLink/magicLinkCore.js +61 -61
  40. package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
  41. package/dist/auth/magicLink/redeemLanding.d.ts +4 -0
  42. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  43. package/dist/auth/magicLink/redeemLanding.js +12 -4
  44. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  45. package/dist/auth/magicLink/types.d.ts +4 -4
  46. package/dist/auth/principals.d.ts +2 -0
  47. package/dist/auth/principals.d.ts.map +1 -1
  48. package/dist/auth/principals.js +6 -2
  49. package/dist/auth/principals.js.map +1 -1
  50. package/dist/cache.d.ts +2 -0
  51. package/dist/cache.d.ts.map +1 -1
  52. package/dist/cache.js +3 -1
  53. package/dist/cache.js.map +1 -1
  54. package/dist/config.d.ts +293 -2
  55. package/dist/config.d.ts.map +1 -1
  56. package/dist/config.js +11 -4
  57. package/dist/config.js.map +1 -1
  58. package/dist/context.d.ts +20 -0
  59. package/dist/context.d.ts.map +1 -1
  60. package/dist/context.js +27 -19
  61. package/dist/context.js.map +1 -1
  62. package/dist/directives/Public.d.ts +2 -0
  63. package/dist/directives/Public.d.ts.map +1 -1
  64. package/dist/directives/Public.js +3 -1
  65. package/dist/directives/Public.js.map +1 -1
  66. package/dist/directives/RequireSystemUser.d.ts +2 -0
  67. package/dist/directives/RequireSystemUser.d.ts.map +1 -1
  68. package/dist/directives/RequireSystemUser.js +3 -1
  69. package/dist/directives/RequireSystemUser.js.map +1 -1
  70. package/dist/generated/graphql-schemas/__mj.d.ts +266 -4
  71. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  72. package/dist/generated/graphql-schemas/__mj.js +1304 -0
  73. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  74. package/dist/generic/CacheInvalidationResolver.d.ts +8 -2
  75. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  76. package/dist/generic/CacheInvalidationResolver.js +7 -3
  77. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  78. package/dist/generic/FireAndForgetHeartbeat.d.ts +3 -1
  79. package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
  80. package/dist/generic/FireAndForgetHeartbeat.js +8 -4
  81. package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
  82. package/dist/generic/PushStatusResolver.d.ts +10 -1
  83. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  84. package/dist/generic/PushStatusResolver.js +11 -3
  85. package/dist/generic/PushStatusResolver.js.map +1 -1
  86. package/dist/generic/ResolverBase.d.ts +27 -1
  87. package/dist/generic/ResolverBase.d.ts.map +1 -1
  88. package/dist/generic/ResolverBase.js +64 -6
  89. package/dist/generic/ResolverBase.js.map +1 -1
  90. package/dist/index.d.ts +6 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +29 -6
  93. package/dist/index.js.map +1 -1
  94. package/dist/integration/CustomColumnPromoter.d.ts +2 -0
  95. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  96. package/dist/integration/CustomColumnPromoter.js +5 -1
  97. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  98. package/dist/integration/EntityMapLifecycle.d.ts +4 -0
  99. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  100. package/dist/integration/EntityMapLifecycle.js +10 -2
  101. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  102. package/dist/logging/NoLog.d.ts +4 -0
  103. package/dist/logging/NoLog.d.ts.map +1 -1
  104. package/dist/logging/NoLog.js +10 -2
  105. package/dist/logging/NoLog.js.map +1 -1
  106. package/dist/logging/StartupLogger.d.ts +2 -0
  107. package/dist/logging/StartupLogger.d.ts.map +1 -1
  108. package/dist/logging/StartupLogger.js +5 -1
  109. package/dist/logging/StartupLogger.js.map +1 -1
  110. package/dist/logging/bootAudit.d.ts +11 -7
  111. package/dist/logging/bootAudit.d.ts.map +1 -1
  112. package/dist/logging/bootAudit.js +15 -7
  113. package/dist/logging/bootAudit.js.map +1 -1
  114. package/dist/logging/boundaryLogPayload.d.ts +4 -0
  115. package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
  116. package/dist/logging/boundaryLogPayload.js +5 -1
  117. package/dist/logging/boundaryLogPayload.js.map +1 -1
  118. package/dist/logging/secretRedactor.d.ts +2 -0
  119. package/dist/logging/secretRedactor.d.ts.map +1 -1
  120. package/dist/logging/secretRedactor.js +8 -4
  121. package/dist/logging/secretRedactor.js.map +1 -1
  122. package/dist/logging/shortenForLog.d.ts +2 -0
  123. package/dist/logging/shortenForLog.d.ts.map +1 -1
  124. package/dist/logging/shortenForLog.js +7 -3
  125. package/dist/logging/shortenForLog.js.map +1 -1
  126. package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
  127. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  128. package/dist/logging/variablesLoggingMiddleware.js +8 -6
  129. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  130. package/dist/metadataRefreshSignal.d.ts +19 -0
  131. package/dist/metadataRefreshSignal.d.ts.map +1 -0
  132. package/dist/metadataRefreshSignal.js +45 -0
  133. package/dist/metadataRefreshSignal.js.map +1 -0
  134. package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
  135. package/dist/multiTenancy/index.d.ts +8 -0
  136. package/dist/multiTenancy/index.d.ts.map +1 -1
  137. package/dist/multiTenancy/index.js +21 -5
  138. package/dist/multiTenancy/index.js.map +1 -1
  139. package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
  140. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  141. package/dist/realtimeWidget/WidgetRouter.js +14 -10
  142. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  143. package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
  144. package/dist/realtimeWidget/WidgetSessionService.js +27 -27
  145. package/dist/realtimeWidget/host-identity.d.ts +5 -1
  146. package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
  147. package/dist/realtimeWidget/host-identity.js +11 -3
  148. package/dist/realtimeWidget/host-identity.js.map +1 -1
  149. package/dist/realtimeWidget/index.d.ts +2 -2
  150. package/dist/realtimeWidget/index.d.ts.map +1 -1
  151. package/dist/realtimeWidget/index.js +2 -2
  152. package/dist/realtimeWidget/index.js.map +1 -1
  153. package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
  154. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  155. package/dist/realtimeWidget/visitorIdentity.js +16 -4
  156. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  157. package/dist/realtimeWidget/widgetCore.d.ts +48 -0
  158. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  159. package/dist/realtimeWidget/widgetCore.js +38 -10
  160. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  161. package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
  162. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
  164. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  165. package/dist/resolvers/AdhocQueryResolver.js +3 -3
  166. package/dist/resolvers/FetchEntityVectorsResolver.js +2 -2
  167. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  168. package/dist/resolvers/FileResolver.js +3 -3
  169. package/dist/resolvers/GeoResolver.d.ts +2 -2
  170. package/dist/resolvers/GeoResolver.js +4 -4
  171. package/dist/resolvers/GetDataResolver.d.ts +14 -0
  172. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  173. package/dist/resolvers/GetDataResolver.js +43 -15
  174. package/dist/resolvers/GetDataResolver.js.map +1 -1
  175. package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -2
  176. package/dist/resolvers/ListOperationsResolver.d.ts +20 -0
  177. package/dist/resolvers/ListOperationsResolver.d.ts.map +1 -1
  178. package/dist/resolvers/ListOperationsResolver.js +68 -28
  179. package/dist/resolvers/ListOperationsResolver.js.map +1 -1
  180. package/dist/resolvers/MCPResolver.d.ts +11 -1
  181. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  182. package/dist/resolvers/MCPResolver.js +7 -3
  183. package/dist/resolvers/MCPResolver.js.map +1 -1
  184. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  185. package/dist/resolvers/QuerySystemUserResolver.js +2 -1
  186. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  187. package/dist/resolvers/RealtimeBridgeResolver.js +3 -3
  188. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +37 -0
  189. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  190. package/dist/resolvers/RealtimeClientSessionResolver.js +101 -11
  191. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  192. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
  193. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -4
  194. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  195. package/dist/resolvers/RunAIAgentResolver.js +74 -42
  196. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  197. package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
  198. package/dist/resolvers/RunAIPromptResolver.js +2 -2
  199. package/dist/resolvers/RunTestResolver.js +5 -5
  200. package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -1
  201. package/dist/resolvers/SearchEntitiesResolver.js +3 -1
  202. package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
  203. package/dist/resolvers/SearchKnowledgeResolver.d.ts +1 -0
  204. package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
  205. package/dist/resolvers/SearchKnowledgeResolver.js +5 -0
  206. package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
  207. package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -1
  208. package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +1 -0
  209. package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -1
  210. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
  211. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  212. package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
  213. package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
  214. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  215. package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
  216. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  217. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  218. package/dist/resolvers/TransactionGroupResolver.js +53 -4
  219. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  220. package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
  221. package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
  222. package/dist/resolvers/adhoc-query-helpers.js +10 -2
  223. package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
  224. package/dist/resolvers/artifact-routing.d.ts +8 -8
  225. package/dist/resolvers/artifact-routing.js +2 -2
  226. package/dist/resolvers/artifact-routing.js.map +1 -1
  227. package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
  228. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  229. package/dist/resolvers/meetingRecordingRegistration.js +24 -8
  230. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  231. package/dist/resolvers/peaksSidecar.d.ts +4 -0
  232. package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
  233. package/dist/resolvers/peaksSidecar.js +10 -2
  234. package/dist/resolvers/peaksSidecar.js.map +1 -1
  235. package/dist/rest/EntityCRUDHandler.d.ts +28 -0
  236. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  237. package/dist/rest/EntityCRUDHandler.js +20 -4
  238. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  239. package/dist/rest/MediaStreamHandler.d.ts +2 -0
  240. package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
  241. package/dist/rest/MediaStreamHandler.js +10 -6
  242. package/dist/rest/MediaStreamHandler.js.map +1 -1
  243. package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
  244. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  245. package/dist/rest/OAuthCallbackHandler.js +15 -3
  246. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  247. package/dist/rest/RESTEndpointHandler.d.ts +6 -4
  248. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  249. package/dist/rest/RESTEndpointHandler.js +15 -11
  250. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  251. package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
  252. package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
  253. package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
  254. package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
  255. package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
  256. package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
  257. package/dist/rest/SignatureWebhookHandler.js +5 -1
  258. package/dist/rest/SignatureWebhookHandler.js.map +1 -1
  259. package/dist/rest/UploadTokenManager.d.ts +24 -8
  260. package/dist/rest/UploadTokenManager.d.ts.map +1 -1
  261. package/dist/rest/UploadTokenManager.js +53 -21
  262. package/dist/rest/UploadTokenManager.js.map +1 -1
  263. package/dist/rest/ViewOperationsHandler.d.ts +20 -0
  264. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  265. package/dist/rest/ViewOperationsHandler.js +20 -4
  266. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  267. package/dist/rest/mediaRange.d.ts +4 -0
  268. package/dist/rest/mediaRange.d.ts.map +1 -1
  269. package/dist/rest/mediaRange.js +10 -2
  270. package/dist/rest/mediaRange.js.map +1 -1
  271. package/dist/rest/setupRESTEndpoints.d.ts +4 -0
  272. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  273. package/dist/rest/setupRESTEndpoints.js +13 -7
  274. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  275. package/dist/serverExtensionReservedRoots.d.ts +2 -0
  276. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  277. package/dist/serverExtensionReservedRoots.js +5 -1
  278. package/dist/serverExtensionReservedRoots.js.map +1 -1
  279. package/dist/services/MJStorageBlobStore.d.ts +41 -0
  280. package/dist/services/MJStorageBlobStore.d.ts.map +1 -0
  281. package/dist/services/MJStorageBlobStore.js +160 -0
  282. package/dist/services/MJStorageBlobStore.js.map +1 -0
  283. package/dist/services/TaskGraphActionRunner.d.ts +13 -8
  284. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  285. package/dist/services/TaskGraphActionRunner.js +16 -15
  286. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  287. package/dist/types.d.ts +2 -0
  288. package/dist/types.d.ts.map +1 -1
  289. package/dist/types.js +7 -3
  290. package/dist/types.js.map +1 -1
  291. package/dist/util.d.ts +12 -3
  292. package/dist/util.d.ts.map +1 -1
  293. package/dist/util.js +19 -8
  294. package/dist/util.js.map +1 -1
  295. package/package.json +92 -92
  296. package/src/__tests__/FileResolverPeaks.test.ts +13 -13
  297. package/src/__tests__/ListOperationsResolver.test.ts +17 -17
  298. package/src/__tests__/MediaStreamHandler.test.ts +28 -28
  299. package/src/__tests__/NoLog.test.ts +11 -11
  300. package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
  301. package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
  302. package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
  303. package/src/__tests__/RealtimeClientSessionResolver.test.ts +229 -0
  304. package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
  305. package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
  306. package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
  307. package/src/__tests__/ResolverBase.isaPromotionCreate.test.ts +412 -0
  308. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +228 -0
  309. package/src/__tests__/SearchEntitiesResolver.readOnlyFallback.test.ts +78 -0
  310. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
  311. package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
  312. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +290 -0
  313. package/src/__tests__/WidgetSessionService.test.ts +8 -4
  314. package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
  315. package/src/__tests__/bootAudit.test.ts +11 -11
  316. package/src/__tests__/boundaryLogPayload.test.ts +6 -6
  317. package/src/__tests__/databaseAbstraction.test.ts +12 -6
  318. package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
  319. package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
  320. package/src/__tests__/magicLink.test.ts +73 -134
  321. package/src/__tests__/magicLinkService.consume.test.ts +224 -0
  322. package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
  323. package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
  324. package/src/__tests__/multiTenancy.security.test.ts +40 -40
  325. package/src/__tests__/multiTenancy.test.ts +18 -18
  326. package/src/__tests__/newUsers.test.ts +24 -23
  327. package/src/__tests__/principals.test.ts +22 -22
  328. package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
  329. package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
  330. package/src/__tests__/secretRedactor.test.ts +12 -12
  331. package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
  332. package/src/__tests__/subscriptionRedaction.test.ts +5 -5
  333. package/src/__tests__/unifiedAuth.test.ts +15 -9
  334. package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
  335. package/src/__tests__/widget.test.ts +45 -45
  336. package/src/__tests__/widgetGuestElevation.test.ts +7 -7
  337. package/src/__tests__/widgetHostIdentity.test.ts +14 -14
  338. package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
  339. package/src/agentSessions/SessionManager.ts +2 -2
  340. package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
  341. package/src/auth/AuthProviderCatalogRouter.ts +6 -1
  342. package/src/auth/__tests__/backward-compatibility.test.ts +3 -3
  343. package/src/auth/exampleNewUserSubClass.ts +2 -2
  344. package/src/auth/index.ts +43 -28
  345. package/src/auth/initializeProviders.ts +25 -5
  346. package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
  347. package/src/auth/magicLink/MagicLinkService.ts +71 -52
  348. package/src/auth/magicLink/index.ts +7 -7
  349. package/src/auth/magicLink/magicLinkCore.ts +96 -64
  350. package/src/auth/magicLink/redeemLanding.ts +14 -4
  351. package/src/auth/magicLink/types.ts +54 -54
  352. package/src/auth/newUsers.ts +1 -1
  353. package/src/auth/principals.ts +11 -2
  354. package/src/cache.ts +4 -1
  355. package/src/config.ts +14 -4
  356. package/src/context.ts +37 -24
  357. package/src/directives/Public.ts +4 -1
  358. package/src/directives/RequireSystemUser.ts +4 -1
  359. package/src/generated/graphql-schemas/__mj.ts +933 -5
  360. package/src/generic/CacheInvalidationResolver.ts +12 -4
  361. package/src/generic/FireAndForgetHeartbeat.ts +19 -14
  362. package/src/generic/PushStatusResolver.ts +21 -7
  363. package/src/generic/ResolverBase.ts +66 -5
  364. package/src/generic/RunViewResolver.ts +22 -22
  365. package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
  366. package/src/index.ts +34 -7
  367. package/src/integration/CustomColumnPromoter.ts +6 -1
  368. package/src/integration/EntityMapLifecycle.ts +20 -2
  369. package/src/logging/NoLog.ts +16 -2
  370. package/src/logging/StartupLogger.ts +6 -1
  371. package/src/logging/bootAudit.ts +26 -16
  372. package/src/logging/boundaryLogPayload.ts +8 -1
  373. package/src/logging/secretRedactor.ts +14 -9
  374. package/src/logging/shortenForLog.ts +8 -3
  375. package/src/logging/variablesLoggingMiddleware.ts +9 -6
  376. package/src/metadataRefreshSignal.ts +45 -0
  377. package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
  378. package/src/multiTenancy/index.ts +29 -5
  379. package/src/realtimeWidget/WidgetRouter.ts +15 -10
  380. package/src/realtimeWidget/WidgetSessionService.ts +76 -76
  381. package/src/realtimeWidget/host-identity.ts +24 -10
  382. package/src/realtimeWidget/index.ts +3 -3
  383. package/src/realtimeWidget/visitorIdentity.ts +39 -8
  384. package/src/realtimeWidget/widgetCore.ts +75 -10
  385. package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
  386. package/src/resolvers/AdhocQueryResolver.ts +3 -3
  387. package/src/resolvers/AgentSessionResolver.ts +3 -3
  388. package/src/resolvers/ColorResolver.ts +6 -6
  389. package/src/resolvers/ComponentRegistryResolver.ts +36 -36
  390. package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
  391. package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
  392. package/src/resolvers/FetchEntityVectorsResolver.ts +2 -2
  393. package/src/resolvers/FileResolver.ts +42 -42
  394. package/src/resolvers/GeoResolver.ts +4 -4
  395. package/src/resolvers/GetDataResolver.ts +50 -15
  396. package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
  397. package/src/resolvers/ListOperationsResolver.ts +78 -28
  398. package/src/resolvers/MCPResolver.ts +30 -15
  399. package/src/resolvers/QueryResolver.ts +13 -13
  400. package/src/resolvers/QuerySystemUserResolver.ts +2 -1
  401. package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
  402. package/src/resolvers/RealtimeClientSessionResolver.ts +126 -13
  403. package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
  404. package/src/resolvers/RunAIAgentResolver.ts +118 -76
  405. package/src/resolvers/RunAIPromptResolver.ts +22 -22
  406. package/src/resolvers/RunTemplateResolver.ts +4 -4
  407. package/src/resolvers/RunTestResolver.ts +25 -25
  408. package/src/resolvers/SearchEntitiesResolver.ts +3 -1
  409. package/src/resolvers/SearchKnowledgeResolver.ts +4 -0
  410. package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +1 -0
  411. package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
  412. package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
  413. package/src/resolvers/TelemetryResolver.ts +79 -79
  414. package/src/resolvers/TransactionGroupResolver.ts +57 -4
  415. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
  416. package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
  417. package/src/resolvers/adhoc-query-helpers.ts +19 -2
  418. package/src/resolvers/artifact-routing.ts +19 -19
  419. package/src/resolvers/meetingRecordingRegistration.ts +44 -10
  420. package/src/resolvers/peaksSidecar.ts +12 -2
  421. package/src/rest/EntityCRUDHandler.ts +24 -4
  422. package/src/rest/MediaStreamHandler.ts +11 -6
  423. package/src/rest/OAuthCallbackHandler.ts +25 -7
  424. package/src/rest/RESTEndpointHandler.ts +20 -15
  425. package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
  426. package/src/rest/SignatureWebhookHandler.ts +6 -1
  427. package/src/rest/UploadTokenManager.ts +66 -30
  428. package/src/rest/ViewOperationsHandler.ts +24 -4
  429. package/src/rest/mediaRange.ts +16 -6
  430. package/src/rest/setupRESTEndpoints.ts +24 -12
  431. package/src/serverExtensionReservedRoots.ts +6 -1
  432. package/src/services/MJStorageBlobStore.ts +209 -0
  433. package/src/services/TaskGraphActionRunner.ts +16 -15
  434. package/src/types.ts +21 -16
  435. package/src/util.ts +22 -9
@@ -0,0 +1,78 @@
1
+ // type-graphql decorators on the resolver need the Reflect.metadata polyfill at import time.
2
+ import 'reflect-metadata';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import type { DatabaseProviderBase, EntitySearchResult, SearchEntityParams } from '@memberjunction/core';
5
+ import { SearchEntitiesResolver } from '../resolvers/SearchEntitiesResolver.js';
6
+ import type { AppContext, ProviderInfo } from '../types.js';
7
+
8
+ /**
9
+ * `SearchEntities` must run on a server that has no read-only database login.
10
+ *
11
+ * The read-only pool exists only when `dbReadOnlyUsername`/`dbReadOnlyPassword` are configured,
12
+ * and most deployments leave them unset. The resolver asked for the read-only provider without
13
+ * allowing a fallback, got `null`, and threw inside its own try/catch — so every client-side
14
+ * `SearchEntity`/`SearchEntities` call over GraphQL came back `Success: false` with no results.
15
+ * Nothing surfaced it: the client maps a failed batch to empty groups, which looks exactly like
16
+ * "no matches". Found by IT52 (SR2) during the 6.2.0-edge.0 release gate.
17
+ */
18
+
19
+ const USER_EMAIL = 'searcher@test';
20
+
21
+ vi.mock('@memberjunction/generic-database-provider', () => ({
22
+ UserCache: {
23
+ Instance: { get Users() { return [{ ID: 'u-1', Email: USER_EMAIL }]; } },
24
+ },
25
+ }));
26
+
27
+ const HIT: EntitySearchResult = {
28
+ entityRecordDocumentId: null,
29
+ recordId: 'E1238F34-0000-0000-0000-000000000001',
30
+ score: 1,
31
+ matchType: 'lexical',
32
+ components: { lexical: 1 },
33
+ } as EntitySearchResult;
34
+
35
+ /** A database provider whose search returns one hit per requested entity. */
36
+ function searchingProvider(): DatabaseProviderBase & { SearchEntities: ReturnType<typeof vi.fn> } {
37
+ const search = vi.fn(async (params: SearchEntityParams[]) => params.map(() => [HIT]));
38
+ return { SearchEntities: search } as unknown as DatabaseProviderBase & { SearchEntities: typeof search };
39
+ }
40
+
41
+ function contextWith(providers: ProviderInfo[]): AppContext {
42
+ return { providers, userPayload: { email: USER_EMAIL } } as unknown as AppContext;
43
+ }
44
+
45
+ const PARAMS = [{ EntityName: 'MJ: Entities', SearchText: 'MJ: Users', Mode: 'lexical' }];
46
+
47
+ describe('SearchEntitiesResolver — provider selection', () => {
48
+ it('searches on the read-write provider when no read-only pool is configured', async () => {
49
+ const readWrite = searchingProvider();
50
+
51
+ const result = await new SearchEntitiesResolver().SearchEntities(
52
+ contextWith([{ provider: readWrite, type: 'Read-Write' }]),
53
+ PARAMS as never,
54
+ );
55
+
56
+ expect(result.ErrorMessage).toBeUndefined();
57
+ expect(result.Success).toBe(true);
58
+ expect(readWrite.SearchEntities).toHaveBeenCalledTimes(1);
59
+ expect(result.Groups[0].Results.map(r => r.RecordID)).toEqual(['E1238F34-0000-0000-0000-000000000001']);
60
+ });
61
+
62
+ it('still prefers the read-only provider when one is configured', async () => {
63
+ const readWrite = searchingProvider();
64
+ const readOnly = searchingProvider();
65
+
66
+ const result = await new SearchEntitiesResolver().SearchEntities(
67
+ contextWith([
68
+ { provider: readWrite, type: 'Read-Write' },
69
+ { provider: readOnly, type: 'Read-Only' },
70
+ ]),
71
+ PARAMS as never,
72
+ );
73
+
74
+ expect(result.Success).toBe(true);
75
+ expect(readOnly.SearchEntities).toHaveBeenCalledTimes(1);
76
+ expect(readWrite.SearchEntities).not.toHaveBeenCalled();
77
+ });
78
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * End-to-end-in-miniature regression test for #4794.
3
+ *
4
+ * The bug: `BuildDurableDeferral` persisted a durable entity action's redacted params as a
5
+ * `LoggedParam[]` ARRAY (`RedactParamsToJSON`'s shape), while `TaskGraphActionRunner.buildParams`
6
+ * reads `Task.InputPayload` back as a name→value RECORD. Every released build with durable dispatch
7
+ * (v6.1.0 onward, including 6.1.4) hit the same real-world failure: `TaskGraphDispatcher`'s
8
+ * `mergedPayload` only merges plain objects, so it silently dropped the array before `buildParams`
9
+ * ever saw it, and the action ran with NONE of its declared inputs. `buildParams` itself has no such
10
+ * guard — `Object.assign(merged, arrayValue)` treats an array's indices as keys — so params named
11
+ * `'0'`, `'1'`, ... would only appear if the array reached it directly, bypassing the dispatcher's
12
+ * drop. That narrower path is exactly what this test exercises, by calling `RunActionForTask`
13
+ * on its own rather than through the dispatcher.
14
+ *
15
+ * This test proves the whole pipe, not just the writer or the reader in isolation: build the
16
+ * payload exactly as the deferral does (`RedactParamsToRecord`), push it through the same
17
+ * `JSON.stringify` / `JSON.parse` round trip `TaskGraphService` performs to persist and reload
18
+ * `Task.InputPayload`, then run it through the real `TaskGraphActionRunner` and assert the action
19
+ * receives its declared parameters BY NAME.
20
+ */
21
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
22
+
23
+ vi.mock('@memberjunction/actions', () => ({
24
+ ActionEngineServer: {
25
+ Instance: {
26
+ Config: vi.fn(),
27
+ Actions: [{ ID: 'A1', Name: 'Log Activity' }],
28
+ RunAction: vi.fn(),
29
+ },
30
+ },
31
+ }));
32
+
33
+ import { ActionEngineServer } from '@memberjunction/actions';
34
+ import { RedactParamsToRecord, ActionParam } from '@memberjunction/actions-base';
35
+ import type { MJActionExecutionLogEntity, MJActionParamEntity, MJEntityActionParamEntity } from '@memberjunction/core-entities';
36
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
37
+ import type { TaskActionRunParams } from '@memberjunction/task-graph';
38
+ import { TaskGraphActionRunner } from '../services/TaskGraphActionRunner';
39
+
40
+ // ── Test fixture builders — same shape as packages/Actions/Base/src/__tests__/ParamRedaction.test.ts ──
41
+
42
+ function actionParam(name: string, value: unknown): ActionParam {
43
+ return { Name: name, Value: value, Type: 'Input' } as ActionParam;
44
+ }
45
+
46
+ function definition(id: string, name: string, logValue: boolean = true): MJActionParamEntity {
47
+ return { ID: id, Name: name, LogValue: logValue } as MJActionParamEntity;
48
+ }
49
+
50
+ function binding(actionParamID: string, valueType: string, logValue: boolean | null = null): MJEntityActionParamEntity {
51
+ return { ActionParamID: actionParamID, ValueType: valueType, LogValue: logValue } as MJEntityActionParamEntity;
52
+ }
53
+
54
+ // The declared parameters for the durable entity action under test: three that log normally, and
55
+ // one ("InternalNotes") whose binding opts out of logging (rule 2 in ParamRedaction.ts).
56
+ const RUNTIME_PARAMS: ActionParam[] = [
57
+ actionParam('TypeCode', 'SystemEvent'),
58
+ actionParam('Title', 'Person created'),
59
+ actionParam('RecordID', 'REC-123'),
60
+ actionParam('InternalNotes', 'do not persist this'),
61
+ ];
62
+ const ACTION_PARAMS: MJActionParamEntity[] = [
63
+ definition('p1', 'TypeCode'),
64
+ definition('p2', 'Title'),
65
+ definition('p3', 'RecordID'),
66
+ definition('p4', 'InternalNotes'),
67
+ ];
68
+ const ENTITY_ACTION_PARAMS: MJEntityActionParamEntity[] = [binding('p4', 'Static', false)];
69
+
70
+ function baseRunParams(inputPayload: unknown): TaskActionRunParams {
71
+ return {
72
+ TaskID: 'T1',
73
+ ActionID: 'A1',
74
+ InputPayload: inputPayload,
75
+ DependencyOutputs: new Map(),
76
+ Provider: {} as IMetadataProvider,
77
+ ContextUser: {} as UserInfo,
78
+ };
79
+ }
80
+
81
+ describe('TaskGraphActionRunner — durable payload round-trips by param name (#4794)', () => {
82
+ beforeEach(() => {
83
+ // vitest's `restoreMocks: true` clears mock implementations before every test, including
84
+ // the ones assigned inside the `vi.mock` factory above — so the return value is (re)armed here.
85
+ vi.mocked(ActionEngineServer.Instance.Config).mockResolvedValue(undefined);
86
+ vi.mocked(ActionEngineServer.Instance.RunAction, { partial: true }).mockResolvedValue({
87
+ Success: true,
88
+ Params: [],
89
+ LogEntry: { ID: 'L1' } as MJActionExecutionLogEntity,
90
+ });
91
+ });
92
+
93
+ it('delivers RedactParamsToRecord\'s output to the action by name, after a JSON round trip', async () => {
94
+ // Exactly what the deferral writes, then exactly what TaskGraphService's JSON column does to it.
95
+ const stored = JSON.parse(JSON.stringify(RedactParamsToRecord(RUNTIME_PARAMS, ACTION_PARAMS, ENTITY_ACTION_PARAMS)));
96
+
97
+ const result = await new TaskGraphActionRunner().RunActionForTask(baseRunParams(stored));
98
+
99
+ expect(result.Success).toBe(true);
100
+ expect(result.ActionLogID).toBe('L1');
101
+
102
+ const runActionCall = vi.mocked(ActionEngineServer.Instance.RunAction).mock.calls[0][0];
103
+ const sentParams = runActionCall.Params as ActionParam[];
104
+
105
+ // The three logged declared names arrive, order-insensitive, with their values intact.
106
+ expect(sentParams.map((p) => p.Name).sort()).toEqual(['RecordID', 'Title', 'TypeCode']);
107
+ expect(sentParams.find((p) => p.Name === 'TypeCode')?.Value).toBe('SystemEvent');
108
+ expect(sentParams.find((p) => p.Name === 'Title')?.Value).toBe('Person created');
109
+ expect(sentParams.find((p) => p.Name === 'RecordID')?.Value).toBe('REC-123');
110
+
111
+ // The binding-suppressed parameter never reaches the action — absent, not present-with-undefined.
112
+ expect(sentParams.some((p) => p.Name === 'InternalNotes')).toBe(false);
113
+ });
114
+
115
+ it('does not turn dependency outputs into params named by the upstream task ID', async () => {
116
+ // The dispatcher keys DependencyOutputs by the upstream task's ID and has ALREADY merged their
117
+ // values into InputPayload (TaskGraphDispatcher.mergedPayload). A runner that also spread the
118
+ // map handed the action one extra param per dependency, named by a GUID and holding that
119
+ // task's whole output — which then landed, unredacted, in ActionExecutionLog.Params.
120
+ const upstreamID = '20924BD7-5128-4150-8404-FB44A55AEA0F';
121
+ const params = baseRunParams({ EntityName: 'MJ: Entities', Record: { ID: 'R1', Name: 'MJ: Tags' } });
122
+ params.DependencyOutputs = new Map([[upstreamID, { Record: { ID: 'R1', Name: 'MJ: Tags' } }]]);
123
+
124
+ await new TaskGraphActionRunner().RunActionForTask(params);
125
+
126
+ const sentParams = vi.mocked(ActionEngineServer.Instance.RunAction).mock.calls[0][0].Params as ActionParam[];
127
+ expect(sentParams.map((p) => p.Name).sort()).toEqual(['EntityName', 'Record']);
128
+ });
129
+ });
@@ -19,7 +19,7 @@ vi.mock('@memberjunction/core', () => ({ LogError: vi.fn() }));
19
19
  import {
20
20
  TASK_GRAPH_FRAMES_TOPIC,
21
21
  TaskGraphFrameBroadcaster,
22
- taskGraphFrameFilter,
22
+ TaskGraphFrameFilter,
23
23
  type TaskGraphFramePayload,
24
24
  } from '../resolvers/TaskGraphFrameResolver';
25
25
  import type { TaskGraphFrame } from '@memberjunction/task-graph';
@@ -44,7 +44,7 @@ const payload = (over: Partial<TaskGraphFramePayload> = {}): TaskGraphFramePaylo
44
44
  const context = (userId: string | null) => (userId ? { userPayload: { userRecord: { ID: userId } } } : {});
45
45
 
46
46
  const check = (over: Partial<TaskGraphFramePayload> = {}, args = GRAPH, userId: string | null = OWNER) =>
47
- taskGraphFrameFilter({ payload: payload(over), args: { parentTaskId: args }, context: context(userId) });
47
+ TaskGraphFrameFilter({ payload: payload(over), args: { parentTaskId: args }, context: context(userId) });
48
48
 
49
49
  describe('taskGraphFrameFilter — the delivery gate', () => {
50
50
  it('delivers a frame for the watched graph to its owner', () => {
@@ -0,0 +1,290 @@
1
+ /**
2
+ * A transaction group whose rows are refused server-side must not report success (issue #4309).
3
+ *
4
+ * `BaseEntity.Save()` and `Delete()` do NOT throw on a logical refusal — they return `false`, and
5
+ * the row is never enrolled, because `TransactionGroup.AddTransaction(...)` is called only from
6
+ * inside `ProviderToUse.Save()`/`Delete()`, which a refusal never reaches. `ExecuteTransactionGroup`
7
+ * discarded that boolean, so:
8
+ *
9
+ * - when EVERY row was refused, the server's group was empty, `TransactionGroupBase.Submit()`
10
+ * took its legitimate "nothing to do" branch and returned `true`, and the resolver reported
11
+ * `Success: true` while serialising each entity's never-persisted in-memory state into
12
+ * `ResultsJSON`;
13
+ * - when only SOME rows were refused, the survivors committed and the caller still saw
14
+ * unqualified success.
15
+ *
16
+ * Reproduced live against MJAPI before this fix: an `ExecuteTransactionGroup` whose two
17
+ * `MJ: User Roles` creates both omitted the NOT NULL `RoleID` returned `Success: true` with zero
18
+ * rows written, and `ErrorMessages` already carrying `"Role ID cannot be null"`.
19
+ *
20
+ * WHY THE PREDICATE IS THE BOOLEAN AND NOT "IS THE GROUP EMPTY". A clean row that is not dirty
21
+ * also fails to enrol, and `Save()` returns `true` for it — correctly, since there is nothing to
22
+ * write. Testing the group's size would turn that legitimate no-op into a failure; testing the
23
+ * return value distinguishes "refused" from "nothing to do". The last test pins this.
24
+ *
25
+ * WHY THE RESOLVER RATHER THAN `Submit()`. An empty group genuinely means "nothing to do" for a
26
+ * caller that enrolled nothing — `transaction-groups.TG1` pins that. The resolver is the only
27
+ * layer that still knows WHICH row was refused and why.
28
+ */
29
+ import 'reflect-metadata';
30
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
31
+ import { BaseEntityResult } from '@memberjunction/core';
32
+
33
+ const logErrorCalls: string[] = [];
34
+ vi.mock('@memberjunction/core', async (importOriginal) => {
35
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
36
+ return { ...actual, LogError: (msg: unknown) => { logErrorCalls.push(String(msg)); } };
37
+ });
38
+
39
+ import {
40
+ TransactionResolver,
41
+ TransactionInputType,
42
+ TransactionItemInputType,
43
+ TransactionOperationType,
44
+ } from '../resolvers/TransactionGroupResolver.js';
45
+ import type { AppContext } from '../types.js';
46
+
47
+ /**
48
+ * A transaction group that records enrolment the way the real one does: `AddTransaction` is
49
+ * reachable only through a provider Save/Delete, so an entity that refuses never appears here, and
50
+ * `Submit()` reproduces `TransactionGroupBase`'s empty-group branch verbatim.
51
+ */
52
+ class FakeTransactionGroup {
53
+ public Enrolled: string[] = [];
54
+ public SubmitCalled = false;
55
+ public SubmitResult = true;
56
+
57
+ public AddTransaction(label: string): void {
58
+ this.Enrolled.push(label);
59
+ }
60
+ public AddVariable(): void { /* no variables in these tests */ }
61
+ public async Submit(): Promise<boolean> {
62
+ this.SubmitCalled = true;
63
+ // TransactionGroupBase.Submit(): an empty group is "nothing to do" and returns true.
64
+ return this.Enrolled.length === 0 ? true : this.SubmitResult;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * What a refusal leaves on `LatestResult` — the shape `MJUserRoleEntityServer.refuse()` registers.
70
+ * A REAL `BaseEntityResult`, so `CompleteMessage` (a prototype getter that folds `Message`, `Error`
71
+ * and `Errors` together) behaves as it does in production rather than as the test wishes.
72
+ */
73
+ function refusalResult(message: string): BaseEntityResult {
74
+ const result = new BaseEntityResult();
75
+ result.Success = false;
76
+ result.Type = 'create';
77
+ result.Message = message;
78
+ return result;
79
+ }
80
+
81
+ function successResult(): BaseEntityResult {
82
+ const result = new BaseEntityResult();
83
+ result.Success = true;
84
+ result.Type = 'create';
85
+ return result;
86
+ }
87
+
88
+ interface FakeEntityOptions {
89
+ /** `false` reproduces a server-side refusal: `Save()`/`Delete()` return false, nothing enrols. */
90
+ accepts: boolean;
91
+ /** True for the not-dirty case: accepted, but nothing to write, so nothing enrols either. */
92
+ clean?: boolean;
93
+ refusalMessage?: string;
94
+ /**
95
+ * What `InnerLoad()` puts on the record, mirroring a real load of an existing row. A `Delete`
96
+ * reports the row it removed, so without this the fake has nothing to report and a test of
97
+ * `ResultsJSON` could not tell an empty payload from a correct one.
98
+ */
99
+ loadedValues?: Record<string, unknown>;
100
+ }
101
+
102
+ /**
103
+ * The minimum of `BaseEntity` the resolver touches. `Save()`/`Delete()` enrol into the group only
104
+ * when they accept AND there is something to write — exactly where `ProviderToUse.Save()` calls
105
+ * `TransactionGroup.AddTransaction(...)`.
106
+ */
107
+ class FakeEntity {
108
+ public TransactionGroup: FakeTransactionGroup | null = null;
109
+ public LatestResult: BaseEntityResult | null = null;
110
+ public PrimaryKeys = [{ Name: 'ID' }];
111
+ private values: Record<string, unknown> = {};
112
+
113
+ constructor(private readonly entityName: string, private readonly options: FakeEntityOptions) {}
114
+
115
+ public SetMany(values: Record<string, unknown>): void {
116
+ this.values = { ...this.values, ...values };
117
+ }
118
+ public async InnerLoad(): Promise<boolean> {
119
+ if (this.options.loadedValues) {
120
+ this.values = { ...this.options.loadedValues };
121
+ }
122
+ return true;
123
+ }
124
+ /**
125
+ * `async`, because the REAL one is: `BaseEntity.GetDataObject` returns `Promise<any>`
126
+ * (`baseEntity.ts:3657`). This fake used to be synchronous, and that divergence is precisely
127
+ * why this suite could not see that `ExecuteTransactionGroup` was calling it without `await` —
128
+ * against a sync fake the resolver stored a plain object and `JSON.stringify` produced real
129
+ * data that production never produced. A fake that is more correct than production hides the
130
+ * bug it exists to catch.
131
+ */
132
+ public async GetDataObject(): Promise<Record<string, unknown>> { return { ...this.values }; }
133
+ public async GetDataObjectJSON(): Promise<string> { return JSON.stringify(this.values); }
134
+
135
+ public async Save(): Promise<boolean> { return this.write(); }
136
+ public async Delete(): Promise<boolean> { return this.write(); }
137
+
138
+ private write(): boolean {
139
+ if (!this.options.accepts) {
140
+ this.LatestResult = refusalResult(this.options.refusalMessage ?? 'refused by a server-side guard');
141
+ return false;
142
+ }
143
+ if (!this.options.clean) {
144
+ this.TransactionGroup?.AddTransaction(this.entityName);
145
+ }
146
+ this.LatestResult = successResult();
147
+ return true;
148
+ }
149
+ }
150
+
151
+ function buildContext(entities: FakeEntity[]): { context: AppContext; group: FakeTransactionGroup } {
152
+ const group = new FakeTransactionGroup();
153
+ let next = 0;
154
+ const provider = {
155
+ CreateTransactionGroup: async () => group,
156
+ GetEntityObject: async () => entities[next++],
157
+ };
158
+ const context = {
159
+ providers: [{ type: 'Read-Write', provider }],
160
+ userPayload: { userRecord: { Email: 'tester@example.test' } },
161
+ } as unknown as AppContext;
162
+ return { context, group };
163
+ }
164
+
165
+ function buildGroupInput(
166
+ count: number,
167
+ operationType: TransactionOperationType = TransactionOperationType.Create
168
+ ): TransactionInputType {
169
+ const items: TransactionItemInputType[] = Array.from({ length: count }, () => ({
170
+ EntityName: 'MJ: User Roles',
171
+ EntityObjectJSON: JSON.stringify({ ID: 'a0000000-0000-0000-0000-000000000001', UserID: 'u1' }),
172
+ OperationType: operationType,
173
+ }));
174
+ return { Items: items, Variables: [] };
175
+ }
176
+
177
+ describe('ExecuteTransactionGroup — rows refused server-side (issue #4309)', () => {
178
+ beforeEach(() => { logErrorCalls.length = 0; });
179
+
180
+ it('reports failure when EVERY row is refused, instead of the empty group reading as success', async () => {
181
+ const entities = [
182
+ new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Role ID cannot be null' }),
183
+ new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Role ID cannot be null' }),
184
+ ];
185
+ const { context, group } = buildContext(entities);
186
+
187
+ const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(2), context);
188
+
189
+ expect(result.Success).toBe(false);
190
+ expect(group.Enrolled).toHaveLength(0);
191
+ });
192
+
193
+ it('carries each refusal reason back in ErrorMessages, index-aligned with Items', async () => {
194
+ const entities = [
195
+ new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'You may only assign a role that you hold yourself' }),
196
+ ];
197
+ const { context } = buildContext(entities);
198
+
199
+ const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(1), context);
200
+
201
+ expect(result.ErrorMessages[0]).toContain('You may only assign a role that you hold yourself');
202
+ });
203
+
204
+ it('fails a PARTIALLY refused group without submitting, so the surviving rows never commit', async () => {
205
+ const entities = [
206
+ new FakeEntity('MJ: User Roles', { accepts: true }),
207
+ new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Name cannot be null' }),
208
+ ];
209
+ const { context, group } = buildContext(entities);
210
+
211
+ const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(2), context);
212
+
213
+ expect(result.Success).toBe(false);
214
+ expect(group.SubmitCalled).toBe(false);
215
+ });
216
+
217
+ it('reports failure when a Delete is refused', async () => {
218
+ const entities = [
219
+ new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'You may only revoke a role that you hold yourself' }),
220
+ ];
221
+ const { context } = buildContext(entities);
222
+
223
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
224
+ buildGroupInput(1, TransactionOperationType.Delete), context
225
+ );
226
+
227
+ expect(result.Success).toBe(false);
228
+ });
229
+
230
+ it('logs the refusal, naming the entity and the reason — it was silent in the server log too', async () => {
231
+ const entities = [
232
+ new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Role ID cannot be null' }),
233
+ ];
234
+ const { context } = buildContext(entities);
235
+
236
+ await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(1), context);
237
+
238
+ expect(logErrorCalls.join('\n')).toContain('MJ: User Roles');
239
+ expect(logErrorCalls.join('\n')).toContain('Role ID cannot be null');
240
+ });
241
+
242
+ it('still reports success when every row is accepted', async () => {
243
+ const entities = [
244
+ new FakeEntity('MJ: User Roles', { accepts: true }),
245
+ new FakeEntity('MJ: User Roles', { accepts: true }),
246
+ ];
247
+ const { context, group } = buildContext(entities);
248
+
249
+ const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(2), context);
250
+
251
+ expect(result.Success).toBe(true);
252
+ expect(group.SubmitCalled).toBe(true);
253
+ });
254
+
255
+ it("returns the deleted row's data in ResultsJSON, not an empty object", async () => {
256
+ // `GetDataObject()` is async, and ExecuteTransactionGroup pushed it WITHOUT awaiting, so
257
+ // PrepareReturnValue's Delete branch stringified a Promise. `JSON.stringify(Promise)` is
258
+ // "{}" — no throw, no type error (the array is `any[]`), just a well-formed empty payload
259
+ // on every Delete a transaction group performs, successful ones included.
260
+ //
261
+ // It is not only a lost payload: GraphQLDataProvider's own Delete transaction callback
262
+ // validates the commit with `pk.Value !== results[pk.FieldName]`, so an empty object makes
263
+ // every key mismatch and reports 'Transaction failed to commit' for a delete that DID
264
+ // commit.
265
+ const deletedRow = { ID: 'a0000000-0000-0000-0000-000000000001', UserID: 'u1', RoleID: 'r1' };
266
+ const entities = [new FakeEntity('MJ: User Roles', { accepts: true, loadedValues: deletedRow })];
267
+ const { context } = buildContext(entities);
268
+
269
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
270
+ buildGroupInput(1, TransactionOperationType.Delete), context
271
+ );
272
+
273
+ expect(result.Success).toBe(true);
274
+ expect(JSON.parse(result.ResultsJSON[0])).toEqual(deletedRow);
275
+ });
276
+
277
+ it('still reports success when accepted rows were CLEAN — an empty group is not itself a refusal', async () => {
278
+ // A not-dirty row returns true from Save() without enrolling. Testing the group's size
279
+ // rather than the return value would turn this legitimate no-op into a failure.
280
+ const entities = [
281
+ new FakeEntity('MJ: User Roles', { accepts: true, clean: true }),
282
+ ];
283
+ const { context, group } = buildContext(entities);
284
+
285
+ const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(1), context);
286
+
287
+ expect(result.Success).toBe(true);
288
+ expect(group.Enrolled).toHaveLength(0);
289
+ });
290
+ });
@@ -21,12 +21,16 @@ vi.mock('../auth/magicLink/MagicLinkService.js', () => ({
21
21
  MagicLinkService: class {},
22
22
  }));
23
23
  vi.mock('../agentSessions/ReturningVisitorRecap.js', () => ({
24
- writeReturningVisitorRecap: async () => undefined,
24
+ WriteReturningVisitorRecap: async () => undefined,
25
+ get writeReturningVisitorRecap() { return this.WriteReturningVisitorRecap; },
25
26
  }));
26
27
  vi.mock('../realtimeWidget/visitorIdentity.js', () => ({
27
- resolveIdentityByEmail: async () => null,
28
- mergeVisitorIdentity: async () => 0,
29
- forgetVisitor: async () => ({ notesArchived: 0, conversationsCleared: 0 }),
28
+ ResolveIdentityByEmail: async () => null,
29
+ get resolveIdentityByEmail() { return this.ResolveIdentityByEmail; },
30
+ MergeVisitorIdentity: async () => 0,
31
+ get mergeVisitorIdentity() { return this.MergeVisitorIdentity; },
32
+ ForgetVisitor: async () => ({ notesArchived: 0, conversationsCleared: 0 }),
33
+ get forgetVisitor() { return this.ForgetVisitor; },
30
34
  }));
31
35
 
32
36
  import { WidgetSessionService } from '../realtimeWidget/WidgetSessionService.js';
@@ -1,50 +1,50 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { exactTotalFromPage, resolveAdhocTotalRowCount } from '../resolvers/adhoc-query-helpers';
2
+ import { ExactTotalFromPage, ResolveAdhocTotalRowCount } from '../resolvers/adhoc-query-helpers';
3
3
 
4
4
  describe('exactTotalFromPage', () => {
5
5
  it('returns the row count when not paging (maxRows null) — the query was uncapped', () => {
6
- expect(exactTotalFromPage(0, 47, null)).toBe(47);
7
- expect(exactTotalFromPage(0, 0, null)).toBe(0);
6
+ expect(ExactTotalFromPage(0, 47, null)).toBe(47);
7
+ expect(ExactTotalFromPage(0, 0, null)).toBe(0);
8
8
  });
9
9
 
10
10
  it('returns startRow + pageLength for a SHORT page (fewer rows than the page size)', () => {
11
- expect(exactTotalFromPage(0, 47, 100)).toBe(47); // page 1, short → exact total
12
- expect(exactTotalFromPage(200, 30, 100)).toBe(230); // page 3, short last page
13
- expect(exactTotalFromPage(100, 0, 100)).toBe(100); // empty page past the end
11
+ expect(ExactTotalFromPage(0, 47, 100)).toBe(47); // page 1, short → exact total
12
+ expect(ExactTotalFromPage(200, 30, 100)).toBe(230); // page 3, short last page
13
+ expect(ExactTotalFromPage(100, 0, 100)).toBe(100); // empty page past the end
14
14
  });
15
15
 
16
16
  it('returns null for a FULL page — a COUNT is required because more rows may exist', () => {
17
- expect(exactTotalFromPage(0, 100, 100)).toBeNull();
18
- expect(exactTotalFromPage(500, 100, 100)).toBeNull();
17
+ expect(ExactTotalFromPage(0, 100, 100)).toBeNull();
18
+ expect(ExactTotalFromPage(500, 100, 100)).toBeNull();
19
19
  });
20
20
 
21
21
  it('treats an over-full page defensively as "needs count" (should not occur with OFFSET/FETCH)', () => {
22
- expect(exactTotalFromPage(0, 150, 100)).toBeNull();
22
+ expect(ExactTotalFromPage(0, 150, 100)).toBeNull();
23
23
  });
24
24
  });
25
25
 
26
26
  describe('resolveAdhocTotalRowCount', () => {
27
27
  it('reads TotalRowCount from the count recordset', () => {
28
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: 2087 }], 100)).toBe(2087);
29
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: 0 }], 100)).toBe(0);
28
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 2087 }], 100)).toBe(2087);
29
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 0 }], 100)).toBe(0);
30
30
  });
31
31
 
32
32
  it('floors a fractional/stringified count', () => {
33
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: 42.9 }], 0)).toBe(42);
34
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: '2087' as unknown as number }], 0)).toBe(2087);
33
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 42.9 }], 0)).toBe(42);
34
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: '2087' as unknown as number }], 0)).toBe(2087);
35
35
  });
36
36
 
37
37
  it('falls back when the count is absent, empty, null, or malformed', () => {
38
- expect(resolveAdhocTotalRowCount(null, 100)).toBe(100);
39
- expect(resolveAdhocTotalRowCount(undefined, 100)).toBe(100);
40
- expect(resolveAdhocTotalRowCount([], 100)).toBe(100);
41
- expect(resolveAdhocTotalRowCount([{}], 100)).toBe(100);
42
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: null }], 100)).toBe(100);
38
+ expect(ResolveAdhocTotalRowCount(null, 100)).toBe(100);
39
+ expect(ResolveAdhocTotalRowCount(undefined, 100)).toBe(100);
40
+ expect(ResolveAdhocTotalRowCount([], 100)).toBe(100);
41
+ expect(ResolveAdhocTotalRowCount([{}], 100)).toBe(100);
42
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: null }], 100)).toBe(100);
43
43
  });
44
44
 
45
45
  it('falls back on non-finite or negative counts (never reports a misleading total)', () => {
46
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: Number.NaN }], 100)).toBe(100);
47
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: -5 }], 100)).toBe(100);
48
- expect(resolveAdhocTotalRowCount([{ TotalRowCount: Infinity }], 100)).toBe(100);
46
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: Number.NaN }], 100)).toBe(100);
47
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: -5 }], 100)).toBe(100);
48
+ expect(ResolveAdhocTotalRowCount([{ TotalRowCount: Infinity }], 100)).toBe(100);
49
49
  });
50
50
  });