@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
@@ -151,6 +151,7 @@ export class TestQuerySQLResolver extends ResolverBase {
151
151
 
152
152
  return {
153
153
  Success: result.Success,
154
+ // fls-guard: query rows; the SQL is refused before it runs if it reads a field the caller is denied
154
155
  Results: result.Results ? JSON.stringify(result.Results) : undefined,
155
156
  RowCount: result.RowCount,
156
157
  ExecutionTime: result.ExecutionTime,
@@ -1,9 +1,10 @@
1
1
  import { Arg, Ctx, Field, InputType, Int, Mutation, ObjectType, registerEnumType } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { CompositeKey, EntityInfo, IMetadataProvider, KeyValuePair, LogError, Metadata, TransactionVariable, TransactionGroupBase, BaseEntity, EntityDeleteOptions, EntitySaveOptions } from '@memberjunction/core';
3
+ import { AssertClientInputWritableForUser, CompositeKey, EntityInfo, IMetadataProvider, KeyValuePair, LogError, Metadata, RecordChangeMetadataSource, TransactionVariable, TransactionGroupBase, BaseEntity, UserInfo } from '@memberjunction/core';
4
4
  import { SafeJSONParse, ValuesEqual } from '@memberjunction/global';
5
5
  import { GetReadWriteProvider } from '../util.js';
6
6
  import { ResolverBase } from '../generic/ResolverBase.js';
7
+ import { ProjectValuesForCaller } from '../generic/fieldSecurityTransport.js';
7
8
 
8
9
  export enum TransactionVariableType {
9
10
  Define = "Define",
@@ -148,16 +149,18 @@ export class TransactionResolver extends ResolverBase {
148
149
  // BEFORE any Save()/Delete() runs below: a 'Use' variable sets its field at Submit(), after
149
150
  // every server-side write check has passed, so an entity whose check depends on a field's
150
151
  // final value must be able to see that a variable will set it.
152
+ const user: UserInfo = context.userPayload.userRecord;
151
153
  for (const item of group.Items) {
152
- entityObjects.push(await md.GetEntityObject(item.EntityName, context.userPayload.userRecord));
154
+ entityObjects.push(await md.GetEntityObject(item.EntityName, user));
153
155
  }
156
+ this.AssertNetworkInputWritable(group, entityObjects, user);
154
157
  this.AddNetworkVariables(group, entityObjects, tg);
155
158
 
156
159
  for (const [index, item] of group.Items.entries()) {
157
160
  const entity = entityObjects[index];
158
161
 
159
162
  // get the values from the payload
160
- const itemValues = SafeJSONParse(item.EntityObjectJSON);
163
+ const itemValues = SafeJSONParse<Record<string, unknown>>(item.EntityObjectJSON) ?? {};
161
164
  // build a primary key for the item
162
165
  const pkey = new CompositeKey(entity.PrimaryKeys.map(pk => {
163
166
  const kv = new KeyValuePair();
@@ -171,8 +174,10 @@ export class TransactionResolver extends ResolverBase {
171
174
  if (item.OperationType === "Update") {
172
175
  await entity.InnerLoad(pkey);
173
176
  if (typeof entity.GetDataObject === 'function') {
177
+ // fls-guard: pre-update snapshot; projected before the delta is built
174
178
  preUpdateValues.set(index, await entity.GetDataObject());
175
179
  } else if (typeof entity.GetAll === 'function') {
180
+ // fls-guard: pre-update snapshot; projected before the delta is built
176
181
  preUpdateValues.set(index, { ...(entity.GetAll() as Record<string, unknown>) });
177
182
  }
178
183
  }
@@ -197,6 +202,7 @@ export class TransactionResolver extends ResolverBase {
197
202
  // Awaiting costs nothing here — DataObjectParams defaults
198
203
  // includeRelatedEntityData to false, which guards the method's only awaited
199
204
  // work, so with no params this is GetAll() plus a filter loop.
205
+ // fls-guard: projected in PrepareReturnValue
200
206
  objectValues.push(await entity.GetDataObject());
201
207
  entity.TransactionGroup = tg;
202
208
  if (!await entity.Delete()) {
@@ -221,17 +227,17 @@ export class TransactionResolver extends ResolverBase {
221
227
  `TransactionResolver::ExecuteTransactionGroup --- ${refusals.length} of ${group.Items.length} ` +
222
228
  `item(s) were refused, so the group was not submitted:\n${refusals.join('\n')}`
223
229
  );
224
- return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
230
+ return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues, md, user);
225
231
  }
226
232
 
227
233
  // after all that, we are ready to roll, so let's run the TG
228
234
  if (await tg.Submit()) {
229
235
  // success!
230
- return await this.PrepareReturnValue(true, entityObjects, objectValues, group, preUpdateValues);
236
+ return await this.PrepareReturnValue(true, entityObjects, objectValues, group, preUpdateValues, md, user);
231
237
  }
232
238
  else {
233
239
  // failure, send back the results
234
- return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
240
+ return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues, md, user);
235
241
  }
236
242
  }
237
243
  catch (err) {
@@ -240,6 +246,32 @@ export class TransactionResolver extends ResolverBase {
240
246
  }
241
247
  }
242
248
 
249
+ /**
250
+ * Field-level security gate on everything the client sent, run before any item is applied.
251
+ *
252
+ * Each create or update payload must name only fields the caller may read, and so must every transaction
253
+ * variable: a `Define` on a denied field would copy its value into another item's readable
254
+ * field at `Submit()`, and a `Use` would write it. A key that names no field is refused with
255
+ * the same message as a denied one (see `AssertClientInputWritableForUser`). Callers denied
256
+ * nothing on an entity are not affected. A delete payload is not gated: only its primary key
257
+ * is read, and the record is loaded from the database.
258
+ */
259
+ protected AssertNetworkInputWritable(group: TransactionInputType, entityObjects: BaseEntity[], user: UserInfo): void {
260
+ for (const [index, item] of group.Items.entries()) {
261
+ if (item.OperationType === TransactionOperationType.Delete) {
262
+ continue;
263
+ }
264
+ const values = SafeJSONParse<Record<string, unknown>>(item.EntityObjectJSON);
265
+ AssertClientInputWritableForUser(entityObjects[index]?.EntityInfo, values, user);
266
+ }
267
+ for (const networkVar of group.Variables ?? []) {
268
+ const target = entityObjects[networkVar.ItemIndex];
269
+ if (target && networkVar.FieldName) {
270
+ AssertClientInputWritableForUser(target.EntityInfo, { [networkVar.FieldName]: null }, user);
271
+ }
272
+ }
273
+ }
274
+
243
275
  /**
244
276
  * Adds the network payload's variables to the group, mapping each one's item index to the entity
245
277
  * object built for that item.
@@ -261,13 +293,18 @@ export class TransactionResolver extends ResolverBase {
261
293
  entityObjects: BaseEntity[],
262
294
  objectValues: unknown[],
263
295
  group: TransactionInputType,
264
- preUpdateValues?: Map<number, Record<string, unknown>>
296
+ preUpdateValues?: Map<number, Record<string, unknown>>,
297
+ provider?: RecordChangeMetadataSource,
298
+ user?: UserInfo
265
299
  ): Promise<TransactionOutputType> {
266
300
  const jsonResults = [];
267
301
  for (let i = 0; i < group.Items.length; i++) {
268
302
  const item = group.Items[i];
303
+ const project = (values: Record<string, unknown>) =>
304
+ ProjectValuesForCaller(entityObjects[i]?.EntityInfo, values, user, provider);
269
305
  if (item.OperationType === 'Delete') {
270
- jsonResults.push(JSON.stringify(objectValues[i]));
306
+ const deleted = objectValues[i];
307
+ jsonResults.push(JSON.stringify(deleted && typeof deleted === 'object' ? project(deleted as Record<string, unknown>) : deleted));
271
308
  }
272
309
  else if (item.OperationType === 'Update') {
273
310
  jsonResults.push(
@@ -276,13 +313,15 @@ export class TransactionResolver extends ResolverBase {
276
313
  objectValues[i],
277
314
  preUpdateValues?.get(i),
278
315
  group,
279
- i
316
+ i,
317
+ project
280
318
  )
281
319
  );
282
320
  }
283
321
  else {
284
- // create, return what is in the database
285
- jsonResults.push(await entityObjects[i].GetDataObjectJSON());
322
+ // create, return what is in the database, as the caller may read it
323
+ // fls-guard: projected by project()
324
+ jsonResults.push(JSON.stringify(project(await entityObjects[i].GetDataObject())));
286
325
  }
287
326
  }
288
327
 
@@ -303,20 +342,32 @@ export class TransactionResolver extends ResolverBase {
303
342
  * - Any field whose post-save value differs from its pre-update DB snapshot (`preSaveValues`),
304
343
  * capturing virtual fields, triggers, or server-side `BeforeSave()` mutations automatically
305
344
  * while omitting unchanged columns.
345
+ *
346
+ * Both rows are projected for the caller BEFORE they are compared. Comparing a full-width
347
+ * post-save row against a snapshot taken from a narrowed load would count every denied value
348
+ * as changed, and the delta would then deliver exactly the values the caller may not read.
306
349
  */
307
350
  private async serializeUpdateReturnValue(
308
351
  entity: BaseEntity,
309
352
  sentValues: unknown,
310
353
  preSaveValues: Record<string, unknown> | undefined,
311
354
  group: TransactionInputType,
312
- itemIndex: number
355
+ itemIndex: number,
356
+ project: (values: Record<string, unknown>) => Record<string, unknown>
313
357
  ): Promise<string> {
314
- const fullDataObj: Record<string, unknown> | null = typeof entity.GetDataObject === 'function'
358
+ const rawDataObj: Record<string, unknown> | null = typeof entity.GetDataObject === 'function'
359
+ // fls-guard: projected by project() below
315
360
  ? (await entity.GetDataObject() as Record<string, unknown>)
361
+ // fls-guard: projected by project() below
316
362
  : SafeJSONParse<Record<string, unknown>>(await entity.GetDataObjectJSON());
317
- if (!fullDataObj || typeof fullDataObj !== 'object' || !sentValues || typeof sentValues !== 'object') {
318
- return await entity.GetDataObjectJSON();
363
+ if (!rawDataObj || typeof rawDataObj !== 'object') {
364
+ return JSON.stringify(rawDataObj);
365
+ }
366
+ const fullDataObj = project(rawDataObj);
367
+ if (!sentValues || typeof sentValues !== 'object') {
368
+ return JSON.stringify(fullDataObj);
319
369
  }
370
+ preSaveValues = preSaveValues ? project(preSaveValues) : preSaveValues;
320
371
 
321
372
  const sentRecord = sentValues as Record<string, unknown>;
322
373
  const includeKeys = new Set<string>([
@@ -5,6 +5,7 @@ import { MJUserView_, MJUserViewResolverBase } from '../generated/generated.js';
5
5
  import { UserResolver } from './UserResolver.js';
6
6
  import { MJUserViewEntity, MJUserViewEntityExtended } from '@memberjunction/core-entities';
7
7
  import { GetReadOnlyProvider, GetReadWriteProvider } from '../util.js';
8
+ import { ProjectEntityObjectForCaller } from '../generic/fieldSecurityTransport.js';
8
9
 
9
10
  @Resolver(MJUserView_)
10
11
  export class UserViewResolver extends MJUserViewResolverBase {
@@ -63,7 +64,7 @@ export class UserViewResolver extends MJUserViewResolverBase {
63
64
  await viewEntity.UpdateWhereClause(true); // force regeneration regardless of dirty state — that is the whole point of this endpoint
64
65
 
65
66
  if (await viewEntity.Save()) {
66
- return viewEntity.GetAll();
67
+ return ProjectEntityObjectForCaller(viewEntity, u, p);
67
68
  } else {
68
69
  throw new Error('Failed to update where clause');
69
70
  }
@@ -90,4 +90,47 @@ describe('RecordDependencyResolver', () => {
90
90
  expect(result[0].IsSoftLink).toBeUndefined();
91
91
  expect(result[0].EntityIDFieldName).toBeUndefined();
92
92
  });
93
+
94
+ it('forwards contextUser and options to provider and maps FieldDisplayName', async () => {
95
+ const mockDep: RecordDependency = {
96
+ EntityName: 'Users',
97
+ RelatedEntityName: 'MJ: Task Links',
98
+ FieldName: 'RecordID',
99
+ FieldDisplayName: 'Linked Record',
100
+ PrimaryKey: new CompositeKey([new KeyValuePair('ID', 'TL-200')]),
101
+ IsSoftLink: true,
102
+ EntityIDFieldName: 'EntityID',
103
+ };
104
+
105
+ const getRecordDepsSpy = vi.fn().mockResolvedValue([mockDep]);
106
+ const mockProvider = {
107
+ GetRecordDependencies: getRecordDepsSpy,
108
+ } as unknown as IMetadataProvider;
109
+
110
+ vi.mocked(GetReadOnlyProvider).mockReturnValue(mockProvider as never);
111
+
112
+ const resolver = new RecordDependencyResolver();
113
+ const mockUser = { ID: 'u-1', Email: 'test@example.com' };
114
+ const mockContext = {
115
+ dataSource: {},
116
+ userPayload: { userRecord: mockUser },
117
+ providers: [],
118
+ } as unknown as AppContext;
119
+ const mockPubSub = {} as PubSubEngine;
120
+ const options = { LinkType: 'soft' as const, IncludeSystemEntities: false };
121
+
122
+ const result = await resolver.GetRecordDependencies(
123
+ 'Users',
124
+ { KeyValuePairs: [{ FieldName: 'ID', Value: 'USER-1' }] },
125
+ mockContext,
126
+ mockPubSub,
127
+ options
128
+ );
129
+
130
+ expect(getRecordDepsSpy).toHaveBeenCalledTimes(1);
131
+ expect(getRecordDepsSpy.mock.calls[0][0]).toBe('Users');
132
+ expect(getRecordDepsSpy.mock.calls[0][2]).toBe(mockUser);
133
+ expect(getRecordDepsSpy.mock.calls[0][3]).toEqual(options);
134
+ expect(result[0].FieldDisplayName).toBe('Linked Record');
135
+ });
93
136
  });
@@ -1,7 +1,8 @@
1
1
  import {
2
- BaseEntity, CompositeKey, EntityDeleteOptions, EntityPermissionType,
2
+ AssertClientInputWritableForUser, BaseEntity, CompositeKey, EntityDeleteOptions, EntityPermissionType,
3
3
  EntitySaveOptions, LogError, Metadata, UserInfo
4
4
  } from '@memberjunction/core';
5
+ import { ProjectValuesForCaller } from '../generic/fieldSecurityTransport.js';
5
6
 
6
7
  /**
7
8
  * Entity CRUD Implementation Functions for REST endpoints
@@ -42,6 +43,9 @@ export class EntityCRUDHandler {
42
43
  delete data.options;
43
44
  }
44
45
 
46
+ // Field-level security: refuse a key the caller may not read, or that names no field
47
+ AssertClientInputWritableForUser(entity.EntityInfo, data, user);
48
+
45
49
  // Set values on the entity
46
50
  for (const key in data) {
47
51
  entity.Set(key, data[key]);
@@ -70,7 +74,7 @@ export class EntityCRUDHandler {
70
74
  }
71
75
 
72
76
  // Get entity data for response
73
- const entityData = await entity.GetDataObject();
77
+ const entityData = await this.responseData(entity, user, md);
74
78
  return { success: true, entity: entityData };
75
79
  } catch (error: any) {
76
80
  LogError(error);
@@ -114,7 +118,7 @@ export class EntityCRUDHandler {
114
118
  }
115
119
 
116
120
  // Get entity data for response
117
- const entityData = await entity.GetDataObject();
121
+ const entityData = await this.responseData(entity, user, md);
118
122
  return { success: true, entity: entityData };
119
123
  } catch (error: any) {
120
124
  LogError(error);
@@ -171,6 +175,9 @@ export class EntityCRUDHandler {
171
175
  delete data.options;
172
176
  }
173
177
 
178
+ // Field-level security: refuse a key the caller may not read, or that names no field
179
+ AssertClientInputWritableForUser(entity.EntityInfo, data, user);
180
+
174
181
  // Update entity with new values
175
182
  for (const key in data) {
176
183
  entity.Set(key, data[key]);
@@ -179,7 +186,7 @@ export class EntityCRUDHandler {
179
186
  // Check if entity is dirty
180
187
  if (!entity.Dirty && !options.IgnoreDirtyState) {
181
188
  // Nothing changed, return success
182
- const entityData = await entity.GetDataObject();
189
+ const entityData = await this.responseData(entity, user, md);
183
190
  return { success: true, entity: entityData };
184
191
  }
185
192
 
@@ -206,7 +213,7 @@ export class EntityCRUDHandler {
206
213
  }
207
214
 
208
215
  // Get entity data for response
209
- const entityData = await entity.GetDataObject();
216
+ const entityData = await this.responseData(entity, user, md);
210
217
  return { success: true, entity: entityData };
211
218
  } catch (error: any) {
212
219
  LogError(error);
@@ -273,6 +280,16 @@ export class EntityCRUDHandler {
273
280
  return this.DeleteEntity(entityName, id, options, user);
274
281
  }
275
282
 
283
+ /**
284
+ * The record's field values for the response, reduced to what `user` may READ. An entity
285
+ * object built for a restricted user holds no denied value already; this is the backstop for
286
+ * one that does.
287
+ */
288
+ private static async responseData(entity: BaseEntity, user: UserInfo, md: Metadata): Promise<Record<string, unknown>> {
289
+ // fls-guard: projected by ProjectValuesForCaller
290
+ return ProjectValuesForCaller(entity.EntityInfo, await entity.GetDataObject() as Record<string, unknown>, user, md);
291
+ }
292
+
276
293
  /**
277
294
  * Build the record key from the `:id` path segment. A single-column key (any column name) is
278
295
  * the bare value; a composite key is the URL-encoded `Field1|Value1||Field2|Value2` segment.
@@ -2,7 +2,7 @@ import express from 'express';
2
2
  import {
3
3
  BaseEntity, CompositeKey, EntityDeleteOptions, EntityInfo,
4
4
  EntityPermissionType, EntitySaveOptions, LogError, Metadata,
5
- RunView, RunViewParams, UserInfo
5
+ RunView, RunViewParams, StripServerOnlyRunViewParams, UserInfo
6
6
  } from '@memberjunction/core';
7
7
  import { EntityCRUDHandler } from './EntityCRUDHandler.js';
8
8
  import { ViewOperationsHandler } from './ViewOperationsHandler.js';
@@ -499,10 +499,11 @@ export class RESTEndpointHandler {
499
499
 
500
500
  const user = req['mjUser'];
501
501
 
502
- // Create RunViewParams from the request body
502
+ // Create RunViewParams from the request body, without the fields only server code sets;
503
+ // the time zone comes from the x-mj-timezone header, as on the GraphQL path
503
504
  const params: RunViewParams = {
504
- EntityName: entityName,
505
- ...viewParams
505
+ ...StripServerOnlyRunViewParams({ ...viewParams, EntityName: entityName }),
506
+ TimeZone: req.userPayload?.TimeZone
506
507
  };
507
508
 
508
509
  const result = await ViewOperationsHandler.runView(params, user);
@@ -533,7 +534,9 @@ export class RESTEndpointHandler {
533
534
 
534
535
  // Filter out any views for entities that aren't allowed
535
536
  // using our enhanced entity filtering with wildcards and schema support
536
- const filteredParams = params.filter(p => this.isEntityAllowed(p.EntityName));
537
+ const filteredParams = params
538
+ .filter(p => this.isEntityAllowed(p.EntityName))
539
+ .map(p => ({ ...StripServerOnlyRunViewParams(p), TimeZone: req.userPayload?.TimeZone }));
537
540
 
538
541
  // If all requested entities were filtered out, return an error
539
542
  if (filteredParams.length === 0 && params.length > 0) {
@@ -34,7 +34,7 @@ import {
34
34
  LoadOwnedRealtimeSession, RealtimeSessionNotFoundError, RealtimeSessionOwnershipError,
35
35
  } from '../realtimeSessions/ownedRealtimeSession.js';
36
36
  import type { IRecordingAssemblyScheduler } from '../realtimeSessions/RecordingAssemblyScheduler.js';
37
- import { AuthorizeSessionAccess, LoadSessionConversationExternalID } from '../realtimeSessions/sessionAccess.js';
37
+ import { AuthorizeSessionAccess, LoadSessionConversationScope } from '../realtimeSessions/sessionAccess.js';
38
38
 
39
39
  /** Where {@link CreateRealtimeRecordingRouter} is mounted (under the reserved `/realtime` root). */
40
40
  export const REALTIME_RECORDING_MOUNT_PATH = '/realtime/recording';
@@ -76,8 +76,8 @@ export interface RealtimeRecordingRouterDeps {
76
76
  /** The authenticated caller, or null (answered 401). */
77
77
  ResolveCaller(req: Request): RealtimeRecordingCaller | null;
78
78
  LoadOwnedSession: typeof LoadOwnedRealtimeSession;
79
- /** The session conversation's per-session scope id, for the anonymous-guest half of the access decision. */
80
- LoadConversationExternalID: typeof LoadSessionConversationExternalID;
79
+ /** The session conversation's scope id and whether the caller could read it: the scope half of the access decision. */
80
+ LoadConversationScope: typeof LoadSessionConversationScope;
81
81
  /** The ledger for one request; `contextUser` is the caller (used for logging by the procs). */
82
82
  CreateLedger(provider: DatabaseProviderBase, contextUser: UserInfo): IRealtimeRecordingLedger;
83
83
  Scheduler: IRecordingAssemblyScheduler;
@@ -258,16 +258,19 @@ async function handleTerminal(deps: RealtimeRecordingRouterDeps, req: Request, r
258
258
 
259
259
  /**
260
260
  * Loads the session through the shared ownership gate, then applies the per-session scope rule of
261
- * {@link AuthorizeSessionAccess}. The gate alone checks `UserID`, which every anonymous widget guest shares
262
- * (they all authenticate as the one Anonymous user); without the scope rule one guest could write shards to
263
- * another guest's recording, and a different-bytes shard would mark it Failed. The owner half of the
264
- * decision repeats the gate's check on purpose: the decision is one rule, applied whole. Named users carry
265
- * no scope and are unaffected.
261
+ * {@link AuthorizeSessionAccess}. The production gate (`LoadOwnedRealtimeSession`) already applies the same
262
+ * rule, so this is a deliberate second check at the HTTP boundary: `LoadOwnedSession` is an injected dependency,
263
+ * and without the scope rule one anonymous guest could write shards to another guest's recording (every guest
264
+ * shares the one Anonymous user), and a different-bytes shard would mark it Failed. It costs one extra
265
+ * conversation read per request. Named users without a scope are unaffected.
266
266
  */
267
267
  async function loadAuthorizedSession(deps: RealtimeRecordingRouterDeps, sessionID: string, caller: RealtimeRecordingCaller): Promise<MJAIAgentSessionEntity> {
268
268
  const session = await deps.LoadOwnedSession(sessionID, caller.ContextUser, caller.Provider);
269
- const externalID = await deps.LoadConversationExternalID(session, caller.ContextUser, caller.Provider);
270
- const decision = AuthorizeSessionAccess({ SessionUserID: session.UserID, ConversationExternalID: externalID }, caller.ContextUser);
269
+ const conversation = await deps.LoadConversationScope(session, caller.ContextUser, caller.Provider);
270
+ const decision = AuthorizeSessionAccess(
271
+ { SessionUserID: session.UserID, ConversationExternalID: conversation.ExternalID, ConversationReadable: conversation.Readable },
272
+ caller.ContextUser,
273
+ );
271
274
  if (decision.Allowed === false) {
272
275
  LogError(`RealtimeRecordingHandler: refused user ${caller.ContextUser.ID} on session ${sessionID}: ${decision.Reason}`);
273
276
  throw new RecordingRequestError(403, 'Not authorized for this realtime session');
@@ -1,8 +1,9 @@
1
1
  import {
2
- BaseEntity, LogError, Metadata, RunView, RunViewParams,
2
+ BaseEntity, EntityInfo, LogError, Metadata, RunView, RunViewParams,
3
3
  RunViewResult, UserInfo, type IRunViewProvider
4
4
  } from '@memberjunction/core';
5
5
  import { ClientClauseScreen } from '@memberjunction/generic-database-provider';
6
+ import { ProjectRowsForCaller } from '../generic/fieldSecurityTransport.js';
6
7
 
7
8
  /**
8
9
  * View Operations Implementation for REST endpoints
@@ -40,7 +41,7 @@ export class ViewOperationsHandler {
40
41
  // Execute the view
41
42
  const result = await runView.RunView(params, user);
42
43
 
43
- return { success: true, result };
44
+ return { success: true, result: this.projectResult(result, entity, user, md) };
44
45
  } catch (error) {
45
46
  LogError(error);
46
47
  return { success: false, error: (error as Error)?.message || 'Unknown error' };
@@ -60,6 +61,7 @@ export class ViewOperationsHandler {
60
61
  // Validate and sanitize each set of parameters against the provider that runs the views
61
62
  const md = new Metadata(); // global-provider-ok: REST endpoint — no per-request provider injection in REST middleware yet
62
63
  const runView = new RunView();
64
+ const entities: EntityInfo[] = [];
63
65
  for (const params of paramsArray) {
64
66
  // Validate entity exists
65
67
  const entity = md.Entities.find(e => e.Name === params.EntityName);
@@ -81,12 +83,14 @@ export class ViewOperationsHandler {
81
83
 
82
84
  // Sanitize parameters
83
85
  this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
86
+ entities.push(entity);
84
87
  }
85
88
 
86
89
  // Execute the views
87
90
  const results = await runView.RunViews(paramsArray, user);
88
91
 
89
- return { success: true, results };
92
+ const projected = results?.map((r, i) => this.projectResult(r, entities[i], user, md));
93
+ return { success: true, results: projected?.some((r, i) => r !== results[i]) ? projected : results };
90
94
  } catch (error) {
91
95
  LogError(error);
92
96
  return { success: false, error: (error as Error)?.message || 'Unknown error' };
@@ -120,7 +124,7 @@ export class ViewOperationsHandler {
120
124
  this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
121
125
 
122
126
  // Execute the view
123
- return await runView.RunView(params, user);
127
+ return this.projectResult(await runView.RunView(params, user), entity, user, md);
124
128
  } catch (error) {
125
129
  LogError(error);
126
130
  throw error;
@@ -193,6 +197,19 @@ export class ViewOperationsHandler {
193
197
  return this.GetEntityViews(entityName, user);
194
198
  }
195
199
 
200
+ /**
201
+ * The result with its rows reduced to what `user` may READ. The provider already narrows the
202
+ * rows it reads for a restricted caller; this is the backstop for any that arrive wider. The
203
+ * same result object comes back when no row needed changing.
204
+ */
205
+ private static projectResult(result: RunViewResult, entity: EntityInfo, user: UserInfo, md: Metadata): RunViewResult {
206
+ if (!result?.Results?.length) {
207
+ return result;
208
+ }
209
+ const rows = ProjectRowsForCaller(entity, result.Results as Record<string, unknown>[], user, md);
210
+ return rows === result.Results ? result : { ...result, Results: rows };
211
+ }
212
+
196
213
  /**
197
214
  * Sanitize and validate RunViewParams. Client filter and sort text passes the same clause
198
215
  * screen as the GraphQL RunView resolvers, parsed in the dialect of `provider`, the provider
@@ -231,8 +248,9 @@ export class ViewOperationsHandler {
231
248
  params.StartRow = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed as number)) : undefined;
232
249
  }
233
250
 
234
- // Default ResultType if not provided
235
- if (!params.ResultType) {
251
+ // Plain rows only on the wire. An entity object serializes its internal state rather than
252
+ // its projected field values, so a caller may not ask for one; a count carries no row.
253
+ if (params.ResultType !== 'count_only') {
236
254
  params.ResultType = 'simple';
237
255
  }
238
256
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Supported MJServer horizontal scaling roles (`MJ_SERVER_ROLE`).
3
+ * - `'all'` (default): Monolithic mode — serves HTTP/GraphQL/WebSocket traffic AND runs all background worker loops.
4
+ * - `'api'`: Stateless API/WebSocket edge — serves HTTP/GraphQL/WebSocket traffic ONLY; skips background schedulers,
5
+ * queue dispatchers, and janitors so event-loop responsiveness is isolated from heavy background jobs.
6
+ * - `'worker'`: Background worker replica — runs `ScheduledJobsService`, `IntegrationSyncWorkerService`,
7
+ * `StartTaskGraphDispatcher`, `StartWorkQueueHost`, `SessionJanitor`, and orphan reconcilers while keeping
8
+ * only lightweight healthcheck/metrics HTTP endpoints active.
9
+ */
10
+ export type ServerRole = 'all' | 'api' | 'worker';
11
+
12
+ const VALID_SERVER_ROLES: ReadonlySet<string> = new Set<ServerRole>(['all', 'api', 'worker']);
13
+
14
+ /**
15
+ * Resolves the server role from `MJ_SERVER_ROLE` or `mj.config.cjs`'s `serverRole`.
16
+ * Defaults to `'all'` when unset or blank. Throws on unrecognized values so deployment typos
17
+ * fail fast at startup instead of silently running the wrong workload profile.
18
+ */
19
+ export function ResolveServerRole(rawValue?: string | null): ServerRole {
20
+ const trimmed = rawValue?.trim().toLowerCase();
21
+ if (!trimmed) {
22
+ return 'all';
23
+ }
24
+ if (VALID_SERVER_ROLES.has(trimmed)) {
25
+ return trimmed as ServerRole;
26
+ }
27
+ throw new Error(
28
+ `Invalid MJ_SERVER_ROLE "${rawValue}". Expected one of: 'all', 'api', 'worker'.`
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Returns `true` when this server instance should start background worker loops
34
+ * (`ScheduledJobsService`, `IntegrationSyncWorkerService`, `StartTaskGraphDispatcher`,
35
+ * `StartWorkQueueHost`, `SessionJanitor`, `StartBridgeOrphanReconciliation`, and
36
+ * `OrphanDetailReconciler`).
37
+ */
38
+ export function ShouldRunBackgroundWorkers(role: ServerRole): boolean {
39
+ return role === 'all' || role === 'worker';
40
+ }
41
+
42
+ /**
43
+ * Returns `true` when this server instance is designated to serve interactive
44
+ * API / GraphQL / WebSocket client traffic.
45
+ */
46
+ export function ShouldServeInteractiveTraffic(role: ServerRole): boolean {
47
+ return role === 'all' || role === 'api';
48
+ }