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

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 (401) hide show
  1. package/README.md +27 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +3 -3
  4. package/dist/agentSessions/SessionJanitor.js +3 -3
  5. package/dist/agentSessions/SessionManager.d.ts +15 -5
  6. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionManager.js +34 -12
  8. package/dist/agentSessions/SessionManager.js.map +1 -1
  9. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  10. package/dist/auth/AuthProviderCatalogRouter.js +2 -0
  11. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +51 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/initializeProviders.d.ts +10 -0
  16. package/dist/auth/initializeProviders.d.ts.map +1 -1
  17. package/dist/auth/initializeProviders.js +37 -1
  18. package/dist/auth/initializeProviders.js.map +1 -1
  19. package/dist/auth/localDevAuth.d.ts +79 -0
  20. package/dist/auth/localDevAuth.d.ts.map +1 -0
  21. package/dist/auth/localDevAuth.js +439 -0
  22. package/dist/auth/localDevAuth.js.map +1 -0
  23. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  24. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  26. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  27. package/dist/auth/magicLink/types.d.ts +12 -0
  28. package/dist/auth/magicLink/types.d.ts.map +1 -1
  29. package/dist/auth/sessionUserClone.d.ts +1 -1
  30. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  31. package/dist/auth/sessionUserClone.js +3 -2
  32. package/dist/auth/sessionUserClone.js.map +1 -1
  33. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  34. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  35. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  36. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  37. package/dist/broadcast/index.d.ts +6 -0
  38. package/dist/broadcast/index.d.ts.map +1 -0
  39. package/dist/broadcast/index.js +6 -0
  40. package/dist/broadcast/index.js.map +1 -0
  41. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  42. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  43. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  44. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  45. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  46. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  47. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  48. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  49. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  50. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  51. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  52. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  53. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  54. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  55. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  56. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  57. package/dist/cache.d.ts +3 -2
  58. package/dist/cache.d.ts.map +1 -1
  59. package/dist/cache.js.map +1 -1
  60. package/dist/config.d.ts +496 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +61 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/context.d.ts +3 -1
  65. package/dist/context.d.ts.map +1 -1
  66. package/dist/context.js +29 -8
  67. package/dist/context.js.map +1 -1
  68. package/dist/coreRoutes.d.ts.map +1 -1
  69. package/dist/coreRoutes.js +14 -0
  70. package/dist/coreRoutes.js.map +1 -1
  71. package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +3366 -418
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +154 -79
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  80. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  81. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  82. package/dist/generic/PubSubManager.d.ts +10 -0
  83. package/dist/generic/PubSubManager.d.ts.map +1 -1
  84. package/dist/generic/PubSubManager.js +44 -1
  85. package/dist/generic/PubSubManager.js.map +1 -1
  86. package/dist/generic/PushStatusResolver.d.ts +39 -6
  87. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  88. package/dist/generic/PushStatusResolver.js +93 -11
  89. package/dist/generic/PushStatusResolver.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +65 -25
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +201 -101
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +11 -3
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +79 -13
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  99. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  100. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  101. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  102. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  103. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  104. package/dist/generic/fieldSecurityTransport.js +30 -0
  105. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  106. package/dist/index.d.ts +3 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +122 -66
  109. package/dist/index.js.map +1 -1
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +6 -1
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  114. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  115. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  116. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  117. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  118. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  119. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  120. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
  121. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  122. package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
  123. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  124. package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
  125. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  126. package/dist/realtimeSessions/sessionAccess.js +125 -22
  127. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  128. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  129. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  130. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  131. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  132. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  133. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  134. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  135. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  136. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  137. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  138. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  139. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  140. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  141. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  142. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  143. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  144. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  145. package/dist/realtimeWidget/host-identity.js +1 -1
  146. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  147. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  148. package/dist/realtimeWidget/widgetCore.js +17 -1
  149. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  150. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  151. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  152. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  153. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  154. package/dist/resolvers/ActionResolver.d.ts +19 -1
  155. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  156. package/dist/resolvers/ActionResolver.js +63 -12
  157. package/dist/resolvers/ActionResolver.js.map +1 -1
  158. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  159. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  160. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  161. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  162. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  163. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  164. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  165. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  166. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  167. package/dist/resolvers/AgentSessionResolver.js +40 -17
  168. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  169. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  170. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  171. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  172. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  173. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
  174. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  175. package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
  176. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  177. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  178. package/dist/resolvers/FileCategoryResolver.js +1 -0
  179. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  180. package/dist/resolvers/FileResolver.d.ts +5 -0
  181. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  182. package/dist/resolvers/FileResolver.js +13 -4
  183. package/dist/resolvers/FileResolver.js.map +1 -1
  184. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
  186. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  187. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  188. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  189. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  190. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  191. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  192. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  193. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  194. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  195. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  196. package/dist/resolvers/QueryResolver.js +20 -6
  197. package/dist/resolvers/QueryResolver.js.map +1 -1
  198. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  199. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  200. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  201. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  202. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  203. package/dist/resolvers/RSUResolver.js +12 -0
  204. package/dist/resolvers/RSUResolver.js.map +1 -1
  205. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
  206. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  207. package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
  208. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  209. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  211. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  212. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  213. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  214. package/dist/resolvers/RunAIAgentResolver.js +209 -64
  215. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  217. package/dist/resolvers/RunTemplateResolver.js +4 -4
  218. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  219. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  220. package/dist/resolvers/RunTestResolver.js +3 -1
  221. package/dist/resolvers/RunTestResolver.js.map +1 -1
  222. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  223. package/dist/resolvers/SyncDataResolver.js +4 -0
  224. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  225. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  226. package/dist/resolvers/TestQuerySQLResolver.js +1 -0
  227. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  228. package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
  229. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  230. package/dist/resolvers/TransactionGroupResolver.js +60 -15
  231. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  232. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  233. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  234. package/dist/resolvers/UserViewResolver.js +2 -1
  235. package/dist/resolvers/UserViewResolver.js.map +1 -1
  236. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  237. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  238. package/dist/rest/EntityCRUDHandler.js +19 -5
  239. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  240. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  241. package/dist/rest/RESTEndpointHandler.js +8 -5
  242. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  243. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  244. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  245. package/dist/rest/RealtimeRecordingHandler.js +7 -7
  246. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  247. package/dist/rest/ViewOperationsHandler.d.ts +6 -0
  248. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  249. package/dist/rest/ViewOperationsHandler.js +22 -5
  250. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  251. package/dist/serverRoleConfigUnits.d.ts +29 -0
  252. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  253. package/dist/serverRoleConfigUnits.js +33 -0
  254. package/dist/serverRoleConfigUnits.js.map +1 -0
  255. package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
  256. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  257. package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
  258. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  259. package/dist/types.d.ts +23 -1
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +98 -98
  263. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  264. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  265. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  267. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  268. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  269. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  270. package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
  271. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  272. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  273. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  274. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  275. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  276. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  277. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  278. package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
  279. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  280. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  281. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  282. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  283. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  284. package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
  285. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  286. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  287. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
  288. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
  289. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  290. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  291. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  292. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  293. package/src/__tests__/SessionManager.test.ts +95 -0
  294. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
  295. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  296. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  297. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  298. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  299. package/src/__tests__/apiRootBoot.test.ts +1 -1
  300. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  301. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  302. package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
  303. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  304. package/src/__tests__/globalCors.test.ts +3 -3
  305. package/src/__tests__/localDevAuth.test.ts +196 -0
  306. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  307. package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
  308. package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
  309. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  310. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  311. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  312. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  313. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  314. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  315. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  316. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  317. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  318. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  319. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  320. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  321. package/src/__tests__/runViewProperties.test.ts +114 -0
  322. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  323. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  324. package/src/__tests__/sessionUserClone.test.ts +5 -1
  325. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  326. package/src/__tests__/unifiedAuth.test.ts +28 -0
  327. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  328. package/src/agentSessions/SessionJanitor.ts +3 -3
  329. package/src/agentSessions/SessionManager.ts +35 -12
  330. package/src/auth/AuthProviderCatalogRouter.ts +3 -0
  331. package/src/auth/index.ts +56 -12
  332. package/src/auth/initializeProviders.ts +43 -1
  333. package/src/auth/localDevAuth.ts +528 -0
  334. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  335. package/src/auth/magicLink/types.ts +10 -0
  336. package/src/auth/sessionUserClone.ts +3 -2
  337. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  338. package/src/broadcast/index.ts +5 -0
  339. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  340. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  341. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  342. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  343. package/src/cache.ts +2 -1
  344. package/src/config.ts +69 -0
  345. package/src/context.ts +32 -8
  346. package/src/coreRoutes.ts +15 -0
  347. package/src/generated/graphql-schemas/__mj.ts +2239 -102
  348. package/src/generic/CacheInvalidationResolver.ts +135 -78
  349. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  350. package/src/generic/PubSubManager.ts +54 -1
  351. package/src/generic/PushStatusResolver.ts +108 -11
  352. package/src/generic/ResolverBase.ts +237 -107
  353. package/src/generic/RunViewResolver.ts +80 -15
  354. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  355. package/src/generic/fieldSecurityTransport.ts +62 -0
  356. package/src/index.ts +127 -68
  357. package/src/integration/CustomColumnPromoter.ts +6 -1
  358. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  359. package/src/realtimeSessions/README.md +7 -3
  360. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  361. package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
  362. package/src/realtimeSessions/sessionAccess.ts +181 -25
  363. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  364. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  365. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  366. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  367. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  368. package/src/realtimeWidget/host-identity.ts +1 -1
  369. package/src/realtimeWidget/widgetCore.ts +30 -1
  370. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  371. package/src/resolvers/ActionResolver.ts +78 -15
  372. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  373. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  374. package/src/resolvers/AgentSessionResolver.ts +46 -18
  375. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  376. package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
  377. package/src/resolvers/FileCategoryResolver.ts +1 -0
  378. package/src/resolvers/FileResolver.ts +14 -4
  379. package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
  380. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  381. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  382. package/src/resolvers/QueryResolver.ts +20 -6
  383. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  384. package/src/resolvers/RSUResolver.ts +12 -0
  385. package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
  386. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  387. package/src/resolvers/RunAIAgentResolver.ts +258 -82
  388. package/src/resolvers/RunTemplateResolver.ts +6 -4
  389. package/src/resolvers/RunTestResolver.ts +3 -1
  390. package/src/resolvers/SyncDataResolver.ts +4 -0
  391. package/src/resolvers/TestQuerySQLResolver.ts +1 -0
  392. package/src/resolvers/TransactionGroupResolver.ts +66 -15
  393. package/src/resolvers/UserViewResolver.ts +2 -1
  394. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  395. package/src/rest/EntityCRUDHandler.ts +22 -5
  396. package/src/rest/RESTEndpointHandler.ts +8 -5
  397. package/src/rest/RealtimeRecordingHandler.ts +13 -10
  398. package/src/rest/ViewOperationsHandler.ts +24 -6
  399. package/src/serverRoleConfigUnits.ts +48 -0
  400. package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
  401. package/src/types.ts +23 -1
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  AggregateExpression,
3
+ AssertClientInputWritableForUser,
3
4
  BaseEntity,
