@memberjunction/server 6.2.0-edge.4 → 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.
- package/README.md +47 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +91 -30
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +311 -81
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +17 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +44 -10
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +27 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +52 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +632 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +104 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +156 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +1016 -29
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +5125 -434
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +85 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +175 -60
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +94 -12
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +168 -69
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +464 -405
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +91 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -222
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/index.d.ts +2 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -1
- package/dist/realtimeSessions/index.js +2 -0
- package/dist/realtimeSessions/index.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +32 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +56 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +91 -8
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +127 -20
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +46 -3
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +211 -50
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +87 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +292 -105
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +49 -9
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +33 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +18 -10
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +92 -54
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +241 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +4 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +66 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +34 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +150 -27
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.js.map +1 -0
- package/dist/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +9 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +43 -17
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +59 -2
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +626 -16
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts.map +1 -1
- package/dist/sharedCache.js +3 -0
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +369 -18
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +243 -7
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +69 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +20 -28
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +264 -0
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +365 -10
- package/src/__tests__/SessionManager.test.ts +122 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +468 -23
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +109 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/localDevAuth.test.ts +196 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +64 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +669 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/sharedCache.test.ts +5 -3
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +369 -93
- package/src/agentSessions/SessionManager.ts +45 -10
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +29 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/index.ts +57 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +115 -5
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +181 -0
- package/src/generated/graphql-schemas/__mj.ts +3684 -284
- package/src/generic/CacheInvalidationResolver.ts +172 -60
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +109 -12
- package/src/generic/ResolverBase.ts +540 -431
- package/src/generic/RunViewResolver.ts +97 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +239 -238
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/index.ts +2 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +69 -0
- package/src/realtimeSessions/sessionAccess.ts +183 -23
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +248 -55
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +334 -109
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +53 -9
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +31 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +114 -66
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +296 -85
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +69 -13
- package/src/resolvers/TransactionGroupResolver.ts +185 -30
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +467 -0
- package/src/rest/ViewOperationsHandler.ts +52 -18
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/serverRoleConfigUnits.ts +48 -0
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +738 -16
- package/src/sharedCache.ts +3 -0
- package/src/types.ts +23 -1
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import type { EntityInfo, UserInfo } from '@memberjunction/core';
|
|
4
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
5
|
+
import {
|
|
6
|
+
AzureWebPubSubBroadcastProvider,
|
|
7
|
+
AWSIoTCoreBroadcastProvider,
|
|
8
|
+
CacheInvalidationBroadcastManager,
|
|
9
|
+
GCPPubSubBroadcastProvider,
|
|
10
|
+
SequencedRoleBucketBroadcastProvider,
|
|
11
|
+
type AzureWebPubSubGroupMessage,
|
|
12
|
+
type AWSIoTCoreTopicMessage,
|
|
13
|
+
type GCPPubSubTopicMessage,
|
|
14
|
+
} from '../broadcast/index.js';
|
|
15
|
+
import type { CacheInvalidationPayload } from '../generic/CacheInvalidationResolver.js';
|
|
16
|
+
import {
|
|
17
|
+
GetSessionStatusTopic,
|
|
18
|
+
PUSH_STATUS_UPDATES_TOPIC,
|
|
19
|
+
ParseStatusUpdateMessage,
|
|
20
|
+
PublishStatusUpdate,
|
|
21
|
+
ShouldReplicateStatusUpdate,
|
|
22
|
+
type PushStatusNotificationPayload,
|
|
23
|
+
} from '../generic/PushStatusResolver.js';
|
|
24
|
+
import { CreateSessionRoutingPubSub, PubSubManager } from '../generic/PubSubManager.js';
|
|
25
|
+
import {
|
|
26
|
+
ResolveServerRole,
|
|
27
|
+
ShouldRunBackgroundWorkers,
|
|
28
|
+
ShouldServeInteractiveTraffic,
|
|
29
|
+
} from '../serverRoleConfigUnits.js';
|
|
30
|
+
|
|
31
|
+
function makeUser(id: string, roleIds: string[]): UserInfo {
|
|
32
|
+
return {
|
|
33
|
+
ID: id,
|
|
34
|
+
Name: `User ${id}`,
|
|
35
|
+
Email: `${id}@example.com`,
|
|
36
|
+
UserRoles: roleIds.map((r) => ({ UserID: id, RoleID: r, RoleName: r })),
|
|
37
|
+
} as unknown as UserInfo;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makePayload(overrides: Partial<CacheInvalidationPayload> = {}): CacheInvalidationPayload {
|
|
41
|
+
return {
|
|
42
|
+
entityName: 'Invoices',
|
|
43
|
+
primaryKeyValues: '[{"FieldName":"ID","Value":"inv-1"}]',
|
|
44
|
+
action: 'save',
|
|
45
|
+
sourceServerId: 'srv-1',
|
|
46
|
+
timestamp: new Date('2026-10-20T12:00:00Z'),
|
|
47
|
+
recordData: '{"ID":"inv-1","Total":500}',
|
|
48
|
+
...overrides,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('CacheInvalidationBroadcastManager & SequencedRoleBucketBroadcastProvider (Topologies 1 & 2)', () => {
|
|
53
|
+
let provider: SequencedRoleBucketBroadcastProvider;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
provider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
57
|
+
provider: 'SequencedRoleBucket',
|
|
58
|
+
ringBufferSize: 5,
|
|
59
|
+
excludedEntities: ['MJ: Audit Logs'],
|
|
60
|
+
}) as SequencedRoleBucketBroadcastProvider;
|
|
61
|
+
provider.ResetStateForTesting();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('defaults to SequencedRoleBucket and stamps monotonically increasing sequences', () => {
|
|
65
|
+
expect(provider.Topology).toBe('SequencedRoleBucket');
|
|
66
|
+
expect(provider.CurrentSequence).toBe(0);
|
|
67
|
+
|
|
68
|
+
const p1 = provider.RecordInvalidation(makePayload({ primaryKeyValues: 'pk-1' }));
|
|
69
|
+
const p2 = provider.RecordInvalidation(makePayload({ primaryKeyValues: 'pk-2' }));
|
|
70
|
+
|
|
71
|
+
expect(p1.sequence).toBe(1);
|
|
72
|
+
expect(p2.sequence).toBe(2);
|
|
73
|
+
expect(provider.CurrentSequence).toBe(2);
|
|
74
|
+
expect(provider.BufferedEventCount).toBe(2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('suppresses excluded high-churn entities from sequencing and broadcast', () => {
|
|
78
|
+
expect(provider.ShouldBroadcastEntity('MJ: Audit Logs')).toBe(false);
|
|
79
|
+
expect(provider.ShouldBroadcastEntity('Invoices')).toBe(true);
|
|
80
|
+
|
|
81
|
+
const ignored = provider.RecordInvalidation(makePayload({ entityName: 'MJ: Audit Logs' }));
|
|
82
|
+
expect(ignored.sequence).toBeUndefined();
|
|
83
|
+
expect(provider.CurrentSequence).toBe(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('computes canonical, order-independent role fingerprints and memoizes per user object', () => {
|
|
87
|
+
const u1 = makeUser('u1', ['Role-B', 'role-a']);
|
|
88
|
+
const u2 = makeUser('u2', ['ROLE-A', ' role-b ']);
|
|
89
|
+
|
|
90
|
+
const fp1 = provider.GetRoleFingerprint(u1);
|
|
91
|
+
const fp2 = provider.GetRoleFingerprint(u2);
|
|
92
|
+
|
|
93
|
+
expect(fp1).toBe('roles:role-a|role-b');
|
|
94
|
+
expect(fp2).toBe('roles:role-a|role-b');
|
|
95
|
+
// Second call hits WeakMap cache
|
|
96
|
+
expect(provider.GetRoleFingerprint(u1)).toBe(fp1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('coalesces permission & RLS evaluation to O(Roles) instead of O(Subscribers)', () => {
|
|
100
|
+
const getUserPermisionsSpy = vi.fn((user: UserInfo) => {
|
|
101
|
+
const hasDeniedOnly = user.UserRoles.every((r) => r.RoleID === 'role-none');
|
|
102
|
+
return { CanRead: !hasDeniedOnly };
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const fakeEntity = {
|
|
106
|
+
Name: 'Invoices',
|
|
107
|
+
GetUserPermisions: getUserPermisionsSpy,
|
|
108
|
+
Permissions: [
|
|
109
|
+
{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
|
|
110
|
+
{ RoleID: 'role-rls', CanRead: true, Type: 'Allow', ReadRLSFilterID: 'rls-filter-1' },
|
|
111
|
+
{ RoleID: 'role-none', CanRead: false, Type: 'Allow', ReadRLSFilterID: null },
|
|
112
|
+
],
|
|
113
|
+
} as unknown as EntityInfo;
|
|
114
|
+
|
|
115
|
+
vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
116
|
+
const rlsSpy = vi.spyOn(provider, 'ReadIsRowFiltered');
|
|
117
|
+
|
|
118
|
+
const payload = makePayload();
|
|
119
|
+
|
|
120
|
+
// Create 300 subscribers across only 3 distinct role buckets (100 per bucket)
|
|
121
|
+
const openUsers = Array.from({ length: 100 }, (_, i) => makeUser(`open-${i}`, ['role-open']));
|
|
122
|
+
const rlsUsers = Array.from({ length: 100 }, (_, i) => makeUser(`rls-${i}`, ['role-rls']));
|
|
123
|
+
const deniedUsers = Array.from({ length: 100 }, (_, i) => makeUser(`denied-${i}`, ['role-none']));
|
|
124
|
+
|
|
125
|
+
for (const u of openUsers) {
|
|
126
|
+
expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(true);
|
|
127
|
+
const shaped = provider.ShapeEventForSubscriber(payload, u, fakeEntity);
|
|
128
|
+
expect(shaped.PrimaryKeyValues).toBe(payload.primaryKeyValues);
|
|
129
|
+
expect(shaped.RecordData).toBe(payload.recordData);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const u of rlsUsers) {
|
|
133
|
+
expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(true);
|
|
134
|
+
const shaped = provider.ShapeEventForSubscriber(payload, u, fakeEntity);
|
|
135
|
+
expect(shaped.PrimaryKeyValues).toBeUndefined();
|
|
136
|
+
expect(shaped.RecordData).toBeUndefined();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const u of deniedUsers) {
|
|
140
|
+
expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(false);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Despite 300 subscribers, GetUserPermisions ran only 3 times (once per unique role bucket)
|
|
144
|
+
// and ReadIsRowFiltered ran only 2 times (for the 2 allowed role buckets)!
|
|
145
|
+
expect(getUserPermisionsSpy).toHaveBeenCalledTimes(3);
|
|
146
|
+
expect(rlsSpy).toHaveBeenCalledTimes(2);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('serves delta catch-up from the circular ring buffer and coalesces duplicate PK updates', () => {
|
|
150
|
+
const fakeEntity = {
|
|
151
|
+
Name: 'Invoices',
|
|
152
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
153
|
+
Permissions: [{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
154
|
+
} as unknown as EntityInfo;
|
|
155
|
+
vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
156
|
+
|
|
157
|
+
const user = makeUser('u1', ['role-open']);
|
|
158
|
+
|
|
159
|
+
// Record 4 events (two on inv-1, one on inv-2, one on inv-1 again)
|
|
160
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-1', recordData: '{"v":1}' })); // seq 1
|
|
161
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-2', recordData: '{"v":2}' })); // seq 2
|
|
162
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-1', recordData: '{"v":3}' })); // seq 3
|
|
163
|
+
|
|
164
|
+
const catchUp = provider.GetChangesSince(0, user);
|
|
165
|
+
expect(catchUp.RequiresFullRefresh).toBe(false);
|
|
166
|
+
expect(catchUp.CurrentSequence).toBe(3);
|
|
167
|
+
// inv-1 (seq 1 and seq 3) coalesced to latest (seq 3), so 2 events total
|
|
168
|
+
expect(catchUp.Events).toHaveLength(2);
|
|
169
|
+
const inv1Event = catchUp.Events.find((e) => e.PrimaryKeyValues === 'inv-1');
|
|
170
|
+
expect(inv1Event?.RecordData).toBe('{"v":3}');
|
|
171
|
+
expect(inv1Event?.Sequence).toBe(3);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('signals RequiresFullRefresh when sinceSequence has been evicted from the circular ring buffer', () => {
|
|
175
|
+
const fakeEntity = {
|
|
176
|
+
Name: 'Invoices',
|
|
177
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
178
|
+
Permissions: [{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
179
|
+
} as unknown as EntityInfo;
|
|
180
|
+
vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
181
|
+
|
|
182
|
+
const user = makeUser('u1', ['role-open']);
|
|
183
|
+
|
|
184
|
+
// Ring buffer size is 5; push 7 events so sequences 1 and 2 are overwritten (oldest in buffer is 3)
|
|
185
|
+
for (let i = 1; i <= 7; i++) {
|
|
186
|
+
provider.RecordInvalidation(makePayload({ primaryKeyValues: `inv-${i}` }));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Requesting sinceSequence = 1 (missed seq 2, which was evicted) -> RequiresFullRefresh: true
|
|
190
|
+
const evictedCatchUp = provider.GetChangesSince(1, user);
|
|
191
|
+
expect(evictedCatchUp.RequiresFullRefresh).toBe(true);
|
|
192
|
+
expect(evictedCatchUp.Events).toHaveLength(0);
|
|
193
|
+
|
|
194
|
+
// Requesting sinceSequence = 2 (oldest needed is seq 3, still in buffer) -> RequiresFullRefresh: false
|
|
195
|
+
const validCatchUp = provider.GetChangesSince(2, user);
|
|
196
|
+
expect(validCatchUp.RequiresFullRefresh).toBe(false);
|
|
197
|
+
expect(validCatchUp.Events).toHaveLength(5);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('Topology 3 Cloud Multiplexer Providers (AzureWebPubSub, AWSIoTCore, GCPPubSub)', () => {
|
|
202
|
+
afterEach(() => {
|
|
203
|
+
CacheInvalidationBroadcastManager.Instance.Configure({ provider: 'SequencedRoleBucket' });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('AzureWebPubSubBroadcastProvider negotiates signed JWT URLs and publishes role-shaped group messages', async () => {
|
|
207
|
+
const azureProvider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
208
|
+
provider: 'AzureWebPubSub',
|
|
209
|
+
azureWebPubSub: {
|
|
210
|
+
endpoint: 'https://mj-test.webpubsub.azure.com',
|
|
211
|
+
hubName: 'mj_hub',
|
|
212
|
+
accessKey: 'test-secret-access-key-123456',
|
|
213
|
+
tokenTtlMinutes: 30,
|
|
214
|
+
},
|
|
215
|
+
}) as AzureWebPubSubBroadcastProvider;
|
|
216
|
+
|
|
217
|
+
expect(azureProvider).toBeInstanceOf(AzureWebPubSubBroadcastProvider);
|
|
218
|
+
expect(azureProvider.Topology).toBe('AzureWebPubSub');
|
|
219
|
+
|
|
220
|
+
const user = makeUser('u-azure', ['Role-Admin', 'Role-Finance']);
|
|
221
|
+
const negotiated = await azureProvider.NegotiateClientConnection(user);
|
|
222
|
+
expect(negotiated.Provider).toBe('AzureWebPubSub');
|
|
223
|
+
expect(negotiated.Url).toContain('wss://mj-test.webpubsub.azure.com/client/hubs/mj_hub?access_token=');
|
|
224
|
+
expect(negotiated.Token).toBeDefined();
|
|
225
|
+
expect(negotiated.Groups).toEqual(['mj-role-role-admin', 'mj-role-role-finance']);
|
|
226
|
+
|
|
227
|
+
const published: AzureWebPubSubGroupMessage[] = [];
|
|
228
|
+
azureProvider.SetGroupPublisher(async (msg) => {
|
|
229
|
+
published.push(msg);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const fakeEntity = {
|
|
233
|
+
Name: 'Invoices',
|
|
234
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
235
|
+
Permissions: [
|
|
236
|
+
{ RoleID: 'role-admin', CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
|
|
237
|
+
{ RoleID: 'role-rls', CanRead: true, Type: 'Allow', ReadRLSFilterID: 'rls-1' },
|
|
238
|
+
],
|
|
239
|
+
} as unknown as EntityInfo;
|
|
240
|
+
vi.spyOn(azureProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
241
|
+
|
|
242
|
+
azureProvider.RecordInvalidation(makePayload());
|
|
243
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
244
|
+
|
|
245
|
+
expect(published).toHaveLength(2);
|
|
246
|
+
const adminMsg = published.find((m) => m.GroupName === 'mj-role-role-admin');
|
|
247
|
+
const rlsMsg = published.find((m) => m.GroupName === 'mj-role-role-rls');
|
|
248
|
+
expect(adminMsg?.Event.PrimaryKeyValues).toBeDefined();
|
|
249
|
+
expect(rlsMsg?.Event.PrimaryKeyValues).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('AWSIoTCoreBroadcastProvider negotiates SigV4 presigned MQTT URLs and publishes role topics', async () => {
|
|
253
|
+
const awsProvider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
254
|
+
provider: 'AWSIoTCore',
|
|
255
|
+
awsIoTCore: {
|
|
256
|
+
endpoint: 'a1b2c3-ats.iot.us-east-1.amazonaws.com',
|
|
257
|
+
region: 'us-east-1',
|
|
258
|
+
topicPrefix: 'mj/prod/cache',
|
|
259
|
+
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
|
260
|
+
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
261
|
+
},
|
|
262
|
+
}) as AWSIoTCoreBroadcastProvider;
|
|
263
|
+
|
|
264
|
+
expect(awsProvider).toBeInstanceOf(AWSIoTCoreBroadcastProvider);
|
|
265
|
+
const user = makeUser('u-aws', ['role-ops']);
|
|
266
|
+
const negotiated = await awsProvider.NegotiateClientConnection(user);
|
|
267
|
+
expect(negotiated.Provider).toBe('AWSIoTCore');
|
|
268
|
+
expect(negotiated.Url).toContain('wss://a1b2c3-ats.iot.us-east-1.amazonaws.com/mqtt?X-Amz-Algorithm=AWS4-HMAC-SHA256');
|
|
269
|
+
expect(negotiated.Groups).toEqual(['mj/prod/cache/roles/role-ops']);
|
|
270
|
+
|
|
271
|
+
const published: AWSIoTCoreTopicMessage[] = [];
|
|
272
|
+
awsProvider.SetTopicPublisher(async (msg) => {
|
|
273
|
+
published.push(msg);
|
|
274
|
+
});
|
|
275
|
+
const fakeEntity = {
|
|
276
|
+
Name: 'Invoices',
|
|
277
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
278
|
+
Permissions: [{ RoleID: 'role-ops', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
279
|
+
} as unknown as EntityInfo;
|
|
280
|
+
vi.spyOn(awsProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
281
|
+
|
|
282
|
+
awsProvider.RecordInvalidation(makePayload());
|
|
283
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
284
|
+
expect(published).toHaveLength(1);
|
|
285
|
+
expect(published[0].Topic).toBe('mj/prod/cache/roles/role-ops');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('GCPPubSubBroadcastProvider negotiates gateway tokens and publishes Pub/Sub topic messages', async () => {
|
|
289
|
+
const gcpProvider = CacheInvalidationBroadcastManager.Instance.Configure({
|
|
290
|
+
provider: 'GCPPubSub',
|
|
291
|
+
gcpPubSub: {
|
|
292
|
+
projectId: 'mj-cloud-prod',
|
|
293
|
+
topicPrefix: 'mj-invalidation',
|
|
294
|
+
gatewayUrl: 'https://cache-gw.run.app/ws',
|
|
295
|
+
signingSecret: 'gcp-gateway-secret-key',
|
|
296
|
+
},
|
|
297
|
+
}) as GCPPubSubBroadcastProvider;
|
|
298
|
+
|
|
299
|
+
expect(gcpProvider).toBeInstanceOf(GCPPubSubBroadcastProvider);
|
|
300
|
+
const user = makeUser('u-gcp', ['role-analyst']);
|
|
301
|
+
const negotiated = await gcpProvider.NegotiateClientConnection(user);
|
|
302
|
+
expect(negotiated.Provider).toBe('GCPPubSub');
|
|
303
|
+
expect(negotiated.Url).toContain('wss://cache-gw.run.app/ws?token=');
|
|
304
|
+
expect(negotiated.Groups).toEqual(['projects/mj-cloud-prod/topics/mj-invalidation-role-role-analyst']);
|
|
305
|
+
|
|
306
|
+
const published: GCPPubSubTopicMessage[] = [];
|
|
307
|
+
gcpProvider.SetTopicPublisher(async (msg) => {
|
|
308
|
+
published.push(msg);
|
|
309
|
+
});
|
|
310
|
+
const fakeEntity = {
|
|
311
|
+
Name: 'Invoices',
|
|
312
|
+
GetUserPermisions: () => ({ CanRead: true }),
|
|
313
|
+
Permissions: [{ RoleID: 'role-analyst', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
|
|
314
|
+
} as unknown as EntityInfo;
|
|
315
|
+
vi.spyOn(gcpProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
|
|
316
|
+
|
|
317
|
+
gcpProvider.RecordInvalidation(makePayload());
|
|
318
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
319
|
+
expect(published).toHaveLength(1);
|
|
320
|
+
expect(published[0].TopicName).toBe('projects/mj-cloud-prod/topics/mj-invalidation-role-role-analyst');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('PushStatusResolver O(1) session routing and WeakMap JSON memoization', () => {
|
|
325
|
+
it('routes session-specific updates to PUSH_STATUS_UPDATES:session:<id> and wildcard updates to PUSH_STATUS_UPDATES', async () => {
|
|
326
|
+
const rawPublish = vi.fn(async () => undefined);
|
|
327
|
+
const rawPubSub: PubSubEngine = {
|
|
328
|
+
publish: rawPublish,
|
|
329
|
+
subscribe: vi.fn(),
|
|
330
|
+
unsubscribe: vi.fn(),
|
|
331
|
+
asyncIterator: vi.fn(),
|
|
332
|
+
} as unknown as PubSubEngine;
|
|
333
|
+
|
|
334
|
+
PubSubManager.Instance.SetPubSubEngine(rawPubSub);
|
|
335
|
+
const routedPubSub = PubSubManager.Instance.PubSubEngine!;
|
|
336
|
+
|
|
337
|
+
PublishStatusUpdate(routedPubSub, {
|
|
338
|
+
sessionId: 'tab-abc',
|
|
339
|
+
ownerUserId: 'user-1',
|
|
340
|
+
message: '{"type":"Progress"}',
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
PublishStatusUpdate(routedPubSub, {
|
|
344
|
+
sessionId: '*',
|
|
345
|
+
ownerUserId: 'user-1',
|
|
346
|
+
message: '{"type":"Broadcast"}',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(rawPublish).toHaveBeenCalledTimes(2);
|
|
350
|
+
expect(rawPublish).toHaveBeenNthCalledWith(
|
|
351
|
+
1,
|
|
352
|
+
GetSessionStatusTopic('tab-abc'),
|
|
353
|
+
expect.objectContaining({ sessionId: 'tab-abc' })
|
|
354
|
+
);
|
|
355
|
+
expect(rawPublish).toHaveBeenNthCalledWith(
|
|
356
|
+
2,
|
|
357
|
+
PUSH_STATUS_UPDATES_TOPIC,
|
|
358
|
+
expect.objectContaining({ sessionId: '*' })
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('memoizes JSON.parse per PushStatusNotificationPayload object via WeakMap', () => {
|
|
363
|
+
const payload: PushStatusNotificationPayload = {
|
|
364
|
+
sessionId: 'tab-1',
|
|
365
|
+
ownerUserId: 'u-1',
|
|
366
|
+
message: JSON.stringify({ type: 'StreamingContent', step: 1 }),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const parseSpy = vi.spyOn(JSON, 'parse');
|
|
370
|
+
try {
|
|
371
|
+
const first = ParseStatusUpdateMessage(payload);
|
|
372
|
+
const second = ParseStatusUpdateMessage(payload);
|
|
373
|
+
const shouldReplicate = ShouldReplicateStatusUpdate(payload.message, payload);
|
|
374
|
+
|
|
375
|
+
expect(first).toBe(second);
|
|
376
|
+
expect(shouldReplicate).toBe(false);
|
|
377
|
+
expect(parseSpy).toHaveBeenCalledTimes(1);
|
|
378
|
+
} finally {
|
|
379
|
+
parseSpy.mockRestore();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('ServerRole configuration units (Pillar 4)', () => {
|
|
385
|
+
it('resolves all, api, and worker roles case-insensitively and defaults to all', () => {
|
|
386
|
+
expect(ResolveServerRole(undefined)).toBe('all');
|
|
387
|
+
expect(ResolveServerRole('')).toBe('all');
|
|
388
|
+
expect(ResolveServerRole(' API ')).toBe('api');
|
|
389
|
+
expect(ResolveServerRole('WORKER')).toBe('worker');
|
|
390
|
+
expect(ResolveServerRole('all')).toBe('all');
|
|
391
|
+
expect(() => ResolveServerRole('invalid-role')).toThrow(/Invalid MJ_SERVER_ROLE/);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('correctly gates background workers and interactive traffic by role', () => {
|
|
395
|
+
expect(ShouldRunBackgroundWorkers('all')).toBe(true);
|
|
396
|
+
expect(ShouldRunBackgroundWorkers('worker')).toBe(true);
|
|
397
|
+
expect(ShouldRunBackgroundWorkers('api')).toBe(false);
|
|
398
|
+
|
|
399
|
+
expect(ShouldServeInteractiveTraffic('all')).toBe(true);
|
|
400
|
+
expect(ShouldServeInteractiveTraffic('api')).toBe(true);
|
|
401
|
+
expect(ShouldServeInteractiveTraffic('worker')).toBe(false);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
@@ -5,7 +5,11 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
5
5
|
import {
|
|
6
6
|
ConfigureRecordDataBroadcast,
|
|
7
7
|
MayBroadcastRecordData,
|
|
8
|
+
ReadIsRowFiltered,
|
|
9
|
+
ShapeCacheInvalidationEvent,
|
|
8
10
|
} from '../generic/CacheInvalidationResolver.js';
|
|
11
|
+
import { EntityInfo } from '@memberjunction/core';
|
|
12
|
+
import { EmployeeEntity } from './fieldSecurityFixtures.js';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* The cache-invalidation subscription is delivered to every connected client with no per-user
|
|
@@ -60,6 +64,49 @@ describe('MayBroadcastRecordData', () => {
|
|
|
60
64
|
});
|
|
61
65
|
});
|
|
62
66
|
|
|
67
|
+
/**
|
|
68
|
+
* An entity under field-level security never broadcasts its row, whatever the allowlist says.
|
|
69
|
+
*
|
|
70
|
+
* The event reaches every signed-in session that may read the entity, with no per-user
|
|
71
|
+
* projection, so a row of an entity whose fields are restricted per role would hand every
|
|
72
|
+
* session the columns it may not read. The check is the IS-A-aware one: a child whose parent has
|
|
73
|
+
* field security carries the parent's restricted fields as its own.
|
|
74
|
+
*/
|
|
75
|
+
describe('MayBroadcastRecordData: entities under field-level security', () => {
|
|
76
|
+
afterEach(() => ConfigureRecordDataBroadcast([]));
|
|
77
|
+
|
|
78
|
+
it("REGRESSION: refuses an FLS entity even under an explicit '*'", () => {
|
|
79
|
+
ConfigureRecordDataBroadcast(['*']);
|
|
80
|
+
expect(MayBroadcastRecordData('Employees', EmployeeEntity(true))).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('REGRESSION: refuses an FLS entity that is named in the allowlist', () => {
|
|
84
|
+
ConfigureRecordDataBroadcast(['Employees']);
|
|
85
|
+
expect(MayBroadcastRecordData('Employees', EmployeeEntity(true))).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('still allows the same entity with field security off', () => {
|
|
89
|
+
ConfigureRecordDataBroadcast(['Employees']);
|
|
90
|
+
expect(MayBroadcastRecordData('Employees', EmployeeEntity(false))).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("REGRESSION: refuses MJ: Record Changes under '*', with or without its metadata", () => {
|
|
94
|
+
// A Record Change row carries other entities' values in ChangesJSON / FullRecordJSON, and its
|
|
95
|
+
// own FieldLevelSecurityApplies is false; the per-row projection never runs on this push.
|
|
96
|
+
ConfigureRecordDataBroadcast(['*']);
|
|
97
|
+
const recordChanges = new EntityInfo({ ID: 'rc', Name: 'MJ: Record Changes', SchemaName: '__mj', BaseTable: 'RecordChange', BaseView: 'vwRecordChanges', Fields: [] });
|
|
98
|
+
expect(recordChanges.FieldLevelSecurityApplies).toBe(false);
|
|
99
|
+
expect(MayBroadcastRecordData('MJ: Record Changes', recordChanges)).toBe(false);
|
|
100
|
+
expect(MayBroadcastRecordData('MJ: Record Changes')).toBe(false);
|
|
101
|
+
expect(MayBroadcastRecordData(' mj: record changes ')).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('REGRESSION: refuses MJ: Record Changes even when named in the allowlist', () => {
|
|
105
|
+
ConfigureRecordDataBroadcast(['MJ: Record Changes']);
|
|
106
|
+
expect(MayBroadcastRecordData('MJ: Record Changes')).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
63
110
|
/**
|
|
64
111
|
* Importing this module must not drag server config loading in with it.
|
|
65
112
|
*
|
|
@@ -175,3 +222,65 @@ describe('cacheInvalidationFilter', () => {
|
|
|
175
222
|
expect(filter({ payload: { ...event(''), entityName: '' } as never, context: session({ ID: 'u1' }) })).toBe(false);
|
|
176
223
|
});
|
|
177
224
|
});
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Row-level narrowing (MJ#5241): a subscriber who reads the entity only through a row-level security filter learns that a
|
|
228
|
+
* row changed, not which one; a subscriber with an unfiltered read keeps the key; a subscriber with no read is the entity-level
|
|
229
|
+
* filter's business and gets nothing here to shape.
|
|
230
|
+
*/
|
|
231
|
+
describe('ReadIsRowFiltered and ShapeCacheInvalidationEvent', () => {
|
|
232
|
+
const FILTERED_ROLE = 'role-participant';
|
|
233
|
+
const OPEN_ROLE = 'role-staff';
|
|
234
|
+
const entity = {
|
|
235
|
+
Permissions: [
|
|
236
|
+
{ RoleID: FILTERED_ROLE, CanRead: true, Type: 'Allow', ReadRLSFilterID: 'filter-1' },
|
|
237
|
+
{ RoleID: OPEN_ROLE, CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
|
|
238
|
+
{ RoleID: 'role-denied', CanRead: true, Type: 'Deny', ReadRLSFilterID: null },
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
const payload = {
|
|
242
|
+
entityName: 'Spaces',
|
|
243
|
+
primaryKeyValues: 'ID|abc',
|
|
244
|
+
action: 'save',
|
|
245
|
+
sourceServerId: 'srv',
|
|
246
|
+
timestamp: new Date('2026-10-07T00:00:00Z'),
|
|
247
|
+
originSessionId: 'sess',
|
|
248
|
+
recordData: '{"Name":"x"}',
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
it('is row-filtered when every read the user holds carries a filter', () => {
|
|
252
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: FILTERED_ROLE }] })).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('is not row-filtered when any read the user holds is unfiltered, whichever role brings it', () => {
|
|
256
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: OPEN_ROLE }] })).toBe(false);
|
|
257
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: FILTERED_ROLE }, { RoleID: OPEN_ROLE }] })).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('is not row-filtered when the user holds no read at all (the entity-level filter owns that case), and a Deny row grants nothing', () => {
|
|
261
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: 'role-stranger' }] })).toBe(false);
|
|
262
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: 'role-denied' }] })).toBe(false);
|
|
263
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: null })).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('matches role ids without regard to case or whitespace', () => {
|
|
267
|
+
expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: ` ${FILTERED_ROLE.toUpperCase()} ` }] })).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('withholds the key and the row from a row-filtered subscriber, and keeps the rest of the event', () => {
|
|
271
|
+
const shaped = ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: FILTERED_ROLE }] }, entity);
|
|
272
|
+
expect(shaped.PrimaryKeyValues).toBeUndefined();
|
|
273
|
+
expect(shaped.RecordData).toBeUndefined();
|
|
274
|
+
expect(shaped.EntityName).toBe('Spaces');
|
|
275
|
+
expect(shaped.Action).toBe('save');
|
|
276
|
+
expect(shaped.SourceServerID).toBe('srv');
|
|
277
|
+
expect(shaped.OriginSessionID).toBe('sess');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('delivers the event whole to an unfiltered reader, and when the entity or the user is unknown', () => {
|
|
281
|
+
expect(ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: OPEN_ROLE }] }, entity).PrimaryKeyValues).toBe('ID|abc');
|
|
282
|
+
expect(ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: OPEN_ROLE }] }, entity).RecordData).toBe('{"Name":"x"}');
|
|
283
|
+
expect(ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: FILTERED_ROLE }] }, null).PrimaryKeyValues).toBe('ID|abc');
|
|
284
|
+
expect(ShapeCacheInvalidationEvent(payload, undefined, entity).PrimaryKeyValues).toBe('ID|abc');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -128,7 +128,7 @@ describe('TailConversationEvents', () => {
|
|
|
128
128
|
|
|
129
129
|
const runCall = mockRunView.mock.calls.find((c) => c[0].EntityName === 'MJ: AI Agent Runs');
|
|
130
130
|
expect(runCall?.[0].ResultType).toBe('simple');
|
|
131
|
-
expect([...(runCall?.[0].Fields ?? [])].sort()).toEqual(['ID', 'Result', 'Status']);
|
|
131
|
+
expect([...(runCall?.[0].Fields ?? [])].sort()).toEqual(['FinalPayload', 'ID', 'Result', 'Status']);
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it('returns real dates when a plain row carries them as strings', async () => {
|
|
@@ -261,20 +261,20 @@ describe('TailConversationEvents', () => {
|
|
|
261
261
|
it('marks a Running run in flight and withholds the payload', async () => {
|
|
262
262
|
scriptRunView({
|
|
263
263
|
detail: { ID: DETAIL_ID, Status: 'In-Progress' },
|
|
264
|
-
run: { ID: RUN_ID, Status: 'Running',
|
|
264
|
+
run: { ID: RUN_ID, Status: 'Running', FinalPayload: '{"partial":true}' },
|
|
265
265
|
steps: [step(1)],
|
|
266
266
|
});
|
|
267
267
|
const out = await resolver.TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
|
|
268
268
|
|
|
269
269
|
expect(out.IsInFlight).toBe(true);
|
|
270
|
-
// A partial
|
|
270
|
+
// FinalPayload is filled during the run. A partial one could be mistaken for the final answer.
|
|
271
271
|
expect(out.FinalPayload).toBeUndefined();
|
|
272
272
|
});
|
|
273
273
|
|
|
274
274
|
it('releases the payload once the run is terminal, so one call ends the wait', async () => {
|
|
275
275
|
scriptRunView({
|
|
276
276
|
detail: { ID: DETAIL_ID, Status: 'Complete' },
|
|
277
|
-
run: { ID: RUN_ID, Status: 'Completed',
|
|
277
|
+
run: { ID: RUN_ID, Status: 'Completed', FinalPayload: '{"answer":"done"}', Result: null },
|
|
278
278
|
steps: [step(1)],
|
|
279
279
|
});
|
|
280
280
|
const out = await resolver.TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
|
|
@@ -302,7 +302,7 @@ describe('TailConversationEvents', () => {
|
|
|
302
302
|
// Reporting it as terminal would tell a client to complete a message mid-workflow.
|
|
303
303
|
scriptRunView({
|
|
304
304
|
detail: { ID: DETAIL_ID, Status: 'In-Progress' },
|
|
305
|
-
run: { ID: RUN_ID, Status: 'Paused',
|
|
305
|
+
run: { ID: RUN_ID, Status: 'Paused', FinalPayload: '{"started":"workflow"}' },
|
|
306
306
|
steps: [step(1)],
|
|
307
307
|
});
|
|
308
308
|
const out = await resolver.TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
|
|
@@ -353,16 +353,19 @@ describe('TailConversationEvents', () => {
|
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
356
|
+
* `FinalPayload` comes from `AIAgentRun.FinalPayload`, then `Result` (some older rows have no
|
|
357
|
+
* `FinalPayload`), then the conversation detail's message.
|
|
358
|
+
*
|
|
359
|
+
* REGRESSION, found in manual testing: the run payload is agent-dependent — null on 16 of 40
|
|
360
|
+
* completed runs in a real database, including every run of the Loop-type agent used to reproduce
|
|
361
|
+
* #4222. Without the detail-message fallback, a caller waiting on this field to decide a message
|
|
362
|
+
* was finished would wait forever on those agents.
|
|
360
363
|
*/
|
|
361
364
|
describe('TailConversationEvents FinalPayload sourcing', () => {
|
|
362
|
-
it('falls back to the conversation detail message when the run has no
|
|
365
|
+
it('falls back to the conversation detail message when the run has no payload', async () => {
|
|
363
366
|
scriptRunView({
|
|
364
367
|
detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'the answer text' },
|
|
365
|
-
run: { ID: RUN_ID, Status: 'Completed', Result: null },
|
|
368
|
+
run: { ID: RUN_ID, Status: 'Completed', FinalPayload: null, Result: null },
|
|
366
369
|
steps: [],
|
|
367
370
|
});
|
|
368
371
|
|
|
@@ -372,10 +375,34 @@ describe('TailConversationEvents FinalPayload sourcing', () => {
|
|
|
372
375
|
expect(result.FinalPayload).toBe('the answer text');
|
|
373
376
|
});
|
|
374
377
|
|
|
375
|
-
it('prefers the run
|
|
378
|
+
it('prefers the run FinalPayload over the detail message', async () => {
|
|
379
|
+
scriptRunView({
|
|
380
|
+
detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'detail message' },
|
|
381
|
+
run: { ID: RUN_ID, Status: 'Completed', FinalPayload: 'run payload', Result: null },
|
|
382
|
+
steps: [],
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const result = await new ConversationRunEventsResolver().TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
|
|
386
|
+
|
|
387
|
+
expect(result.FinalPayload).toBe('run payload');
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('prefers FinalPayload over Result when a row has both', async () => {
|
|
391
|
+
scriptRunView({
|
|
392
|
+
detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'detail message' },
|
|
393
|
+
run: { ID: RUN_ID, Status: 'Completed', FinalPayload: 'run payload', Result: 'run result' },
|
|
394
|
+
steps: [],
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const result = await new ConversationRunEventsResolver().TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
|
|
398
|
+
|
|
399
|
+
expect(result.FinalPayload).toBe('run payload');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('falls back to Result on an older row that has no FinalPayload', async () => {
|
|
376
403
|
scriptRunView({
|
|
377
404
|
detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'detail message' },
|
|
378
|
-
run: { ID: RUN_ID, Status: 'Completed', Result: 'run result' },
|
|
405
|
+
run: { ID: RUN_ID, Status: 'Completed', FinalPayload: null, Result: 'run result' },
|
|
379
406
|
steps: [],
|
|
380
407
|
});
|
|
381
408
|
|
|
@@ -384,10 +411,25 @@ describe('TailConversationEvents FinalPayload sourcing', () => {
|
|
|
384
411
|
expect(result.FinalPayload).toBe('run result');
|
|
385
412
|
});
|
|
386
413
|
|
|
414
|
+
it('returns the partial FinalPayload a Cancelled run saved, with RunStatus telling the caller', async () => {
|
|
415
|
+
// Cancelling saves the run with no Result, but it keeps the FinalPayload saved during the run.
|
|
416
|
+
scriptRunView({
|
|
417
|
+
detail: { ID: DETAIL_ID, Status: 'Error', Message: 'detail message' },
|
|
418
|
+
run: { ID: RUN_ID, Status: 'Cancelled', FinalPayload: '{"partial":true}', Result: null },
|
|
419
|
+
steps: [],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const result = await new ConversationRunEventsResolver().TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
|
|
423
|
+
|
|
424
|
+
expect(result.IsInFlight).toBe(false);
|
|
425
|
+
expect(result.RunStatus).toBe('Cancelled');
|
|
426
|
+
expect(result.FinalPayload).toBe('{"partial":true}');
|
|
427
|
+
});
|
|
428
|
+
|
|
387
429
|
it('still withholds the payload while the run is in flight', async () => {
|
|
388
430
|
scriptRunView({
|
|
389
431
|
detail: { ID: DETAIL_ID, Status: 'In-Progress', Message: 'partial text' },
|
|
390
|
-
run: { ID: RUN_ID, Status: 'Running', Result: null },
|
|
432
|
+
run: { ID: RUN_ID, Status: 'Running', FinalPayload: '{"partial":true}', Result: null },
|
|
391
433
|
steps: [],
|
|
392
434
|
});
|
|
393
435
|
|