@memberjunction/server 6.2.0-edge.0 → 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 (403) 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 +11 -0
  69. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  70. package/dist/generated/graphql-schemas/__mj.js +48 -0
  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 +3 -1
  81. package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
  82. package/dist/generic/FireAndForgetHeartbeat.js +8 -4
  83. package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
  84. package/dist/generic/PushStatusResolver.d.ts +10 -1
  85. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  86. package/dist/generic/PushStatusResolver.js +11 -3
  87. package/dist/generic/PushStatusResolver.js.map +1 -1
  88. package/dist/generic/ResolverBase.d.ts +5 -1
  89. package/dist/generic/ResolverBase.d.ts.map +1 -1
  90. package/dist/generic/ResolverBase.js +15 -8
  91. package/dist/generic/ResolverBase.js.map +1 -1
  92. package/dist/index.d.ts +6 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +30 -9
  95. package/dist/index.js.map +1 -1
  96. package/dist/integration/CustomColumnPromoter.d.ts +2 -0
  97. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  98. package/dist/integration/CustomColumnPromoter.js +5 -1
  99. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  100. package/dist/integration/EntityMapLifecycle.d.ts +4 -0
  101. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  102. package/dist/integration/EntityMapLifecycle.js +10 -2
  103. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  104. package/dist/logging/NoLog.d.ts +4 -0
  105. package/dist/logging/NoLog.d.ts.map +1 -1
  106. package/dist/logging/NoLog.js +10 -2
  107. package/dist/logging/NoLog.js.map +1 -1
  108. package/dist/logging/StartupLogger.d.ts +2 -0
  109. package/dist/logging/StartupLogger.d.ts.map +1 -1
  110. package/dist/logging/StartupLogger.js +5 -1
  111. package/dist/logging/StartupLogger.js.map +1 -1
  112. package/dist/logging/bootAudit.d.ts +11 -7
  113. package/dist/logging/bootAudit.d.ts.map +1 -1
  114. package/dist/logging/bootAudit.js +15 -7
  115. package/dist/logging/bootAudit.js.map +1 -1
  116. package/dist/logging/boundaryLogPayload.d.ts +4 -0
  117. package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
  118. package/dist/logging/boundaryLogPayload.js +5 -1
  119. package/dist/logging/boundaryLogPayload.js.map +1 -1
  120. package/dist/logging/secretRedactor.d.ts +2 -0
  121. package/dist/logging/secretRedactor.d.ts.map +1 -1
  122. package/dist/logging/secretRedactor.js +8 -4
  123. package/dist/logging/secretRedactor.js.map +1 -1
  124. package/dist/logging/shortenForLog.d.ts +2 -0
  125. package/dist/logging/shortenForLog.d.ts.map +1 -1
  126. package/dist/logging/shortenForLog.js +7 -3
  127. package/dist/logging/shortenForLog.js.map +1 -1
  128. package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
  129. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  130. package/dist/logging/variablesLoggingMiddleware.js +8 -6
  131. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  132. package/dist/metadataRefreshSignal.d.ts +19 -0
  133. package/dist/metadataRefreshSignal.d.ts.map +1 -0
  134. package/dist/metadataRefreshSignal.js +45 -0
  135. package/dist/metadataRefreshSignal.js.map +1 -0
  136. package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
  137. package/dist/multiTenancy/index.d.ts +8 -0
  138. package/dist/multiTenancy/index.d.ts.map +1 -1
  139. package/dist/multiTenancy/index.js +21 -5
  140. package/dist/multiTenancy/index.js.map +1 -1
  141. package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
  142. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  143. package/dist/realtimeWidget/WidgetRouter.js +14 -10
  144. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  145. package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
  146. package/dist/realtimeWidget/WidgetSessionService.js +27 -27
  147. package/dist/realtimeWidget/host-identity.d.ts +5 -1
  148. package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
  149. package/dist/realtimeWidget/host-identity.js +11 -3
  150. package/dist/realtimeWidget/host-identity.js.map +1 -1
  151. package/dist/realtimeWidget/index.d.ts +2 -2
  152. package/dist/realtimeWidget/index.d.ts.map +1 -1
  153. package/dist/realtimeWidget/index.js +2 -2
  154. package/dist/realtimeWidget/index.js.map +1 -1
  155. package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
  156. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  157. package/dist/realtimeWidget/visitorIdentity.js +16 -4
  158. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  159. package/dist/realtimeWidget/widgetCore.d.ts +48 -0
  160. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  161. package/dist/realtimeWidget/widgetCore.js +38 -10
  162. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
  164. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  165. package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
  166. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  167. package/dist/resolvers/AdhocQueryResolver.js +3 -3
  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/RealtimeBridgeResolver.js +3 -3
  185. package/dist/resolvers/RealtimeClientSessionResolver.js +3 -3
  186. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
  187. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -4
  188. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  189. package/dist/resolvers/RunAIAgentResolver.js +69 -38
  190. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  191. package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
  192. package/dist/resolvers/RunAIPromptResolver.js +2 -2
  193. package/dist/resolvers/RunTestResolver.js +5 -5
  194. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
  195. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  196. package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
  197. package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
  198. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  199. package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
  200. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  201. package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
  202. package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
  203. package/dist/resolvers/adhoc-query-helpers.js +10 -2
  204. package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
  205. package/dist/resolvers/artifact-routing.d.ts +8 -8
  206. package/dist/resolvers/artifact-routing.js +2 -2
  207. package/dist/resolvers/artifact-routing.js.map +1 -1
  208. package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
  209. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  210. package/dist/resolvers/meetingRecordingRegistration.js +24 -8
  211. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  212. package/dist/resolvers/peaksSidecar.d.ts +4 -0
  213. package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
  214. package/dist/resolvers/peaksSidecar.js +10 -2
  215. package/dist/resolvers/peaksSidecar.js.map +1 -1
  216. package/dist/rest/EntityCRUDHandler.d.ts +28 -0
  217. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  218. package/dist/rest/EntityCRUDHandler.js +20 -4
  219. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  220. package/dist/rest/MediaStreamHandler.d.ts +2 -0
  221. package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
  222. package/dist/rest/MediaStreamHandler.js +10 -6
  223. package/dist/rest/MediaStreamHandler.js.map +1 -1
  224. package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
  225. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  226. package/dist/rest/OAuthCallbackHandler.js +15 -3
  227. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  228. package/dist/rest/RESTEndpointHandler.d.ts +6 -4
  229. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  230. package/dist/rest/RESTEndpointHandler.js +15 -11
  231. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  232. package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
  233. package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
  234. package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
  235. package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
  236. package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
  237. package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
  238. package/dist/rest/SignatureWebhookHandler.js +5 -1
  239. package/dist/rest/SignatureWebhookHandler.js.map +1 -1
  240. package/dist/rest/UploadTokenManager.d.ts +24 -8
  241. package/dist/rest/UploadTokenManager.d.ts.map +1 -1
  242. package/dist/rest/UploadTokenManager.js +53 -21
  243. package/dist/rest/UploadTokenManager.js.map +1 -1
  244. package/dist/rest/ViewOperationsHandler.d.ts +20 -0
  245. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  246. package/dist/rest/ViewOperationsHandler.js +20 -4
  247. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  248. package/dist/rest/mediaRange.d.ts +4 -0
  249. package/dist/rest/mediaRange.d.ts.map +1 -1
  250. package/dist/rest/mediaRange.js +10 -2
  251. package/dist/rest/mediaRange.js.map +1 -1
  252. package/dist/rest/setupRESTEndpoints.d.ts +4 -0
  253. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  254. package/dist/rest/setupRESTEndpoints.js +13 -7
  255. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  256. package/dist/serverExtensionReservedRoots.d.ts +2 -0
  257. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  258. package/dist/serverExtensionReservedRoots.js +5 -1
  259. package/dist/serverExtensionReservedRoots.js.map +1 -1
  260. package/dist/services/TaskGraphActionRunner.d.ts +13 -8
  261. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  262. package/dist/services/TaskGraphActionRunner.js +16 -15
  263. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  264. package/dist/types.d.ts +2 -0
  265. package/dist/types.d.ts.map +1 -1
  266. package/dist/types.js +7 -3
  267. package/dist/types.js.map +1 -1
  268. package/dist/util.d.ts +6 -0
  269. package/dist/util.d.ts.map +1 -1
  270. package/dist/util.js +13 -5
  271. package/dist/util.js.map +1 -1
  272. package/package.json +92 -92
  273. package/src/__tests__/DeleteOptionsInput.wireShape.test.ts +62 -0
  274. package/src/__tests__/FileResolverPeaks.test.ts +13 -13
  275. package/src/__tests__/ListOperationsResolver.test.ts +17 -17
  276. package/src/__tests__/MediaStreamHandler.test.ts +28 -28
  277. package/src/__tests__/NoLog.test.ts +11 -11
  278. package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
  279. package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
  280. package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
  281. package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
  282. package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
  283. package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
  284. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +228 -0
  285. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
  286. package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
  287. package/src/__tests__/WidgetSessionService.test.ts +8 -4
  288. package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
  289. package/src/__tests__/bootAudit.test.ts +11 -11
  290. package/src/__tests__/boundaryLogPayload.test.ts +6 -6
  291. package/src/__tests__/cacheInvalidationRecordData.test.ts +177 -0
  292. package/src/__tests__/databaseAbstraction.test.ts +12 -6
  293. package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
  294. package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
  295. package/src/__tests__/magicLink.test.ts +73 -134
  296. package/src/__tests__/magicLinkService.consume.test.ts +224 -0
  297. package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
  298. package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
  299. package/src/__tests__/multiTenancy.security.test.ts +40 -40
  300. package/src/__tests__/multiTenancy.test.ts +18 -18
  301. package/src/__tests__/newUsers.test.ts +24 -23
  302. package/src/__tests__/principals.test.ts +22 -22
  303. package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
  304. package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
  305. package/src/__tests__/secretRedactor.test.ts +12 -12
  306. package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
  307. package/src/__tests__/subscriptionRedaction.test.ts +5 -5
  308. package/src/__tests__/unifiedAuth.test.ts +15 -9
  309. package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
  310. package/src/__tests__/widget.test.ts +45 -45
  311. package/src/__tests__/widgetGuestElevation.test.ts +7 -7
  312. package/src/__tests__/widgetHostIdentity.test.ts +14 -14
  313. package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
  314. package/src/agentSessions/SessionManager.ts +2 -2
  315. package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
  316. package/src/auth/AuthProviderCatalogRouter.ts +6 -1
  317. package/src/auth/__tests__/backward-compatibility.test.ts +3 -3
  318. package/src/auth/index.ts +43 -28
  319. package/src/auth/initializeProviders.ts +25 -5
  320. package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
  321. package/src/auth/magicLink/MagicLinkService.ts +71 -52
  322. package/src/auth/magicLink/index.ts +7 -7
  323. package/src/auth/magicLink/magicLinkCore.ts +96 -64
  324. package/src/auth/magicLink/redeemLanding.ts +14 -4
  325. package/src/auth/magicLink/types.ts +54 -54
  326. package/src/auth/newUsers.ts +1 -1
  327. package/src/auth/principals.ts +11 -2
  328. package/src/cache.ts +4 -1
  329. package/src/config.ts +37 -4
  330. package/src/context.ts +37 -24
  331. package/src/directives/Public.ts +4 -1
  332. package/src/directives/RequireSystemUser.ts +4 -1
  333. package/src/generated/graphql-schemas/__mj.ts +37 -0
  334. package/src/generic/CacheInvalidationResolver.ts +148 -4
  335. package/src/generic/DeleteOptionsInput.ts +9 -2
  336. package/src/generic/FireAndForgetHeartbeat.ts +19 -14
  337. package/src/generic/PushStatusResolver.ts +21 -7
  338. package/src/generic/ResolverBase.ts +16 -8
  339. package/src/generic/RunViewResolver.ts +22 -22
  340. package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
  341. package/src/index.ts +35 -9
  342. package/src/integration/CustomColumnPromoter.ts +6 -1
  343. package/src/integration/EntityMapLifecycle.ts +20 -2
  344. package/src/logging/NoLog.ts +16 -2
  345. package/src/logging/StartupLogger.ts +6 -1
  346. package/src/logging/bootAudit.ts +26 -16
  347. package/src/logging/boundaryLogPayload.ts +8 -1
  348. package/src/logging/secretRedactor.ts +14 -9
  349. package/src/logging/shortenForLog.ts +8 -3
  350. package/src/logging/variablesLoggingMiddleware.ts +9 -6
  351. package/src/metadataRefreshSignal.ts +45 -0
  352. package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
  353. package/src/multiTenancy/index.ts +29 -5
  354. package/src/realtimeWidget/WidgetRouter.ts +15 -10
  355. package/src/realtimeWidget/WidgetSessionService.ts +76 -76
  356. package/src/realtimeWidget/host-identity.ts +24 -10
  357. package/src/realtimeWidget/index.ts +3 -3
  358. package/src/realtimeWidget/visitorIdentity.ts +39 -8
  359. package/src/realtimeWidget/widgetCore.ts +75 -10
  360. package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
  361. package/src/resolvers/AdhocQueryResolver.ts +3 -3
  362. package/src/resolvers/AgentSessionResolver.ts +3 -3
  363. package/src/resolvers/ColorResolver.ts +6 -6
  364. package/src/resolvers/ComponentRegistryResolver.ts +36 -36
  365. package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
  366. package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
  367. package/src/resolvers/FileResolver.ts +42 -42
  368. package/src/resolvers/GeoResolver.ts +4 -4
  369. package/src/resolvers/GetDataResolver.ts +50 -15
  370. package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
  371. package/src/resolvers/ListOperationsResolver.ts +78 -28
  372. package/src/resolvers/MCPResolver.ts +30 -15
  373. package/src/resolvers/QueryResolver.ts +13 -13
  374. package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
  375. package/src/resolvers/RealtimeClientSessionResolver.ts +3 -3
  376. package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
  377. package/src/resolvers/RunAIAgentResolver.ts +113 -72
  378. package/src/resolvers/RunAIPromptResolver.ts +22 -22
  379. package/src/resolvers/RunTemplateResolver.ts +4 -4
  380. package/src/resolvers/RunTestResolver.ts +25 -25
  381. package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
  382. package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
  383. package/src/resolvers/TelemetryResolver.ts +79 -79
  384. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
  385. package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
  386. package/src/resolvers/adhoc-query-helpers.ts +19 -2
  387. package/src/resolvers/artifact-routing.ts +19 -19
  388. package/src/resolvers/meetingRecordingRegistration.ts +44 -10
  389. package/src/resolvers/peaksSidecar.ts +12 -2
  390. package/src/rest/EntityCRUDHandler.ts +24 -4
  391. package/src/rest/MediaStreamHandler.ts +11 -6
  392. package/src/rest/OAuthCallbackHandler.ts +25 -7
  393. package/src/rest/RESTEndpointHandler.ts +20 -15
  394. package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
  395. package/src/rest/SignatureWebhookHandler.ts +6 -1
  396. package/src/rest/UploadTokenManager.ts +66 -30
  397. package/src/rest/ViewOperationsHandler.ts +24 -4
  398. package/src/rest/mediaRange.ts +16 -6
  399. package/src/rest/setupRESTEndpoints.ts +24 -12
  400. package/src/serverExtensionReservedRoots.ts +6 -1
  401. package/src/services/TaskGraphActionRunner.ts +16 -15
  402. package/src/types.ts +21 -16
  403. package/src/util.ts +16 -6
