@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.7
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 +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
- package/dist/agentSessions/BridgeHostInstance.js +14 -4
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
- package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
- package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
- package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
- package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +31 -8
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +85 -41
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +14 -4
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +28 -4
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
- package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
- package/dist/agentSessions/conversationBrowsers.js +118 -0
- package/dist/agentSessions/conversationBrowsers.js.map +1 -0
- package/dist/agentSessions/sessionDeadline.d.ts +43 -0
- package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
- package/dist/agentSessions/sessionDeadline.js +68 -0
- package/dist/agentSessions/sessionDeadline.js.map +1 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +5 -4
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/loginEmailResolution.d.ts +41 -0
- package/dist/auth/loginEmailResolution.d.ts.map +1 -0
- package/dist/auth/loginEmailResolution.js +47 -0
- package/dist/auth/loginEmailResolution.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +221 -9
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +7 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +27 -3
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/magicLink/types.js +0 -4
- package/dist/auth/magicLink/types.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +1 -0
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/backgroundSweepConfigUnits.d.ts +231 -0
- package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
- package/dist/backgroundSweepConfigUnits.js +180 -0
- package/dist/backgroundSweepConfigUnits.js.map +1 -0
- package/dist/config.d.ts +906 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +35 -4
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +3 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +6 -4
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1163 -6
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +50 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +321 -132
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +47 -7
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +157 -35
- package/dist/index.js.map +1 -1
- package/dist/integration/ConnectionPause.d.ts +121 -0
- package/dist/integration/ConnectionPause.d.ts.map +1 -0
- package/dist/integration/ConnectionPause.js +183 -0
- package/dist/integration/ConnectionPause.js.map +1 -0
- package/dist/integration/CustomColumnPromoter.d.ts +72 -6
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +87 -46
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +78 -4
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +199 -5
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/integration/InactiveRowWarnings.d.ts +31 -0
- package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
- package/dist/integration/InactiveRowWarnings.js +29 -0
- package/dist/integration/InactiveRowWarnings.js.map +1 -1
- package/dist/integration/ObjectNamesByID.d.ts +28 -0
- package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
- package/dist/integration/ObjectNamesByID.js +87 -0
- package/dist/integration/ObjectNamesByID.js.map +1 -0
- package/dist/integration/PostRestartIntrospect.d.ts +26 -0
- package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
- package/dist/integration/PostRestartIntrospect.js +27 -0
- package/dist/integration/PostRestartIntrospect.js.map +1 -0
- package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
- package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
- package/dist/integration/RSUPostRestartProgress.js +323 -0
- package/dist/integration/RSUPostRestartProgress.js.map +1 -0
- package/dist/integration/RSUProgressBridge.d.ts +41 -2
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
- package/dist/integration/RSUProgressBridge.js +84 -1
- package/dist/integration/RSUProgressBridge.js.map +1 -1
- package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
- package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
- package/dist/integration/RunArtifactAuthorization.js +45 -0
- package/dist/integration/RunArtifactAuthorization.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
- package/dist/integration/SchemaRefreshLaunch.js +3 -2
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
- package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
- package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
- package/dist/integration/ScopedFieldsForBuild.js +91 -0
- package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
- package/dist/integration/TargetConfigGuard.d.ts +40 -0
- package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
- package/dist/integration/TargetConfigGuard.js +40 -0
- package/dist/integration/TargetConfigGuard.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +22 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +1 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +4 -7
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/requestOrigin.d.ts +22 -0
- package/dist/requestOrigin.d.ts.map +1 -0
- package/dist/requestOrigin.js +60 -0
- package/dist/requestOrigin.js.map +1 -0
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +6 -2
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +2 -1
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +34 -13
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -2
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +174 -87
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +185 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +19 -18
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +3 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +2 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +6 -2
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +11 -5
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +2 -4
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +2 -1
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +3 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +11 -2
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +2 -26
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +38 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +2 -1
- package/dist/services/WorkQueueHostService.d.ts.map +1 -1
- package/dist/services/WorkQueueHostService.js +19 -4
- package/dist/services/WorkQueueHostService.js.map +1 -1
- package/dist/services/workQueueConfig.d.ts +2 -2
- package/dist/services/workQueueConfig.js +5 -5
- package/dist/services/workQueueConfig.js.map +1 -1
- package/dist/sharedCache.js +1 -1
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
- package/src/__tests__/ActiveOperations.test.ts +68 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
- package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
- package/src/__tests__/BatchApplyParity.test.ts +109 -0
- package/src/__tests__/BridgeHostInstance.test.ts +42 -0
- package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
- package/src/__tests__/ConnectionPause.test.ts +192 -0
- package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
- package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
- package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
- package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
- package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
- package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
- package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
- package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
- package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
- package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
- package/src/__tests__/MagicLinkKeys.test.ts +102 -0
- package/src/__tests__/MediaAccessKeys.test.ts +57 -10
- package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
- package/src/__tests__/ObjectNamesByID.test.ts +176 -0
- package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
- package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
- package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
- package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
- package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
- package/src/__tests__/RSUProgressBridge.test.ts +188 -5
- package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
- package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
- package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
- package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
- package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
- package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
- package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
- package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
- package/src/__tests__/SessionJanitor.test.ts +195 -4
- package/src/__tests__/SessionManager.test.ts +69 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
- package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
- package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
- package/src/__tests__/WorkQueueHostService.test.ts +25 -7
- package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
- package/src/__tests__/contextRequestOrigin.test.ts +105 -0
- package/src/__tests__/conversationBrowsers.test.ts +62 -0
- package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
- package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
- package/src/__tests__/requestOrigin.test.ts +104 -0
- package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
- package/src/__tests__/sessionDeadline.test.ts +51 -0
- package/src/__tests__/sharedCache.test.ts +11 -0
- package/src/__tests__/unifiedAuth.test.ts +73 -1
- package/src/agentSessions/BridgeHostInstance.ts +20 -5
- package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
- package/src/agentSessions/SessionJanitor.ts +92 -42
- package/src/agentSessions/SessionManager.ts +38 -8
- package/src/agentSessions/conversationBrowsers.ts +143 -0
- package/src/agentSessions/sessionDeadline.ts +81 -0
- package/src/auth/APIKeyScopeAuth.ts +5 -4
- package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
- package/src/auth/loginEmailResolution.ts +67 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
- package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
- package/src/auth/magicLink/MagicLinkService.ts +250 -10
- package/src/auth/magicLink/index.ts +1 -1
- package/src/auth/magicLink/redeemLanding.ts +7 -4
- package/src/auth/magicLink/types.ts +28 -3
- package/src/auth/newUsers.ts +1 -0
- package/src/backgroundSweepConfigUnits.ts +213 -0
- package/src/config.ts +69 -0
- package/src/context.ts +39 -6
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +822 -7
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +414 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/index.ts +201 -35
- package/src/integration/ConnectionPause.ts +222 -0
- package/src/integration/CustomColumnPromoter.ts +123 -53
- package/src/integration/EntityMapLifecycle.ts +229 -6
- package/src/integration/InactiveRowWarnings.ts +50 -0
- package/src/integration/ObjectNamesByID.ts +97 -0
- package/src/integration/PostRestartIntrospect.ts +49 -0
- package/src/integration/RSUPostRestartProgress.ts +359 -0
- package/src/integration/RSUProgressBridge.ts +85 -2
- package/src/integration/RunArtifactAuthorization.ts +65 -0
- package/src/integration/SchemaRefreshLaunch.ts +12 -2
- package/src/integration/ScopedFieldsForBuild.ts +113 -0
- package/src/integration/TargetConfigGuard.ts +71 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
- package/src/realtimeSessions/sessionAccess.ts +30 -0
- package/src/realtimeWidget/WidgetSessionService.ts +2 -0
- package/src/realtimeWidget/visitorIdentity.ts +1 -0
- package/src/realtimeWidget/widgetCore.ts +4 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
- package/src/requestOrigin.ts +64 -0
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +2 -1
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +197 -98
- package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
- package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
- package/src/resolvers/RunAIAgentResolver.ts +265 -86
- package/src/resolvers/RunAIPromptResolver.ts +18 -18
- package/src/resolvers/RunTemplateResolver.ts +3 -1
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/rest/MediaAccessKeys.ts +6 -2
- package/src/rest/OAuthCallbackHandler.ts +13 -5
- package/src/rest/RealtimeRecordingHandler.ts +5 -9
- package/src/rest/ViewOperationsHandler.ts +2 -1
- package/src/rest/setupRESTEndpoints.ts +11 -2
- package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
- package/src/services/TaskGraphActionRunner.ts +2 -23
- package/src/services/TaskGraphAgentRunner.ts +43 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
- package/src/services/WorkQueueHostService.ts +26 -4
- package/src/services/workQueueConfig.ts +5 -5
- package/src/sharedCache.ts +1 -1
- package/src/types.ts +18 -0
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The apply's declared-but-inactive-row warnings have to survive the path that PREFETCHES its schema.
|
|
3
|
+
*
|
|
4
|
+
* The collection used to live inside the resolver's schema REBUILD, so the batch apply lost it twice:
|
|
5
|
+
* its own rebuild passed no collector, and handing the result down to the shared builder took the
|
|
6
|
+
* `prefetchedSourceSchema` branch, which skips the rebuild entirely. An operator whose selected table
|
|
7
|
+
* came back without a column therefore had nothing to read on the batch path while the single path
|
|
8
|
+
* explained it.
|
|
9
|
+
*
|
|
10
|
+
* The warnings never depended on the rebuild — they are the catalog's own `Status` rows — so the
|
|
11
|
+
* gathering is a function of (catalog, integration, requested names). These tests pin it against a
|
|
12
|
+
* literal catalog, which is also the only way to test it at all: the resolver imports schema-builder
|
|
13
|
+
* and schema-engine and cannot be loaded in a unit test.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect } from 'vitest';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { CollectInactiveRowWarnings } from '../integration/InactiveRowWarnings.js';
|
|
19
|
+
import type { CatalogStatusReader } from '../integration/InactiveRowWarnings.js';
|
|
20
|
+
|
|
21
|
+
type Row = { ID: string; Name: string; Status: string | null };
|
|
22
|
+
|
|
23
|
+
/** A catalog of objects, each with its fields. `Status` is the whole question. */
|
|
24
|
+
function catalog(objects: Array<Row & { Fields: Array<{ Name: string; Status: string | null }> }>): CatalogStatusReader {
|
|
25
|
+
return {
|
|
26
|
+
GetActiveIntegrationObjects: () => objects.filter(o => o.Status === 'Active'),
|
|
27
|
+
GetIntegrationObjectsByIntegrationID: () => objects,
|
|
28
|
+
GetIntegrationObjectFields: (objectID: string) => objects.find(o => o.ID === objectID)?.Fields ?? [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const contacts = {
|
|
33
|
+
ID: 'io-1', Name: 'Contacts', Status: 'Active',
|
|
34
|
+
Fields: [
|
|
35
|
+
{ Name: 'ID', Status: 'Active' },
|
|
36
|
+
{ Name: 'MiddleName', Status: 'Disabled' },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
const invoices = { ID: 'io-2', Name: 'Invoices', Status: 'Disabled', Fields: [{ Name: 'ID', Status: 'Active' }] };
|
|
40
|
+
|
|
41
|
+
describe('CollectInactiveRowWarnings', () => {
|
|
42
|
+
it('names a deactivated FIELD of an object the apply does materialize', () => {
|
|
43
|
+
const warnings = CollectInactiveRowWarnings(catalog([contacts]), 'int-1', ['Contacts']);
|
|
44
|
+
expect(warnings).toHaveLength(1);
|
|
45
|
+
expect(warnings[0]).toContain('Contacts:');
|
|
46
|
+
expect(warnings[0]).toContain('MiddleName (Disabled)');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('reports a REQUESTED object that is not Active — the caller named it and got nothing', () => {
|
|
50
|
+
const warnings = CollectInactiveRowWarnings(catalog([contacts, invoices]), 'int-1', ['Contacts', 'Invoices']);
|
|
51
|
+
expect(warnings.some(w => w.includes('Invoices (Disabled)'))).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('matches requested names case-insensitively, the way the apply resolves them', () => {
|
|
55
|
+
const warnings = CollectInactiveRowWarnings(catalog([contacts]), 'int-1', ['contacts']);
|
|
56
|
+
expect(warnings[0]).toContain('MiddleName (Disabled)');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('reads fields ONLY for the objects in scope', () => {
|
|
60
|
+
// An out-of-scope object's own dropped fields are not this apply's business, and on a large
|
|
61
|
+
// catalog reporting them would bury the one line that matters.
|
|
62
|
+
const other = {
|
|
63
|
+
ID: 'io-3', Name: 'Events', Status: 'Active',
|
|
64
|
+
Fields: [{ Name: 'Venue', Status: 'Disabled' }],
|
|
65
|
+
};
|
|
66
|
+
const warnings = CollectInactiveRowWarnings(catalog([contacts, other]), 'int-1', ['Contacts']);
|
|
67
|
+
expect(warnings.join(' ')).not.toContain('Venue');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('stays quiet about deactivated OBJECTS when nothing was requested by name', () => {
|
|
71
|
+
// The unfiltered apply. Hundreds of deactivated objects are the normal state of a large
|
|
72
|
+
// catalog; the field warnings for what IS materialized still come through.
|
|
73
|
+
const warnings = CollectInactiveRowWarnings(catalog([contacts, invoices]), 'int-1', []);
|
|
74
|
+
expect(warnings.join(' ')).not.toContain('Invoices');
|
|
75
|
+
expect(warnings.join(' ')).toContain('MiddleName (Disabled)');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('says nothing when everything in scope is Active', () => {
|
|
79
|
+
const clean = {
|
|
80
|
+
ID: 'io-9', Name: 'Clean', Status: 'Active',
|
|
81
|
+
Fields: [{ Name: 'ID', Status: 'Active' }],
|
|
82
|
+
};
|
|
83
|
+
expect(CollectInactiveRowWarnings(catalog([clean]), 'int-1', ['Clean'])).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Under a per-connection catalog the engine answers [] for a connection's fields during an apply, so
|
|
89
|
+
* every caller has to hand the helper the fields it loaded itself, or field-level warnings vanish.
|
|
90
|
+
* Read from source: the callers are resolver mutations that need a provider and a pipeline to run.
|
|
91
|
+
*/
|
|
92
|
+
describe('every inactive-row warning caller passes the connection\'s own fields', () => {
|
|
93
|
+
const SRC = readFileSync(join(__dirname, '..', 'resolvers', 'IntegrationDiscoveryResolver.ts'), 'utf-8');
|
|
94
|
+
const calls = [...SRC.matchAll(/this\.collectInactiveRowWarnings\(([\s\S]*?)\)\)?;/g)].map(m => m[1]);
|
|
95
|
+
|
|
96
|
+
it('finds the callers', () => {
|
|
97
|
+
expect(calls.length).toBeGreaterThanOrEqual(3);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('passes scopedFields at every call', () => {
|
|
101
|
+
for (const args of calls) expect(args).toMatch(/scopedFields\s*,?\s*$/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('loads the fields BEFORE the batch branch, so the prefetched path has them too', () => {
|
|
105
|
+
const at = SRC.indexOf('if (prefetchedSourceSchema) {');
|
|
106
|
+
const load = SRC.lastIndexOf('const scopedFields = await LoadScopedFieldsForBuild(', at);
|
|
107
|
+
expect(at).toBeGreaterThan(-1);
|
|
108
|
+
expect(load).toBeGreaterThan(-1);
|
|
109
|
+
expect(at - load).toBeLessThan(400);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('refreshes the catalog caches through the provider the mutation already holds', () => {
|
|
113
|
+
expect(SRC).not.toMatch(/refreshIntegrationCatalogCaches\(user\);/);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* everything.txt C6: pausing a connection must actually pause it — and resuming must put back
|
|
3
|
+
* what pause took, not "everything".
|
|
4
|
+
*
|
|
5
|
+
* Each test below pins a way this went wrong or would go wrong. The two that matter most are
|
|
6
|
+
* `DecidePauseWrite` (a second pause must not erase the first pause's record) and the two guards
|
|
7
|
+
* in `DecideSchedulesToResume` (never resurrect a schedule the operator turned off themselves).
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import {
|
|
11
|
+
DecideSchedulesToPause,
|
|
12
|
+
DecidePauseWrite,
|
|
13
|
+
DecideSchedulesToResume,
|
|
14
|
+
ReadPausedSchedules,
|
|
15
|
+
WritePausedSchedules,
|
|
16
|
+
DescribeCancelScope,
|
|
17
|
+
DescribeCancelOutcome,
|
|
18
|
+
DescribePauseOutcome,
|
|
19
|
+
PAUSED_SCHEDULES_KEY,
|
|
20
|
+
type ScheduleJobState,
|
|
21
|
+
type PausedScheduleRecord,
|
|
22
|
+
} from '../integration/ConnectionPause.js';
|
|
23
|
+
|
|
24
|
+
const job = (ID: string, Status: string | null, Kind: 'sync' | 'discovery' = 'sync'): ScheduleJobState =>
|
|
25
|
+
({ ID, Status, Kind });
|
|
26
|
+
|
|
27
|
+
describe('DecideSchedulesToPause', () => {
|
|
28
|
+
it('moves only the Active jobs', () => {
|
|
29
|
+
const out = DecideSchedulesToPause([job('a', 'Active'), job('b', 'Paused', 'discovery'), job('c', 'Active', 'discovery')]);
|
|
30
|
+
expect(out.map(r => r.ID)).toEqual(['a', 'c']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('leaves an already-Paused job out of the record, so resume cannot turn it on', () => {
|
|
34
|
+
// The whole point: 'b' was off before the pause. If it appeared here, resume would
|
|
35
|
+
// silently start a schedule the operator had deliberately stopped.
|
|
36
|
+
const out = DecideSchedulesToPause([job('b', 'Paused')]);
|
|
37
|
+
expect(out).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('ignores statuses that are neither Active nor Paused', () => {
|
|
41
|
+
expect(DecideSchedulesToPause([job('x', 'Disabled'), job('y', null)])).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('carries the kind through for the operator message', () => {
|
|
45
|
+
expect(DecideSchedulesToPause([job('a', 'Active', 'discovery')])).toEqual([{ ID: 'a', Kind: 'discovery' }]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('DecidePauseWrite — the second pause must not erase the first', () => {
|
|
50
|
+
it('writes the record on a first pause', () => {
|
|
51
|
+
expect(DecidePauseWrite([], [{ ID: 'a', Kind: 'sync' }])).toEqual([{ ID: 'a', Kind: 'sync' }]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('REFUSES to overwrite a stored record with the empty set a re-pause produces', () => {
|
|
55
|
+
// Deactivate an already-deactivated connection: every job is Paused, so nothing is
|
|
56
|
+
// "moved" and newlyPaused is []. Writing that would lose the record and strand both
|
|
57
|
+
// schedules off forever, with no error anywhere.
|
|
58
|
+
const stored: PausedScheduleRecord[] = [{ ID: 'a', Kind: 'sync' }, { ID: 'b', Kind: 'discovery' }];
|
|
59
|
+
expect(DecidePauseWrite(stored, [])).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('adds what a re-pause DID move to the stored record, keeping the stored entries as they were', () => {
|
|
63
|
+
// A schedule created or switched back on while paused, or a record a resume could not
|
|
64
|
+
// clear. Dropping the newly paused job would leave it paused with nothing to restore it.
|
|
65
|
+
const stored: PausedScheduleRecord[] = [{ ID: 'a', Kind: 'sync' }];
|
|
66
|
+
expect(DecidePauseWrite(stored, [{ ID: 'c', Kind: 'discovery' }])).toEqual([
|
|
67
|
+
{ ID: 'a', Kind: 'sync' },
|
|
68
|
+
{ ID: 'c', Kind: 'discovery' },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('writes nothing when everything the re-pause moved is already recorded, whatever the id casing', () => {
|
|
73
|
+
const stored: PausedScheduleRecord[] = [{ ID: 'abc-1', Kind: 'sync' }];
|
|
74
|
+
expect(DecidePauseWrite(stored, [{ ID: 'ABC-1', Kind: 'sync' }])).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('writes nothing when there is neither a stored record nor anything moved', () => {
|
|
78
|
+
expect(DecidePauseWrite([], [])).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('DecideSchedulesToResume', () => {
|
|
83
|
+
it('restores exactly the recorded jobs', () => {
|
|
84
|
+
const stored: PausedScheduleRecord[] = [{ ID: 'a', Kind: 'sync' }];
|
|
85
|
+
expect(DecideSchedulesToResume(stored, [job('a', 'Paused'), job('b', 'Paused')])).toEqual(['a']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('never touches a job that was NOT recorded, however it looks now', () => {
|
|
89
|
+
// 'b' is Paused and looks identical to 'a'. Only the record distinguishes them.
|
|
90
|
+
const out = DecideSchedulesToResume([{ ID: 'a', Kind: 'sync' }], [job('a', 'Paused'), job('b', 'Paused')]);
|
|
91
|
+
expect(out).not.toContain('b');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('does not resurrect a recorded job that has since been deleted', () => {
|
|
95
|
+
expect(DecideSchedulesToResume([{ ID: 'gone', Kind: 'sync' }], [job('a', 'Paused')])).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('does not rewrite a recorded job someone already set back to Active', () => {
|
|
99
|
+
expect(DecideSchedulesToResume([{ ID: 'a', Kind: 'sync' }], [job('a', 'Active')])).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns nothing when the record is empty', () => {
|
|
103
|
+
expect(DecideSchedulesToResume([], [job('a', 'Paused')])).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Configuration round-trip', () => {
|
|
108
|
+
it('preserves unrelated keys when writing', () => {
|
|
109
|
+
const cfg = JSON.stringify({ discoveryMaxRecords: 50, catalogSource: 'perConnection' });
|
|
110
|
+
const out = JSON.parse(WritePausedSchedules(cfg, [{ ID: 'a', Kind: 'sync' }]));
|
|
111
|
+
expect(out.discoveryMaxRecords).toBe(50);
|
|
112
|
+
expect(out.catalogSource).toBe('perConnection');
|
|
113
|
+
expect(out[PAUSED_SCHEDULES_KEY]).toEqual([{ ID: 'a', Kind: 'sync' }]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('removes the key entirely when the record is cleared, rather than leaving []', () => {
|
|
117
|
+
const cfg = WritePausedSchedules(JSON.stringify({ a: 1 }), [{ ID: 'x', Kind: 'sync' }]);
|
|
118
|
+
const cleared = JSON.parse(WritePausedSchedules(cfg, []));
|
|
119
|
+
expect(PAUSED_SCHEDULES_KEY in cleared).toBe(false);
|
|
120
|
+
expect(cleared.a).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('round-trips', () => {
|
|
124
|
+
const rec: PausedScheduleRecord[] = [{ ID: 'a', Kind: 'sync' }, { ID: 'b', Kind: 'discovery' }];
|
|
125
|
+
expect(ReadPausedSchedules(WritePausedSchedules(null, rec))).toEqual(rec);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('reads nothing from null, junk, an array, or a non-array value', () => {
|
|
129
|
+
expect(ReadPausedSchedules(null)).toEqual([]);
|
|
130
|
+
expect(ReadPausedSchedules('not json {')).toEqual([]);
|
|
131
|
+
expect(ReadPausedSchedules('[1,2,3]')).toEqual([]);
|
|
132
|
+
expect(ReadPausedSchedules(JSON.stringify({ [PAUSED_SCHEDULES_KEY]: 'nope' }))).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('drops malformed entries instead of yielding an id-less record', () => {
|
|
136
|
+
// An entry with no ID would produce a restore target of undefined.
|
|
137
|
+
const cfg = JSON.stringify({ [PAUSED_SCHEDULES_KEY]: [{ Kind: 'sync' }, null, 7, { ID: '', Kind: 'sync' }, { ID: 'ok' }] });
|
|
138
|
+
expect(ReadPausedSchedules(cfg)).toEqual([{ ID: 'ok', Kind: 'sync' }]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('replaces, rather than refuses, when Configuration is unparseable', () => {
|
|
142
|
+
const out = JSON.parse(WritePausedSchedules('{broken', [{ ID: 'a', Kind: 'sync' }]));
|
|
143
|
+
expect(out[PAUSED_SCHEDULES_KEY]).toEqual([{ ID: 'a', Kind: 'sync' }]);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('DescribeCancelScope — reports what is TRUE, not what is reassuring', () => {
|
|
148
|
+
it('says none when nothing is live', () => {
|
|
149
|
+
expect(DescribeCancelScope({ liveRunPresent: false, durableRequestRecorded: false, runningInThisProcess: true }))
|
|
150
|
+
.toBe('none');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('prefers durable when the request was recorded', () => {
|
|
154
|
+
expect(DescribeCancelScope({ liveRunPresent: true, durableRequestRecorded: true, runningInThisProcess: false }))
|
|
155
|
+
.toBe('durable');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('falls back to this-process when only the in-process registry knows', () => {
|
|
159
|
+
expect(DescribeCancelScope({ liveRunPresent: true, durableRequestRecorded: false, runningInThisProcess: true }))
|
|
160
|
+
.toBe('this-process');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('admits unknown when a run is live and neither signal reached it', () => {
|
|
164
|
+
// The tenant has no CancelRequestedAt column and the owner is another process.
|
|
165
|
+
expect(DescribeCancelScope({ liveRunPresent: true, durableRequestRecorded: false, runningInThisProcess: false }))
|
|
166
|
+
.toBe('unknown');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('never promises a stop it cannot deliver', () => {
|
|
170
|
+
expect(DescribeCancelOutcome('unknown')).not.toMatch(/will stop/i);
|
|
171
|
+
expect(DescribeCancelOutcome('unknown')).toMatch(/no new sync will start/i);
|
|
172
|
+
expect(DescribeCancelOutcome('durable')).toMatch(/will stop after the current batch/i);
|
|
173
|
+
expect(DescribeCancelOutcome('none')).toBe('');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('DescribePauseOutcome', () => {
|
|
178
|
+
it('names both schedules when both moved', () => {
|
|
179
|
+
const msg = DescribePauseOutcome([{ ID: 'a', Kind: 'sync' }, { ID: 'b', Kind: 'discovery' }], false);
|
|
180
|
+
expect(msg).toMatch(/data sync and schema refresh/);
|
|
181
|
+
expect(msg).toMatch(/schedules\./);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('is singular for one', () => {
|
|
185
|
+
expect(DescribePauseOutcome([{ ID: 'a', Kind: 'discovery' }], false)).toBe('Paused the schema refresh schedule.');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('distinguishes "already paused" from "nothing to pause"', () => {
|
|
189
|
+
expect(DescribePauseOutcome([], true)).toMatch(/already paused/i);
|
|
190
|
+
expect(DescribePauseOutcome([], false)).toMatch(/No active schedules/i);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* everything.txt C6 — "deactivate" has to stop the connection, not just flag it.
|
|
3
|
+
*
|
|
4
|
+
* Before this, IsActive=false stopped syncs (the engine checks it) and nothing else. The cron
|
|
5
|
+
* jobs kept firing, DISCOVERY was never gated at all — so a paused connection went on rescanning
|
|
6
|
+
* the vendor on a timer — and an in-flight sync ran to the end. Each test below pins one of the
|
|
7
|
+
* three things that now have to happen, plus the two things that must NOT.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const SRC = readFileSync(join(__dirname, '..', 'resolvers', 'IntegrationDiscoveryResolver.ts'), 'utf-8');
|
|
14
|
+
|
|
15
|
+
function methodBody(name: string): string {
|
|
16
|
+
const i = SRC.indexOf(`async ${name}(`);
|
|
17
|
+
expect(i, `${name} not found`).toBeGreaterThan(-1);
|
|
18
|
+
const rest = SRC.slice(i);
|
|
19
|
+
const end = rest.search(/\n @(Query|Mutation)\(/);
|
|
20
|
+
return end === -1 ? rest : rest.slice(0, end);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('IntegrationDeactivateConnection — pause means pause', () => {
|
|
24
|
+
const body = () => methodBody('IntegrationDeactivateConnection');
|
|
25
|
+
|
|
26
|
+
it('writes IsActive=false before anything else can fail', () => {
|
|
27
|
+
// The flag is what every other actor reads. If a later step throws, the connection must
|
|
28
|
+
// still be paused — a half-done pause that left IsActive true would be the worst outcome.
|
|
29
|
+
const b = body();
|
|
30
|
+
const flag = b.indexOf('ci.IsActive = false');
|
|
31
|
+
const schedules = b.indexOf('findScheduledJobsForConnection');
|
|
32
|
+
expect(flag).toBeGreaterThan(-1);
|
|
33
|
+
expect(schedules).toBeGreaterThan(flag);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('pauses the scheduled jobs', () => {
|
|
37
|
+
expect(body()).toMatch(/DecideSchedulesToPause/);
|
|
38
|
+
expect(body()).toMatch(/setScheduledJobStatus\([^)]*'Paused'/s);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('records what it paused through the guard that survives a second pause', () => {
|
|
42
|
+
// DecidePauseWrite is the only thing standing between a re-pause and a lost record.
|
|
43
|
+
expect(body()).toMatch(/DecidePauseWrite/);
|
|
44
|
+
expect(body()).toMatch(/WritePausedSchedules/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('asks an in-flight sync to stop', () => {
|
|
48
|
+
expect(body()).toMatch(/CancelSyncAsync/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('reports how far the cancel actually reached instead of assuming', () => {
|
|
52
|
+
const b = body();
|
|
53
|
+
expect(b).toMatch(/DescribeCancelScope/);
|
|
54
|
+
expect(b).toMatch(/IsSyncRunningInThisProcess/);
|
|
55
|
+
expect(b).toMatch(/CancelScope/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('NEVER touches entity maps — those are the user\'s table selection, not pause state', () => {
|
|
59
|
+
const b = body();
|
|
60
|
+
expect(b).not.toMatch(/DisableUnselectedEntityMaps/);
|
|
61
|
+
expect(b).not.toMatch(/SetEntityMapEnabled/);
|
|
62
|
+
expect(b).not.toMatch(/Company Integration Entity Maps/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns the richer type, so a caller can tell a real stop from a hope', () => {
|
|
66
|
+
expect(SRC).toMatch(/@Mutation\(\(\) => DeactivateConnectionOutput\)\s*\n\s*async IntegrationDeactivateConnection/);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('IntegrationReactivateConnection — restore exactly what pause took', () => {
|
|
71
|
+
const body = () => methodBody('IntegrationReactivateConnection');
|
|
72
|
+
|
|
73
|
+
it('restores through the record, not by activating everything', () => {
|
|
74
|
+
const b = body();
|
|
75
|
+
expect(b).toMatch(/DecideSchedulesToResume/);
|
|
76
|
+
// The tell for the bug this avoids: no unconditional sweep to Active.
|
|
77
|
+
expect(b).not.toMatch(/jobs\.map\([^)]*'Active'/s);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('consumes the record so the NEXT pause is not treated as a re-pause', () => {
|
|
81
|
+
// DecidePauseWrite refuses to write when a record is stored. Leaving a consumed record
|
|
82
|
+
// behind would make every future pause a silent no-op.
|
|
83
|
+
expect(body()).toMatch(/WritePausedSchedules\(ci\.Configuration, \[\]\)/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('still does not rescan the source by default', () => {
|
|
87
|
+
// Resuming a connection and rescanning its schema stay separate decisions.
|
|
88
|
+
expect(body()).toMatch(/@Arg\("runSchemaRefresh"[^\n]*defaultValue: false/);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('a Save() that returns false is never mistaken for one that worked', () => {
|
|
93
|
+
// Save() reports a logical failure by returning false, not by throwing. An unchecked save of the
|
|
94
|
+
// pause record strands the schedules it paused; an unchecked save of the cleared record makes the
|
|
95
|
+
// next pause record nothing.
|
|
96
|
+
for (const name of ['IntegrationDeactivateConnection', 'IntegrationReactivateConnection']) {
|
|
97
|
+
it(`${name} checks every ci.Save()`, () => {
|
|
98
|
+
const b = methodBody(name);
|
|
99
|
+
const all = b.match(/ci\.Save\(\)/g) ?? [];
|
|
100
|
+
const checked = b.match(/if \(!await ci\.Save\(\)\)/g) ?? [];
|
|
101
|
+
expect(all.length).toBeGreaterThan(1);
|
|
102
|
+
expect(checked.length).toBe(all.length);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
it('pause saves its record BEFORE it moves any job, and moves nothing if that save fails', () => {
|
|
107
|
+
const b = methodBody('IntegrationDeactivateConnection');
|
|
108
|
+
const record = b.indexOf('ci.Configuration = WritePausedSchedules(');
|
|
109
|
+
const recordSave = b.indexOf('if (!await ci.Save())', record);
|
|
110
|
+
const firstMove = b.indexOf("setScheduledJobStatus(rec.ID, 'Paused'");
|
|
111
|
+
expect(record).toBeGreaterThan(-1);
|
|
112
|
+
expect(recordSave).toBeGreaterThan(record);
|
|
113
|
+
expect(firstMove).toBeGreaterThan(recordSave);
|
|
114
|
+
expect(b.slice(recordSave, firstMove)).toMatch(/throw new Error/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('resume says so on every reply when the record will not clear', () => {
|
|
118
|
+
const b = methodBody('IntegrationReactivateConnection');
|
|
119
|
+
expect(b).toMatch(/scheduleNote = ' The record of paused schedules could not be cleared/);
|
|
120
|
+
const carried = (b.match(/\+ scheduleNote/g) ?? []).length + (b.match(/\$\{scheduleNote\}/g) ?? []).length;
|
|
121
|
+
expect(carried).toBe(4);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('the connection job finder escapes through the shared helpers', () => {
|
|
126
|
+
it('validates the connection id with sqlUuid and quotes job type ids with EscapeSQLString', () => {
|
|
127
|
+
const i = SRC.indexOf('private async findScheduledJobsForConnection');
|
|
128
|
+
const b = SRC.slice(i, SRC.indexOf('private async describeIfPaused'));
|
|
129
|
+
expect(i).toBeGreaterThan(-1);
|
|
130
|
+
expect(b).not.toMatch(/\.replace\(\/'\/g/);
|
|
131
|
+
expect(b).toMatch(/sqlUuid\(companyIntegrationID, 'companyIntegrationID'\)/);
|
|
132
|
+
expect(b).toMatch(/EscapeSQLString\(id\)/);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('a paused connection is not scanned', () => {
|
|
137
|
+
it('the detached discovery start refuses', () => {
|
|
138
|
+
expect(methodBody('IntegrationStartSchemaRefresh')).toMatch(/describeIfPaused/);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('the synchronous refresh refuses', () => {
|
|
142
|
+
expect(methodBody('IntegrationRefreshConnectorSchema')).toMatch(/describeIfPaused/);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('schema evolution refuses', () => {
|
|
146
|
+
expect(methodBody('IntegrationSchemaEvolution')).toMatch(/describeIfPaused/);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('both lock-taking paths check BEFORE acquiring the maintenance lock, so a refusal leaks no lock', () => {
|
|
150
|
+
for (const name of ['IntegrationRefreshConnectorSchema', 'IntegrationSchemaEvolution']) {
|
|
151
|
+
const b = methodBody(name);
|
|
152
|
+
const gate = b.indexOf('describeIfPaused');
|
|
153
|
+
const lock = b.indexOf('AcquireMaintenanceLock');
|
|
154
|
+
expect(gate, `${name}: gate missing`).toBeGreaterThan(-1);
|
|
155
|
+
expect(lock, `${name}: lock missing`).toBeGreaterThan(-1);
|
|
156
|
+
expect(gate, `${name}: gate must precede the lock`).toBeLessThan(lock);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('the gate fails OPEN — an unreadable connection is not declared paused', () => {
|
|
161
|
+
const i = SRC.indexOf('private async describeIfPaused');
|
|
162
|
+
expect(i).toBeGreaterThan(-1);
|
|
163
|
+
const b = SRC.slice(i, i + 1400);
|
|
164
|
+
expect(b).toMatch(/catch[\s\S]*return null/);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pause and resume, driven through the resolver with a Save() that returns false.
|
|
3
|
+
*
|
|
4
|
+
* Save() reports a logical failure by returning false rather than throwing, so the only way to
|
|
5
|
+
* prove a failed save is handled is to run the mutation against one. ConnectionPauseResolver.test.ts
|
|
6
|
+
* pins the shape of the code; these pin what happens.
|
|
7
|
+
*/
|
|
8
|
+
// type-graphql decorators call `Reflect.getMetadata`, which only exists once this polyfill is
|
|
9
|
+
// loaded. MUST precede any import that pulls in the resolver file.
|
|
10
|
+
import 'reflect-metadata';
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
13
|
+
|
|
14
|
+
vi.mock('type-graphql', () => {
|
|
15
|
+
const noopDecorator = () => () => undefined;
|
|
16
|
+
return {
|
|
17
|
+
Resolver: noopDecorator, Mutation: noopDecorator, Query: noopDecorator, Subscription: noopDecorator,
|
|
18
|
+
ObjectType: noopDecorator, InputType: noopDecorator, Field: noopDecorator, Arg: noopDecorator,
|
|
19
|
+
Args: noopDecorator, Ctx: noopDecorator, PubSub: noopDecorator, Root: noopDecorator,
|
|
20
|
+
Directive: noopDecorator, Authorized: noopDecorator, UseMiddleware: noopDecorator,
|
|
21
|
+
createUnionType: () => class {}, registerEnumType: () => undefined,
|
|
22
|
+
AuthorizationError: class AuthorizationError extends Error {},
|
|
23
|
+
Float: class {}, Int: class {}, ID: class {},
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
vi.mock('../config.js', () => ({ configInfo: { databaseSettings: { requestTimeout: 120_000 } } }));
|
|
28
|
+
|
|
29
|
+
const state = vi.hoisted(() => ({
|
|
30
|
+
/** Every Save() result in order; anything past the end saves. */
|
|
31
|
+
saves: [] as boolean[],
|
|
32
|
+
/** The order things happened in, across the connection row and the jobs. */
|
|
33
|
+
events: [] as string[],
|
|
34
|
+
ci: null as null | Record<string, unknown>,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock('../util.js', async importOriginal => ({
|
|
38
|
+
...(await importOriginal<typeof import('../util.js')>()),
|
|
39
|
+
GetReadWriteProvider: () => ({
|
|
40
|
+
GetEntityObject: async () => state.ci,
|
|
41
|
+
}),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const { IntegrationDiscoveryResolver } = await import('../resolvers/IntegrationDiscoveryResolver.js');
|
|
45
|
+
const { IntegrationEngine } = await import('@memberjunction/integration-engine');
|
|
46
|
+
const { ReadPausedSchedules, WritePausedSchedules } = await import('../integration/ConnectionPause.js');
|
|
47
|
+
|
|
48
|
+
type Job = { ID: string; Status: string; Kind: 'sync' | 'discovery' };
|
|
49
|
+
|
|
50
|
+
function connection(configuration: string | null): Record<string, unknown> {
|
|
51
|
+
const ci: Record<string, unknown> = {
|
|
52
|
+
IsActive: true,
|
|
53
|
+
ScheduleEnabled: true,
|
|
54
|
+
Configuration: configuration,
|
|
55
|
+
LatestResult: { Message: 'the database said no' },
|
|
56
|
+
InnerLoad: async () => true,
|
|
57
|
+
Save: async () => {
|
|
58
|
+
const ok = state.saves.length > 0 ? state.saves.shift()! : true;
|
|
59
|
+
state.events.push(`save:${ok ? 'ok' : 'failed'}:${ReadPausedSchedules(ci.Configuration as string).map(r => r.ID).join(',')}`);
|
|
60
|
+
return ok;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
return ci;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolverWith(jobs: Job[]) {
|
|
67
|
+
return {
|
|
68
|
+
getAuthenticatedUser: () => ({ ID: 'user-1' }),
|
|
69
|
+
findScheduledJobsForConnection: async () => jobs,
|
|
70
|
+
setScheduledJobStatus: async (id: string, status: string) => {
|
|
71
|
+
state.events.push(`job:${id}:${status}`);
|
|
72
|
+
return true;
|
|
73
|
+
},
|
|
74
|
+
formatError: (e: unknown) => String(e),
|
|
75
|
+
startSchemaRefreshPipelineDetached: () => undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ctx = { providers: [] } as never;
|
|
80
|
+
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
state.saves = [];
|
|
83
|
+
state.events = [];
|
|
84
|
+
vi.spyOn(IntegrationEngine, 'GetSyncProgressAsync').mockResolvedValue(null as never);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('IntegrationDeactivateConnection with a failing Save()', () => {
|
|
88
|
+
const jobs: Job[] = [
|
|
89
|
+
{ ID: 'job-sync', Status: 'Active', Kind: 'sync' },
|
|
90
|
+
{ ID: 'job-disc', Status: 'Active', Kind: 'discovery' },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
it('saves the record of what it will pause before it pauses anything', async () => {
|
|
94
|
+
state.ci = connection(null);
|
|
95
|
+
const out = await IntegrationDiscoveryResolver.prototype.IntegrationDeactivateConnection.call(resolverWith(jobs), 'ci-1', ctx);
|
|
96
|
+
expect(out.Success).toBe(true);
|
|
97
|
+
expect(out.SchedulesPaused).toBe(2);
|
|
98
|
+
expect(state.events.slice(0, 4)).toEqual([
|
|
99
|
+
'save:ok:', // IsActive = false
|
|
100
|
+
'save:ok:job-sync,job-disc', // the record, before any job moves
|
|
101
|
+
'job:job-sync:Paused',
|
|
102
|
+
'job:job-disc:Paused',
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('pauses nothing, and says so, when the record will not save', async () => {
|
|
107
|
+
state.ci = connection(null);
|
|
108
|
+
state.saves = [true, false];
|
|
109
|
+
const out = await IntegrationDiscoveryResolver.prototype.IntegrationDeactivateConnection.call(resolverWith(jobs), 'ci-1', ctx);
|
|
110
|
+
expect(out.Success).toBe(true);
|
|
111
|
+
expect(out.SchedulesPaused).toBe(0);
|
|
112
|
+
expect(state.events.filter(e => e.startsWith('job:'))).toEqual([]);
|
|
113
|
+
expect(out.Message).toContain('Could not pause the schedules');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('stops at the flag when IsActive will not save', async () => {
|
|
117
|
+
state.ci = connection(null);
|
|
118
|
+
state.saves = [false];
|
|
119
|
+
const out = await IntegrationDiscoveryResolver.prototype.IntegrationDeactivateConnection.call(resolverWith(jobs), 'ci-1', ctx);
|
|
120
|
+
expect(out.Success).toBe(false);
|
|
121
|
+
expect(state.events).toEqual(['save:failed:']);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('IntegrationReactivateConnection with a failing Save()', () => {
|
|
126
|
+
const paused: Job[] = [{ ID: 'job-sync', Status: 'Paused', Kind: 'sync' }];
|
|
127
|
+
const record = WritePausedSchedules(null, [{ ID: 'job-sync', Kind: 'sync' }]);
|
|
128
|
+
|
|
129
|
+
it('restores the recorded jobs and clears the record', async () => {
|
|
130
|
+
state.ci = connection(record);
|
|
131
|
+
const out = await IntegrationDiscoveryResolver.prototype.IntegrationReactivateConnection.call(resolverWith(paused), 'ci-1', false, false, ctx);
|
|
132
|
+
expect(out).toEqual({ Success: true, Message: 'Reactivated' });
|
|
133
|
+
expect(state.events).toEqual(['save:ok:job-sync', 'job:job-sync:Active', 'save:ok:']);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('says so when the record will not clear, instead of reporting a clean resume', async () => {
|
|
137
|
+
state.ci = connection(record);
|
|
138
|
+
state.saves = [true, false];
|
|
139
|
+
const out = await IntegrationDiscoveryResolver.prototype.IntegrationReactivateConnection.call(resolverWith(paused), 'ci-1', false, false, ctx);
|
|
140
|
+
expect(out.Success).toBe(true);
|
|
141
|
+
expect(out.Message).toContain('could not be cleared');
|
|
142
|
+
});
|
|
143
|
+
});
|