@memberjunction/server 6.1.4 → 6.2.0-edge.1
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/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts +2 -0
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +5 -1
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionManager.js +2 -2
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +8 -0
- package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -1
- package/dist/agentSessions/remoteBrowserGoalEngine.js +21 -5
- package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts +2 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +5 -1
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +2 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +20 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +20 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +8 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +21 -5
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +9 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +22 -10
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +65 -51
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +2 -2
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +2 -2
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +54 -33
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +61 -61
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +4 -0
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +12 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +4 -4
- package/dist/auth/principals.d.ts +2 -0
- package/dist/auth/principals.d.ts.map +1 -1
- package/dist/auth/principals.js +6 -2
- package/dist/auth/principals.js.map +1 -1
- package/dist/cache.d.ts +2 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +3 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +293 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +20 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -19
- package/dist/context.js.map +1 -1
- package/dist/directives/Public.d.ts +2 -0
- package/dist/directives/Public.d.ts.map +1 -1
- package/dist/directives/Public.js +3 -1
- package/dist/directives/Public.js.map +1 -1
- package/dist/directives/RequireSystemUser.d.ts +2 -0
- package/dist/directives/RequireSystemUser.d.ts.map +1 -1
- package/dist/directives/RequireSystemUser.js +3 -1
- package/dist/directives/RequireSystemUser.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +266 -4
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1304 -0
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +8 -2
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +7 -3
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +3 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +8 -4
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +10 -1
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +11 -3
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +27 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +64 -6
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts +2 -0
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +5 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +4 -0
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +10 -2
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/logging/NoLog.d.ts +4 -0
- package/dist/logging/NoLog.d.ts.map +1 -1
- package/dist/logging/NoLog.js +10 -2
- package/dist/logging/NoLog.js.map +1 -1
- package/dist/logging/StartupLogger.d.ts +2 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +5 -1
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/logging/bootAudit.d.ts +11 -7
- package/dist/logging/bootAudit.d.ts.map +1 -1
- package/dist/logging/bootAudit.js +15 -7
- package/dist/logging/bootAudit.js.map +1 -1
- package/dist/logging/boundaryLogPayload.d.ts +4 -0
- package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
- package/dist/logging/boundaryLogPayload.js +5 -1
- package/dist/logging/boundaryLogPayload.js.map +1 -1
- package/dist/logging/secretRedactor.d.ts +2 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -1
- package/dist/logging/secretRedactor.js +8 -4
- package/dist/logging/secretRedactor.js.map +1 -1
- package/dist/logging/shortenForLog.d.ts +2 -0
- package/dist/logging/shortenForLog.d.ts.map +1 -1
- package/dist/logging/shortenForLog.js +7 -3
- package/dist/logging/shortenForLog.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +8 -6
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/metadataRefreshSignal.d.ts +19 -0
- package/dist/metadataRefreshSignal.d.ts.map +1 -0
- package/dist/metadataRefreshSignal.js +45 -0
- package/dist/metadataRefreshSignal.js.map +1 -0
- package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
- package/dist/multiTenancy/index.d.ts +8 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +21 -5
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +14 -10
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
- package/dist/realtimeWidget/WidgetSessionService.js +27 -27
- package/dist/realtimeWidget/host-identity.d.ts +5 -1
- package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
- package/dist/realtimeWidget/host-identity.js +11 -3
- package/dist/realtimeWidget/host-identity.js.map +1 -1
- package/dist/realtimeWidget/index.d.ts +2 -2
- package/dist/realtimeWidget/index.d.ts.map +1 -1
- package/dist/realtimeWidget/index.js +2 -2
- package/dist/realtimeWidget/index.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +16 -4
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +48 -0
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +38 -10
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +3 -3
- package/dist/resolvers/FetchEntityVectorsResolver.js +2 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +3 -3
- package/dist/resolvers/GeoResolver.d.ts +2 -2
- package/dist/resolvers/GeoResolver.js +4 -4
- package/dist/resolvers/GetDataResolver.d.ts +14 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +43 -15
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -2
- package/dist/resolvers/ListOperationsResolver.d.ts +20 -0
- package/dist/resolvers/ListOperationsResolver.d.ts.map +1 -1
- package/dist/resolvers/ListOperationsResolver.js +68 -28
- package/dist/resolvers/ListOperationsResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts +11 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +7 -3
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +2 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +3 -3
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +37 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +101 -11
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -4
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +74 -42
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +2 -2
- package/dist/resolvers/RunTestResolver.js +5 -5
- package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.js +3 -1
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts +1 -0
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +5 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +1 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +53 -4
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
- package/dist/resolvers/adhoc-query-helpers.js +10 -2
- package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
- package/dist/resolvers/artifact-routing.d.ts +8 -8
- package/dist/resolvers/artifact-routing.js +2 -2
- package/dist/resolvers/artifact-routing.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +24 -8
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/resolvers/peaksSidecar.d.ts +4 -0
- package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
- package/dist/resolvers/peaksSidecar.js +10 -2
- package/dist/resolvers/peaksSidecar.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +28 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +20 -4
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/MediaStreamHandler.d.ts +2 -0
- package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
- package/dist/rest/MediaStreamHandler.js +10 -6
- package/dist/rest/MediaStreamHandler.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +15 -3
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts +6 -4
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +15 -11
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
- package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
- package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
- package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
- package/dist/rest/SignatureWebhookHandler.js +5 -1
- package/dist/rest/SignatureWebhookHandler.js.map +1 -1
- package/dist/rest/UploadTokenManager.d.ts +24 -8
- package/dist/rest/UploadTokenManager.d.ts.map +1 -1
- package/dist/rest/UploadTokenManager.js +53 -21
- package/dist/rest/UploadTokenManager.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +20 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +20 -4
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/mediaRange.d.ts +4 -0
- package/dist/rest/mediaRange.d.ts.map +1 -1
- package/dist/rest/mediaRange.js +10 -2
- package/dist/rest/mediaRange.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +4 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +13 -7
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +2 -0
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +5 -1
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +41 -0
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -0
- package/dist/services/MJStorageBlobStore.js +160 -0
- package/dist/services/MJStorageBlobStore.js.map +1 -0
- package/dist/services/TaskGraphActionRunner.d.ts +13 -8
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +16 -15
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -3
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +12 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -8
- package/dist/util.js.map +1 -1
- package/package.json +92 -92
- package/src/__tests__/FileResolverPeaks.test.ts +13 -13
- package/src/__tests__/ListOperationsResolver.test.ts +17 -17
- package/src/__tests__/MediaStreamHandler.test.ts +28 -28
- package/src/__tests__/NoLog.test.ts +11 -11
- package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
- package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
- package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +229 -0
- package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
- package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
- package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
- package/src/__tests__/ResolverBase.isaPromotionCreate.test.ts +412 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +228 -0
- package/src/__tests__/SearchEntitiesResolver.readOnlyFallback.test.ts +78 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
- package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +290 -0
- package/src/__tests__/WidgetSessionService.test.ts +8 -4
- package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
- package/src/__tests__/bootAudit.test.ts +11 -11
- package/src/__tests__/boundaryLogPayload.test.ts +6 -6
- package/src/__tests__/databaseAbstraction.test.ts +12 -6
- package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
- package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
- package/src/__tests__/magicLink.test.ts +73 -134
- package/src/__tests__/magicLinkService.consume.test.ts +224 -0
- package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
- package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
- package/src/__tests__/multiTenancy.security.test.ts +40 -40
- package/src/__tests__/multiTenancy.test.ts +18 -18
- package/src/__tests__/newUsers.test.ts +24 -23
- package/src/__tests__/principals.test.ts +22 -22
- package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
- package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
- package/src/__tests__/secretRedactor.test.ts +12 -12
- package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
- package/src/__tests__/subscriptionRedaction.test.ts +5 -5
- package/src/__tests__/unifiedAuth.test.ts +15 -9
- package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
- package/src/__tests__/widget.test.ts +45 -45
- package/src/__tests__/widgetGuestElevation.test.ts +7 -7
- package/src/__tests__/widgetHostIdentity.test.ts +14 -14
- package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
- package/src/agentSessions/SessionManager.ts +2 -2
- package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
- package/src/auth/AuthProviderCatalogRouter.ts +6 -1
- package/src/auth/__tests__/backward-compatibility.test.ts +3 -3
- package/src/auth/exampleNewUserSubClass.ts +2 -2
- package/src/auth/index.ts +43 -28
- package/src/auth/initializeProviders.ts +25 -5
- package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
- package/src/auth/magicLink/MagicLinkService.ts +71 -52
- package/src/auth/magicLink/index.ts +7 -7
- package/src/auth/magicLink/magicLinkCore.ts +96 -64
- package/src/auth/magicLink/redeemLanding.ts +14 -4
- package/src/auth/magicLink/types.ts +54 -54
- package/src/auth/newUsers.ts +1 -1
- package/src/auth/principals.ts +11 -2
- package/src/cache.ts +4 -1
- package/src/config.ts +14 -4
- package/src/context.ts +37 -24
- package/src/directives/Public.ts +4 -1
- package/src/directives/RequireSystemUser.ts +4 -1
- package/src/generated/graphql-schemas/__mj.ts +933 -5
- package/src/generic/CacheInvalidationResolver.ts +12 -4
- package/src/generic/FireAndForgetHeartbeat.ts +19 -14
- package/src/generic/PushStatusResolver.ts +21 -7
- package/src/generic/ResolverBase.ts +66 -5
- package/src/generic/RunViewResolver.ts +22 -22
- package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
- package/src/index.ts +34 -7
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/integration/EntityMapLifecycle.ts +20 -2
- package/src/logging/NoLog.ts +16 -2
- package/src/logging/StartupLogger.ts +6 -1
- package/src/logging/bootAudit.ts +26 -16
- package/src/logging/boundaryLogPayload.ts +8 -1
- package/src/logging/secretRedactor.ts +14 -9
- package/src/logging/shortenForLog.ts +8 -3
- package/src/logging/variablesLoggingMiddleware.ts +9 -6
- package/src/metadataRefreshSignal.ts +45 -0
- package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
- package/src/multiTenancy/index.ts +29 -5
- package/src/realtimeWidget/WidgetRouter.ts +15 -10
- package/src/realtimeWidget/WidgetSessionService.ts +76 -76
- package/src/realtimeWidget/host-identity.ts +24 -10
- package/src/realtimeWidget/index.ts +3 -3
- package/src/realtimeWidget/visitorIdentity.ts +39 -8
- package/src/realtimeWidget/widgetCore.ts +75 -10
- package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
- package/src/resolvers/AdhocQueryResolver.ts +3 -3
- package/src/resolvers/AgentSessionResolver.ts +3 -3
- package/src/resolvers/ColorResolver.ts +6 -6
- package/src/resolvers/ComponentRegistryResolver.ts +36 -36
- package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
- package/src/resolvers/FetchEntityVectorsResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +42 -42
- package/src/resolvers/GeoResolver.ts +4 -4
- package/src/resolvers/GetDataResolver.ts +50 -15
- package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
- package/src/resolvers/ListOperationsResolver.ts +78 -28
- package/src/resolvers/MCPResolver.ts +30 -15
- package/src/resolvers/QueryResolver.ts +13 -13
- package/src/resolvers/QuerySystemUserResolver.ts +2 -1
- package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
- package/src/resolvers/RealtimeClientSessionResolver.ts +126 -13
- package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
- package/src/resolvers/RunAIAgentResolver.ts +118 -76
- package/src/resolvers/RunAIPromptResolver.ts +22 -22
- package/src/resolvers/RunTemplateResolver.ts +4 -4
- package/src/resolvers/RunTestResolver.ts +25 -25
- package/src/resolvers/SearchEntitiesResolver.ts +3 -1
- package/src/resolvers/SearchKnowledgeResolver.ts +4 -0
- package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +1 -0
- package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
- package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
- package/src/resolvers/TelemetryResolver.ts +79 -79
- package/src/resolvers/TransactionGroupResolver.ts +57 -4
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
- package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
- package/src/resolvers/adhoc-query-helpers.ts +19 -2
- package/src/resolvers/artifact-routing.ts +19 -19
- package/src/resolvers/meetingRecordingRegistration.ts +44 -10
- package/src/resolvers/peaksSidecar.ts +12 -2
- package/src/rest/EntityCRUDHandler.ts +24 -4
- package/src/rest/MediaStreamHandler.ts +11 -6
- package/src/rest/OAuthCallbackHandler.ts +25 -7
- package/src/rest/RESTEndpointHandler.ts +20 -15
- package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
- package/src/rest/SignatureWebhookHandler.ts +6 -1
- package/src/rest/UploadTokenManager.ts +66 -30
- package/src/rest/ViewOperationsHandler.ts +24 -4
- package/src/rest/mediaRange.ts +16 -6
- package/src/rest/setupRESTEndpoints.ts +24 -12
- package/src/serverExtensionReservedRoots.ts +6 -1
- package/src/services/MJStorageBlobStore.ts +209 -0
- package/src/services/TaskGraphActionRunner.ts +16 -15
- package/src/types.ts +21 -16
- package/src/util.ts +22 -9
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// type-graphql decorators on the resolver need the Reflect.metadata polyfill at import time.
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import type { DatabaseProviderBase, EntitySearchResult, SearchEntityParams } from '@memberjunction/core';
|
|
5
|
+
import { SearchEntitiesResolver } from '../resolvers/SearchEntitiesResolver.js';
|
|
6
|
+
import type { AppContext, ProviderInfo } from '../types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `SearchEntities` must run on a server that has no read-only database login.
|
|
10
|
+
*
|
|
11
|
+
* The read-only pool exists only when `dbReadOnlyUsername`/`dbReadOnlyPassword` are configured,
|
|
12
|
+
* and most deployments leave them unset. The resolver asked for the read-only provider without
|
|
13
|
+
* allowing a fallback, got `null`, and threw inside its own try/catch — so every client-side
|
|
14
|
+
* `SearchEntity`/`SearchEntities` call over GraphQL came back `Success: false` with no results.
|
|
15
|
+
* Nothing surfaced it: the client maps a failed batch to empty groups, which looks exactly like
|
|
16
|
+
* "no matches". Found by IT52 (SR2) during the 6.2.0-edge.0 release gate.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const USER_EMAIL = 'searcher@test';
|
|
20
|
+
|
|
21
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({
|
|
22
|
+
UserCache: {
|
|
23
|
+
Instance: { get Users() { return [{ ID: 'u-1', Email: USER_EMAIL }]; } },
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const HIT: EntitySearchResult = {
|
|
28
|
+
entityRecordDocumentId: null,
|
|
29
|
+
recordId: 'E1238F34-0000-0000-0000-000000000001',
|
|
30
|
+
score: 1,
|
|
31
|
+
matchType: 'lexical',
|
|
32
|
+
components: { lexical: 1 },
|
|
33
|
+
} as EntitySearchResult;
|
|
34
|
+
|
|
35
|
+
/** A database provider whose search returns one hit per requested entity. */
|
|
36
|
+
function searchingProvider(): DatabaseProviderBase & { SearchEntities: ReturnType<typeof vi.fn> } {
|
|
37
|
+
const search = vi.fn(async (params: SearchEntityParams[]) => params.map(() => [HIT]));
|
|
38
|
+
return { SearchEntities: search } as unknown as DatabaseProviderBase & { SearchEntities: typeof search };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function contextWith(providers: ProviderInfo[]): AppContext {
|
|
42
|
+
return { providers, userPayload: { email: USER_EMAIL } } as unknown as AppContext;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const PARAMS = [{ EntityName: 'MJ: Entities', SearchText: 'MJ: Users', Mode: 'lexical' }];
|
|
46
|
+
|
|
47
|
+
describe('SearchEntitiesResolver — provider selection', () => {
|
|
48
|
+
it('searches on the read-write provider when no read-only pool is configured', async () => {
|
|
49
|
+
const readWrite = searchingProvider();
|
|
50
|
+
|
|
51
|
+
const result = await new SearchEntitiesResolver().SearchEntities(
|
|
52
|
+
contextWith([{ provider: readWrite, type: 'Read-Write' }]),
|
|
53
|
+
PARAMS as never,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(result.ErrorMessage).toBeUndefined();
|
|
57
|
+
expect(result.Success).toBe(true);
|
|
58
|
+
expect(readWrite.SearchEntities).toHaveBeenCalledTimes(1);
|
|
59
|
+
expect(result.Groups[0].Results.map(r => r.RecordID)).toEqual(['E1238F34-0000-0000-0000-000000000001']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('still prefers the read-only provider when one is configured', async () => {
|
|
63
|
+
const readWrite = searchingProvider();
|
|
64
|
+
const readOnly = searchingProvider();
|
|
65
|
+
|
|
66
|
+
const result = await new SearchEntitiesResolver().SearchEntities(
|
|
67
|
+
contextWith([
|
|
68
|
+
{ provider: readWrite, type: 'Read-Write' },
|
|
69
|
+
{ provider: readOnly, type: 'Read-Only' },
|
|
70
|
+
]),
|
|
71
|
+
PARAMS as never,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(result.Success).toBe(true);
|
|
75
|
+
expect(readOnly.SearchEntities).toHaveBeenCalledTimes(1);
|
|
76
|
+
expect(readWrite.SearchEntities).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end-in-miniature regression test for #4794.
|
|
3
|
+
*
|
|
4
|
+
* The bug: `BuildDurableDeferral` persisted a durable entity action's redacted params as a
|
|
5
|
+
* `LoggedParam[]` ARRAY (`RedactParamsToJSON`'s shape), while `TaskGraphActionRunner.buildParams`
|
|
6
|
+
* reads `Task.InputPayload` back as a name→value RECORD. Every released build with durable dispatch
|
|
7
|
+
* (v6.1.0 onward, including 6.1.4) hit the same real-world failure: `TaskGraphDispatcher`'s
|
|
8
|
+
* `mergedPayload` only merges plain objects, so it silently dropped the array before `buildParams`
|
|
9
|
+
* ever saw it, and the action ran with NONE of its declared inputs. `buildParams` itself has no such
|
|
10
|
+
* guard — `Object.assign(merged, arrayValue)` treats an array's indices as keys — so params named
|
|
11
|
+
* `'0'`, `'1'`, ... would only appear if the array reached it directly, bypassing the dispatcher's
|
|
12
|
+
* drop. That narrower path is exactly what this test exercises, by calling `RunActionForTask`
|
|
13
|
+
* on its own rather than through the dispatcher.
|
|
14
|
+
*
|
|
15
|
+
* This test proves the whole pipe, not just the writer or the reader in isolation: build the
|
|
16
|
+
* payload exactly as the deferral does (`RedactParamsToRecord`), push it through the same
|
|
17
|
+
* `JSON.stringify` / `JSON.parse` round trip `TaskGraphService` performs to persist and reload
|
|
18
|
+
* `Task.InputPayload`, then run it through the real `TaskGraphActionRunner` and assert the action
|
|
19
|
+
* receives its declared parameters BY NAME.
|
|
20
|
+
*/
|
|
21
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
22
|
+
|
|
23
|
+
vi.mock('@memberjunction/actions', () => ({
|
|
24
|
+
ActionEngineServer: {
|
|
25
|
+
Instance: {
|
|
26
|
+
Config: vi.fn(),
|
|
27
|
+
Actions: [{ ID: 'A1', Name: 'Log Activity' }],
|
|
28
|
+
RunAction: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
import { ActionEngineServer } from '@memberjunction/actions';
|
|
34
|
+
import { RedactParamsToRecord, ActionParam } from '@memberjunction/actions-base';
|
|
35
|
+
import type { MJActionExecutionLogEntity, MJActionParamEntity, MJEntityActionParamEntity } from '@memberjunction/core-entities';
|
|
36
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
37
|
+
import type { TaskActionRunParams } from '@memberjunction/task-graph';
|
|
38
|
+
import { TaskGraphActionRunner } from '../services/TaskGraphActionRunner';
|
|
39
|
+
|
|
40
|
+
// ── Test fixture builders — same shape as packages/Actions/Base/src/__tests__/ParamRedaction.test.ts ──
|
|
41
|
+
|
|
42
|
+
function actionParam(name: string, value: unknown): ActionParam {
|
|
43
|
+
return { Name: name, Value: value, Type: 'Input' } as ActionParam;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function definition(id: string, name: string, logValue: boolean = true): MJActionParamEntity {
|
|
47
|
+
return { ID: id, Name: name, LogValue: logValue } as MJActionParamEntity;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function binding(actionParamID: string, valueType: string, logValue: boolean | null = null): MJEntityActionParamEntity {
|
|
51
|
+
return { ActionParamID: actionParamID, ValueType: valueType, LogValue: logValue } as MJEntityActionParamEntity;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The declared parameters for the durable entity action under test: three that log normally, and
|
|
55
|
+
// one ("InternalNotes") whose binding opts out of logging (rule 2 in ParamRedaction.ts).
|
|
56
|
+
const RUNTIME_PARAMS: ActionParam[] = [
|
|
57
|
+
actionParam('TypeCode', 'SystemEvent'),
|
|
58
|
+
actionParam('Title', 'Person created'),
|
|
59
|
+
actionParam('RecordID', 'REC-123'),
|
|
60
|
+
actionParam('InternalNotes', 'do not persist this'),
|
|
61
|
+
];
|
|
62
|
+
const ACTION_PARAMS: MJActionParamEntity[] = [
|
|
63
|
+
definition('p1', 'TypeCode'),
|
|
64
|
+
definition('p2', 'Title'),
|
|
65
|
+
definition('p3', 'RecordID'),
|
|
66
|
+
definition('p4', 'InternalNotes'),
|
|
67
|
+
];
|
|
68
|
+
const ENTITY_ACTION_PARAMS: MJEntityActionParamEntity[] = [binding('p4', 'Static', false)];
|
|
69
|
+
|
|
70
|
+
function baseRunParams(inputPayload: unknown): TaskActionRunParams {
|
|
71
|
+
return {
|
|
72
|
+
TaskID: 'T1',
|
|
73
|
+
ActionID: 'A1',
|
|
74
|
+
InputPayload: inputPayload,
|
|
75
|
+
DependencyOutputs: new Map(),
|
|
76
|
+
Provider: {} as IMetadataProvider,
|
|
77
|
+
ContextUser: {} as UserInfo,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe('TaskGraphActionRunner — durable payload round-trips by param name (#4794)', () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
// vitest's `restoreMocks: true` clears mock implementations before every test, including
|
|
84
|
+
// the ones assigned inside the `vi.mock` factory above — so the return value is (re)armed here.
|
|
85
|
+
vi.mocked(ActionEngineServer.Instance.Config).mockResolvedValue(undefined);
|
|
86
|
+
vi.mocked(ActionEngineServer.Instance.RunAction, { partial: true }).mockResolvedValue({
|
|
87
|
+
Success: true,
|
|
88
|
+
Params: [],
|
|
89
|
+
LogEntry: { ID: 'L1' } as MJActionExecutionLogEntity,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('delivers RedactParamsToRecord\'s output to the action by name, after a JSON round trip', async () => {
|
|
94
|
+
// Exactly what the deferral writes, then exactly what TaskGraphService's JSON column does to it.
|
|
95
|
+
const stored = JSON.parse(JSON.stringify(RedactParamsToRecord(RUNTIME_PARAMS, ACTION_PARAMS, ENTITY_ACTION_PARAMS)));
|
|
96
|
+
|
|
97
|
+
const result = await new TaskGraphActionRunner().RunActionForTask(baseRunParams(stored));
|
|
98
|
+
|
|
99
|
+
expect(result.Success).toBe(true);
|
|
100
|
+
expect(result.ActionLogID).toBe('L1');
|
|
101
|
+
|
|
102
|
+
const runActionCall = vi.mocked(ActionEngineServer.Instance.RunAction).mock.calls[0][0];
|
|
103
|
+
const sentParams = runActionCall.Params as ActionParam[];
|
|
104
|
+
|
|
105
|
+
// The three logged declared names arrive, order-insensitive, with their values intact.
|
|
106
|
+
expect(sentParams.map((p) => p.Name).sort()).toEqual(['RecordID', 'Title', 'TypeCode']);
|
|
107
|
+
expect(sentParams.find((p) => p.Name === 'TypeCode')?.Value).toBe('SystemEvent');
|
|
108
|
+
expect(sentParams.find((p) => p.Name === 'Title')?.Value).toBe('Person created');
|
|
109
|
+
expect(sentParams.find((p) => p.Name === 'RecordID')?.Value).toBe('REC-123');
|
|
110
|
+
|
|
111
|
+
// The binding-suppressed parameter never reaches the action — absent, not present-with-undefined.
|
|
112
|
+
expect(sentParams.some((p) => p.Name === 'InternalNotes')).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('does not turn dependency outputs into params named by the upstream task ID', async () => {
|
|
116
|
+
// The dispatcher keys DependencyOutputs by the upstream task's ID and has ALREADY merged their
|
|
117
|
+
// values into InputPayload (TaskGraphDispatcher.mergedPayload). A runner that also spread the
|
|
118
|
+
// map handed the action one extra param per dependency, named by a GUID and holding that
|
|
119
|
+
// task's whole output — which then landed, unredacted, in ActionExecutionLog.Params.
|
|
120
|
+
const upstreamID = '20924BD7-5128-4150-8404-FB44A55AEA0F';
|
|
121
|
+
const params = baseRunParams({ EntityName: 'MJ: Entities', Record: { ID: 'R1', Name: 'MJ: Tags' } });
|
|
122
|
+
params.DependencyOutputs = new Map([[upstreamID, { Record: { ID: 'R1', Name: 'MJ: Tags' } }]]);
|
|
123
|
+
|
|
124
|
+
await new TaskGraphActionRunner().RunActionForTask(params);
|
|
125
|
+
|
|
126
|
+
const sentParams = vi.mocked(ActionEngineServer.Instance.RunAction).mock.calls[0][0].Params as ActionParam[];
|
|
127
|
+
expect(sentParams.map((p) => p.Name).sort()).toEqual(['EntityName', 'Record']);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -19,7 +19,7 @@ vi.mock('@memberjunction/core', () => ({ LogError: vi.fn() }));
|
|
|
19
19
|
import {
|
|
20
20
|
TASK_GRAPH_FRAMES_TOPIC,
|
|
21
21
|
TaskGraphFrameBroadcaster,
|
|
22
|
-
|
|
22
|
+
TaskGraphFrameFilter,
|
|
23
23
|
type TaskGraphFramePayload,
|
|
24
24
|
} from '../resolvers/TaskGraphFrameResolver';
|
|
25
25
|
import type { TaskGraphFrame } from '@memberjunction/task-graph';
|
|
@@ -44,7 +44,7 @@ const payload = (over: Partial<TaskGraphFramePayload> = {}): TaskGraphFramePaylo
|
|
|
44
44
|
const context = (userId: string | null) => (userId ? { userPayload: { userRecord: { ID: userId } } } : {});
|
|
45
45
|
|
|
46
46
|
const check = (over: Partial<TaskGraphFramePayload> = {}, args = GRAPH, userId: string | null = OWNER) =>
|
|
47
|
-
|
|
47
|
+
TaskGraphFrameFilter({ payload: payload(over), args: { parentTaskId: args }, context: context(userId) });
|
|
48
48
|
|
|
49
49
|
describe('taskGraphFrameFilter — the delivery gate', () => {
|
|
50
50
|
it('delivers a frame for the watched graph to its owner', () => {
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transaction group whose rows are refused server-side must not report success (issue #4309).
|
|
3
|
+
*
|
|
4
|
+
* `BaseEntity.Save()` and `Delete()` do NOT throw on a logical refusal — they return `false`, and
|
|
5
|
+
* the row is never enrolled, because `TransactionGroup.AddTransaction(...)` is called only from
|
|
6
|
+
* inside `ProviderToUse.Save()`/`Delete()`, which a refusal never reaches. `ExecuteTransactionGroup`
|
|
7
|
+
* discarded that boolean, so:
|
|
8
|
+
*
|
|
9
|
+
* - when EVERY row was refused, the server's group was empty, `TransactionGroupBase.Submit()`
|
|
10
|
+
* took its legitimate "nothing to do" branch and returned `true`, and the resolver reported
|
|
11
|
+
* `Success: true` while serialising each entity's never-persisted in-memory state into
|
|
12
|
+
* `ResultsJSON`;
|
|
13
|
+
* - when only SOME rows were refused, the survivors committed and the caller still saw
|
|
14
|
+
* unqualified success.
|
|
15
|
+
*
|
|
16
|
+
* Reproduced live against MJAPI before this fix: an `ExecuteTransactionGroup` whose two
|
|
17
|
+
* `MJ: User Roles` creates both omitted the NOT NULL `RoleID` returned `Success: true` with zero
|
|
18
|
+
* rows written, and `ErrorMessages` already carrying `"Role ID cannot be null"`.
|
|
19
|
+
*
|
|
20
|
+
* WHY THE PREDICATE IS THE BOOLEAN AND NOT "IS THE GROUP EMPTY". A clean row that is not dirty
|
|
21
|
+
* also fails to enrol, and `Save()` returns `true` for it — correctly, since there is nothing to
|
|
22
|
+
* write. Testing the group's size would turn that legitimate no-op into a failure; testing the
|
|
23
|
+
* return value distinguishes "refused" from "nothing to do". The last test pins this.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE RESOLVER RATHER THAN `Submit()`. An empty group genuinely means "nothing to do" for a
|
|
26
|
+
* caller that enrolled nothing — `transaction-groups.TG1` pins that. The resolver is the only
|
|
27
|
+
* layer that still knows WHICH row was refused and why.
|
|
28
|
+
*/
|
|
29
|
+
import 'reflect-metadata';
|
|
30
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
31
|
+
import { BaseEntityResult } from '@memberjunction/core';
|
|
32
|
+
|
|
33
|
+
const logErrorCalls: string[] = [];
|
|
34
|
+
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
35
|
+
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
36
|
+
return { ...actual, LogError: (msg: unknown) => { logErrorCalls.push(String(msg)); } };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
TransactionResolver,
|
|
41
|
+
TransactionInputType,
|
|
42
|
+
TransactionItemInputType,
|
|
43
|
+
TransactionOperationType,
|
|
44
|
+
} from '../resolvers/TransactionGroupResolver.js';
|
|
45
|
+
import type { AppContext } from '../types.js';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A transaction group that records enrolment the way the real one does: `AddTransaction` is
|
|
49
|
+
* reachable only through a provider Save/Delete, so an entity that refuses never appears here, and
|
|
50
|
+
* `Submit()` reproduces `TransactionGroupBase`'s empty-group branch verbatim.
|
|
51
|
+
*/
|
|
52
|
+
class FakeTransactionGroup {
|
|
53
|
+
public Enrolled: string[] = [];
|
|
54
|
+
public SubmitCalled = false;
|
|
55
|
+
public SubmitResult = true;
|
|
56
|
+
|
|
57
|
+
public AddTransaction(label: string): void {
|
|
58
|
+
this.Enrolled.push(label);
|
|
59
|
+
}
|
|
60
|
+
public AddVariable(): void { /* no variables in these tests */ }
|
|
61
|
+
public async Submit(): Promise<boolean> {
|
|
62
|
+
this.SubmitCalled = true;
|
|
63
|
+
// TransactionGroupBase.Submit(): an empty group is "nothing to do" and returns true.
|
|
64
|
+
return this.Enrolled.length === 0 ? true : this.SubmitResult;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What a refusal leaves on `LatestResult` — the shape `MJUserRoleEntityServer.refuse()` registers.
|
|
70
|
+
* A REAL `BaseEntityResult`, so `CompleteMessage` (a prototype getter that folds `Message`, `Error`
|
|
71
|
+
* and `Errors` together) behaves as it does in production rather than as the test wishes.
|
|
72
|
+
*/
|
|
73
|
+
function refusalResult(message: string): BaseEntityResult {
|
|
74
|
+
const result = new BaseEntityResult();
|
|
75
|
+
result.Success = false;
|
|
76
|
+
result.Type = 'create';
|
|
77
|
+
result.Message = message;
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function successResult(): BaseEntityResult {
|
|
82
|
+
const result = new BaseEntityResult();
|
|
83
|
+
result.Success = true;
|
|
84
|
+
result.Type = 'create';
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface FakeEntityOptions {
|
|
89
|
+
/** `false` reproduces a server-side refusal: `Save()`/`Delete()` return false, nothing enrols. */
|
|
90
|
+
accepts: boolean;
|
|
91
|
+
/** True for the not-dirty case: accepted, but nothing to write, so nothing enrols either. */
|
|
92
|
+
clean?: boolean;
|
|
93
|
+
refusalMessage?: string;
|
|
94
|
+
/**
|
|
95
|
+
* What `InnerLoad()` puts on the record, mirroring a real load of an existing row. A `Delete`
|
|
96
|
+
* reports the row it removed, so without this the fake has nothing to report and a test of
|
|
97
|
+
* `ResultsJSON` could not tell an empty payload from a correct one.
|
|
98
|
+
*/
|
|
99
|
+
loadedValues?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The minimum of `BaseEntity` the resolver touches. `Save()`/`Delete()` enrol into the group only
|
|
104
|
+
* when they accept AND there is something to write — exactly where `ProviderToUse.Save()` calls
|
|
105
|
+
* `TransactionGroup.AddTransaction(...)`.
|
|
106
|
+
*/
|
|
107
|
+
class FakeEntity {
|
|
108
|
+
public TransactionGroup: FakeTransactionGroup | null = null;
|
|
109
|
+
public LatestResult: BaseEntityResult | null = null;
|
|
110
|
+
public PrimaryKeys = [{ Name: 'ID' }];
|
|
111
|
+
private values: Record<string, unknown> = {};
|
|
112
|
+
|
|
113
|
+
constructor(private readonly entityName: string, private readonly options: FakeEntityOptions) {}
|
|
114
|
+
|
|
115
|
+
public SetMany(values: Record<string, unknown>): void {
|
|
116
|
+
this.values = { ...this.values, ...values };
|
|
117
|
+
}
|
|
118
|
+
public async InnerLoad(): Promise<boolean> {
|
|
119
|
+
if (this.options.loadedValues) {
|
|
120
|
+
this.values = { ...this.options.loadedValues };
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* `async`, because the REAL one is: `BaseEntity.GetDataObject` returns `Promise<any>`
|
|
126
|
+
* (`baseEntity.ts:3657`). This fake used to be synchronous, and that divergence is precisely
|
|
127
|
+
* why this suite could not see that `ExecuteTransactionGroup` was calling it without `await` —
|
|
128
|
+
* against a sync fake the resolver stored a plain object and `JSON.stringify` produced real
|
|
129
|
+
* data that production never produced. A fake that is more correct than production hides the
|
|
130
|
+
* bug it exists to catch.
|
|
131
|
+
*/
|
|
132
|
+
public async GetDataObject(): Promise<Record<string, unknown>> { return { ...this.values }; }
|
|
133
|
+
public async GetDataObjectJSON(): Promise<string> { return JSON.stringify(this.values); }
|
|
134
|
+
|
|
135
|
+
public async Save(): Promise<boolean> { return this.write(); }
|
|
136
|
+
public async Delete(): Promise<boolean> { return this.write(); }
|
|
137
|
+
|
|
138
|
+
private write(): boolean {
|
|
139
|
+
if (!this.options.accepts) {
|
|
140
|
+
this.LatestResult = refusalResult(this.options.refusalMessage ?? 'refused by a server-side guard');
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (!this.options.clean) {
|
|
144
|
+
this.TransactionGroup?.AddTransaction(this.entityName);
|
|
145
|
+
}
|
|
146
|
+
this.LatestResult = successResult();
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildContext(entities: FakeEntity[]): { context: AppContext; group: FakeTransactionGroup } {
|
|
152
|
+
const group = new FakeTransactionGroup();
|
|
153
|
+
let next = 0;
|
|
154
|
+
const provider = {
|
|
155
|
+
CreateTransactionGroup: async () => group,
|
|
156
|
+
GetEntityObject: async () => entities[next++],
|
|
157
|
+
};
|
|
158
|
+
const context = {
|
|
159
|
+
providers: [{ type: 'Read-Write', provider }],
|
|
160
|
+
userPayload: { userRecord: { Email: 'tester@example.test' } },
|
|
161
|
+
} as unknown as AppContext;
|
|
162
|
+
return { context, group };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildGroupInput(
|
|
166
|
+
count: number,
|
|
167
|
+
operationType: TransactionOperationType = TransactionOperationType.Create
|
|
168
|
+
): TransactionInputType {
|
|
169
|
+
const items: TransactionItemInputType[] = Array.from({ length: count }, () => ({
|
|
170
|
+
EntityName: 'MJ: User Roles',
|
|
171
|
+
EntityObjectJSON: JSON.stringify({ ID: 'a0000000-0000-0000-0000-000000000001', UserID: 'u1' }),
|
|
172
|
+
OperationType: operationType,
|
|
173
|
+
}));
|
|
174
|
+
return { Items: items, Variables: [] };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
describe('ExecuteTransactionGroup — rows refused server-side (issue #4309)', () => {
|
|
178
|
+
beforeEach(() => { logErrorCalls.length = 0; });
|
|
179
|
+
|
|
180
|
+
it('reports failure when EVERY row is refused, instead of the empty group reading as success', async () => {
|
|
181
|
+
const entities = [
|
|
182
|
+
new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Role ID cannot be null' }),
|
|
183
|
+
new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Role ID cannot be null' }),
|
|
184
|
+
];
|
|
185
|
+
const { context, group } = buildContext(entities);
|
|
186
|
+
|
|
187
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(2), context);
|
|
188
|
+
|
|
189
|
+
expect(result.Success).toBe(false);
|
|
190
|
+
expect(group.Enrolled).toHaveLength(0);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('carries each refusal reason back in ErrorMessages, index-aligned with Items', async () => {
|
|
194
|
+
const entities = [
|
|
195
|
+
new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'You may only assign a role that you hold yourself' }),
|
|
196
|
+
];
|
|
197
|
+
const { context } = buildContext(entities);
|
|
198
|
+
|
|
199
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(1), context);
|
|
200
|
+
|
|
201
|
+
expect(result.ErrorMessages[0]).toContain('You may only assign a role that you hold yourself');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('fails a PARTIALLY refused group without submitting, so the surviving rows never commit', async () => {
|
|
205
|
+
const entities = [
|
|
206
|
+
new FakeEntity('MJ: User Roles', { accepts: true }),
|
|
207
|
+
new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Name cannot be null' }),
|
|
208
|
+
];
|
|
209
|
+
const { context, group } = buildContext(entities);
|
|
210
|
+
|
|
211
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(2), context);
|
|
212
|
+
|
|
213
|
+
expect(result.Success).toBe(false);
|
|
214
|
+
expect(group.SubmitCalled).toBe(false);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('reports failure when a Delete is refused', async () => {
|
|
218
|
+
const entities = [
|
|
219
|
+
new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'You may only revoke a role that you hold yourself' }),
|
|
220
|
+
];
|
|
221
|
+
const { context } = buildContext(entities);
|
|
222
|
+
|
|
223
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(
|
|
224
|
+
buildGroupInput(1, TransactionOperationType.Delete), context
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(result.Success).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('logs the refusal, naming the entity and the reason — it was silent in the server log too', async () => {
|
|
231
|
+
const entities = [
|
|
232
|
+
new FakeEntity('MJ: User Roles', { accepts: false, refusalMessage: 'Role ID cannot be null' }),
|
|
233
|
+
];
|
|
234
|
+
const { context } = buildContext(entities);
|
|
235
|
+
|
|
236
|
+
await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(1), context);
|
|
237
|
+
|
|
238
|
+
expect(logErrorCalls.join('\n')).toContain('MJ: User Roles');
|
|
239
|
+
expect(logErrorCalls.join('\n')).toContain('Role ID cannot be null');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('still reports success when every row is accepted', async () => {
|
|
243
|
+
const entities = [
|
|
244
|
+
new FakeEntity('MJ: User Roles', { accepts: true }),
|
|
245
|
+
new FakeEntity('MJ: User Roles', { accepts: true }),
|
|
246
|
+
];
|
|
247
|
+
const { context, group } = buildContext(entities);
|
|
248
|
+
|
|
249
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(2), context);
|
|
250
|
+
|
|
251
|
+
expect(result.Success).toBe(true);
|
|
252
|
+
expect(group.SubmitCalled).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("returns the deleted row's data in ResultsJSON, not an empty object", async () => {
|
|
256
|
+
// `GetDataObject()` is async, and ExecuteTransactionGroup pushed it WITHOUT awaiting, so
|
|
257
|
+
// PrepareReturnValue's Delete branch stringified a Promise. `JSON.stringify(Promise)` is
|
|
258
|
+
// "{}" — no throw, no type error (the array is `any[]`), just a well-formed empty payload
|
|
259
|
+
// on every Delete a transaction group performs, successful ones included.
|
|
260
|
+
//
|
|
261
|
+
// It is not only a lost payload: GraphQLDataProvider's own Delete transaction callback
|
|
262
|
+
// validates the commit with `pk.Value !== results[pk.FieldName]`, so an empty object makes
|
|
263
|
+
// every key mismatch and reports 'Transaction failed to commit' for a delete that DID
|
|
264
|
+
// commit.
|
|
265
|
+
const deletedRow = { ID: 'a0000000-0000-0000-0000-000000000001', UserID: 'u1', RoleID: 'r1' };
|
|
266
|
+
const entities = [new FakeEntity('MJ: User Roles', { accepts: true, loadedValues: deletedRow })];
|
|
267
|
+
const { context } = buildContext(entities);
|
|
268
|
+
|
|
269
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(
|
|
270
|
+
buildGroupInput(1, TransactionOperationType.Delete), context
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
expect(result.Success).toBe(true);
|
|
274
|
+
expect(JSON.parse(result.ResultsJSON[0])).toEqual(deletedRow);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('still reports success when accepted rows were CLEAN — an empty group is not itself a refusal', async () => {
|
|
278
|
+
// A not-dirty row returns true from Save() without enrolling. Testing the group's size
|
|
279
|
+
// rather than the return value would turn this legitimate no-op into a failure.
|
|
280
|
+
const entities = [
|
|
281
|
+
new FakeEntity('MJ: User Roles', { accepts: true, clean: true }),
|
|
282
|
+
];
|
|
283
|
+
const { context, group } = buildContext(entities);
|
|
284
|
+
|
|
285
|
+
const result = await new TransactionResolver().ExecuteTransactionGroup(buildGroupInput(1), context);
|
|
286
|
+
|
|
287
|
+
expect(result.Success).toBe(true);
|
|
288
|
+
expect(group.Enrolled).toHaveLength(0);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -21,12 +21,16 @@ vi.mock('../auth/magicLink/MagicLinkService.js', () => ({
|
|
|
21
21
|
MagicLinkService: class {},
|
|
22
22
|
}));
|
|
23
23
|
vi.mock('../agentSessions/ReturningVisitorRecap.js', () => ({
|
|
24
|
-
|
|
24
|
+
WriteReturningVisitorRecap: async () => undefined,
|
|
25
|
+
get writeReturningVisitorRecap() { return this.WriteReturningVisitorRecap; },
|
|
25
26
|
}));
|
|
26
27
|
vi.mock('../realtimeWidget/visitorIdentity.js', () => ({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
ResolveIdentityByEmail: async () => null,
|
|
29
|
+
get resolveIdentityByEmail() { return this.ResolveIdentityByEmail; },
|
|
30
|
+
MergeVisitorIdentity: async () => 0,
|
|
31
|
+
get mergeVisitorIdentity() { return this.MergeVisitorIdentity; },
|
|
32
|
+
ForgetVisitor: async () => ({ notesArchived: 0, conversationsCleared: 0 }),
|
|
33
|
+
get forgetVisitor() { return this.ForgetVisitor; },
|
|
30
34
|
}));
|
|
31
35
|
|
|
32
36
|
import { WidgetSessionService } from '../realtimeWidget/WidgetSessionService.js';
|
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { ExactTotalFromPage, ResolveAdhocTotalRowCount } from '../resolvers/adhoc-query-helpers';
|
|
3
3
|
|
|
4
4
|
describe('exactTotalFromPage', () => {
|
|
5
5
|
it('returns the row count when not paging (maxRows null) — the query was uncapped', () => {
|
|
6
|
-
expect(
|
|
7
|
-
expect(
|
|
6
|
+
expect(ExactTotalFromPage(0, 47, null)).toBe(47);
|
|
7
|
+
expect(ExactTotalFromPage(0, 0, null)).toBe(0);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
it('returns startRow + pageLength for a SHORT page (fewer rows than the page size)', () => {
|
|
11
|
-
expect(
|
|
12
|
-
expect(
|
|
13
|
-
expect(
|
|
11
|
+
expect(ExactTotalFromPage(0, 47, 100)).toBe(47); // page 1, short → exact total
|
|
12
|
+
expect(ExactTotalFromPage(200, 30, 100)).toBe(230); // page 3, short last page
|
|
13
|
+
expect(ExactTotalFromPage(100, 0, 100)).toBe(100); // empty page past the end
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('returns null for a FULL page — a COUNT is required because more rows may exist', () => {
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
17
|
+
expect(ExactTotalFromPage(0, 100, 100)).toBeNull();
|
|
18
|
+
expect(ExactTotalFromPage(500, 100, 100)).toBeNull();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it('treats an over-full page defensively as "needs count" (should not occur with OFFSET/FETCH)', () => {
|
|
22
|
-
expect(
|
|
22
|
+
expect(ExactTotalFromPage(0, 150, 100)).toBeNull();
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
describe('resolveAdhocTotalRowCount', () => {
|
|
27
27
|
it('reads TotalRowCount from the count recordset', () => {
|
|
28
|
-
expect(
|
|
29
|
-
expect(
|
|
28
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 2087 }], 100)).toBe(2087);
|
|
29
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 0 }], 100)).toBe(0);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it('floors a fractional/stringified count', () => {
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
33
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 42.9 }], 0)).toBe(42);
|
|
34
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: '2087' as unknown as number }], 0)).toBe(2087);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('falls back when the count is absent, empty, null, or malformed', () => {
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
41
|
-
expect(
|
|
42
|
-
expect(
|
|
38
|
+
expect(ResolveAdhocTotalRowCount(null, 100)).toBe(100);
|
|
39
|
+
expect(ResolveAdhocTotalRowCount(undefined, 100)).toBe(100);
|
|
40
|
+
expect(ResolveAdhocTotalRowCount([], 100)).toBe(100);
|
|
41
|
+
expect(ResolveAdhocTotalRowCount([{}], 100)).toBe(100);
|
|
42
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: null }], 100)).toBe(100);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
it('falls back on non-finite or negative counts (never reports a misleading total)', () => {
|
|
46
|
-
expect(
|
|
47
|
-
expect(
|
|
48
|
-
expect(
|
|
46
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: Number.NaN }], 100)).toBe(100);
|
|
47
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: -5 }], 100)).toBe(100);
|
|
48
|
+
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: Infinity }], 100)).toBe(100);
|
|
49
49
|
});
|
|
50
50
|
});
|