4
5
  BaseEntityEvent,
5
6
  CompositeKey,
@@ -14,8 +15,10 @@ import {
14
15
  KeyValuePair,
15
16
  LogDebug,
16
17
  LogError,
18
+ LogErrorEx,
17
19
  LogStatus,
18
20
  Metadata,
21
+ ProjectEntityValuesForUser,
19
22
  ReadableFieldsTransportKey,
20
23
  RecordChangeFieldSecurityProjector,
21
24
  RecordChangePayloadFields,
@@ -23,8 +26,17 @@ import {
23
26
  RunViewParams,
24
27
  RunViewResult,
25
28
  UserInfo,
29
+ ViewPropertyValues,
26
30
  } from '@memberjunction/core';
27
- import { MJAuditLogEntity, MJErrorLogEntity, MJUserViewEntityExtended } from '@memberjunction/core-entities';
31
+ import { MJAuditLogEntity, MJErrorLogEntity, MJUserViewEntityExtended, UserViewEngine } from '@memberjunction/core-entities';
32
+
33
+ /**
34
+ * Extended RunViewResult carrying a non-enumerable reference to the resolved view entity
35
+ * so downstream resolvers (e.g. RunViewResolver) do not re-query `MJ: User Views` a third time.
36
+ */
37
+ export type RunViewResultWithResolvedView<T = unknown> = RunViewResult<T> & {
38
+ _resolvedViewEntity?: MJUserViewEntityExtended;
39
+ };
28
40
  import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
29
41
  import { ClientClauseScreen, UserCache, type EntityBaseViewAllowList } from '@memberjunction/generic-database-provider';
30
42
  import { PubSubEngine, AuthorizationError } from 'type-graphql';
@@ -37,11 +49,11 @@ import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
37
49
  import { RunViewGenericParams, UserPayload } from '../types.js';
38
50
  import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver.js';
39
51
  import { DeleteOptionsInput } from './DeleteOptionsInput.js';
40
- import { MJEvent, MJEventType, MJGlobal, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
52
+ import { MJEvent, MJEventType, MJGlobal, UUIDsEqual, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
41
53
  import { SQLParser, IsBalancedSQLFragment, LexSQL } from '@memberjunction/sql-parser';
42
54
  import type { SQLParserDialect } from '@memberjunction/sql-dialect';
43
55
  import { EncryptionEngine, IsEncryptionKeyUnavailable, ReportEncryptionKeyUnavailable } from '@memberjunction/encryption';
44
- import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate } from './PushStatusResolver.js';
56
+ import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate, StatusUpdateOwnerID } from './PushStatusResolver.js';
45
57
  import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData } from './CacheInvalidationResolver.js';
