@memberjunction/server 6.2.0-edge.0 → 6.2.0-edge.2

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 (459) 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/index.d.ts +20 -1
  16. package/dist/auth/index.d.ts.map +1 -1
  17. package/dist/auth/index.js +20 -10
  18. package/dist/auth/index.js.map +1 -1
  19. package/dist/auth/initializeProviders.d.ts +8 -0
  20. package/dist/auth/initializeProviders.d.ts.map +1 -1
  21. package/dist/auth/initializeProviders.js +21 -5
  22. package/dist/auth/initializeProviders.js.map +1 -1
  23. package/dist/auth/magicLink/MagicLinkRouter.d.ts +9 -0
  24. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkRouter.js +22 -10
  26. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  27. package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
  28. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  29. package/dist/auth/magicLink/MagicLinkService.js +65 -51
  30. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  31. package/dist/auth/magicLink/index.d.ts +2 -2
  32. package/dist/auth/magicLink/index.d.ts.map +1 -1
  33. package/dist/auth/magicLink/index.js +2 -2
  34. package/dist/auth/magicLink/index.js.map +1 -1
  35. package/dist/auth/magicLink/magicLinkCore.d.ts +54 -33
  36. package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
  37. package/dist/auth/magicLink/magicLinkCore.js +61 -61
  38. package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
  39. package/dist/auth/magicLink/redeemLanding.d.ts +4 -0
  40. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  41. package/dist/auth/magicLink/redeemLanding.js +12 -4
  42. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  43. package/dist/auth/magicLink/types.d.ts +4 -4
  44. package/dist/auth/principals.d.ts +2 -0
  45. package/dist/auth/principals.d.ts.map +1 -1
  46. package/dist/auth/principals.js +6 -2
  47. package/dist/auth/principals.js.map +1 -1
  48. package/dist/cache.d.ts +2 -0
  49. package/dist/cache.d.ts.map +1 -1
  50. package/dist/cache.js +3 -1
  51. package/dist/cache.js.map +1 -1
  52. package/dist/config.d.ts +346 -2
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +34 -4
  55. package/dist/config.js.map +1 -1
  56. package/dist/context.d.ts +20 -0
  57. package/dist/context.d.ts.map +1 -1
  58. package/dist/context.js +27 -19
  59. package/dist/context.js.map +1 -1
  60. package/dist/directives/Public.d.ts +2 -0
  61. package/dist/directives/Public.d.ts.map +1 -1
  62. package/dist/directives/Public.js +3 -1
  63. package/dist/directives/Public.js.map +1 -1
  64. package/dist/directives/RequireSystemUser.d.ts +2 -0
  65. package/dist/directives/RequireSystemUser.d.ts.map +1 -1
  66. package/dist/directives/RequireSystemUser.js +3 -1
  67. package/dist/directives/RequireSystemUser.js.map +1 -1
  68. package/dist/generated/graphql-schemas/__mj.d.ts +1021 -0
  69. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  70. package/dist/generated/graphql-schemas/__mj.js +7368 -2145
  71. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  72. package/dist/generic/CacheInvalidationResolver.d.ts +94 -4
  73. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  74. package/dist/generic/CacheInvalidationResolver.js +122 -3
  75. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  76. package/dist/generic/DeleteOptionsInput.d.ts +8 -1
  77. package/dist/generic/DeleteOptionsInput.d.ts.map +1 -1
  78. package/dist/generic/DeleteOptionsInput.js +1 -1
  79. package/dist/generic/DeleteOptionsInput.js.map +1 -1
  80. package/dist/generic/FireAndForgetHeartbeat.d.ts +18 -2
  81. package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
  82. package/dist/generic/FireAndForgetHeartbeat.js +24 -6
  83. package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +31 -0
  85. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -0
  86. package/dist/generic/OrphanedConversationDetailReconciler.js +215 -0
  87. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -0
  88. package/dist/generic/PushStatusResolver.d.ts +48 -1
  89. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  90. package/dist/generic/PushStatusResolver.js +95 -4
  91. package/dist/generic/PushStatusResolver.js.map +1 -1
  92. package/dist/generic/ResolverBase.d.ts +18 -2
  93. package/dist/generic/ResolverBase.d.ts.map +1 -1
  94. package/dist/generic/ResolverBase.js +64 -55
  95. package/dist/generic/ResolverBase.js.map +1 -1
  96. package/dist/index.d.ts +9 -0
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +120 -11
  99. package/dist/index.js.map +1 -1
  100. package/dist/integration/CustomColumnPromoter.d.ts +2 -0
  101. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  102. package/dist/integration/CustomColumnPromoter.js +5 -1
  103. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  104. package/dist/integration/EntityMapLifecycle.d.ts +4 -0
  105. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  106. package/dist/integration/EntityMapLifecycle.js +10 -2
  107. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  108. package/dist/logging/NoLog.d.ts +4 -0
  109. package/dist/logging/NoLog.d.ts.map +1 -1
  110. package/dist/logging/NoLog.js +10 -2
  111. package/dist/logging/NoLog.js.map +1 -1
  112. package/dist/logging/StartupLogger.d.ts +2 -0
  113. package/dist/logging/StartupLogger.d.ts.map +1 -1
  114. package/dist/logging/StartupLogger.js +5 -1
  115. package/dist/logging/StartupLogger.js.map +1 -1
  116. package/dist/logging/bootAudit.d.ts +11 -7
  117. package/dist/logging/bootAudit.d.ts.map +1 -1
  118. package/dist/logging/bootAudit.js +15 -7
  119. package/dist/logging/bootAudit.js.map +1 -1
  120. package/dist/logging/boundaryLogPayload.d.ts +4 -0
  121. package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
  122. package/dist/logging/boundaryLogPayload.js +5 -1
  123. package/dist/logging/boundaryLogPayload.js.map +1 -1
  124. package/dist/logging/secretRedactor.d.ts +2 -0
  125. package/dist/logging/secretRedactor.d.ts.map +1 -1
  126. package/dist/logging/secretRedactor.js +8 -4
  127. package/dist/logging/secretRedactor.js.map +1 -1
  128. package/dist/logging/shortenForLog.d.ts +2 -0
  129. package/dist/logging/shortenForLog.d.ts.map +1 -1
  130. package/dist/logging/shortenForLog.js +7 -3
  131. package/dist/logging/shortenForLog.js.map +1 -1
  132. package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
  133. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  134. package/dist/logging/variablesLoggingMiddleware.js +8 -6
  135. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  136. package/dist/metadataRefreshSignal.d.ts +19 -0
  137. package/dist/metadataRefreshSignal.d.ts.map +1 -0
  138. package/dist/metadataRefreshSignal.js +45 -0
  139. package/dist/metadataRefreshSignal.js.map +1 -0
  140. package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
  141. package/dist/multiTenancy/index.d.ts +8 -0
  142. package/dist/multiTenancy/index.d.ts.map +1 -1
  143. package/dist/multiTenancy/index.js +21 -5
  144. package/dist/multiTenancy/index.js.map +1 -1
  145. package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
  146. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  147. package/dist/realtimeWidget/WidgetRouter.js +14 -10
  148. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  149. package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
  150. package/dist/realtimeWidget/WidgetSessionService.js +27 -27
  151. package/dist/realtimeWidget/host-identity.d.ts +5 -1
  152. package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
  153. package/dist/realtimeWidget/host-identity.js +11 -3
  154. package/dist/realtimeWidget/host-identity.js.map +1 -1
  155. package/dist/realtimeWidget/index.d.ts +2 -2
  156. package/dist/realtimeWidget/index.d.ts.map +1 -1
  157. package/dist/realtimeWidget/index.js +2 -2
  158. package/dist/realtimeWidget/index.js.map +1 -1
  159. package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
  160. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  161. package/dist/realtimeWidget/visitorIdentity.js +16 -4
  162. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  163. package/dist/realtimeWidget/widgetCore.d.ts +48 -0
  164. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  165. package/dist/realtimeWidget/widgetCore.js +38 -10
  166. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  167. package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
  168. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  169. package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
  170. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  171. package/dist/resolvers/AdhocQueryResolver.js +3 -3
  172. package/dist/resolvers/AgentRunStatusPublisher.d.ts +55 -0
  173. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -0
  174. package/dist/resolvers/AgentRunStatusPublisher.js +141 -0
  175. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -0
  176. package/dist/resolvers/ConversationRunEventsResolver.d.ts +90 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -0
  178. package/dist/resolvers/ConversationRunEventsResolver.js +283 -0
  179. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -0
  180. package/dist/resolvers/DuplicateEntryCheckResolver.d.ts +98 -0
  181. package/dist/resolvers/DuplicateEntryCheckResolver.d.ts.map +1 -0
  182. package/dist/resolvers/DuplicateEntryCheckResolver.js +227 -0
  183. package/dist/resolvers/DuplicateEntryCheckResolver.js.map +1 -0
  184. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  185. package/dist/resolvers/FetchEntityVectorsResolver.js +2 -1
  186. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  187. package/dist/resolvers/FileResolver.js +3 -3
  188. package/dist/resolvers/GeoResolver.d.ts +2 -2
  189. package/dist/resolvers/GeoResolver.js +4 -4
  190. package/dist/resolvers/GetDataResolver.d.ts +14 -0
  191. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  192. package/dist/resolvers/GetDataResolver.js +43 -15
  193. package/dist/resolvers/GetDataResolver.js.map +1 -1
  194. package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -2
  195. package/dist/resolvers/ListOperationsResolver.d.ts +20 -0
  196. package/dist/resolvers/ListOperationsResolver.d.ts.map +1 -1
  197. package/dist/resolvers/ListOperationsResolver.js +68 -28
  198. package/dist/resolvers/ListOperationsResolver.js.map +1 -1
  199. package/dist/resolvers/MCPResolver.d.ts +11 -1
  200. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  201. package/dist/resolvers/MCPResolver.js +7 -3
  202. package/dist/resolvers/MCPResolver.js.map +1 -1
  203. package/dist/resolvers/MergeRecordsResolver.d.ts +7 -0
  204. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  205. package/dist/resolvers/MergeRecordsResolver.js +16 -2
  206. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  207. package/dist/resolvers/RealtimeBridgeResolver.js +3 -3
  208. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +44 -6
  209. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RealtimeClientSessionResolver.js +93 -37
  211. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  212. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
  213. package/dist/resolvers/RunAIAgentResolver.d.ts +8 -18
  214. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  215. package/dist/resolvers/RunAIAgentResolver.js +85 -184
  216. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  217. package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
  218. package/dist/resolvers/RunAIPromptResolver.js +2 -2
  219. package/dist/resolvers/RunDecisionResolver.d.ts +149 -0
  220. package/dist/resolvers/RunDecisionResolver.d.ts.map +1 -0
  221. package/dist/resolvers/RunDecisionResolver.js +396 -0
  222. package/dist/resolvers/RunDecisionResolver.js.map +1 -0
  223. package/dist/resolvers/RunTestResolver.js +5 -5
  224. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
  225. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  226. package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
  227. package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
  228. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  229. package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
  230. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  231. package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
  232. package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
  233. package/dist/resolvers/VectorizeEntityResolver.js +14 -10
  234. package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
  235. package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
  236. package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
  237. package/dist/resolvers/adhoc-query-helpers.js +10 -2
  238. package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
  239. package/dist/resolvers/artifact-routing.d.ts +8 -8
  240. package/dist/resolvers/artifact-routing.js +2 -2
  241. package/dist/resolvers/artifact-routing.js.map +1 -1
  242. package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
  243. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  244. package/dist/resolvers/meetingRecordingRegistration.js +24 -8
  245. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  246. package/dist/resolvers/peaksSidecar.d.ts +4 -0
  247. package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
  248. package/dist/resolvers/peaksSidecar.js +10 -2
  249. package/dist/resolvers/peaksSidecar.js.map +1 -1
  250. package/dist/rest/EntityCRUDHandler.d.ts +28 -0
  251. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  252. package/dist/rest/EntityCRUDHandler.js +20 -4
  253. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  254. package/dist/rest/MediaStreamHandler.d.ts +2 -0
  255. package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
  256. package/dist/rest/MediaStreamHandler.js +10 -6
  257. package/dist/rest/MediaStreamHandler.js.map +1 -1
  258. package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
  259. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  260. package/dist/rest/OAuthCallbackHandler.js +15 -3
  261. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  262. package/dist/rest/RESTEndpointHandler.d.ts +6 -4
  263. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  264. package/dist/rest/RESTEndpointHandler.js +15 -11
  265. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  266. package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
  267. package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
  268. package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
  269. package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
  270. package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
  271. package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
  272. package/dist/rest/SignatureWebhookHandler.js +5 -1
  273. package/dist/rest/SignatureWebhookHandler.js.map +1 -1
  274. package/dist/rest/UploadTokenManager.d.ts +24 -8
  275. package/dist/rest/UploadTokenManager.d.ts.map +1 -1
  276. package/dist/rest/UploadTokenManager.js +53 -21
  277. package/dist/rest/UploadTokenManager.js.map +1 -1
  278. package/dist/rest/ViewOperationsHandler.d.ts +20 -0
  279. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  280. package/dist/rest/ViewOperationsHandler.js +20 -4
  281. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  282. package/dist/rest/mediaRange.d.ts +4 -0
  283. package/dist/rest/mediaRange.d.ts.map +1 -1
  284. package/dist/rest/mediaRange.js +10 -2
  285. package/dist/rest/mediaRange.js.map +1 -1
  286. package/dist/rest/setupRESTEndpoints.d.ts +4 -0
  287. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  288. package/dist/rest/setupRESTEndpoints.js +13 -7
  289. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  290. package/dist/serverExtensionReservedRoots.d.ts +2 -0
  291. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  292. package/dist/serverExtensionReservedRoots.js +5 -1
  293. package/dist/serverExtensionReservedRoots.js.map +1 -1
  294. package/dist/services/TaskGraphActionRunner.d.ts +13 -8
  295. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  296. package/dist/services/TaskGraphActionRunner.js +16 -15
  297. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  298. package/dist/types.d.ts +2 -0
  299. package/dist/types.d.ts.map +1 -1
  300. package/dist/types.js +7 -3
  301. package/dist/types.js.map +1 -1
  302. package/dist/util.d.ts +6 -0
  303. package/dist/util.d.ts.map +1 -1
  304. package/dist/util.js +13 -5
  305. package/dist/util.js.map +1 -1
  306. package/package.json +94 -92
  307. package/src/__tests__/AgentRunStatusPublisher.test.ts +175 -0
  308. package/src/__tests__/DeleteOptionsInput.wireShape.test.ts +62 -0
  309. package/src/__tests__/DuplicateEntryCheckResolver.test.ts +300 -0
  310. package/src/__tests__/FileResolverPeaks.test.ts +13 -13
  311. package/src/__tests__/ListOperationsResolver.test.ts +17 -17
  312. package/src/__tests__/MediaStreamHandler.test.ts +28 -28
  313. package/src/__tests__/NoLog.test.ts +11 -11
  314. package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
  315. package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
  316. package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
  317. package/src/__tests__/RealtimeClientSessionResolver.test.ts +216 -2
  318. package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
  319. package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
  320. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +7 -1
  321. package/src/__tests__/RemoteBrowserAudioStream.test.ts +7 -1
  322. package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
  323. package/src/__tests__/RemoteBrowserSnapshot.test.ts +7 -1
  324. package/src/__tests__/ResolverBase.clientOldValues.test.ts +52 -0
  325. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +398 -0
  326. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +18 -27
  327. package/src/__tests__/RunDecisionResolver.test.ts +699 -0
  328. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +131 -0
  329. package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
  330. package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
  331. package/src/__tests__/WidgetSessionService.test.ts +8 -4
  332. package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
  333. package/src/__tests__/bootAudit.test.ts +11 -11
  334. package/src/__tests__/boundaryLogPayload.test.ts +6 -6
  335. package/src/__tests__/cacheInvalidationRecordData.test.ts +177 -0
  336. package/src/__tests__/conversationRunEvents.test.ts +399 -0
  337. package/src/__tests__/databaseAbstraction.test.ts +12 -6
  338. package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
  339. package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
  340. package/src/__tests__/magicLink.test.ts +73 -134
  341. package/src/__tests__/magicLinkService.consume.test.ts +224 -0
  342. package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
  343. package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
  344. package/src/__tests__/multiTenancy.security.test.ts +40 -40
  345. package/src/__tests__/multiTenancy.test.ts +18 -18
  346. package/src/__tests__/newUsers.test.ts +24 -23
  347. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +283 -0
  348. package/src/__tests__/principals.test.ts +22 -22
  349. package/src/__tests__/pushStatusFanOut.test.ts +206 -0
  350. package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
  351. package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
  352. package/src/__tests__/secretRedactor.test.ts +12 -12
  353. package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
  354. package/src/__tests__/subscriptionRedaction.test.ts +5 -5
  355. package/src/__tests__/unifiedAuth.test.ts +15 -9
  356. package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
  357. package/src/__tests__/widget.test.ts +45 -45
  358. package/src/__tests__/widgetGuestElevation.test.ts +7 -7
  359. package/src/__tests__/widgetHostIdentity.test.ts +14 -14
  360. package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
  361. package/src/agentSessions/SessionManager.ts +2 -2
  362. package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
  363. package/src/auth/AuthProviderCatalogRouter.ts +6 -1
  364. package/src/auth/__tests__/backward-compatibility.test.ts +4 -3
  365. package/src/auth/index.ts +43 -28
  366. package/src/auth/initializeProviders.ts +25 -5
  367. package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
  368. package/src/auth/magicLink/MagicLinkService.ts +71 -52
  369. package/src/auth/magicLink/index.ts +7 -7
  370. package/src/auth/magicLink/magicLinkCore.ts +96 -64
  371. package/src/auth/magicLink/redeemLanding.ts +14 -4
  372. package/src/auth/magicLink/types.ts +54 -54
  373. package/src/auth/newUsers.ts +1 -1
  374. package/src/auth/principals.ts +11 -2
  375. package/src/cache.ts +4 -1
  376. package/src/config.ts +37 -4
  377. package/src/context.ts +37 -24
  378. package/src/directives/Public.ts +4 -1
  379. package/src/directives/RequireSystemUser.ts +4 -1
  380. package/src/generated/graphql-schemas/__mj.ts +3674 -4
  381. package/src/generic/CacheInvalidationResolver.ts +148 -4
  382. package/src/generic/DeleteOptionsInput.ts +9 -2
  383. package/src/generic/FireAndForgetHeartbeat.ts +35 -16
  384. package/src/generic/OrphanedConversationDetailReconciler.ts +267 -0
  385. package/src/generic/PushStatusResolver.ts +118 -8
  386. package/src/generic/ResolverBase.ts +65 -57
  387. package/src/generic/RunViewResolver.ts +22 -22
  388. package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
  389. package/src/index.ts +137 -11
  390. package/src/integration/CustomColumnPromoter.ts +6 -1
  391. package/src/integration/EntityMapLifecycle.ts +20 -2
  392. package/src/logging/NoLog.ts +16 -2
  393. package/src/logging/StartupLogger.ts +6 -1
  394. package/src/logging/bootAudit.ts +26 -16
  395. package/src/logging/boundaryLogPayload.ts +8 -1
  396. package/src/logging/secretRedactor.ts +14 -9
  397. package/src/logging/shortenForLog.ts +8 -3
  398. package/src/logging/variablesLoggingMiddleware.ts +9 -6
  399. package/src/metadataRefreshSignal.ts +45 -0
  400. package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
  401. package/src/multiTenancy/index.ts +29 -5
  402. package/src/realtimeWidget/WidgetRouter.ts +15 -10
  403. package/src/realtimeWidget/WidgetSessionService.ts +76 -76
  404. package/src/realtimeWidget/host-identity.ts +24 -10
  405. package/src/realtimeWidget/index.ts +3 -3
  406. package/src/realtimeWidget/visitorIdentity.ts +39 -8
  407. package/src/realtimeWidget/widgetCore.ts +75 -10
  408. package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
  409. package/src/resolvers/AdhocQueryResolver.ts +3 -3
  410. package/src/resolvers/AgentRunStatusPublisher.ts +175 -0
  411. package/src/resolvers/AgentSessionResolver.ts +3 -3
  412. package/src/resolvers/ColorResolver.ts +6 -6
  413. package/src/resolvers/ComponentRegistryResolver.ts +36 -36
  414. package/src/resolvers/ConversationRunEventsResolver.ts +308 -0
  415. package/src/resolvers/DuplicateEntryCheckResolver.ts +232 -0
  416. package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
  417. package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
  418. package/src/resolvers/FetchEntityVectorsResolver.ts +2 -1
  419. package/src/resolvers/FileResolver.ts +42 -42
  420. package/src/resolvers/GeoResolver.ts +4 -4
  421. package/src/resolvers/GetDataResolver.ts +50 -15
  422. package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
  423. package/src/resolvers/ListOperationsResolver.ts +78 -28
  424. package/src/resolvers/MCPResolver.ts +30 -15
  425. package/src/resolvers/MergeRecordsResolver.ts +14 -2
  426. package/src/resolvers/QueryResolver.ts +13 -13
  427. package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
  428. package/src/resolvers/RealtimeClientSessionResolver.ts +96 -38
  429. package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
  430. package/src/resolvers/RunAIAgentResolver.ts +134 -239
  431. package/src/resolvers/RunAIPromptResolver.ts +22 -22
  432. package/src/resolvers/RunDecisionResolver.ts +435 -0
  433. package/src/resolvers/RunTemplateResolver.ts +4 -4
  434. package/src/resolvers/RunTestResolver.ts +25 -25
  435. package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
  436. package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
  437. package/src/resolvers/TelemetryResolver.ts +79 -79
  438. package/src/resolvers/VectorizeEntityResolver.ts +16 -11
  439. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
  440. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +93 -0
  441. package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
  442. package/src/resolvers/adhoc-query-helpers.ts +19 -2
  443. package/src/resolvers/artifact-routing.ts +19 -19
  444. package/src/resolvers/meetingRecordingRegistration.ts +44 -10
  445. package/src/resolvers/peaksSidecar.ts +12 -2
  446. package/src/rest/EntityCRUDHandler.ts +24 -4
  447. package/src/rest/MediaStreamHandler.ts +11 -6
  448. package/src/rest/OAuthCallbackHandler.ts +25 -7
  449. package/src/rest/RESTEndpointHandler.ts +20 -15
  450. package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
  451. package/src/rest/SignatureWebhookHandler.ts +6 -1
  452. package/src/rest/UploadTokenManager.ts +66 -30
  453. package/src/rest/ViewOperationsHandler.ts +24 -4
  454. package/src/rest/mediaRange.ts +16 -6
  455. package/src/rest/setupRESTEndpoints.ts +24 -12
  456. package/src/serverExtensionReservedRoots.ts +6 -1
  457. package/src/services/TaskGraphActionRunner.ts +16 -15
  458. package/src/types.ts +21 -16
  459. package/src/util.ts +16 -6