@@ -35,7 +35,7 @@ import { CommunicationEngine } from '@memberjunction/communication-engine';
35
35
  import { Message } from '@memberjunction/communication-types';
36
36
  import { configInfo, type MagicLinkConfig } from '../../config.js';
37
37
  import { MagicLinkKeyManager } from './MagicLinkKeys.js';
38
- import { generateRawToken, generateSessionId, hashToken, evaluateInvite, buildSessionClaims, buildConsumeInviteSQL, canIssueInvites, isRoleGrantable, MAGIC_LINK_TOKEN_PREFIX } from './magicLinkCore.js';
38
+ import { GenerateRawToken, GenerateSessionId, HashToken, EvaluateInvite, BuildSessionClaims, BuildConsumeInvitePostgresSQL, CanIssueInvites, IsRoleGrantable, MAGIC_LINK_TOKEN_PREFIX } from './magicLinkCore.js';
39
39
  import type {
40
40
  CreateMagicLinkInviteParams,
41
41
  CreateMagicLinkInviteResult,
@@ -48,6 +48,21 @@ const REDEMPTION_ENTITY = 'MJ: Magic Link Redemptions';
48
48
  /** Shared Anonymous principal — seeded by the Phase-4 migration; must match that UUID. */
49
49
  const ANONYMOUS_USER_ID = '273910DF-28F1-45C1-A8F8-6E9AD8E5F008';
50
50
 
51
+ /** Outcome of the atomic single-use consume — see {@link MagicLinkService.consumeInvite}. */
52
+ type ConsumeOutcome = 'won' | 'lost' | 'failed';
53
+
54
+ /**
55
+ * The SQL Server guarded single-use procedure (migration V202609291627__v6.2.x__MagicLink_Consume_Invite_Sproc).
56
+ * A procedure, not raw DML, because on SQL Server MJ grants its runtime roles EXECUTE on procedures
57
+ * and SELECT on views — never DML on base tables — so on any host whose MJAPI login is not db_owner
58
+ * a raw UPDATE is refused (#4753). Ownership chaining lets the procedure update the table on the
59
+ * caller's behalf. PostgreSQL has no counterpart function; see {@link BuildConsumeInvitePostgresSQL}.
60
+ */
61
+ const CONSUME_INVITE_PROC = 'spConsumeMagicLinkInvite';
62
+
63
+ /** MagicLinkInvite base table (core schema) — the PostgreSQL consume updates it directly. */
64
+ const INVITE_TABLE = 'MagicLinkInvite';
65
+
51
66
  /** Outcome of provisioning a redeeming user. */
52
67
  interface ProvisionResult {
53
68
  success: boolean;
@@ -90,11 +105,11 @@ export class MagicLinkService {
90
105
  // external user already holding a restricted magic-link session — could
91
106
  // issue invites and escalate.
92
107
  const callerRoleNames = (creatingUser.UserRoles ?? []).map((r) => r.Role).filter((n): n is string => !!n);
93
- if (!canIssueInvites(creatingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
108
+ if (!CanIssueInvites(creatingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
94
109
  return { success: false, errorCode: 'forbidden', error: 'Not authorized to issue magic-link invites.' };
95
110
  }
96
111
 
97
- const roleId = params.roleId ?? this.resolveRestrictedRoleId(md);
112
+ const roleId = params.RoleId ?? this.resolveRestrictedRoleId(md);
98
113
  if (!roleId) {
99
114
  return { success: false, error: `Restricted role '${this.config.restrictedRoleName}' not found and no roleId supplied.` };
100
115
  }
@@ -106,7 +121,7 @@ export class MagicLinkService {
106
121
  if (!roleToGrant) {
107
122
  return { success: false, errorCode: 'invalid_role', error: `Role '${roleId}' not found.` };
108
123
  }
109
- if (!isRoleGrantable(roleToGrant.Name, this.config.restrictedRoleName, this.config.grantableRoleNames)) {
124
+ if (!IsRoleGrantable(roleToGrant.Name, this.config.restrictedRoleName, this.config.grantableRoleNames)) {
110
125
  return {
111
126
  success: false,
112
127
  errorCode: 'invalid_role',
@@ -114,23 +129,23 @@ export class MagicLinkService {
114
129
  };
115
130
  }
116
131
 
117
- const app = md.Applications.find((a) => UUIDsEqual(a.ID, params.applicationId));
132
+ const app = md.Applications.find((a) => UUIDsEqual(a.ID, params.ApplicationId));
118
133
  if (!app) {
119
- return { success: false, error: `Application '${params.applicationId}' not found.` };
134
+ return { success: false, error: `Application '${params.ApplicationId}' not found.` };
120
135
  }
121
136
 
122
- const rawToken = generateRawToken();
123
- const expiresInHours = params.expiresInHours ?? this.config.defaultExpiresInHours;
137
+ const rawToken = GenerateRawToken();
138
+ const expiresInHours = params.ExpiresInHours ?? this.config.defaultExpiresInHours;
124
139
  const expiresAt = new Date(Date.now() + expiresInHours * 3600 * 1000);
125
140
 
126
141
  const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, creatingUser);
127
142
  invite.NewRecord();
128
- invite.TokenHash = hashToken(rawToken);
143
+ invite.TokenHash = HashToken(rawToken);
129
144
  invite.Email = params.email;
130
- invite.ApplicationID = params.applicationId;
145
+ invite.ApplicationID = params.ApplicationId;
131
146
  invite.RoleID = roleId;
132
147
  invite.ExpiresAt = expiresAt;
133
- invite.MaxUses = params.maxUses ?? 1;
148
+ invite.MaxUses = params.MaxUses ?? 1;
134
149
  invite.UseCount = 0;
135
150
  invite.CreatedByUserID = creatingUser.ID;
136
151
  invite.Status = 'Active';
@@ -143,7 +158,7 @@ export class MagicLinkService {
143
158
  // the single ApplicationID/RoleID. Additive — the columns stay authoritative for
144
159
  // redemption today; this populates the child tables for the eventual switch to
145
160
  // multi-scope reads. Best-effort so a child-row hiccup never fails issuance.
146
- await this.writeInviteScopeChildRows(invite.ID, params.applicationId, roleId, creatingUser, md);
161
+ await this.writeInviteScopeChildRows(invite.ID, params.ApplicationId, roleId, creatingUser, md);
147
162
 
148
163
  const redemptionUrl = this.buildRedemptionUrl(rawToken);
149
164
 
@@ -173,9 +188,9 @@ export class MagicLinkService {
173
188
  * session JWT. Runs in the public (pre-auth) path, so it uses the global
174
189
  * provider and a configured provisioning context user.
175
190
  *
176
- * Single-use is enforced by a compare-and-swap UPDATE ({@link consumeInvite})
177
- * BEFORE the token is minted — two concurrent redemptions of a single-use
178
- * link race on the same row and exactly one wins. The consume is the gate:
191
+ * Single-use is enforced by the guarded UPDATE inside the `spConsumeMagicLinkInvite`
192
+ * procedure ({@link consumeInvite}) BEFORE the token is minted — two concurrent
193
+ * redemptions of a single-use link race on the same row and exactly one wins. The consume is the gate:
179
194
  * if a later step fails, the use is already burned (fail-closed). That is the
180
195
  * correct trade-off for a single-use credential — burning a token on a server
181
196
  * error is recoverable (re-issue); allowing replay is not.
@@ -208,7 +223,7 @@ export class MagicLinkService {
208
223
  const md = Metadata.Provider; // global-provider-ok: server-side magic-link service; runs under the server's single default provider
209
224
  const provider = md as DatabaseProviderBase;
210
225
 
211
- const tokenHash = hashToken(rawToken);
226
+ const tokenHash = HashToken(rawToken);
212
227
  const rv = new RunView();
213
228
  const found = await rv.RunView<MJMagicLinkInviteEntity>(
214
229
  {
@@ -231,7 +246,7 @@ export class MagicLinkService {
231
246
 
232
247
  // Fast, friendly pre-check (returns a precise reason). NOT the authority —
233
248
  // the atomic consume below is what actually enforces single-use.
234
- const eligibility = evaluateInvite(invite, Date.now());
249
+ const eligibility = EvaluateInvite(invite, Date.now());
235
250
  if (!eligibility.ok) {
236
251
  return done({ success: false, errorCode: eligibility.errorCode, error: `Invite is ${eligibility.errorCode}.` });
237
252
  }
@@ -252,14 +267,18 @@ export class MagicLinkService {
252
267
  return done({ success: false, errorCode: 'server_error', error: 'Invite role not found.' });
253
268
  }
254
269
 
255
- // Atomic consume BEFORE minting. If we lose the race (0 rows updated), the
256
- // invite was concurrently consumed/expired — re-evaluate the now-stale
257
- // in-memory copy only to surface a precise code; default to 'consumed'.
258
- if (!(await this.consumeInvite(invite, provider, contextUser))) {
270
+ // Atomic consume BEFORE minting. A statement that never ran (permissions,
271
+ // missing procedure, connection) is a server fault, not a lost race — report
272
+ // it as such so operators aren't sent hunting a double-redeem (#4753).
273
+ const consume = await this.consumeInvite(invite, provider, contextUser);
274
+ if (consume === 'failed') {
275
+ return done({ success: false, errorCode: 'server_error', error: 'Could not redeem the invite due to a server error.' });
276
+ }
277
+ if (consume === 'lost') {
259
278
  // Lost the race or the invite expired between the pre-check and the
260
279
  // consume. The in-memory copy still looks eligible if only the DB-side
261
280
  // use count changed, so default that case to 'consumed'.
262
- const recheck = evaluateInvite(invite, Date.now());
281
+ const recheck = EvaluateInvite(invite, Date.now());
263
282
  return done({ success: false, errorCode: recheck.ok ? 'consumed' : recheck.errorCode, error: 'Invite already redeemed or expired.' });
264
283
  }
265
284
 
@@ -273,7 +292,7 @@ export class MagicLinkService {
273
292
 
274
293
  const nowSeconds = Math.floor(Date.now() / 1000);
275
294
  const isAnon = invite.IdentityMode === 'anonymous';
276
- const claims = buildSessionClaims({
295
+ const claims = BuildSessionClaims({
277
296
  issuer: this.publicUrl,
278
297
  audience: this.config.audience,
279
298
  inviteId: invite.ID,
@@ -286,7 +305,7 @@ export class MagicLinkService {
286
305
  anonymous: isAnon,
287
306
  // Per-session id for anon forensics (correlates one session's activity since all
288
307
  // anon redemptions share the Anonymous principal). Carried into the redemption audit row.
289
- sessionId: isAnon ? generateSessionId() : undefined,
308
+ sessionId: isAnon ? GenerateSessionId() : undefined,
290
309
  // Resource-share scope (Phase 5): the single shared resource this link grants.
291
310
  resourceId: invite.ResourceID ?? undefined,
292
311
  nowSeconds,
@@ -639,39 +658,39 @@ export class MagicLinkService {
639
658
  }
640
659
 
641
660
  /**
642
- * Atomically consumes one use of the invite via a compare-and-swap UPDATE.
643
- * The WHERE clause re-checks every eligibility condition (Active, not
644
- * exhausted, not expired) at the DB level, so the increment and the guard are
645
- * a single atomic operation. Returns true ONLY when this call updated the row
646
- * (won the race); concurrent redemptions of a single-use link see 0 rows.
661
+ * Atomically consumes one use of the invite with a single guarded UPDATE that re-checks every
662
+ * eligibility condition (Active, not exhausted, not expired) at the DB level, so the increment and
663
+ * the guard are one atomic operation. Concurrent redemptions of a single-use link race on the row
664
+ * and exactly one gets the row back.
647
665
  *
648
- * Dialect-aware: SQL Server uses an `OUTPUT`-into-table-variable win-detect;
649
- * PostgreSQL uses `UPDATE … WHERE … RETURNING` (the WHERE guard is itself the
650
- * atomic single-use gate). The platform is read from the provider itself
651
- * (`PlatformKey`), not from process env, so this is correct even under a
652
- * non-default per-connection provider. The invite ID always binds as a
653
- * parameter (`@p0` / `$1`), never interpolated.
666
+ * The statement differs per platform because the grants do:
667
+ * - **SQL Server** calls {@link CONSUME_INVITE_PROC} (`EXEC … @ID=@p0`). Runtime roles have
668
+ * EXECUTE on procedures but no DML on base tables (#4753).
669
+ * - **PostgreSQL** runs {@link BuildConsumeInvitePostgresSQL}'s `UPDATE … RETURNING` directly.
670
+ * Runtime roles hold table DML there, and no PG counterpart of the procedure exists.
671
+ * The ID is always a bound parameter.
654
672
  *
655
- * Fail-closed: any DB error returns false and the redemption is rejected.
673
+ * Returns `'won'` (this call consumed a use), `'lost'` (the guard matched nothing: consumed,
674
+ * revoked or expired concurrently) or `'failed'` (the statement never ran — permissions, a missing
675
+ * procedure, a connection error). `'failed'` is kept distinct from `'lost'` on purpose: collapsing
676
+ * them is what made #4753's permission error surface to users as "already redeemed".
656
677
  */
657
- private async consumeInvite(invite: MJMagicLinkInviteEntity, provider: DatabaseProviderBase, contextUser: UserInfo): Promise<boolean> {
678
+ private async consumeInvite(invite: MJMagicLinkInviteEntity, provider: DatabaseProviderBase, contextUser: UserInfo): Promise<ConsumeOutcome> {
679
+ const isPg = provider.PlatformKey === 'postgresql';
680
+ const statement = isPg ? `PostgreSQL guarded UPDATE on ${INVITE_TABLE}` : CONSUME_INVITE_PROC;
658
681
  try {
659
- const entityInfo = provider.EntityByName(INVITE_ENTITY);
660
- if (!entityInfo) {
661
- LogError(`[MagicLink] Entity metadata for '${INVITE_ENTITY}' not found; cannot consume invite.`);
662
- return false;
663
- }
664
- const isPg = provider.PlatformKey === 'postgresql';
665
- // PG identifiers are auto-quoted by PostgreSQLDataProvider.ExecuteSQL, so
666
- // pass the bare `schema.table`; SQL Server takes the bracket-quoted form.
667
- const table = isPg ? `${entityInfo.SchemaName}.${entityInfo.BaseTable}` : `[${entityInfo.SchemaName}].[${entityInfo.BaseTable}]`;
668
- // OUTPUT/RETURNING yields one row iff the WHERE matched — the atomic single-use gate.
669
- const sql = buildConsumeInviteSQL(table, isPg ? 'postgresql' : 'sqlserver');
670
- const rows = await provider.ExecuteSQL<{ ID: string }>(sql, [invite.ID], { isMutation: true }, contextUser);
671
- return Array.isArray(rows) && rows.length === 1;
682
+ const sql = isPg
683
+ ? BuildConsumeInvitePostgresSQL(`${provider.MJCoreSchemaName}.${INVITE_TABLE}`)
684
+ : provider.Dialect.ProcedureCallSyntax(provider.MJCoreSchemaName, CONSUME_INVITE_PROC, [`@ID=${provider.BuildParameterPlaceholder(0)}`]);
685
+ const rows = await provider.ExecuteSQL<{ ID: string }>(sql, [invite.ID], { isMutation: true, description: `MagicLink ${statement}` }, contextUser);
686
+ if (!Array.isArray(rows)) {
687
+ LogError(`[MagicLink] ${statement} returned no result set for invite ${invite.ID}; treating the consume as failed.`);
688
+ return 'failed';
689
+ }
690
+ return rows.length === 1 ? 'won' : 'lost';
672
691
  } catch (e) {
673
- LogError(`[MagicLink] Atomic consume failed for invite ${invite.ID}: ${e instanceof Error ? e.message : String(e)}`);
674
- return false;
692
+ LogError(`[MagicLink] ${statement} FAILED for invite ${invite.ID} — the statement never ran, so this is NOT a lost race: ${e instanceof Error ? e.message : String(e)}`);
693
+ return 'failed';
675
694
  }
676
695
  }
677
696
 
@@ -1,18 +1,18 @@
1
1
  export * from './types.js';
2
2
  export {
3
- generateRawToken,
4
- hashToken,
5
- evaluateInvite,
6
- buildSessionClaims,
3
+ GenerateRawToken, generateRawToken,
4
+ HashToken, hashToken,
5
+ EvaluateInvite, evaluateInvite,
6
+ BuildSessionClaims, buildSessionClaims,
7
7
  MAGIC_LINK_TOKEN_PREFIX,
8
8
  type InviteEvaluationInput,
9
9
  } from './magicLinkCore.js';
10
10
  export { MagicLinkKeyManager } from './MagicLinkKeys.js';
11
11
  export { MagicLinkService } from './MagicLinkService.js';
12
12
  export {
13
- createMagicLinkHandler,
14
- createMagicLinkJwksRouter,
15
- registerMagicLinkAuthProvider,
13
+ CreateMagicLinkHandler, createMagicLinkHandler,
14
+ CreateMagicLinkJwksRouter, createMagicLinkJwksRouter,
15
+ RegisterMagicLinkAuthProvider, registerMagicLinkAuthProvider,
16
16
  MAGIC_LINK_MOUNT_PATH,
17
17
  MAGIC_LINK_JWKS_PATH,
18
18
  } from './MagicLinkRouter.js';
@@ -13,25 +13,40 @@ import type { MagicLinkJWTClaims, MagicLinkScopeEntry, RedeemErrorCode } from '.
13
13
  export const MAGIC_LINK_TOKEN_PREFIX = 'mj_ml_';
14
14
 
15
15
  /** Generates a cryptographically random raw magic-link token. */
16
- export function generateRawToken(): string {
16
+ export function GenerateRawToken(): string {
17
17
  return MAGIC_LINK_TOKEN_PREFIX + randomBytes(32).toString('hex');
18
18
  }
19
19
 
20
+ /** @deprecated Use {@link GenerateRawToken}. */
21
+ export function generateRawToken(): string {
22
+ return GenerateRawToken();
23
+ }
24
+
20
25
  /** Generates an opaque per-session id (anonymous-session forensics correlation). */
21
- export function generateSessionId(): string {
26
+ export function GenerateSessionId(): string {
22
27
  return randomBytes(16).toString('base64url');
23
28
  }
24
29
 
30
+ /** @deprecated Use {@link GenerateSessionId}. */
31
+ export function generateSessionId(): string {
32
+ return GenerateSessionId();
33
+ }
34
+
25
35
  /**
26
36
  * SHA-256 hash of a raw token, base64url-encoded — only the hash is ever
27
37
  * persisted. base64url (43 chars) is shorter than hex (64 chars), URL-safe, and
28
38
  * carries the full 256 bits. Both write (CreateInvite) and read (RedeemInvite)
29
39
  * paths call this, so the encoding stays internally consistent.
30
40
  */
31
- export function hashToken(rawToken: string): string {
41
+ export function HashToken(rawToken: string): string {
32
42
  return createHash('sha256').update(rawToken).digest('base64url');
33
43
  }
34
44
 
45
+ /** @deprecated Use {@link HashToken}. */
46
+ export function hashToken(rawToken: string): string {
47
+ return HashToken(rawToken);
48
+ }
49
+
35
50
  /** Minimal shape of an invite needed for redemption eligibility. */
36
51
  export interface InviteEvaluationInput {
37
52
  Status: string;
@@ -54,7 +69,7 @@ function normalizeName(name: string): string {
54
69
  * (including an external user already holding a restricted magic-link session)
55
70
  * from minting invites.
56
71
  */
57
- export function canIssueInvites(
72
+ export function CanIssueInvites(
58
73
  userType: string | null | undefined,
59
74
  userRoleNames: readonly string[],
60
75
  issuerRoleNames: readonly string[],
@@ -69,13 +84,22 @@ export function canIssueInvites(
69
84
  return userRoleNames.some((r) => allowed.has(normalizeName(r)));
70
85
  }
71
86
 
87
+ /** @deprecated Use {@link CanIssueInvites}. */
88
+ export function canIssueInvites(
89
+ userType: string | null | undefined,
90
+ userRoleNames: readonly string[],
91
+ issuerRoleNames: readonly string[],
92
+ ): boolean {
93
+ return CanIssueInvites(userType, userRoleNames, issuerRoleNames);
94
+ }
95
+
72
96
  /**
73
97
  * Pure check for WHAT role an invite may grant. The restricted role is always
74
98
  * grantable; any other role must be explicitly listed in `grantableRoleNames`.
75
99
  * Applied to every caller (Owners included) so a privileged role can never be
76
100
  * attached to an external magic-link user unless the deployment opts in.
77
101
  */
78
- export function isRoleGrantable(
102
+ export function IsRoleGrantable(
79
103
  roleName: string | null | undefined,
80
104
  restrictedRoleName: string,
81
105
  grantableRoleNames: readonly string[],
@@ -88,8 +112,17 @@ export function isRoleGrantable(
88
112
  return allowed.has(target);
89
113
  }
90
114
 
115
+ /** @deprecated Use {@link IsRoleGrantable}. */
116
+ export function isRoleGrantable(
117
+ roleName: string | null | undefined,
118
+ restrictedRoleName: string,
119
+ grantableRoleNames: readonly string[],
120
+ ): boolean {
121
+ return IsRoleGrantable(roleName, restrictedRoleName, grantableRoleNames);
122
+ }
123
+
91
124
  /** Pure redemption-eligibility check. Returns ok + an error code when not. */
92
- export function evaluateInvite(invite: InviteEvaluationInput, nowMs: number): { ok: boolean; errorCode?: RedeemErrorCode } {
125
+ export function EvaluateInvite(invite: InviteEvaluationInput, nowMs: number): { ok: boolean; errorCode?: RedeemErrorCode } {
93
126
  if (invite.Status === 'Revoked') {
94
127
  return { ok: false, errorCode: 'revoked' };
95
128
  }
@@ -106,75 +139,43 @@ export function evaluateInvite(invite: InviteEvaluationInput, nowMs: number): {
106
139
  return { ok: true };
107
140
  }
108
141
 
109
- /** SQL dialect for {@link buildConsumeInviteSQL}. */
110
- export type ConsumeInviteDialect = 'sqlserver' | 'postgresql';
142
+ /** @deprecated Use {@link EvaluateInvite}. */
143
+ export function evaluateInvite(invite: InviteEvaluationInput, nowMs: number): { ok: boolean; errorCode?: RedeemErrorCode } {
144
+ return EvaluateInvite(invite, nowMs);
145
+ }
111
146
 
112
- /** SQL Server table identifier — bracket-quoted `[schema].[table]` (word chars only). */
113
- const QUALIFIED_TABLE_PATTERN = /^\[\w+\]\.\[\w+\]$/;
114
147
  /** PostgreSQL table identifier — `schema.table` (word chars only); auto-quoted downstream. */
115
148
  const QUALIFIED_TABLE_PATTERN_PG = /^\w+\.\w+$/;
116
149
 
117
150
  /**
118
- * Builds the atomic compare-and-swap UPDATE that consumes one use of an invite,
119
- * in the given SQL dialect.
120
- *
121
- * The WHERE clause re-checks every eligibility condition at the DB level, so the
122
- * increment and the guard are a single atomic operation: concurrent redemptions
123
- * of a single-use link race on the row and exactly one matches. This is what
124
- * actually enforces single-use — the JS-side `evaluateInvite` is only a friendly
125
- * pre-check. The matched row's ID is returned to the caller (via `OUTPUT` on SQL
126
- * Server, `RETURNING` on PostgreSQL) so it can detect a win (exactly one row).
151
+ * Builds the PostgreSQL atomic compare-and-swap UPDATE that consumes one use of an invite.
127
152
  *
128
- * The invite ID MUST be bound as a parameter by the caller (`@p0` on SQL Server,
129
- * `$1` on PostgreSQL) — it is never interpolated into the string, so this builder
130
- * is injection-safe regardless of the ID's contents.
153
+ * PostgreSQL only. SQL Server consumes through the granted `spConsumeMagicLinkInvite` procedure,
154
+ * because its runtime roles are never granted table DML (#4753). PostgreSQL needs neither: its
155
+ * runtime roles hold INSERT/UPDATE/DELETE on every `__mj` table
156
+ * (V202605040300__v5.33.x__Unblock_PostgreSQL_End_To_End.pg-only.sql), and no PG counterpart of
157
+ * the procedure exists — the release-time converter leaves `CREATE PROCEDURE` unhandled.
131
158
  *
132
- * **SQL Server** — `OUTPUT` goes `INTO` a table variable (not a bare OUTPUT):
133
- * SQL Server forbids a bare OUTPUT clause on a table that has enabled triggers,
134
- * and CodeGen adds an `__mj_UpdatedAt` trigger to every MJ table. The trailing
135
- * SELECT returns the matched row(s) regardless of trigger behavior.
159
+ * The WHERE re-checks every eligibility condition (the same predicate as the procedure), so the
160
+ * increment and the guard are one atomic statement: concurrent redemptions of a single-use link
161
+ * serialize on the row lock and exactly one matches. `RETURNING ID` yields the row iff the guard
162
+ * matched. The invite ID MUST be bound by the caller as `$1` — it is never interpolated.
136
163
  *
137
- * **PostgreSQL** — the `UPDATE … WHERE` guard IS itself the atomic single-use
138
- * gate: the row is locked for the UPDATE's duration, so concurrent redemptions
139
- * serialize and only the first matching `Status='Active' AND UseCount < MaxUses`
140
- * wins; `RETURNING` yields the row iff the guard matched (equivalent to the SS
141
- * `OUTPUT`-into-table win-detect). PascalCase identifiers are auto-quoted by
142
- * `PostgreSQLDataProvider.ExecuteSQL`, so the table is passed unquoted (`schema.table`).
143
- *
144
- * `qualifiedTable` is asserted to match the dialect's whitelist pattern before
145
- * interpolation. The caller derives it from `EntityInfo` (never user input), so
146
- * this is defense-in-depth: even a future careless caller cannot turn this into
147
- * an injection vector — a non-conforming table throws.
148
- *
149
- * @param qualifiedTable `[schema].[table]` (sqlserver) or `schema.table` (postgresql) for MagicLinkInvite
150
- * @param dialect target SQL dialect (defaults to `'sqlserver'`)
164
+ * `qualifiedTable` is `schema.table` unquoted (PostgreSQLDataProvider.ExecuteSQL auto-quotes the
165
+ * PascalCase identifiers) and is asserted against a whitelist before interpolation — the caller
166
+ * never passes user input, so this is defense-in-depth.
151
167
  */
152
- export function buildConsumeInviteSQL(qualifiedTable: string, dialect: ConsumeInviteDialect = 'sqlserver'): string {
153
- if (dialect === 'postgresql') {
154
- if (!QUALIFIED_TABLE_PATTERN_PG.test(qualifiedTable)) {
155
- throw new Error(`buildConsumeInviteSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
156
- }
157
- return (
158
- `UPDATE ${qualifiedTable} ` +
159
- `SET UseCount = UseCount + 1, ` +
160
- `ConsumedAt = COALESCE(ConsumedAt, (now() AT TIME ZONE 'utc')), ` +
161
- `Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END ` +
162
- `WHERE ID = $1 AND Status = 'Active' AND UseCount < MaxUses AND ExpiresAt > (now() AT TIME ZONE 'utc') ` +
163
- `RETURNING ID;`
164
- );
165
- }
166
- if (!QUALIFIED_TABLE_PATTERN.test(qualifiedTable)) {
167
- throw new Error(`buildConsumeInviteSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
168
+ export function BuildConsumeInvitePostgresSQL(qualifiedTable: string): string {
169
+ if (!QUALIFIED_TABLE_PATTERN_PG.test(qualifiedTable)) {
170
+ throw new Error(`BuildConsumeInvitePostgresSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
168
171
  }
169
172
  return (
170
- `DECLARE @consumed TABLE (ID UNIQUEIDENTIFIER); ` +
171
173
  `UPDATE ${qualifiedTable} ` +
172
174
  `SET UseCount = UseCount + 1, ` +
173
- `ConsumedAt = COALESCE(ConsumedAt, SYSUTCDATETIME()), ` +
175
+ `ConsumedAt = COALESCE(ConsumedAt, (now() AT TIME ZONE 'utc')), ` +
174
176
  `Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END ` +
175
- `OUTPUT INSERTED.ID INTO @consumed ` +
176
- `WHERE ID = @p0 AND Status = 'Active' AND UseCount < MaxUses AND ExpiresAt > SYSUTCDATETIME(); ` +
177
- `SELECT ID FROM @consumed;`
177
+ `WHERE ID = $1 AND Status = 'Active' AND UseCount < MaxUses AND ExpiresAt > (now() AT TIME ZONE 'utc') ` +
178
+ `RETURNING ID;`
178
179
  );
179
180
  }
180
181
 
@@ -184,7 +185,7 @@ export function buildConsumeInviteSQL(qualifiedTable: string, dialect: ConsumeIn
184
185
  * of scopes across multiple redeemed links — carried in the re-minted JWT, never as
185
186
  * roles on a shared user, so anonymous sessions can't accrete into a superuser.
186
187
  */
187
- export function unionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, next: MagicLinkScopeEntry): MagicLinkScopeEntry[] {
188
+ export function UnionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, next: MagicLinkScopeEntry): MagicLinkScopeEntry[] {
188
189
  const result = [...(prior ?? [])];
189
190
  if (!result.some((s) => s.inviteId === next.inviteId)) {
190
191
  result.push(next);
@@ -192,8 +193,13 @@ export function unionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, n
192
193
  return result;
193
194
  }
194
195
 
196
+ /** @deprecated Use {@link UnionScopes}. */
197
+ export function unionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, next: MagicLinkScopeEntry): MagicLinkScopeEntry[] {
198
+ return UnionScopes(prior, next);
199
+ }
200
+
195
201
  /** Builds the session-token claims (pure). */
196
- export function buildSessionClaims(args: {
202
+ export function BuildSessionClaims(args: {
197
203
  issuer: string;
198
204
  audience: string;
199
205
  inviteId: string;
@@ -236,9 +242,35 @@ export function buildSessionClaims(args: {
236
242
  mj_app_id: args.applicationId,
237
243
  mj_role: args.roleName,
238
244
  mj_invited_by: args.invitedByUserId,
239
- mj_scopes: unionScopes(args.priorScopes, scopeEntry),
245
+ mj_scopes: UnionScopes(args.priorScopes, scopeEntry),
240
246
  mj_anon: args.anonymous ? true : undefined,
241
247
  mj_sid: args.sessionId,
242
248
  mj_magic_link: true,
243
249
  };
244
250
  }
251
+
252
+ /** @deprecated Use {@link BuildSessionClaims}. */
253
+ export function buildSessionClaims(args: {
254
+ issuer: string;
255
+ audience: string;
256
+ inviteId: string;
257
+ email: string;
258
+ firstName?: string;
259
+ lastName?: string;
260
+ applicationId: string;
261
+ roleName: string;
262
+ invitedByUserId?: string;
263
+ /** True for anonymous sessions — the server enforces scope from mj_scopes, not roles. */
264
+ anonymous?: boolean;
265
+ /** Opaque per-session id (anonymous forensics correlation). */
266
+ sessionId?: string;
267
+ /** Prior session's scope union to carry forward (multi-link anonymous sessions). */
268
+ priorScopes?: readonly MagicLinkScopeEntry[];
269
+ /** Resource-share/embed scope for this link's entry. */
270
+ resourceType?: string;
271
+ resourceId?: string;
272
+ nowSeconds: number;
273
+ ttlSeconds: number;
274
+ }): MagicLinkJWTClaims {
275
+ return BuildSessionClaims(args);
276
+ }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  /** Minimal HTML-escape for safe interpolation into attribute/text content. */
8
- export function escapeHtml(s: string): string {
8
+ export function EscapeHtml(s: string): string {
9
9
  return s
10
10
  .replace(/&/g, '&amp;')
11
11
  .replace(/</g, '&lt;')
@@ -14,6 +14,11 @@ export function escapeHtml(s: string): string {
14
14
  .replace(/'/g, '&#39;');
15
15
  }
16
16
 
17
+ /** @deprecated Use {@link EscapeHtml}. */
18
+ export function escapeHtml(s: string): string {
19
+ return EscapeHtml(s);
20
+ }
21
+
17
22
  /**
18
23
  * Builds the redemption landing page served by `GET /magic-link/redeem`.
19
24
  *
@@ -27,9 +32,9 @@ export function escapeHtml(s: string): string {
27
32
  * @param token the raw magic-link token (embedded in a hidden field)
28
33
  * @param actionPath the same-origin path the form POSTs to (e.g. `/magic-link/redeem`)
29
34
  */
30
- export function buildRedeemLandingHtml(token: string, actionPath: string): string {
31
- const t = escapeHtml(token);
32
- const action = escapeHtml(actionPath);
35
+ export function BuildRedeemLandingHtml(token: string, actionPath: string): string {
36
+ const t = EscapeHtml(token);
37
+ const action = EscapeHtml(actionPath);
33
38
  // Hardcoded colors are intentional here — this is a standalone server-rendered
34
39
  // HTML response, not Angular component CSS, so the design-token rule doesn't apply.
35
40
  return `<!doctype html>
@@ -60,3 +65,8 @@ export function buildRedeemLandingHtml(token: string, actionPath: string): strin
60
65
  </body>
61
66
  </html>`;
62
67
  }
68
+
69
+ /** @deprecated Use {@link BuildRedeemLandingHtml}. */
70
+ export function buildRedeemLandingHtml(token: string, actionPath: string): string {
71
+ return BuildRedeemLandingHtml(token, actionPath);
72
+ }