46
58
  import { PubSubManager } from './PubSubManager.js';
47
59
  import { FieldMapper } from '@memberjunction/graphql-dataprovider';
@@ -51,11 +63,18 @@ import { Subscription } from 'rxjs';
51
63
  * Entities whose update is always hydrated from the stored row, never from the client's `OldValues___`, though they
52
64
  * do not track record changes: their server-side write rules compare against the prior state, which a client could
53
65
  * otherwise supply itself (lower-case names). See `ResolverBase.MustLoadTruthFromDatabase`.
66
+ *
67
+ * `mj: conversations` and `mj: ai agent sessions` carry the anonymous realtime-session ownership key
68
+ * (`ExternalID` / `ConversationID`), which their server entities refuse an anonymous visitor to change — a rule
69
+ * that decides on `Dirty`, so the baseline must be the stored row, not a client-supplied one (#4931).
54
70
  */
55
71
  const ENTITIES_THAT_ALWAYS_LOAD_STORED_ROW: ReadonlySet<string> = new Set([
56
72
  'mj: record changes',
57
73
  'mj: conversation details',
58
74
  'mj: data context items',
75
+ 'mj: conversations',
76
+ 'mj: ai agent sessions',
77
+ 'mj: ai agent runs',
59
78
  ]);
60
79
 
61
80
  export class ResolverBase {
@@ -120,7 +139,7 @@ export class ResolverBase {
120
139
  if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
121
140
 
122
141
  const denied = deniedReadFields ?? (
123
- entityInfo.EnableFieldLevelSecurity && contextUser
142
+ entityInfo.FieldLevelSecurityApplies && contextUser
124
143
  ? entityInfo.GetDeniedReadFields(contextUser)
125
144
  : null
126
145
  );
@@ -369,6 +388,7 @@ export class ResolverBase {
369
388
  if (!loaded) {
370
389
  return null;
371
390
  }
391
+ // fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
372
392
  const mapped = await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), contextUser);
373
393
  return mapped as T;
374
394
  }
@@ -559,6 +579,53 @@ export class ResolverBase {
559
579
  }
560
580
  }
561
581
 