@@ -2,30 +2,30 @@ import { describe, it, expect } from 'vitest';
2
2
  import jwt from 'jsonwebtoken';
3
3
  import { createPublicKey, type JsonWebKey } from 'node:crypto';
4
4
  import {
5
- generateRawToken,
6
- hashToken,
7
- evaluateInvite,
8
- buildSessionClaims,
9
- buildConsumeInviteSQL,
10
- canIssueInvites,
11
- isRoleGrantable,
12
- unionScopes,
5
+ GenerateRawToken,
6
+ HashToken,
7
+ EvaluateInvite,
8
+ BuildSessionClaims,
9
+ BuildConsumeInvitePostgresSQL,
10
+ CanIssueInvites,
11
+ IsRoleGrantable,
12
+ UnionScopes,
13
13
  MAGIC_LINK_TOKEN_PREFIX,
14
14
  } from '../auth/magicLink/magicLinkCore.js';
15
- import { buildRedeemLandingHtml, escapeHtml } from '../auth/magicLink/redeemLanding.js';
15
+ import { BuildRedeemLandingHtml, EscapeHtml } from '../auth/magicLink/redeemLanding.js';
16
16
  import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
17
17
 
18
18
  describe('magic-link core', () => {
19
19
  describe('generateRawToken', () => {
20
20
  it('prefixes tokens and uses 32 bytes of hex entropy', () => {
21
- const t = generateRawToken();
21
+ const t = GenerateRawToken();
22
22
  expect(t.startsWith(MAGIC_LINK_TOKEN_PREFIX)).toBe(true);
23
23
  const body = t.slice(MAGIC_LINK_TOKEN_PREFIX.length);
24
24
  expect(body).toMatch(/^[0-9a-f]{64}$/);
25
25
  });
26
26
 
27
27
  it('produces unique tokens', () => {
28
- const set = new Set(Array.from({ length: 100 }, () => generateRawToken()));
28
+ const set = new Set(Array.from({ length: 100 }, () => GenerateRawToken()));
29
29
  expect(set.size).toBe(100);
30
30
  });
31
31
  });
@@ -33,15 +33,15 @@ describe('magic-link core', () => {
33
33
  describe('hashToken', () => {
34
34
  it('is deterministic and base64url-encoded sha256 (43 chars, URL-safe alphabet)', () => {
35
35
  const raw = 'mj_ml_abc';
36
- expect(hashToken(raw)).toBe(hashToken(raw));
36
+ expect(HashToken(raw)).toBe(HashToken(raw));
37
37
  // base64url of 32 bytes = 43 chars, no padding, only [A-Za-z0-9_-]
38
- expect(hashToken(raw)).toMatch(/^[A-Za-z0-9_-]{43}$/);
38
+ expect(HashToken(raw)).toMatch(/^[A-Za-z0-9_-]{43}$/);
39
39
  });
40
40
 
41
41
  it('differs for different inputs and never equals the raw token', () => {
42
- const raw = generateRawToken();
43
- expect(hashToken(raw)).not.toBe(hashToken(generateRawToken()));
44
- expect(hashToken(raw)).not.toBe(raw);
42
+ const raw = GenerateRawToken();
43
+ expect(HashToken(raw)).not.toBe(HashToken(GenerateRawToken()));
44
+ expect(HashToken(raw)).not.toBe(raw);
45
45
  });
46
46
  });
47
47
 
@@ -51,37 +51,37 @@ describe('magic-link core', () => {
51
51
  const past = new Date(now - 3600_000);
52
52
 
53
53
  it('accepts an active, unexpired, unused invite', () => {
54
- expect(evaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: true });
54
+ expect(EvaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: true });
55
55
  });
56
56
 
57
57
  it('rejects revoked invites first', () => {
58
- expect(evaluateInvite({ Status: 'Revoked', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'revoked' });
58
+ expect(EvaluateInvite({ Status: 'Revoked', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'revoked' });
59
59
  });
60
60
 
61
61
  it('rejects expired invites', () => {
62
- expect(evaluateInvite({ Status: 'Active', ExpiresAt: past, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'expired' });
62
+ expect(EvaluateInvite({ Status: 'Active', ExpiresAt: past, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'expired' });
63
63
  });
64
64
 
65
65
  it('rejects consumed invites (by status)', () => {
66
- expect(evaluateInvite({ Status: 'Consumed', ExpiresAt: future, MaxUses: 1, UseCount: 1 }, now)).toEqual({ ok: false, errorCode: 'consumed' });
66
+ expect(EvaluateInvite({ Status: 'Consumed', ExpiresAt: future, MaxUses: 1, UseCount: 1 }, now)).toEqual({ ok: false, errorCode: 'consumed' });
67
67
  });
68
68
 
69
69
  it('rejects when use count reaches max', () => {
70
- expect(evaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 3, UseCount: 3 }, now)).toEqual({ ok: false, errorCode: 'consumed' });
70
+ expect(EvaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 3, UseCount: 3 }, now)).toEqual({ ok: false, errorCode: 'consumed' });
71
71
  });
72
72
 
73
73
  it('allows multi-use invites that still have uses left', () => {
74
- expect(evaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 3, UseCount: 2 }, now)).toEqual({ ok: true });
74
+ expect(EvaluateInvite({ Status: 'Active', ExpiresAt: future, MaxUses: 3, UseCount: 2 }, now)).toEqual({ ok: true });
75
75
  });
76
76
 
77
77
  it('rejects unknown statuses as invalid', () => {
78
- expect(evaluateInvite({ Status: 'Pending', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'invalid' });
78
+ expect(EvaluateInvite({ Status: 'Pending', ExpiresAt: future, MaxUses: 1, UseCount: 0 }, now)).toEqual({ ok: false, errorCode: 'invalid' });
79
79
  });
80
80
  });
81
81
 
82
82
  describe('buildSessionClaims', () => {
83
83
  it('scopes the token to exactly the given app and role and marks it magic-link', () => {
84
- const claims = buildSessionClaims({
84
+ const claims = BuildSessionClaims({
85
85
  issuer: 'http://localhost:4051',
86
86
  audience: 'mj-magic-link',
87
87
  inviteId: 'INVITE-1',
@@ -103,7 +103,7 @@ describe('magic-link core', () => {
103
103
  });
104
104
 
105
105
  it('carries mj_invited_by when an inviter is supplied (attribution claim)', () => {
106
- const claims = buildSessionClaims({
106
+ const claims = BuildSessionClaims({
107
107
  issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
108
108
  applicationId: 'APP-1', roleName: 'Magic Link Baseline',
109
109
  invitedByUserId: 'USER-42', nowSeconds: 1000, ttlSeconds: 3600,
@@ -112,7 +112,7 @@ describe('magic-link core', () => {
112
112
  });
113
113
 
114
114
  it('omits mj_invited_by when no inviter is supplied', () => {
115
- const claims = buildSessionClaims({
115
+ const claims = BuildSessionClaims({
116
116
  issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
117
117
  applicationId: 'APP-1', roleName: 'Magic Link Baseline',
118
118
  nowSeconds: 1000, ttlSeconds: 3600,
@@ -121,7 +121,7 @@ describe('magic-link core', () => {
121
121
  });
122
122
 
123
123
  it('always emits a single-entry mj_scopes for the current link', () => {
124
- const claims = buildSessionClaims({
124
+ const claims = BuildSessionClaims({
125
125
  issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
126
126
  applicationId: 'APP-1', roleName: 'Magic Link Baseline', nowSeconds: 1000, ttlSeconds: 3600,
127
127
  });
@@ -129,7 +129,7 @@ describe('magic-link core', () => {
129
129
  });
130
130
 
131
131
  it('marks anonymous sessions and carries the per-session id + prior-scope union', () => {
132
- const claims = buildSessionClaims({
132
+ const claims = BuildSessionClaims({
133
133
  issuer: 'i', audience: 'a', inviteId: 'INVITE-2', email: 'anon@x',
134
134
  applicationId: 'APP-2', roleName: 'Guest', anonymous: true, sessionId: 'SID-9',
135
135
  priorScopes: [{ inviteId: 'INVITE-1', appId: 'APP-1', role: 'Guest' }],
@@ -142,7 +142,7 @@ describe('magic-link core', () => {
142
142
  });
143
143
 
144
144
  it('does NOT mark mj_anon for email sessions', () => {
145
- const claims = buildSessionClaims({
145
+ const claims = BuildSessionClaims({
146
146
  issuer: 'i', audience: 'a', inviteId: 'INVITE-1', email: 'e@x.com',
147
147
  applicationId: 'APP-1', roleName: 'Magic Link Baseline', nowSeconds: 1000, ttlSeconds: 3600,
148
148
  });
@@ -155,84 +155,26 @@ describe('magic-link core', () => {
155
155
  const b = { inviteId: 'I2', appId: 'A2', role: 'R' };
156
156
 
157
157
  it('appends a new scope entry', () => {
158
- expect(unionScopes([a], b)).toEqual([a, b]);
158
+ expect(UnionScopes([a], b)).toEqual([a, b]);
159
159
  });
160
160
 
161
161
  it('is idempotent by inviteId — re-redeeming the same link never accretes a duplicate', () => {
162
- expect(unionScopes([a, b], { inviteId: 'I1', appId: 'A1', role: 'R' })).toEqual([a, b]);
162
+ expect(UnionScopes([a, b], { inviteId: 'I1', appId: 'A1', role: 'R' })).toEqual([a, b]);
163
163
  });
164
164
 
165
165
  it('handles an empty/undefined prior union', () => {
166
- expect(unionScopes(undefined, a)).toEqual([a]);
167
- expect(unionScopes([], a)).toEqual([a]);
166
+ expect(UnionScopes(undefined, a)).toEqual([a]);
167
+ expect(UnionScopes([], a)).toEqual([a]);
168
168
  });
169
169
  });
170
170
 
171
- describe('buildConsumeInviteSQL', () => {
172
- const table = '[__mj].[MagicLinkInvite]';
173
- const sql = buildConsumeInviteSQL(table);
174
-
175
- it('targets the supplied qualified table', () => {
176
- expect(sql).toContain(`UPDATE ${table} `);
177
- });
178
-
179
- it('increments UseCount', () => {
180
- expect(sql).toContain('UseCount = UseCount + 1');
181
- });
182
-
183
- it('stamps ConsumedAt only the first time (COALESCE preserves an existing value)', () => {
184
- expect(sql).toContain('ConsumedAt = COALESCE(ConsumedAt, SYSUTCDATETIME())');
185
- });
186
-
187
- it('flips Status to Consumed exactly when the last use is taken', () => {
188
- expect(sql).toContain("Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END");
189
- });
190
-
191
- it('returns the affected row via OUTPUT INTO a table var so the caller can detect a win (exactly one row)', () => {
192
- // Must be OUTPUT ... INTO, not a bare OUTPUT: SQL Server forbids a bare
193
- // OUTPUT clause on a table with enabled triggers, and CodeGen adds an
194
- // __mj_UpdatedAt trigger to every MJ table. Regression guard for that bug.
195
- expect(sql).toContain('OUTPUT INSERTED.ID INTO @consumed');
196
- expect(sql).not.toMatch(/OUTPUT INSERTED\.ID(?!\s+INTO)/);
197
- expect(sql).toContain('SELECT ID FROM @consumed');
198
- });
199
-
200
- it('guards atomically on Active + not-exhausted + not-expired — this IS the single-use gate', () => {
201
- const where = sql.slice(sql.indexOf('WHERE'));
202
- expect(where).toContain("Status = 'Active'");
203
- expect(where).toContain('UseCount < MaxUses');
204
- expect(where).toContain('ExpiresAt > SYSUTCDATETIME()');
205
- });
206
-
207
- it('binds the invite ID as a parameter, never interpolated (injection-safe)', () => {
208
- expect(sql).toContain('ID = @p0');
209
- // The id must not be string-interpolated with quotes around a value.
210
- expect(sql).not.toMatch(/ID = '/);
211
- });
212
-
213
- it('is a self-contained batch: declare table var, update-with-output, select', () => {
214
- // Intentionally a 3-statement batch (the OUTPUT-INTO requirement). The ID is
215
- // still parameterized, so the batch carries no interpolated user input.
216
- expect(sql).toContain('DECLARE @consumed TABLE (ID UNIQUEIDENTIFIER)');
217
- expect(sql.match(/;/g)?.length).toBe(3);
218
- expect(sql.trim().endsWith(';')).toBe(true);
219
- });
220
-
221
- it('rejects a non-whitelisted table identifier (defense-in-depth against injection)', () => {
222
- // Only bracket-quoted [schema].[table] of word chars is accepted, even though
223
- // the caller derives the table from EntityInfo and never from user input.
224
- expect(() => buildConsumeInviteSQL('__mj.MagicLinkInvite')).toThrow();
225
- expect(() => buildConsumeInviteSQL('[__mj].[MagicLinkInvite]; DROP TABLE x;--')).toThrow();
226
- expect(() => buildConsumeInviteSQL('[__mj].[Magic Link]')).toThrow();
227
- expect(() => buildConsumeInviteSQL(table)).not.toThrow();
228
- });
229
- });
230
-
231
- describe('buildConsumeInviteSQL (postgresql dialect)', () => {
232
- // PG uses `schema.table` (unquoted — PostgreSQLDataProvider.ExecuteSQL auto-quotes
233
- // the PascalCase identifiers). SS uses the bracket-quoted `[schema].[table]` form.
171
+ describe('BuildConsumeInvitePostgresSQL', () => {
172
+ // PostgreSQL only: SQL Server consumes through spConsumeMagicLinkInvite (#4753), but PG has no
173
+ // such function and its runtime roles hold table DML, so PG keeps this direct guarded UPDATE.
174
+ // PG uses `schema.table` unquoted — PostgreSQLDataProvider.ExecuteSQL auto-quotes the
175
+ // PascalCase identifiers.
234
176
  const pgTable = '__mj.MagicLinkInvite';
235
- const pgSql = buildConsumeInviteSQL(pgTable, 'postgresql');
177
+ const pgSql = BuildConsumeInvitePostgresSQL(pgTable);
236
178
 
237
179
  it('targets the supplied unquoted schema.table', () => {
238
180
  expect(pgSql).toContain(`UPDATE ${pgTable} `);
@@ -246,63 +188,60 @@ describe('magic-link core', () => {
246
188
 
247
189
  it('binds the invite ID as PG positional param $1, not T-SQL @p0 (injection-safe)', () => {
248
190
  expect(pgSql).toContain('ID = $1');
249
- expect(pgSql).not.toContain('@p0');
250
- expect(pgSql).not.toMatch(/@/); // no T-SQL @-anything survives on PG
191
+ expect(pgSql).not.toMatch(/@/);
251
192
  expect(pgSql).not.toMatch(/ID = '/);
252
193
  });
253
194
 
254
195
  it('uses PG timestamp expressions, not T-SQL SYSUTCDATETIME()', () => {
255
196
  expect(pgSql).not.toContain('SYSUTCDATETIME');
256
- expect(pgSql).toContain("(now() AT TIME ZONE 'utc')");
257
197
  expect(pgSql).toContain("ConsumedAt = COALESCE(ConsumedAt, (now() AT TIME ZONE 'utc'))");
258
198
  });
259
199
 
260
- it('guards atomically on Active + not-exhausted + not-expired (same predicate as SS)', () => {
200
+ it('guards atomically on Active + not-exhausted + not-expired (same predicate as spConsumeMagicLinkInvite)', () => {
261
201
  const where = pgSql.slice(pgSql.indexOf('WHERE'));
262
202
  expect(where).toContain("Status = 'Active'");
263
203
  expect(where).toContain('UseCount < MaxUses');
264
204
  expect(where).toContain("ExpiresAt > (now() AT TIME ZONE 'utc')");
265
205
  });
266
206
 
267
- it('increments UseCount and flips Status on the last use (same semantics as SS)', () => {
207
+ it('increments UseCount and flips Status on the last use (same semantics as spConsumeMagicLinkInvite)', () => {
268
208
  expect(pgSql).toContain('UseCount = UseCount + 1');
269
209
  expect(pgSql).toContain("Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END");
270
210
  });
271
211
 
272
- it('rejects a non-whitelisted PG table identifier (defense-in-depth against injection)', () => {
273
- // Bracket-quoted (SS) form is not a valid PG identifier here.
274
- expect(() => buildConsumeInviteSQL('[__mj].[MagicLinkInvite]', 'postgresql')).toThrow();
275
- expect(() => buildConsumeInviteSQL('__mj.MagicLinkInvite; DROP TABLE x;--', 'postgresql')).toThrow();
276
- expect(() => buildConsumeInviteSQL('__mj.Magic Link', 'postgresql')).toThrow();
277
- expect(() => buildConsumeInviteSQL(pgTable, 'postgresql')).not.toThrow();
212
+ it('rejects a non-whitelisted table identifier (defense-in-depth against injection)', () => {
213
+ expect(() => BuildConsumeInvitePostgresSQL('[__mj].[MagicLinkInvite]')).toThrow();
214
+ expect(() => BuildConsumeInvitePostgresSQL('__mj.MagicLinkInvite; DROP TABLE x;--')).toThrow();
215
+ expect(() => BuildConsumeInvitePostgresSQL('__mj.Magic Link')).toThrow();
216
+ expect(() => BuildConsumeInvitePostgresSQL(pgTable)).not.toThrow();
278
217
  });
279
218
  });
280
219
 
281
220
  describe('canIssueInvites', () => {
282
221
  it('always allows Owners, regardless of issuer-role config (case/space-insensitive)', () => {
283
- expect(canIssueInvites('Owner', [], [])).toBe(true);
284
- expect(canIssueInvites(' owner ', [], [])).toBe(true);
222
+ expect(CanIssueInvites('Owner', [], [])).toBe(true);
223
+ expect(CanIssueInvites(' owner ', [], [])).toBe(true);
285
224
  });
286
225
 
287
226
  it('Owner-only by default: a non-Owner with no configured issuer roles is denied', () => {
288
- expect(canIssueInvites('User', ['Developer', 'Magic Link Baseline'], [])).toBe(false);
227
+ expect(CanIssueInvites('User', ['Developer', 'Magic Link Baseline'], [])).toBe(false);
289
228
  });
290
229
 
291
230
  it('denies an external user holding the restricted role (the escalation we are blocking)', () => {
292
231
  // restricted role is never an issuer role, so this stays false even if someone
293
232
  // mistakenly leaves issuerRoleNames empty
294
- expect(canIssueInvites('User', ['Magic Link Baseline'], [])).toBe(false);
233
+ expect(CanIssueInvites('User', ['Magic Link Baseline'], [])).toBe(false);
295
234
  });
296
235
 
297
236
  it('allows a non-Owner only when one of their roles is a configured issuer role', () => {
298
- expect(canIssueInvites('User', ['Sales Admin'], ['Sales Admin'])).toBe(true);
299
- expect(canIssueInvites('User', ['sales admin'], ['Sales Admin'])).toBe(true); // case-insensitive
300
- expect(canIssueInvites('User', ['Marketing'], ['Sales Admin'])).toBe(false);
237
+ expect(CanIssueInvites('User', ['Sales Admin'], ['Sales Admin'])).toBe(true);
238
+ expect(CanIssueInvites('User', ['sales admin'], ['Sales Admin'])).toBe(true); // case-insensitive
239
+ expect(CanIssueInvites('User', ['Marketing'], ['Sales Admin'])).toBe(false);
301
240
  });
302
241
 
303
242
  it('handles null/undefined type and empty role lists safely', () => {
304
- expect(canIssueInvites(null, [], ['X'])).toBe(false);
305
- expect(canIssueInvites(undefined, ['X'], ['X'])).toBe(true);
243
+ expect(CanIssueInvites(null, [], ['X'])).toBe(false);
244
+ expect(CanIssueInvites(undefined, ['X'], ['X'])).toBe(true);
306
245
  });
307
246
  });
308
247
 
@@ -310,41 +249,41 @@ describe('magic-link core', () => {
310
249
  const restricted = 'Magic Link Baseline';
311
250
 
312
251
  it('always allows the restricted role (case/space-insensitive)', () => {
313
- expect(isRoleGrantable('Magic Link Baseline', restricted, [])).toBe(true);
314
- expect(isRoleGrantable(' magic link baseline ', restricted, [])).toBe(true);
252
+ expect(IsRoleGrantable('Magic Link Baseline', restricted, [])).toBe(true);
253
+ expect(IsRoleGrantable(' magic link baseline ', restricted, [])).toBe(true);
315
254
  });
316
255
 
317
256
  it('rejects a privileged role by default — blocks roleId=Owner escalation', () => {
318
- expect(isRoleGrantable('Owner', restricted, [])).toBe(false);
319
- expect(isRoleGrantable('Administrator', restricted, [])).toBe(false);
257
+ expect(IsRoleGrantable('Owner', restricted, [])).toBe(false);
258
+ expect(IsRoleGrantable('Administrator', restricted, [])).toBe(false);
320
259
  });
321
260
 
322
261
  it('allows additional roles only when explicitly opted in', () => {
323
- expect(isRoleGrantable('Read Only Guest', restricted, ['Read Only Guest'])).toBe(true);
324
- expect(isRoleGrantable('read only guest', restricted, ['Read Only Guest'])).toBe(true);
325
- expect(isRoleGrantable('Owner', restricted, ['Read Only Guest'])).toBe(false);
262
+ expect(IsRoleGrantable('Read Only Guest', restricted, ['Read Only Guest'])).toBe(true);
263
+ expect(IsRoleGrantable('read only guest', restricted, ['Read Only Guest'])).toBe(true);
264
+ expect(IsRoleGrantable('Owner', restricted, ['Read Only Guest'])).toBe(false);
326
265
  });
327
266
 
328
267
  it('rejects empty/null role names', () => {
329
- expect(isRoleGrantable('', restricted, [])).toBe(false);
330
- expect(isRoleGrantable(null, restricted, [])).toBe(false);
268
+ expect(IsRoleGrantable('', restricted, [])).toBe(false);
269
+ expect(IsRoleGrantable(null, restricted, [])).toBe(false);
331
270
  });
332
271
  });
333
272
 
334
273
  describe('escapeHtml', () => {
335
274
  it('escapes the five significant HTML characters', () => {
336
- expect(escapeHtml(`<>&"'`)).toBe('&lt;&gt;&amp;&quot;&#39;');
275
+ expect(EscapeHtml(`<>&"'`)).toBe('&lt;&gt;&amp;&quot;&#39;');
337
276
  });
338
277
 
339
278
  it('leaves a normal magic-link token untouched', () => {
340
- const t = generateRawToken();
341
- expect(escapeHtml(t)).toBe(t);
279
+ const t = GenerateRawToken();
280
+ expect(EscapeHtml(t)).toBe(t);
342
281
  });
343
282
  });
344
283
 
345
284
  describe('buildRedeemLandingHtml', () => {
346
- const token = generateRawToken();
347
- const html = buildRedeemLandingHtml(token, '/magic-link/redeem');
285
+ const token = GenerateRawToken();
286
+ const html = BuildRedeemLandingHtml(token, '/magic-link/redeem');
348
287
 
349
288
  it('renders a POST form to the redeem path (GET stays side-effect-free)', () => {
350
289
  expect(html).toContain('method="POST"');
@@ -365,7 +304,7 @@ describe('magic-link core', () => {
365
304
  });
366
305
 
367
306
  it('escapes a token containing HTML metacharacters into the form value', () => {
368
- const evil = buildRedeemLandingHtml('a"><script>x</script>', '/magic-link/redeem');
307
+ const evil = BuildRedeemLandingHtml('a"><script>x</script>', '/magic-link/redeem');
369
308
  expect(evil).not.toContain('<script>x</script>');
370
309
  expect(evil).toContain('&lt;script&gt;');
371
310
  });
@@ -377,7 +316,7 @@ describe('MagicLinkKeyManager', () => {
377
316
  const km = MagicLinkKeyManager.Instance;
378
317
  km.Initialize(); // ephemeral keypair
379
318
 
380
- const claims = buildSessionClaims({
319
+ const claims = BuildSessionClaims({
381
320
  issuer: 'http://localhost:4051',
382
321
  audience: 'mj-magic-link',
383
322
  inviteId: 'INVITE-1',
@@ -414,7 +353,7 @@ describe('MagicLinkKeyManager', () => {
414
353
  it('rejects a tampered token', () => {
415
354
  const km = MagicLinkKeyManager.Instance;
416
355
  km.Initialize();
417
- const claims = buildSessionClaims({
356
+ const claims = BuildSessionClaims({
418
357
  issuer: 'http://localhost:4051',
419
358
  audience: 'mj-magic-link',
420
359
  inviteId: 'INVITE-2',
@@ -0,0 +1,224 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
2
+
3
+ // MagicLinkService is the imperative shell — importing it pulls the full
4
+ // data-provider and communication stacks in at module load. Stub those out;
5
+ // @memberjunction/core stays real (Metadata, RunView, LogError).
6
+ vi.mock('@memberjunction/generic-database-provider', () => ({
7
+ UserCache: { Instance: { Users: [] } },
8
+ }));
9
+ vi.mock('@memberjunction/communication-engine', () => ({
10
+ CommunicationEngine: { Instance: {} },
11
+ }));
12
+ vi.mock('@memberjunction/communication-types', () => ({
13
+ Message: class {},
14
+ }));
15
+ vi.mock('../config.js', () => ({
16
+ configInfo: {},
17
+ }));
18
+
19
+ import { Metadata, RunView, type DatabaseProviderBase, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
20
+ import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
21
+ import type { MagicLinkConfig } from '../config.js';
22
+ import { MagicLinkService } from '../auth/magicLink/MagicLinkService.js';
23
+ import { MAGIC_LINK_TOKEN_PREFIX, BuildConsumeInvitePostgresSQL } from '../auth/magicLink/magicLinkCore.js';
24
+
25
+ type Platform = 'sqlserver' | 'postgresql';
26
+
27
+ /** Provider surface consumeInvite/RedeemInvite touch, with real-shaped dialect behavior. */
28
+ interface FakeProvider {
29
+ PlatformKey: Platform;
30
+ MJCoreSchemaName: string;
31
+ Roles: { ID: string; Name: string }[];
32
+ Applications: { ID: string; Name: string; Path: string }[];
33
+ Dialect: { ProcedureCallSyntax: (schema: string, name: string, params: string[]) => string };
34
+ BuildParameterPlaceholder: (index: number) => string;
35
+ ExecuteSQL: Mock;
36
+ }
37
+
38
+ function makeProvider(platform: Platform): FakeProvider {
39
+ const isPg = platform === 'postgresql';
40
+ return {
41
+ PlatformKey: platform,
42
+ MJCoreSchemaName: '__mj',
43
+ Roles: [{ ID: 'role-1', Name: 'Magic Link Baseline' }],
44
+ Applications: [],
45
+ Dialect: {
46
+ ProcedureCallSyntax: isPg
47
+ ? (s, n, p) => `SELECT * FROM ${s}."${n}"(${p.join(', ')})`
48
+ : (s, n, p) => `EXEC [${s}].[${n}] ${p.join(', ')}`,
49
+ },
50
+ BuildParameterPlaceholder: isPg ? (i) => `$${i + 1}` : (i) => `@p${i}`,
51
+ ExecuteSQL: vi.fn(),
52
+ };
53
+ }
54
+
55
+ const INVITE_ID = '11111111-2222-3333-4444-555555555555';
56
+
57
+ function makeInvite(): MJMagicLinkInviteEntity {
58
+ return {
59
+ ID: INVITE_ID,
60
+ Status: 'Active',
61
+ UseCount: 0,
62
+ MaxUses: 1,
63
+ ExpiresAt: new Date(Date.now() + 3600_000),
64
+ RoleID: 'role-1',
65
+ CreatedByUserID: 'inviter-1',
66
+ ApplicationID: 'app-1',
67
+ Email: 'g@example.com',
68
+ } as unknown as MJMagicLinkInviteEntity;
69
+ }
70
+
71
+ /** Private surface of MagicLinkService the tests drive or stub. */
72
+ interface ServicePrivates {
73
+ consumeInvite: (invite: MJMagicLinkInviteEntity, provider: DatabaseProviderBase, contextUser: UserInfo) => Promise<string>;
74
+ resolveProvisioningContextUser: () => UserInfo | null;
75
+ isInviterActive: (id: string) => boolean;
76
+ recordRedemption: () => Promise<void>;
77
+ provisionUser: () => Promise<{ success: boolean; error?: string }>;
78
+ }
79
+
80
+ describe('MagicLinkService.consumeInvite (#4753)', () => {
81
+ let service: MagicLinkService;
82
+ let privates: ServicePrivates;
83
+ let invite: MJMagicLinkInviteEntity;
84
+ const contextUser = { ID: 'ctx-user' } as UserInfo;
85
+
86
+ beforeEach(() => {
87
+ service = new MagicLinkService('https://mj.example.com', {} as MagicLinkConfig);
88
+ privates = service as unknown as ServicePrivates;
89
+ invite = makeInvite();
90
+ });
91
+
92
+ afterEach(() => {
93
+ Metadata.Provider = null as unknown as IMetadataProvider;
94
+ });
95
+
96
+ async function consume(provider: FakeProvider): Promise<string> {
97
+ return privates.consumeInvite(invite, provider as unknown as DatabaseProviderBase, contextUser);
98
+ }
99
+
100
+ it('SQL Server: calls spConsumeMagicLinkInvite with the ID bound by name, never interpolated', async () => {
101
+ const provider = makeProvider('sqlserver');
102
+ provider.ExecuteSQL.mockResolvedValue([{ ID: INVITE_ID }]);
103
+ await consume(provider);
104
+
105
+ expect(provider.ExecuteSQL).toHaveBeenCalledTimes(1);
106
+ const [sql, params, options, user] = provider.ExecuteSQL.mock.calls[0];
107
+ expect(sql).toBe('EXEC [__mj].[spConsumeMagicLinkInvite] @ID=@p0');
108
+ expect(params).toEqual([INVITE_ID]);
109
+ expect(options).toEqual(expect.objectContaining({ isMutation: true }));
110
+ expect(user).toBe(contextUser);
111
+ expect(sql).not.toContain(INVITE_ID);
112
+ expect(String(sql).toUpperCase()).not.toContain('UPDATE');
113
+ });
114
+
115
+ // PG has no spConsumeMagicLinkInvite function (the release-time converter does not emit
116
+ // procedures) and PG's runtime roles hold table DML, so PG keeps the direct guarded UPDATE.
117
+ it('PostgreSQL: runs the guarded UPDATE … RETURNING directly, with the ID bound as $1', async () => {
118
+ const provider = makeProvider('postgresql');
119
+ provider.ExecuteSQL.mockResolvedValue([{ ID: INVITE_ID }]);
120
+ await consume(provider);
121
+
122
+ expect(provider.ExecuteSQL).toHaveBeenCalledTimes(1);
123
+ const [sql, params, options, user] = provider.ExecuteSQL.mock.calls[0];
124
+ expect(sql).toBe(BuildConsumeInvitePostgresSQL('__mj.MagicLinkInvite'));
125
+ expect(sql).not.toContain('spConsumeMagicLinkInvite');
126
+ expect(params).toEqual([INVITE_ID]);
127
+ expect(options).toEqual(expect.objectContaining({ isMutation: true }));
128
+ expect(user).toBe(contextUser);
129
+ expect(sql).not.toContain(INVITE_ID);
130
+ });
131
+
132
+ it("PostgreSQL: maps one row to 'won', zero rows to 'lost', and a thrown error to 'failed'", async () => {
133
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
134
+ const provider = makeProvider('postgresql');
135
+ provider.ExecuteSQL.mockResolvedValueOnce([{ ID: INVITE_ID }]).mockResolvedValueOnce([]).mockRejectedValueOnce(new Error('connection reset'));
136
+
137
+ expect(await consume(provider)).toBe('won');
138
+ expect(await consume(provider)).toBe('lost');
139
+ expect(await consume(provider)).toBe('failed');
140
+ });
141
+
142
+ it("returns 'won' when exactly one row comes back", async () => {
143
+ const provider = makeProvider('sqlserver');
144
+ provider.ExecuteSQL.mockResolvedValue([{ ID: INVITE_ID }]);
145
+ expect(await consume(provider)).toBe('won');
146
+ });
147
+
148
+ it("returns 'lost' when zero rows come back", async () => {
149
+ const provider = makeProvider('sqlserver');
150
+ provider.ExecuteSQL.mockResolvedValue([]);
151
+ expect(await consume(provider)).toBe('lost');
152
+ });
153
+
154
+ it("returns 'failed' and logs the invite ID when ExecuteSQL throws", async () => {
155
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
156
+ const provider = makeProvider('sqlserver');
157
+ provider.ExecuteSQL.mockRejectedValue(new Error("The EXECUTE permission was denied on the object 'spConsumeMagicLinkInvite'"));
158
+
159
+ expect(await consume(provider)).toBe('failed');
160
+ const logged = errorSpy.mock.calls.map((call) => call.map(String).join(' '));
161
+ expect(logged.some((line) => line.includes(INVITE_ID) && line.includes('spConsumeMagicLinkInvite'))).toBe(true);
162
+ });
163
+
164
+ it("returns 'failed' when ExecuteSQL returns a non-array", async () => {
165
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
166
+ const provider = makeProvider('sqlserver');
167
+ provider.ExecuteSQL.mockResolvedValue(undefined);
168
+
169
+ expect(await consume(provider)).toBe('failed');
170
+ const logged = errorSpy.mock.calls.map((call) => call.map(String).join(' '));
171
+ expect(logged.some((line) => line.includes(INVITE_ID))).toBe(true);
172
+ });
173
+ });
174
+
175
+ describe('MagicLinkService.RedeemInvite consume outcome mapping (#4753)', () => {
176
+ let service: MagicLinkService;
177
+ let provider: FakeProvider;
178
+ let provisionUser: Mock;
179
+ const contextUser = { ID: 'ctx-user' } as UserInfo;
180
+ const rawToken = MAGIC_LINK_TOKEN_PREFIX + 'x';
181
+
182
+ beforeEach(() => {
183
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
184
+ provider = makeProvider('sqlserver');
185
+ Metadata.Provider = provider as unknown as IMetadataProvider;
186
+
187
+ service = new MagicLinkService('https://mj.example.com', {} as MagicLinkConfig);
188
+ const privates = service as unknown as ServicePrivates;
189
+ vi.spyOn(RunView.prototype, 'RunView').mockResolvedValue({ Success: true, Results: [makeInvite()] } as Awaited<ReturnType<RunView['RunView']>>);
190
+ vi.spyOn(privates, 'resolveProvisioningContextUser').mockReturnValue(contextUser);
191
+ vi.spyOn(privates, 'isInviterActive').mockReturnValue(true);
192
+ vi.spyOn(privates, 'recordRedemption').mockResolvedValue(undefined);
193
+ provisionUser = vi.fn().mockResolvedValue({ success: false, error: 'stop-here' });
194
+ privates.provisionUser = provisionUser;
195
+ });
196
+
197
+ afterEach(() => {
198
+ Metadata.Provider = null as unknown as IMetadataProvider;
199
+ });
200
+
201
+ it('RedeemInvite maps a failed consume to server_error, not consumed, and never provisions', async () => {
202
+ provider.ExecuteSQL.mockRejectedValue(new Error("The EXECUTE permission was denied on the object 'spConsumeMagicLinkInvite'"));
203
+ const result = await service.RedeemInvite(rawToken);
204
+
205
+ expect(result).toEqual(expect.objectContaining({ success: false, errorCode: 'server_error' }));
206
+ expect(provisionUser).not.toHaveBeenCalled();
207
+ });
208
+
209
+ it('RedeemInvite still reports consumed when the race is lost', async () => {
210
+ provider.ExecuteSQL.mockResolvedValue([]);
211
+ const result = await service.RedeemInvite(rawToken);
212
+
213
+ expect(result).toEqual(expect.objectContaining({ success: false, errorCode: 'consumed' }));
214
+ expect(provisionUser).not.toHaveBeenCalled();
215
+ });
216
+
217
+ it('RedeemInvite proceeds to provisioning when the consume wins', async () => {
218
+ provider.ExecuteSQL.mockResolvedValue([{ ID: INVITE_ID }]);
219
+ const result = await service.RedeemInvite(rawToken);
220
+
221
+ expect(provisionUser).toHaveBeenCalledTimes(1);
222
+ expect(result).toEqual(expect.objectContaining({ success: false, errorCode: 'provisioning_failed' }));
223
+ });
224
+ });