@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
@@ -1,5 +1,5 @@
1
1
  import { Arg, Field, ID, ObjectType, PubSubEngine, Resolver, ResolverFilterData, Root, Subscription } from 'type-graphql';
2
- import { UUIDsEqual } from '@memberjunction/global';
2
+ import { MJGlobal, UUIDsEqual } from '@memberjunction/global';
3
3
  import { UserPayload } from '../types.js';
4
4
 
5
5
  export const PUSH_STATUS_UPDATES_TOPIC = 'PUSH_STATUS_UPDATES';
@@ -7,13 +7,13 @@ export const PUSH_STATUS_UPDATES_TOPIC = 'PUSH_STATUS_UPDATES';
7
7
  @ObjectType()
8
8
  export class PushStatusNotification {
9
9
  @Field(() => String, { nullable: true })
10
- message?: string;
10
+ message?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
11
11
 
12
12
  @Field((_type) => Date)
13
- date!: Date;
13
+ date!: Date; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
14
14
 
15
15
  @Field((_type) => ID)
16
- sessionId!: string;
16
+ sessionId!: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
17
17
  }
18
18
 
19
19
  /**
@@ -32,6 +32,89 @@ export interface PushStatusNotificationPayload {
32
32
  sessionId: string;
33
33
  /** Authenticated user the update belongs to. Server-side filter key; never sent to the client. */
34
34
  ownerUserId: string;
35
+ /**
36
+ * `MJGlobal.ProcessUUID` of the instance that first published this update. Used only for
37
+ * cross-instance echo suppression: an instance that receives its own message back from the
38
+ * message bus must drop it, or every push is delivered twice.
39
+ */
40
+ SourceServerId?: string;
41
+ }
42
+
43
+ /** Receives every locally-published status update, for fan-out to other server instances. */
44
+ export type PushStatusPublishHook = (payload: PushStatusNotificationPayload) => void;
45
+
46
+ let _publishHook: PushStatusPublishHook | undefined;
47
+
48
+ /**
49
+ * Register (or clear, with no arg) the cross-instance fan-out hook.
50
+ *
51
+ * Inversion of control, deliberately: this module must not know that Redis exists. The server
52
+ * registers the hook at startup only when a message bus is configured, so a single-instance
53
+ * deployment behaves exactly as before.
54
+ */
55
+ export function SetPushStatusPublishHook(hook?: PushStatusPublishHook): void {
56
+ _publishHook = hook;
57
+ }
58
+
59
+ /**
60
+ * Validate an update received from the cross-instance message bus.
61
+ *
62
+ * The bus carries messages from other processes, so every field is type-checked before the update
63
+ * is republished onto the local topic, where {@link StatusUpdatesFilter} compares `ownerUserId`
64
+ * with the connection's user.
65
+ *
66
+ * @param raw The JSON text taken off the bus.
67
+ * @param localServerId This process's `MJGlobal.ProcessUUID`, used to drop our own echo.
68
+ * @returns The update to republish, or `null` to drop it.
69
+ */
70
+ export function ParseReplicatedStatusUpdate(raw: string, localServerId: string): PushStatusNotificationPayload | null {
71
+ let parsed: unknown;
72
+ try {
73
+ parsed = JSON.parse(raw);
74
+ } catch {
75
+ return null;
76
+ }
77
+ if (typeof parsed !== 'object' || parsed === null) {
78
+ return null;
79
+ }
80
+
81
+ const { sessionId, ownerUserId, message, SourceServerId } = parsed as Record<string, unknown>;
82
+ const source = typeof SourceServerId === 'string' ? SourceServerId : undefined;
83
+ const body = typeof message === 'string' ? message : undefined;
84
+ if ((SourceServerId != null && source === undefined) || (message != null && body === undefined)) {
85
+ return null; // a present field of the wrong type
86
+ }
87
+ if (source === localServerId) {
88
+ return null; // our own message, echoed back
89
+ }
90
+ if (typeof sessionId !== 'string' || !sessionId || typeof ownerUserId !== 'string' || !ownerUserId) {
91
+ return null; // fail closed: an update with no valid identity can never be routed safely
92
+ }
93
+
94
+ return { sessionId, ownerUserId, message: body, SourceServerId: source };
95
+ }
96
+
97
+ /**
98
+ * Whether an update is worth sending to other server instances.
99
+ *
100
+ * Streaming content is excluded. It arrives at hundreds of messages per second and each one carries
101
+ * a full serialized agent run, so replicating it would cost far more bandwidth than the delivery it
102
+ * buys — and a token delta is worthless to a client that has already missed the ones before it.
103
+ * Progress and completion are low-rate and each is individually meaningful, which is exactly the
104
+ * kind of message that must survive landing on the wrong replica.
105
+ */
106
+ export function ShouldReplicateStatusUpdate(message?: string): boolean {
107
+ if (!message) {
108
+ return false;
109
+ }
110
+ try {
111
+ const parsed: unknown = JSON.parse(message);
112
+ const type = (parsed as { type?: unknown })?.type;
113
+ return type !== 'StreamingContent';
114
+ } catch {
115
+ // A plain-string message (not the resolvers' JSON envelope) is rare and low-rate. Replicate it.
116
+ return true;
117
+ }
35
118
  }