582
+ /**
583
+ * Resolves a User View by name, consulting the in-memory {@link UserViewEngine} cache first
584
+ * and falling back to a database `RunView` only on a cache miss.
585
+ */
586
+ protected async resolveUserViewByName(
587
+ viewName: string,
588
+ provider: DatabaseProviderBase,
589
+ contextUser?: UserInfo,
590
+ ): Promise<MJUserViewEntityExtended | null> {
591
+ if (UserViewEngine.Instance.Loaded) {
592
+ const cached = UserViewEngine.Instance.GetViewByName(viewName);
593
+ if (cached) {
594
+ return cached;
595
+ }
596
+ }
597
+ const rv = provider as unknown as IRunViewProvider;
598
+ const result = await rv.RunView<MJUserViewEntityExtended>({
599
+ EntityName: 'MJ: User Views',
600
+ // SECURITY: the view name is client-supplied and lands inside a SQL literal.
601
+ ExtraFilter: "Name='" + EscapeSQLString(viewName) + "'",
602
+ }, contextUser);
603
+ if (result && result.Success && result.Results.length > 0) {
604
+ return result.Results[0];
605
+ }
606
+ return null;
607
+ }
608
+
609
+ /**
610
+ * Resolves a User View by ID, consulting the in-memory {@link UserViewEngine} cache first
611
+ * and falling back to an entity `Load()` only on a cache miss.
612
+ */
613
+ protected async resolveUserViewByID(
614
+ viewID: string,
615
+ provider: DatabaseProviderBase,
616
+ contextUser?: UserInfo,
617
+ ): Promise<MJUserViewEntityExtended | null> {
618
+ if (UserViewEngine.Instance.Loaded) {
619
+ const cached = UserViewEngine.Instance.GetViewById(viewID);
620
+ if (cached) {
621
+ return cached;
622
+ }
623
+ }
624
+ const viewInfo = await provider.GetEntityObject<MJUserViewEntityExtended>('MJ: User Views', contextUser);
625
+ await viewInfo.Load(viewID);
626
+ return viewInfo;
627
+ }
628
+
562
629
  async RunViewByNameGeneric(viewInput: RunViewByNameInput, provider: DatabaseProviderBase, userPayload: UserPayload, pubSub: PubSubEngine) {
563
630
  try {
564
631
  // Log aggregate input for debugging
@@ -566,14 +633,8 @@ export class ResolverBase {
566
633
  LogStatus(`[ResolverBase] RunViewByNameGeneric received aggregates: viewName=${viewInput.ViewName}, aggregateCount=${viewInput.Aggregates.length}, aggregates=${JSON.stringify(viewInput.Aggregates.map(a => ({ expression: a.expression, alias: a.alias })))}`);
567
634
  }
568
635
 
569
- const rv = provider as any as IRunViewProvider;
570
- const result = await rv.RunView<MJUserViewEntityExtended>({
571
- EntityName: 'MJ: User Views',
572
- // SECURITY: the view name is client-supplied and lands inside a SQL literal.
573
- ExtraFilter: "Name='" + EscapeSQLString(viewInput.ViewName) + "'",
574
- }, userPayload.userRecord);
575
- if (result && result.Success && result.Results.length > 0) {
576
- const viewInfo = result.Results[0];
636
+ const viewInfo = await this.resolveUserViewByName(viewInput.ViewName, provider, userPayload.userRecord);
637
+ if (viewInfo) {
577
638
  return this.RunViewGenericInternal(
578
639
  provider,
579
640
  viewInfo,
@@ -598,7 +659,9 @@ export class ResolverBase {
598
659
  : undefined,
599
660
  viewInput.BypassCache,
600
661
  viewInput.DataSource,
601
- viewInput.IncludeBinaryFields
662
+ viewInput.IncludeBinaryFields,
663
+ viewInput.AlternateBaseView,
664
+ viewInput.Properties
602
665
  );
603
666
  }
604
667
  else {
@@ -619,8 +682,10 @@ export class ResolverBase {
619
682
  }
620
683
 
621
684
  const contextUser = this.GetUserFromPayload(userPayload);
622
- const viewInfo = await provider.GetEntityObject<MJUserViewEntityExtended>('MJ: User Views', contextUser);
623
- await viewInfo.Load(viewInput.ViewID);
685
+ const viewInfo = await this.resolveUserViewByID(viewInput.ViewID, provider, contextUser);
686
+ if (!viewInfo) {
687
+ return null;
688
+ }
624
689
  return this.RunViewGenericInternal(
625
690
  provider,
626
691
  viewInfo,
@@ -643,7 +708,9 @@ export class ResolverBase {
643
708
  undefined,
644
709
  viewInput.BypassCache,
645
710
  viewInput.DataSource,
646
- viewInput.IncludeBinaryFields
711
+ viewInput.IncludeBinaryFields,
712
+ viewInput.AlternateBaseView,
713
+ viewInput.Properties
647
714
  );
648
715
  } catch (err) {
649
716
  console.log(err);
@@ -691,7 +758,8 @@ export class ResolverBase {
691
758
  undefined,
692
759
  viewInput.BypassCache,
693
760
  viewInput.DataSource,
694
- viewInput.IncludeBinaryFields
761
+ viewInput.IncludeBinaryFields,
762
+ viewInput.AlternateBaseView
695
763
  );
696
764
  } catch (err) {
697
765
  console.log(err);
@@ -712,15 +780,14 @@ export class ResolverBase {
712
780
  let viewInfo: MJUserViewEntityExtended | null = null;
713
781
 
714
782
  if (viewInput.ViewName) {
715
- viewInfo = this.safeFirstArrayElement(await this.findBy(provider, 'MJ: User Views', { Name: viewInput.ViewName }, userPayload.userRecord));
783
+ viewInfo = await this.resolveUserViewByName(viewInput.ViewName, provider, userPayload.userRecord);
716
784
  // Populate EntityName on the input so callers (e.g. RunViews resolver) can
717
785
  // look up the entity without re-querying the view
718
786
  if (viewInfo && !viewInput.EntityName) {
719
787
  viewInput.EntityName = viewInfo.Entity;
720
788
  }
721
789
  } else if (viewInput.ViewID) {
722
- viewInfo = await provider.GetEntityObject<MJUserViewEntityExtended>('MJ: User Views', contextUser);
723
- await viewInfo.Load(viewInput.ViewID);
790
+ viewInfo = await this.resolveUserViewByID(viewInput.ViewID, provider, contextUser);
724
791
  // Populate EntityName on the input so callers can look up the entity
725
792
  if (viewInfo && !viewInput.EntityName) {
726
793
  viewInput.EntityName = viewInfo.Entity;
@@ -767,6 +834,8 @@ export class ResolverBase {
767
834
  bypassCache: viewInput.BypassCache,
768
835
  dataSource: viewInput.DataSource,
769
836
  includeBinaryFields: viewInput.IncludeBinaryFields,
837
+ alternateBaseView: viewInput.AlternateBaseView,
838
+ properties: viewInput.Properties,
770
839
  });
771
840
  } catch (err) {
772
841
  LogError(err);
@@ -778,6 +847,31 @@ export class ResolverBase {
778
847
  return results;
779
848
  }
780
849
 
850
+ /**
851
+ * What an entity event may carry to a CloudEvents sink or a log line.
852
+ *
853
+ * The whole row for an entity without field security. For one where it applies (its own flag
854
+ * or an IS-A ancestor's), and for `MJ: Record Changes` (whose payload columns hold other
855
+ * entities' values), only the primary key and the names of the fields that changed: the sink
856
+ * is outside MemberJunction with no reader to project the row for, and these events fire for
857
+ * server-internal saves too, whose objects hold every column.
858
+ */
859
+ protected EntityEventPayload(baseEntity: BaseEntity, changedFieldNames?: string[]): Record<string, unknown> {
860
+ const entityInfo = baseEntity.EntityInfo;
861
+ if (!entityInfo?.FieldLevelSecurityApplies && !RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo)) {
862
+ // fls-guard: entity without field security and not the audit trail; whole row is the documented payload
863
+ return baseEntity.GetAll();
864
+ }
865
+ return {
866
+ // Read through Get() rather than PrimaryKey, which builds every field object of a raw-mode record.
867
+ PrimaryKey: (entityInfo.PrimaryKeys ?? []).map((pk) => ({ FieldName: pk.Name, Value: baseEntity.Get(pk.Name) })),
868
+ // A completed save has already rebased the object, so nothing on it is dirty: the event
869
+ // carries the names the save wrote. Other events (save_started) still read them off the object.
870
+ // GetDirtyFieldNames answers without building field objects for a record still in raw mode.
871
+ ChangedFields: changedFieldNames ?? baseEntity.GetDirtyFieldNames(),
872
+ };
873
+ }
874
+
781
875
  private static _priorEmittedData: {Entity: string, PKey: CompositeKey}[] = [];
782
876
  protected async EmitCloudEvent({ component, event, eventCode, args }: MJEvent) {
783
877
  if (ResolverBase._emit && event === MJEventType.ComponentEvent && eventCode === BaseEntity.BaseEventCode) {
@@ -785,7 +879,7 @@ export class ResolverBase {
785
879
  const type = `MemberJunction.${event}${extendedType}`;
786
880
  const source = `${process.env.CLOUDEVENTS_SOURCE ?? 'MemberJunction'}`;
787
881
  const subject = args instanceof BaseEntityEvent ? args.baseEntity.EntityInfo.CodeName : undefined;
788
- const data = args?.baseEntity?.GetAll() ?? {};
882
+ const data = args?.baseEntity ? this.EntityEventPayload(args.baseEntity, args.ChangedFieldNames) : {};
789
883
 
790
884
  const cloudEvent = new CloudEvent({ type, source, subject, data });
791
885
 
@@ -1145,7 +1239,9 @@ export class ResolverBase {
1145
1239
  afterKey?: CompositeKey,
1146
1240
  bypassCache?: boolean,
1147
1241
  dataSource?: 'Live' | 'Materialized',
1148
- includeBinaryFields?: boolean
1242
+ includeBinaryFields?: boolean,
1243
+ alternateBaseView?: string,
1244
+ properties?: ViewPropertyValues
1149
1245
  ) {
1150
1246
  try {
1151
1247
  if (!viewInfo || !userPayload) return null;
@@ -1207,6 +1303,7 @@ export class ResolverBase {
1207
1303
  {
1208
1304
  ViewID: viewInfo.ID,
1209
1305
  ViewName: viewInfo.Name,
1306
+ ViewEntity: viewInfo.ID ? viewInfo : undefined,
1210
1307
  EntityName: viewInfo.Entity,
1211
1308
  ExtraFilter: extraFilter,
1212
1309
  OrderBy: orderBy,
@@ -1227,10 +1324,22 @@ export class ResolverBase {
1227
1324
  BypassCache: bypassCache,
1228
1325
  DataSource: dataSource,
1229
1326
  IncludeBinaryFields: includeBinaryFields,
1327
+ AlternateBaseView: alternateBaseView,
1328
+ Properties: properties,
1329
+ TimeZone: userPayload?.TimeZone,
1230
1330
  },
1231
1331
  user
1232
1332
  );
1233
1333
 
1334
+ if (result && typeof result === 'object') {
1335
+ Object.defineProperty(result, '_resolvedViewEntity', {
1336
+ value: viewInfo,
1337
+ enumerable: false,
1338
+ configurable: true,
1339
+ writable: true,
1340
+ });
1341
+ }
1342
+
1234
1343
  // Log aggregate results for debugging
1235
1344
  if (aggregates?.length) {
1236
1345
  LogStatus(`[ResolverBase] RunView result aggregate info: entityName=${viewInfo.Entity}, hasAggregateResults=${!!result?.AggregateResults}, aggregateResultCount=${result?.AggregateResults?.length || 0}, aggregateExecutionTime=${result?.AggregateExecutionTime}, aggregateResults=${JSON.stringify(result?.AggregateResults)}`);
@@ -1261,7 +1370,8 @@ export class ResolverBase {
1261
1370
  await this.ArrayFilterEncryptedFieldsForAPI(
1262
1371
  viewInfo.Entity,
1263
1372
  result.Results as Record<string, unknown>[],
1264
- user
1373
+ user,
1374
+ md
1265
1375
  );
1266
1376
  }
1267
1377
 
@@ -1357,6 +1467,7 @@ export class ResolverBase {
1357
1467
  runViewParams.push({
1358
1468
  ViewID: param.viewInfo.ID,
1359
1469
  ViewName: param.viewInfo.Name,
1470
+ ViewEntity: param.viewInfo.ID ? param.viewInfo : undefined,
1360
1471
  EntityName: param.viewInfo.Entity,
1361
1472
  ExtraFilter: param.extraFilter,
1362
1473
  OrderBy: param.orderBy,
@@ -1377,6 +1488,9 @@ export class ResolverBase {
1377
1488
  BypassCache: param.bypassCache,
1378
1489
  DataSource: param.dataSource,
1379
1490
  IncludeBinaryFields: param.includeBinaryFields,
1491
+ AlternateBaseView: param.alternateBaseView,
1492
+ Properties: param.properties,
1493
+ TimeZone: param.userPayload?.TimeZone,
1380
1494
  });
1381
1495
  }
1382
1496
 
@@ -1398,7 +1512,8 @@ export class ResolverBase {
1398
1512
  await this.ArrayFilterEncryptedFieldsForAPI(
1399
1513
  entityName,
1400
1514
  runViewResult.Results as Record<string, unknown>[],
1401
- contextUser
1515
+ contextUser,
1516
+ md
1402
1517
  );
1403
1518
  }
1404
1519
  }
@@ -1537,8 +1652,9 @@ export class ResolverBase {
1537
1652
  * skip redundant re-fetches (it already handled the event locally).
1538
1653
  *
1539
1654
  * The row itself rides along ONLY for entities opted in via
1540
- * `cacheSettings.recordDataBroadcastEntities` — this event reaches every connected client
1541
- * unfiltered, so the row would otherwise be readable by sessions that could not read the record.
1655
+ * `cacheSettings.recordDataBroadcastEntities`, and never for one under field-level security —
1656
+ * this event reaches every connected client unprojected, so the row would otherwise be readable
1657
+ * by sessions that could not read the record or its restricted fields.
1542
1658
  */
1543
1659
  protected PublishCacheInvalidation(entityObject: BaseEntity, action: 'save' | 'delete', userPayload: UserPayload): void {
1544
1660
  const entityName = entityObject.EntityInfo.Name;
@@ -1550,7 +1666,8 @@ export class ResolverBase {
1550
1666
  timestamp: new Date(),
1551
1667
  originSessionId: userPayload?.sessionId || null,
1552
1668
  recordData:
1553
- action === 'save' && MayBroadcastRecordData(entityName)
1669
+ action === 'save' && MayBroadcastRecordData(entityName, entityObject.EntityInfo)
1670
+ // fls-guard: MayBroadcastRecordData refuses FLS entities and Record Changes
1554
1671
  ? JSON.stringify(entityObject.GetAll())
1555
1672
  : undefined,
1556
1673
  });
@@ -1558,16 +1675,17 @@ export class ResolverBase {
1558
1675
 
1559
1676
  /**
1560
1677
  * Publishes a push-status update to the client on {@link PUSH_STATUS_UPDATES_TOPIC}, stamping the
1561
- * authenticated owner's user ID from `userPayload` so the subscription filter can bind delivery
1562
- * to identity (see B49 / `statusUpdatesFilter`). The ergonomic wrapper every resolver should use
1563
- * instead of calling `pubSub.publish` on the topic directly — it makes omitting identity
1564
- * impossible. Non-resolver publishers (services, the liveness heartbeat) call the shared
1565
- * `publishStatusUpdate()` function directly with an explicit `ownerUserId`.
1678
+ * owner key of `userPayload` (`StatusUpdateOwnerID`: the request's user, or the widget guest an
1679
+ * elevated payload stands in for) so the subscription filter can bind delivery to identity (see B49 /
1680
+ * `StatusUpdatesFilter`). The ergonomic wrapper every resolver should use instead of calling
1681
+ * `pubSub.publish` on the topic directly — it makes omitting identity impossible. Non-resolver
1682
+ * publishers (services, the liveness heartbeat) call the shared `PublishStatusUpdate()` function
1683
+ * directly with an explicit `ownerUserId`.
1566
1684
  */
1567
1685
  protected PublishStatusUpdate(pubSub: PubSubEngine, sessionId: string, message: string | undefined, userPayload: UserPayload): void {
1568
1686
  PublishStatusUpdate(pubSub, {
1569
1687
  sessionId,
1570
- ownerUserId: userPayload?.userRecord?.ID ?? '',
1688
+ ownerUserId: StatusUpdateOwnerID(userPayload),
1571
1689
  message,
1572
1690
  });
1573
1691
  }
@@ -1591,23 +1709,16 @@ export class ResolverBase {
1591
1709
  return;
1592
1710
  }
1593
1711
 
1594
- const baseEntityValues = baseEntity ? baseEntity.GetAll() : null;
1712
+ const baseEntityValues = baseEntity ? this.EntityEventPayload(baseEntity, event.args instanceof BaseEntityEvent ? event.args.ChangedFieldNames : undefined) : null;
1595
1713
  const eventToLog = { entityName: entityObject.EntityInfo.Name, baseEntity: baseEntityValues, event: event.event, eventCode: event.eventCode };
1596
1714
  LogDebug(`ResolverBase.ListenForEntityMessages: Received Event from within MJGlobal.Instance.GetEventListener() callback. Will call EmitCloudEvent() next\nEvent data:\n${JSON.stringify(eventToLog)}`);
1597
1715
  await this.EmitCloudEvent(event);
1598
1716
  LogDebug(`ResolverBase.ListenForEntityMessages: EmitCloudEvent() completed successfully`);
1599
1717
 
1600
- if (event.args && event.args instanceof BaseEntityEvent) {
1601
- const baseEntityEvent = event.args as BaseEntityEvent;
1602
- // message from our entity object, relay it to the client
1718
+ if (event.args && event.args instanceof BaseEntityEvent && this.EntityEventBelongsToSession(event.args, userPayload)) {
1719
+ // message from an entity object of this session's user, relay it to the client
1603
1720
  LogDebug('ResolverBase.ListenForEntityMessages: About to publish PUSH_STATUS_UPDATES_TOPIC');
1604
- this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
1605
- status: 'OK',
1606
- type: 'EntityObjectStatusMessage',
1607
- entityName: baseEntityEvent.baseEntity.EntityInfo.Name,
1608
- primaryKey: baseEntityEvent.baseEntity.PrimaryKey,
1609
- message: event.args.payload,
1610
- }), userPayload);
1721
+ this.PublishStatusUpdate(pubSub, userPayload.sessionId, this.EntityStatusMessage(event.args), userPayload);
1611
1722
  }
1612
1723
  }
1613
1724
  });
@@ -1615,6 +1726,37 @@ export class ResolverBase {
1615
1726
  }
1616
1727
  }
1617
1728
 
1729
+ /**
1730
+ * Whether an entity event may be relayed to the session whose call created the listener. The
1731
+ * listener is shared by every caller in the process for that entity, so an event raised by an
1732
+ * object loaded for another user (or for the system user) is never relayed to this session.
1733
+ */
1734
+ protected EntityEventBelongsToSession(event: BaseEntityEvent, userPayload: UserPayload): boolean {
1735
+ const sessionUserId = userPayload?.userRecord?.ID;
1736
+ const objectUserId = event.baseEntity?.ContextCurrentUser?.ID;
1737
+ return Boolean(sessionUserId && objectUserId && UUIDsEqual(sessionUserId, objectUserId));
1738
+ }
1739
+
1740
+ /**
1741
+ * The status message relayed for an entity event: the entity, key and event type. The event's
1742
+ * payload is included only when it cannot carry record values: never for a delete (its payload
1743
+ * is the whole deleted row), an entity under field-level security, or `MJ: Record Changes`.
1744
+ */
1745
+ protected EntityStatusMessage(event: BaseEntityEvent): string {
1746
+ const entityInfo = event.baseEntity.EntityInfo;
1747
+ const mayCarryPayload = event.type !== 'delete'
1748
+ && !entityInfo.FieldLevelSecurityApplies
1749
+ && !RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo);
1750
+ return JSON.stringify({
1751
+ status: 'OK',
1752
+ type: 'EntityObjectStatusMessage',
1753
+ entityName: entityInfo.Name,
1754
+ primaryKey: event.baseEntity.PrimaryKey,
1755
+ eventType: event.type,
1756
+ message: mayCarryPayload ? event.payload : undefined,
1757
+ });
1758
+ }
1759
+
1618
1760
  /**
1619
1761
  * IS-A PROMOTION over the wire: an EXISTING parent record gaining a subtype — an Animal that is
1620
1762
  * now also a Dog. It arrives as the CHILD's create mutation carrying the existing parent's
@@ -1691,6 +1833,9 @@ export class ResolverBase {
1691
1833
  // IS-A promotion: bind the new child to its EXISTING parent row BEFORE the field
1692
1834
  // assignments, so values the client sent for parent fields land on the loaded parent
1693
1835
  // (as an update) instead of being wiped by the load.
1836
+ // Field-level security: refuse a key the caller may not read, or that names no field, before
1837
+ // anything is applied (the same gate as the update path).
1838
+ AssertClientInputWritableForUser(entityObject.EntityInfo, fieldsForSet, this.GetUserFromPayload(userPayload));
1694
1839
  await this.attachToExistingParentIfPromotion(entityObject, fieldsForSet);
1695
1840
  entityObject.SetMany(fieldsForSet);
1696
1841
 
@@ -1707,6 +1852,7 @@ export class ResolverBase {
1707
1852
  // Cache invalidation is now handled globally by the MJGlobal listener in index.ts
1708
1853
  const contextUser = this.GetUserFromPayload(userPayload);
1709
1854
  // MapFieldNamesToCodeNames now handles encryption filtering as well
1855
+ // fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
1710
1856
  return await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), contextUser);
1711
1857
  }
1712
1858
  // save failed, throw error with message
@@ -1747,12 +1893,11 @@ export class ResolverBase {
1747
1893
  });
1748
1894
 
1749
1895
  // Field-level security: any value the client sends for a field it cannot READ is
1750
- // fabricated by construction (the field was stripped from every payload the client ever
1751
- // received), so strip such values before anything applies them. When any field is
1752
- // denied we also force the truth-load branch below — client OldValues for denied fields
1753
- // are equally fabricated, so hydrating from them (the no-DB-load path) would write
1754
- // fabricated state into the denied columns on save.
1755
- const hasDeniedReadFields = this.StripDeniedReadFieldsFromClientInput(entityInfo, userInfo, input, clientNewValues);
1896
+ // fabricated by construction (the field was absent from every payload the client ever
1897
+ // received), so the input is refused before anything applies it. When any field is
1898
+ // denied we also force the truth-load branch below, so the denied columns keep what the
1899
+ // database holds rather than anything derived from the client's OldValues.
1900
+ const hasDeniedReadFields = this.AssertClientInputFieldSecurity(entityInfo, userInfo, input, clientNewValues);
1756
1901
  // The audit-trail equivalent. Its `true` matters most on THIS entity: 'MJ: Record Changes'
1757
1902
  // has TrackRecordChanges off, so without it a client sending OldValues takes the branch below
1758
1903
  // that hydrates from those values — never loading what the database actually holds.
@@ -1815,6 +1960,7 @@ export class ResolverBase {
1815
1960
  // Cache invalidation is now handled globally by the MJGlobal listener in index.ts
1816
1961
 
1817
1962
  // MapFieldNamesToCodeNames now handles encryption filtering as well
1963
+ // fls-guard: MapFieldNamesToCodeNames drops the fields the caller may not read
1818
1964
  return await this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll(), userInfo);
1819
1965
  } else {
1820
1966
  // CompleteMessage, not Message. A validation refusal puts its reasons in `Errors` and leaves
@@ -1871,7 +2017,7 @@ export class ResolverBase {
1871
2017
  * writable fields in `OldValues___`, so {@link hasCompleteOldValues} returns `false` and forces the
1872
2018
  * database load (`InnerLoad`) so untouched columns retain their stored values.
1873
2019
  *
1874
- * `EnableFieldLevelSecurity` is the security-critical one, and it is NOT redundant with
2020
+ * `FieldLevelSecurityApplies` (own flag or an IS-A ancestor's) is the security-critical one, and it is NOT redundant with
1875
2021
  * `hasDeniedReadFields`. That flag reports READ denials, and the canonical FLS configuration is
1876
2022
  * Read Allow + Update Deny — which leaves it false. Such a caller would be hydrated from its own
1877
2023
  * `OldValues___`, and a value it supplies there for an update-denied field arrives through
@@ -1926,73 +2072,50 @@ export class ResolverBase {
1926
2072
  !this.hasCompleteOldValues(entityInfo, input.OldValues___) ||
1927
2073
  hasDeniedReadFields ||
1928
2074
  hasNarrowedAuditPayload ||
1929
- entityInfo.EnableFieldLevelSecurity ||
2075
+ entityInfo.FieldLevelSecurityApplies ||
1930
2076
  !user ||
1931
2077
  entityInfo.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Update, '').length > 0
1932
2078
  );
1933
2079
  }
1934
2080
 
1935
2081
  /**
1936
- * Field-level security guard for the update path.
2082
+ * Field-level security gate on the update path's client input.
1937
2083
  *
1938
- * Every denied-read field is stripped — new values AND OldValues. A field the user cannot
1939
- * read was absent from every payload that client ever received, so any value coming back for
1940
- * it is the transport's invention rather than user intent, and applying it would silently
1941
- * overwrite the real column. Stripping is what makes "load a record, edit an unrelated field,
1942
- * save" safe for a restricted user.
2084
+ * For a caller denied at least one field on the entity, refuses (with a `FieldSecurityError`)
2085
+ * any key in the new values or in `OldValues___` that names a field the caller cannot READ, or
2086
+ * that names no field at all, with the same message for both (see
2087
+ * `AssertClientInputWritableForUser`), so the refusal does not reveal which columns exist.
1943
2088
  *
1944
- * There is no split by update permission: Read is required for Update, so a user denied read
1945
- * is denied update too and denied-read ∩ denied-update is just denied-read.
2089
+ * Refused rather than dropped: a field the caller cannot read was absent from every payload the
2090
+ * client received, so a value for it can only be fabricated. A well-behaved client never sends
2091
+ * one (it omits fields it did not load), so ordinary traffic is unaffected.
1946
2092
  *
1947
- * Silent narrowing, not rejection — consistent with the output projection the client already
1948
- * experiences, and with the ambiguous-error rule (naming the field would confirm it exists
1949
- * and is restricted).
2093
+ * There is no split by update permission: Read is required for Update, so a user denied read is
2094
+ * denied update too.
1950
2095
  *
1951
- * Returns true when the user has a non-empty denied-read set on this entity. The caller
1952
- * must then hydrate the entity from the DATABASE (never from client OldValues), so denied
1953
- * fields hold true values that an omitted key leaves untouched.
2096
+ * Returns true when the user has a non-empty denied-read set on this entity. The caller must
2097
+ * then hydrate the entity from the DATABASE (never from client OldValues), so denied fields hold
2098
+ * true values the client input never touches.
1954
2099
  */
1955
- protected StripDeniedReadFieldsFromClientInput(
2100
+ protected AssertClientInputFieldSecurity(
1956
2101
  entityInfo: EntityInfo,
1957
2102
  userInfo: UserInfo,
1958
2103
  input: { OldValues___?: Array<{ Key: string; Value: unknown }> } & Record<string, unknown>,
1959
2104
  clientNewValues: Record<string, unknown>
1960
2105
  ): boolean {
1961
- if (!entityInfo.EnableFieldLevelSecurity || !userInfo) {
2106
+ if (!entityInfo.FieldLevelSecurityApplies || !userInfo) {
1962
2107
  return false;
1963
2108
  }
1964
- const deniedReadNames = entityInfo.GetDeniedReadFields(userInfo);
1965
- if (deniedReadNames.size === 0) {
2109
+ if (entityInfo.GetDeniedReadFields(userInfo).size === 0) {
1966
2110
  return false;
1967
2111
  }
1968
-
1969
- // Input keys are entity CodeNames (post ReverseMapInputFieldNames); the denied set holds
1970
- // lowercased field Names — bridge via the field metadata once.
1971
- const deniedReadCodeNames = new Set<string>();
1972
- for (const field of entityInfo.Fields) {
1973
- if (deniedReadNames.has(field.Name.trim().toLowerCase())) {
1974
- deniedReadCodeNames.add(field.CodeName.trim().toLowerCase());
1975
- }
1976
- }
1977
-
1978
- const stripped: string[] = [];
1979
- for (const key of Object.keys(clientNewValues)) {
1980
- if (deniedReadCodeNames.has(key.trim().toLowerCase())) {
1981
- delete clientNewValues[key];
1982
- delete input[key];
1983
- stripped.push(key);
1984
- }
1985
- }
2112
+ AssertClientInputWritableForUser(entityInfo, clientNewValues, userInfo);
1986
2113
  if (Array.isArray(input.OldValues___)) {
1987
- input.OldValues___ = input.OldValues___.filter(
1988
- (item) => !deniedReadCodeNames.has(String(item.Key).trim().toLowerCase())
1989
- );
1990
- }
1991
- if (stripped.length > 0) {
1992
- LogDebug(
1993
- `[FieldSecurity] UpdateRecord on '${entityInfo.Name}' for user ${userInfo.Email}: ` +
1994
- `stripped client-sent value(s) for denied-read field(s) ${stripped.join(', ')}`
1995
- );
2114
+ const oldValueKeys: Record<string, unknown> = {};
2115
+ for (const item of input.OldValues___) {
2116
+ oldValueKeys[String(item.Key)] = null;
2117
+ }
2118
+ AssertClientInputWritableForUser(entityInfo, oldValueKeys, userInfo);
1996
2119
  }
1997
2120
  return true;
1998
2121
  }
@@ -2133,6 +2256,7 @@ export class ResolverBase {
2133
2256
 
2134
2257
  // clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
2135
2258
  // with the DB values
2259
+ // fls-guard: compared on the server only, never returned
2136
2260
  const dbValues = entityObject.GetAll();
2137
2261
 
2138
2262
  // now we need to compare clientOldValues and dbValues and have a new array that has entries for any differences and have FieldName, clientOldValue and dbValue as properties
@@ -2217,16 +2341,16 @@ export class ResolverBase {
2217
2341
 
2218
2342
  // Log as warning to console and ErrorLog table instead of throwing error
2219
2343
  console.warn('Entity save inconsistency detected but allowing save to continue:', JSON.stringify(msg));
2220
- LogError({
2221
- service: 'ResolverBase',
2222
- operation: 'TestAndSetClientOldValuesToDBValues',
2223
- error: `Entity save inconsistency detected: ${JSON.stringify(msg)}`,
2224
- details: {
2344
+ LogErrorEx({
2345
+ message: `Entity save inconsistency detected: ${JSON.stringify(msg)}`,
2346
+ severity: 'warning',
2347
+ category: 'ResolverBase.TestAndSetClientOldValuesToDBValues',
2348
+ metadata: {
2225
2349
  entityName: entityObject.EntityInfo.Name,
2226
2350
  clientDifferences: clientDifferences,
2227
2351
  dbDifferences: dbDifferences,
2228
- overlap: overlap
2229
- }
2352
+ overlap: overlap,
2353
+ },
2230
2354
  });
2231
2355
 
2232
2356
  // Create ErrorLog record in the database — use the entity's bound provider so the
@@ -2234,7 +2358,8 @@ export class ResolverBase {
2234
2358
  try {
2235
2359
  const md = entityObject.ProviderToUse as unknown as IMetadataProvider;
2236
2360
  const errorLogEntity = await md.GetEntityObject<MJErrorLogEntity>('MJ: Error Logs', contextUser);
2237
- errorLogEntity.Code = 'ENTITY_SAVE_INCONSISTENCY';
2361
+ // Code is NCHAR(20); a longer value is rejected at the database
2362
+ errorLogEntity.Code = 'SAVE_INCONSISTENCY';
2238
2363
  errorLogEntity.Message = `Entity save inconsistency detected for ${entityObject.EntityInfo.Name}: ${JSON.stringify(msg)}`;
2239
2364
  errorLogEntity.Status = 'Warning';
2240
2365
  errorLogEntity.Category = 'Entity Save';
@@ -2242,7 +2367,9 @@ export class ResolverBase {
2242
2367
 
2243
2368
  const saveResult = await errorLogEntity.Save();
2244
2369
  if (!saveResult) {
2245
- console.error('Failed to save ErrorLog record');
2370
+ console.error(
2371
+ `Failed to save ErrorLog record for ${entityObject.EntityInfo.Name}: ${errorLogEntity.LatestResult?.CompleteMessage}`
2372
+ );
2246
2373
  }
2247
2374
  } catch (errorLogError) {
2248
2375
  console.error('Error creating ErrorLog record:', errorLogError);
@@ -2281,7 +2408,10 @@ export class ResolverBase {
2281
2408
  extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
2282
2409
  });
2283
2410
  }
2284
- const returnValue = entityObject.GetAll(); // grab the values before we delete so we can return last state before delete if we are successful.
2411
+ // Grab the values before we delete so we can return the last state if the delete succeeds,
2412
+ // projected for the caller in case the object holds a column they may not read.
2413
+ // fls-guard: projected by ProjectEntityValuesForUser
2414
+ const returnValue = ProjectEntityValuesForUser(provider, entityObject.EntityInfo, entityObject.GetAll(), this.GetUserFromPayload(userPayload));
2285
2415
 
2286
2416
  this.ListenForEntityMessages(entityObject, pubSub, userPayload);
2287
2417