36
119
 
37
120
  interface PushStatusNotificationArgs {
@@ -55,13 +138,31 @@ export interface StatusUpdateParams {
55
138
  * payload shape and the required-identity guarantee live in exactly one place. Adding a field to
56
139
  * the push is a one-line change here; a new publisher physically cannot omit `ownerUserId`.
57
140
  */
58
- export function publishStatusUpdate(pubSub: PubSubEngine, params: StatusUpdateParams): void {
141
+ export function PublishStatusUpdate(pubSub: PubSubEngine, params: StatusUpdateParams): void {
59
142
  const payload: PushStatusNotificationPayload = {
60
143
  sessionId: params.sessionId,
61
144
  ownerUserId: params.ownerUserId,
62
145
  message: params.message,
146
+ SourceServerId: MJGlobal.Instance.ProcessUUID,
63
147
  };
64
148
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
149
+
150
+ // Fan out to other instances. Only reached by updates originating HERE: a message arriving from
151
+ // the bus is republished straight onto the local topic and never comes back through this
152
+ // function, so there is no loop to break beyond the SourceServerId check on the receiving side.
153
+ if (_publishHook && ShouldReplicateStatusUpdate(params.message)) {
154
+ try {
155
+ _publishHook(payload);
156
+ } catch {
157
+ // Fan-out is an optimization over the durable tail query. A message bus that is down must
158
+ // never break local delivery, which is the path that works for the common case.
159
+ }
160
+ }
161
+ }
162
+
163
+ /** @deprecated Use {@link PublishStatusUpdate}. */
164
+ export function publishStatusUpdate(pubSub: PubSubEngine, params: StatusUpdateParams): void {
165
+ return PublishStatusUpdate(pubSub, params);
65
166
  }
66
167
 
67
168
  /** Minimal shape of the subscription's connection context needed by the filter. */
@@ -82,7 +183,7 @@ export interface StatusUpdatesFilterContext {
82
183
  * `sessionId` is no longer sufficient. Fails CLOSED — a missing owner or connection identity never
83
184
  * matches.
84
185
  */
85
- export function statusUpdatesFilter(data: {
186
+ export function StatusUpdatesFilter(data: {
86
187
  payload: PushStatusNotificationPayload;
87
188
  args: PushStatusNotificationArgs;
88
189
  context: StatusUpdatesFilterContext | undefined;
@@ -98,14 +199,23 @@ export function statusUpdatesFilter(data: {
98
199
  return UUIDsEqual(payload.ownerUserId, connectionUserId);
99
200
  }
100
201
 
202
+ /** @deprecated Use {@link StatusUpdatesFilter}. */
203
+ export function statusUpdatesFilter(data: {
204
+ payload: PushStatusNotificationPayload;
205
+ args: PushStatusNotificationArgs;
206
+ context: StatusUpdatesFilterContext | undefined;
207
+ }): boolean {
208
+ return StatusUpdatesFilter(data);
209
+ }
210
+
101
211
  @Resolver()
102
212
  export class PushStatusResolver {
103
213
  @Subscription(() => PushStatusNotification, {
104
214
  topics: PUSH_STATUS_UPDATES_TOPIC,
105
215
  filter: (data: ResolverFilterData<PushStatusNotificationPayload, PushStatusNotificationArgs, StatusUpdatesFilterContext>) =>
106
- statusUpdatesFilter(data),
216
+ StatusUpdatesFilter(data),
107
217
  })
108
- statusUpdates(
218
+ statusUpdates( // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
109
219
  @Root() { message }: PushStatusNotificationPayload,
110
220
  @Arg('sessionId', () => String) sessionId: string
111
221
  ): PushStatusNotification {
@@ -4,6 +4,7 @@ import {
4
4
  BaseEntityEvent,
5
5
  CompositeKey,
6
6
  DatabaseProviderBase,
7
+ EntityFieldInfo,
7
8
  EntityFieldTSType,
8
9
  EntityInfo,
9
10
  EntityPermissionType,
@@ -40,8 +41,8 @@ import { MJEvent, MJEventType, MJGlobal, ENCRYPTED_SENTINEL, EscapeSQLString, Is
40
41
  import { SQLParser } from '@memberjunction/sql-parser';
41
42
  import { PostgreSQLDialect, SQLServerDialect, type SQLParserDialect } from '@memberjunction/sql-dialect';
42
43
  import { EncryptionEngine } from '@memberjunction/encryption';
43
- import { PUSH_STATUS_UPDATES_TOPIC, publishStatusUpdate } from './PushStatusResolver.js';
44
- import { CACHE_INVALIDATION_TOPIC } from './CacheInvalidationResolver.js';
44
+ import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate } from './PushStatusResolver.js';
45
+ import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData } from './CacheInvalidationResolver.js';
45
46
  import { PubSubManager } from './PubSubManager.js';
46
47
  import { FieldMapper } from '@memberjunction/graphql-dataprovider';
47
48
  import { Subscription } from 'rxjs';
@@ -51,7 +52,7 @@ export class ResolverBase {
51
52
  private static _cloudeventsHeaders = process.env.CLOUDEVENTS_HTTP_HEADERS ? JSON.parse(process.env.CLOUDEVENTS_HTTP_HEADERS) : {};
52
53
 
53
54
  private static _eventSubscriptionKey: string = '___MJServer___ResolverBase___EventSubscriptions';
54
- private get EventSubscriptions(): Map<string, Subscription> {
55
+ private get eventSubscriptions(): Map<string, Subscription> {
55
56
  // here we use the global object store instead of a static member becuase in some cases based on import code paths/bundling/etc, the static member
56
57
  // could actually be duplicated and we'd end up with multiple instances of the same map, which would be bad.
57
58
  const g = MJGlobal.Instance.GetGlobalObjectStore();
@@ -1450,16 +1451,24 @@ export class ResolverBase {
1450
1451
  * Publishes a CACHE_INVALIDATION event to connected browser clients after a successful
1451
1452
  * entity save or delete. Includes the originSessionId so the originating browser can
1452
1453
  * skip redundant re-fetches (it already handled the event locally).
1454
+ *
1455
+ * The row itself rides along ONLY for entities opted in via
1456
+ * `cacheSettings.recordDataBroadcastEntities` — this event reaches every connected client
1457
+ * unfiltered, so the row would otherwise be readable by sessions that could not read the record.
1453
1458
  */
1454
1459
  protected PublishCacheInvalidation(entityObject: BaseEntity, action: 'save' | 'delete', userPayload: UserPayload): void {
1460
+ const entityName = entityObject.EntityInfo.Name;
1455
1461
  PubSubManager.Instance.Publish(CACHE_INVALIDATION_TOPIC, {
1456
- entityName: entityObject.EntityInfo.Name,
1462
+ entityName,
1457
1463
  primaryKeyValues: JSON.stringify(entityObject.PrimaryKey.KeyValuePairs),
1458
1464
  action,
1459
1465
  sourceServerId: MJGlobal.Instance.ProcessUUID,
1460
1466
  timestamp: new Date(),
1461
1467
  originSessionId: userPayload?.sessionId || null,
1462
- recordData: action === 'save' ? JSON.stringify(entityObject.GetAll()) : undefined,
1468
+ recordData:
1469
+ action === 'save' && MayBroadcastRecordData(entityName)
1470
+ ? JSON.stringify(entityObject.GetAll())
1471
+ : undefined,
1463
1472
  });
1464
1473
  }
1465
1474
 
@@ -1472,7 +1481,7 @@ export class ResolverBase {
1472
1481
  * `publishStatusUpdate()` function directly with an explicit `ownerUserId`.
1473
1482
  */
1474
1483
  protected PublishStatusUpdate(pubSub: PubSubEngine, sessionId: string, message: string | undefined, userPayload: UserPayload): void {
1475
- publishStatusUpdate(pubSub, {
1484
+ PublishStatusUpdate(pubSub, {
1476
1485
  sessionId,
1477
1486
  ownerUserId: userPayload?.userRecord?.ID ?? '',
1478
1487
  message,
@@ -1485,7 +1494,7 @@ export class ResolverBase {
1485
1494
  // cause issues with multiple messages for the same event.
1486
1495
  const uniqueKey = entityObject.EntityInfo.Name;
1487
1496
 
1488
- if (!this.EventSubscriptions.has(uniqueKey)) {
1497
+ if (!this.eventSubscriptions.has(uniqueKey)) {
1489
1498
  // listen for events from the entityObject in case it is a long running task and we can push messages back to the client via pubSub
1490
1499
  LogDebug(`ResolverBase.ListenForEntityMessages: About to call MJGlobal.Instance.GetEventListener() to get the event listener subscription for ${uniqueKey}`);
1491
1500
  const theSub = MJGlobal.Instance.GetEventListener(false).subscribe(async (event: MJEvent) => {
@@ -1518,7 +1527,7 @@ export class ResolverBase {
1518
1527
  }
1519
1528
  }
1520
1529
  });
1521
- this.EventSubscriptions.set(uniqueKey, theSub);
1530
+ this.eventSubscriptions.set(uniqueKey, theSub);
1522
1531
  }
1523
1532
  }
1524
1533
 
@@ -1684,7 +1693,7 @@ export class ResolverBase {
1684
1693
  await this.TestAndSetClientOldValuesToDBValues(input, clientNewValues, entityObject, userInfo);
1685
1694
  } else {
1686
1695
  // no OldValues, so we can just set the new values from input
1687
- entityObject.SetMany(input);
1696
+ entityObject.SetMany(clientNewValues);
1688
1697
  }
1689
1698
  } else {
1690
1699
  // Use a generic message to avoid leaking whether a record exists — distinguishing
@@ -1696,8 +1705,11 @@ export class ResolverBase {
1696
1705
  } else {
1697
1706
  // we get here if we are NOT tracking changes and we DO have OldValues, so we can load from them
1698
1707
  const oldValues = {};
1699
- // for each item in the oldValues array, add it to the oldValues object
1700
- input.OldValues___?.forEach((item) => (oldValues[item.Key] = item.Value));
1708
+ // for each item in the oldValues array, add it to the oldValues object, typed like the field
1709
+ input.OldValues___?.forEach((item) => {
1710
+ const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
1711
+ oldValues[item.Key] = field ? this.ClientOldValueToFieldValue(field, item.Value) : item.Value;
1712
+ });
1701
1713
 
1702
1714
  // 1) load the old values, this will be the initial state of the object
1703
1715
  await entityObject.LoadFromData(oldValues);
@@ -1764,6 +1776,11 @@ export class ResolverBase {
1764
1776
  *
1765
1777
  * Ordered so the boolean flag is evaluated last: the extra load lands only on entities that have
1766
1778
  * the feature switched on, which is almost none of them.
1779
+ *
1780
+ * `MJ: Record Changes` always loads from the database: its server class allows an update only when
1781
+ * Comments is the one dirty field, and "dirty" compared against client-supplied OldValues lets a
1782
+ * caller pin forged audit columns as both old and new values. It doesn't track its own changes, so
1783
+ * nothing else forces the load.
1767
1784
  */
1768
1785
  protected MustLoadTruthFromDatabase(
1769
1786
  entityInfo: EntityInfo,
@@ -1773,6 +1790,7 @@ export class ResolverBase {
1773
1790
  ): boolean {
1774
1791
  return (
1775
1792
  entityInfo.TrackRecordChanges ||
1793
+ entityInfo.Name.trim().toLowerCase() === 'mj: record changes' ||
1776
1794
  !input.OldValues___ ||
1777
1795
  hasDeniedReadFields ||
1778
1796
  hasNarrowedAuditPayload ||
@@ -1909,6 +1927,41 @@ export class ResolverBase {
1909
1927
  return true;
1910
1928
  }
1911
1929
 
1930
+ /**
1931
+ * Converts one client-sent old value (always a string on the wire; dates as epoch milliseconds,
1932
+ * the GraphQL Timestamp form) to the field's TypeScript type, so it compares equal to the stored
1933
+ * value. Both the OldValues comparison and the load-from-OldValues path use it: without it a date
1934
+ * old value becomes an Invalid Date and an unchanged date field reads as edited.
1935
+ */
1936
+ protected ClientOldValueToFieldValue(field: EntityFieldInfo | undefined, raw: unknown): string | number | boolean | Date | null {
1937
+ let val: unknown = raw;
1938
+ if ((val === null || val === undefined) && field && field.DefaultValue !== null && field.DefaultValue !== undefined && !field.AllowsNull)
1939
+ val = field.DefaultValue; // set default value as the field was never set and it does NOT allow nulls
1940
+ if (val === undefined) val = null;
1941
+ // A null old value stays null, except a boolean, which the comparison has always read as false.
1942
+ if (field?.TSType === EntityFieldTSType.Boolean && val === null) return false;
1943
+ if (val === null) return null;
1944
+ if (!field) {
1945
+ // No field metadata to convert by: pass scalars through, stringify anything else.
1946
+ return typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val instanceof Date ? val : String(val);
1947
+ }
1948
+
1949
+ const text = String(val);
1950
+ switch (field.TSType) {
1951
+ case EntityFieldTSType.Number: {
1952
+ const integer = ['int', 'smallint', 'bigint', 'tinyint'].includes((field.Type as string).toLowerCase());
1953
+ return integer ? parseInt(text) : parseFloat(text);
1954
+ }
1955
+ case EntityFieldTSType.Boolean:
1956
+ return !(text === 'false' || text === '0' || parseInt(text) === 0);
1957
+ case EntityFieldTSType.Date:
1958
+ // Epoch milliseconds (the GraphQL Timestamp form) arrive as a numeric string.
1959
+ return new Date(text.trim() !== '' && !isNaN(Number(text)) ? parseInt(text) : text);
1960
+ default:
1961
+ return text; // already a string
1962
+ }
1963
+ }
1964
+
1912
1965
  /**
1913
1966
  * This routine compares the OldValues property in the input object to the values in the DB that we just loaded. If there are differences, we need to check to see if the client
1914
1967
  * is trying to update any of those fields (e.g. overlap). If there is overlap, we throw an error. If there is no overlap, we can proceed with the update even if the DB Values
@@ -1923,52 +1976,7 @@ export class ResolverBase {
1923
1976
  input.OldValues___.forEach((item) => {
1924
1977
  // we need to do a quick transform on the values to make sure they match the TS Type for the given field because item.Value will always be a string
1925
1978
  const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
1926
- let val = item.Value;
1927
- if ((val === null || val === undefined) && field.DefaultValue !== null && field.DefaultValue !== undefined && !field.AllowsNull)
1928
- val = field.DefaultValue; // set default value as the field was never set and it does NOT allow nulls
1929
-
1930
- if (field) {
1931
- switch (field.TSType) {
1932
- case EntityFieldTSType.Number:
1933
- if (val == null && val == undefined) {
1934
- val = null;
1935
- }
1936
- else {
1937
- let typeLowered = (field.Type as string).toLowerCase();
1938
-
1939
- switch (typeLowered) {
1940
- case 'int':
1941
- case 'smallint':
1942
- case 'bigint':
1943
- case 'tinyint':
1944
- val = parseInt(val);
1945
- break;
1946
- case 'money':
1947
- case 'smallmoney':
1948
- case 'decimal':
1949
- case 'numeric':
1950
- case 'float':
1951
- val = parseFloat(val);
1952
- break;
1953
- default:
1954
- val = parseFloat(val);
1955
- break;
1956
- }
1957
- }
1958
- break;
1959
- case EntityFieldTSType.Boolean:
1960
- val = val === null || val === undefined || val === 'false' || val === '0' || parseInt(val) === 0 ? false : true;
1961
- break;
1962
- case EntityFieldTSType.Date:
1963
- // first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
1964
- if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val)) val = parseInt(val);
1965
-
1966
- val = val !== null && val !== undefined ? new Date(val) : null;
1967
- break;
1968
- default:
1969
- break; // already a string
1970
- }
1971
- }
1979
+ const val = this.ClientOldValueToFieldValue(field, item.Value);
1972
1980
  clientOldValues[item.Key] = val;
1973
1981
  });
1974
1982
 
@@ -45,13 +45,13 @@ export class AggregateExpressionInput {
45
45
  @Field(() => String, {
46
46
  description: 'SQL expression for the aggregate (e.g., "SUM(OrderTotal)", "COUNT(*)", "AVG(Price)")'
47
47
  })
48
- expression: string;
48
+ expression: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
49
49
 
50
50
  @Field(() => String, {
51
51
  nullable: true,
52
52
  description: 'Optional alias for the result (used in error messages and debugging)'
53
53
  })
54
- alias?: string;
54
+ alias?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
55
55
  }
56
56
 
57
57
  /**
@@ -60,22 +60,22 @@ export class AggregateExpressionInput {
60
60
  @ObjectType()
61
61
  export class AggregateResultOutput {
62
62
  @Field(() => String, { description: 'The expression that was calculated' })
63
- expression: string;
63
+ expression: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
64
64
 
65
65
  @Field(() => String, { description: 'The alias (or expression if no alias provided)' })
66
- alias: string;
66
+ alias: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
67
67
 
68
68
  @Field(() => String, {
69
69
  nullable: true,
70
70
  description: 'The calculated value as a JSON string (preserves type information)'
71
71
  })
72
- value?: string;
72
+ value?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
73
73
 
74
74
  @Field(() => String, {
75
75
  nullable: true,
76
76
  description: 'Error message if calculation failed'
77
77
  })
78
- error?: string;
78
+ error?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
79
79
  }
80
80
 
81
81
  //****************************************************************************
@@ -573,22 +573,22 @@ export class RunViewGenericInput {
573
573
  @InputType()
574
574
  export class RunViewCacheStatusInput {
575
575
  @Field(() => String, { description: 'The maximum __mj_UpdatedAt value from cached results' })
576
- maxUpdatedAt: string;
576
+ maxUpdatedAt: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
577
577
 
578
578
  @Field(() => Int, { description: 'The number of rows in cached results' })
579
- rowCount: number;
579
+ rowCount: number; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
580
580
  }
581
581
 
582
582
  @InputType()
583
583
  export class RunViewWithCacheCheckInput {
584
584
  @Field(() => RunDynamicViewInput, { description: 'The RunView parameters' })
585
- params: RunDynamicViewInput;
585
+ params: RunDynamicViewInput; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
586
586
 
587
587
  @Field(() => RunViewCacheStatusInput, {
588
588
  nullable: true,
589
589
  description: 'Optional cache status - if provided, server will check if cache is current'
590
590
  })
591
- cacheStatus?: RunViewCacheStatusInput;
591
+ cacheStatus?: RunViewCacheStatusInput; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
592
592
  }
593
593
 
594
594
  @ObjectType()
@@ -596,21 +596,21 @@ export class DifferentialDataOutput {
596
596
  @Field(() => [RunViewGenericResultRow], {
597
597
  description: 'Records that have been created or updated since the client\'s maxUpdatedAt'
598
598
  })
599
- updatedRows: RunViewGenericResultRow[];
599
+ updatedRows: RunViewGenericResultRow[]; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
600
600
 
601
601
  @Field(() => [String], {
602
602
  description: 'Primary key values (as concatenated strings) of records that have been deleted'
603
603
  })
604
- deletedRecordIDs: string[];
604
+ deletedRecordIDs: string[]; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
605
605
  }
606
606
 
607
607
  @ObjectType()
608
608
  export class RunViewWithCacheCheckResultOutput {
609
609
  @Field(() => Int, { description: 'The index of this view in the batch request' })
610
- viewIndex: number;
610
+ viewIndex: number; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
611
611
 
612
612
  @Field(() => String, { description: "'current', 'differential', 'stale', or 'error'" })
613
- status: string;
613
+ status: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
614
614
 
615
615
  @Field(() => [RunViewGenericResultRow], {
616
616
  nullable: true,
@@ -622,34 +622,34 @@ export class RunViewWithCacheCheckResultOutput {
622
622
  nullable: true,
623
623
  description: 'Differential update data - only populated when status is differential'
624
624
  })
625
- differentialData?: DifferentialDataOutput;
625
+ differentialData?: DifferentialDataOutput; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
626
626
 
627
627
  @Field(() => String, { nullable: true, description: 'Max __mj_UpdatedAt from results when stale or differential' })
628
- maxUpdatedAt?: string;
628
+ maxUpdatedAt?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
629
629
 
630
630
  @Field(() => Int, { nullable: true, description: 'Row count of results when stale or differential (total after applying delta)' })
631
- rowCount?: number;
631
+ rowCount?: number; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
632
632
 
633
633
  @Field(() => String, { nullable: true, description: 'Error message if status is error' })
634
- errorMessage?: string;
634
+ errorMessage?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
635
635
 
636
636
  @Field(() => [AggregateResultOutput], {
637
637
  nullable: true,
638
638
  description: 'Aggregate results when status is stale and aggregates were requested (B40 — previously never marshalled, so CacheLocal callers got no aggregates at all)'
639
639
  })
640
- aggregateResults?: AggregateResultOutput[];
640
+ aggregateResults?: AggregateResultOutput[]; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
641
641
  }
642
642
 
643
643
  @ObjectType()
644
644
  export class RunViewsWithCacheCheckOutput {
645
645
  @Field(() => Boolean, { description: 'Whether the overall operation succeeded' })
646
- success: boolean;
646
+ success: boolean; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
647
647
 
648
648
  @Field(() => [RunViewWithCacheCheckResultOutput], { description: 'Results for each view in the batch' })
649
- results: RunViewWithCacheCheckResultOutput[];
649
+ results: RunViewWithCacheCheckResultOutput[]; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
650
650
 
651
651
  @Field(() => String, { nullable: true, description: 'Overall error message if success is false' })
652
- errorMessage?: string;
652
+ errorMessage?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
653
653
  }
654
654
 
655
655
  //****************************************************************************
@@ -2,7 +2,7 @@ import 'reflect-metadata';
2
2
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
3
  import { PubSubEngine } from 'type-graphql';
4
4
  import {
5
- startLivenessPulse,
5
+ StartLivenessPulse,
6
6
  DEFAULT_PULSE_INTERVAL_MS,
7
7
  HEARTBEAT_MESSAGE_TYPE,
8
8
  } from '../FireAndForgetHeartbeat';
@@ -31,14 +31,14 @@ describe('startLivenessPulse', () => {
31
31
 
32
32
  it('does not publish before the first interval elapses', () => {
33
33
  const pubSub = makePubSub();
34
- startLivenessPulse({ pubSub, sessionId: 's1', resolver: 'TestResolver' });
34
+ StartLivenessPulse({ pubSub, sessionId: 's1', resolver: 'TestResolver' });
35
35
  vi.advanceTimersByTime(DEFAULT_PULSE_INTERVAL_MS - 1);
36
36
  expect(pubSub.publish).not.toHaveBeenCalled();
37
37
  });
38
38
 
39
39
  it('publishes a heartbeat on the push-status topic each interval', () => {
40
40
  const pubSub = makePubSub();
41
- startLivenessPulse({ pubSub, sessionId: 's1', resolver: 'TestResolver', intervalMs: 1000 });
41
+ StartLivenessPulse({ pubSub, sessionId: 's1', resolver: 'TestResolver', intervalMs: 1000 });
42
42
 
43
43
  vi.advanceTimersByTime(3000);
44
44
 
@@ -52,7 +52,7 @@ describe('startLivenessPulse', () => {
52
52
 
53
53
  it('includes the enriched status snapshot from readStatus', () => {
54
54
  const pubSub = makePubSub();
55
- startLivenessPulse({
55
+ StartLivenessPulse({
56
56
  pubSub,
57
57
  sessionId: 's1',
58
58
  resolver: 'RunAIAgentResolver',
@@ -68,7 +68,7 @@ describe('startLivenessPulse', () => {
68
68
 
69
69
  it('publishes an empty data object when readStatus throws (loop survives)', () => {
70
70
  const pubSub = makePubSub();
71
- startLivenessPulse({
71
+ StartLivenessPulse({
72
72
  pubSub,
73
73
  sessionId: 's1',
74
74
  resolver: 'RunAIAgentResolver',
@@ -87,12 +87,12 @@ describe('startLivenessPulse', () => {
87
87
 
88
88
  it('stops publishing after stop() is called', () => {
89
89
  const pubSub = makePubSub();
90
- const handle = startLivenessPulse({ pubSub, sessionId: 's1', resolver: 'TestResolver', intervalMs: 1000 });
90
+ const handle = StartLivenessPulse({ pubSub, sessionId: 's1', resolver: 'TestResolver', intervalMs: 1000 });
91
91
 
92
92
  vi.advanceTimersByTime(2000);
93
93
  expect(pubSub.publish).toHaveBeenCalledTimes(2);
94
94
 
95
- handle.stop();
95
+ handle.Stop();
96
96
  vi.advanceTimersByTime(5000);
97
97
  expect(pubSub.publish).toHaveBeenCalledTimes(2);
98
98
  });