@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4
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 +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +4567 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +246 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +25 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +9188 -3649
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +62 -6
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +179 -35
- 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 +37 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -159
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +330 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +439 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -94
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
- package/src/__tests__/MeetingResolver.test.ts +448 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +255 -3
- package/src/context.ts +26 -34
- package/src/generated/graphql-schemas/__mj.ts +22701 -18838
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +210 -36
- package/src/generic/RunViewResolver.ts +37 -2
- package/src/index.ts +236 -170
- package/src/isolatedProvider.ts +22 -0
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +312 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
- package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +556 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +12 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* load-truth-from-DB branch so denied fields hold real values a stripped SetMany never
|
|
12
12
|
* touches.
|
|
13
13
|
*/
|
|
14
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, beforeAll, afterAll } from 'vitest';
|
|
15
15
|
|
|
16
16
|
// ─── Hoisted mocks ────────────────────────────────────────────────────────
|
|
17
17
|
const { mockUserCacheUsers } = vi.hoisted(() => ({
|
|
@@ -116,7 +116,7 @@ vi.mock('@memberjunction/core-entities', () => ({}));
|
|
|
116
116
|
|
|
117
117
|
// ─── Import after mocks ──────────────────────────────────────────────────
|
|
118
118
|
import { ResolverBase } from '../generic/ResolverBase';
|
|
119
|
-
import { EntityInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
|
|
119
|
+
import { EntityInfo, Metadata, RowLevelSecurityFilterInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
|
|
120
120
|
|
|
121
121
|
// ─── Metadata builders (mirrors MJCore's fieldSecurity.enforcement tests) ──
|
|
122
122
|
|
|
@@ -192,8 +192,9 @@ class TestResolver extends ResolverBase {
|
|
|
192
192
|
return this.StripDeniedReadFieldsFromClientInput(entityInfo, userInfo, input, clientNewValues);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
/** The user defaults to HR, whose permission rows carry no row filter; pass null for a missing user. */
|
|
196
|
+
public TestMustLoadTruth(entityInfo: EntityInfo, input: ClientInput, hasDeniedReadFields: boolean, hasNarrowedAuditPayload: boolean, user: UserInfo | null = buildUser([HR_ROLE_ID])): boolean {
|
|
197
|
+
return this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload, user ?? undefined);
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -481,3 +482,85 @@ describe('ResolverBase.MustLoadTruthFromDatabase', () => {
|
|
|
481
482
|
expect(resolver.TestMustLoadTruth(new EntityInfo(tracked), withOldValues(), false, false)).toBe(true);
|
|
482
483
|
});
|
|
483
484
|
});
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* #4919: a caller under an Update row filter must not be hydrated from its own OldValues.
|
|
488
|
+
*
|
|
489
|
+
* The save's post-image RLS check skips its query when no column the filter reads is dirty. Hydrated
|
|
490
|
+
* from OldValues, "dirty" is whatever the client claims: an Intern bound to `Status = 'Draft'` sends
|
|
491
|
+
* Status = 'Published' and claims 'Published' as the old value too, so Status is clean, the check is
|
|
492
|
+
* skipped, and the update moves the row out of the Intern's scope.
|
|
493
|
+
*/
|
|
494
|
+
describe('ResolverBase.MustLoadTruthFromDatabase: Update row filters (#4919)', () => {
|
|
495
|
+
const DRAFTS_ONLY = 'F0000000-0000-0000-0000-000000004919';
|
|
496
|
+
let resolver: TestResolver;
|
|
497
|
+
let savedProvider: typeof Metadata.Provider;
|
|
498
|
+
|
|
499
|
+
beforeAll(() => {
|
|
500
|
+
savedProvider = Metadata.Provider;
|
|
501
|
+
Metadata.Provider = {
|
|
502
|
+
Entities: [],
|
|
503
|
+
RowLevelSecurityFilters: [new RowLevelSecurityFilterInfo({ ID: DRAFTS_ONLY, Name: 'Drafts only', FilterText: "Status = 'Draft'" })],
|
|
504
|
+
} as unknown as typeof Metadata.Provider;
|
|
505
|
+
});
|
|
506
|
+
afterAll(() => {
|
|
507
|
+
Metadata.Provider = savedProvider;
|
|
508
|
+
});
|
|
509
|
+
beforeEach(() => {
|
|
510
|
+
resolver = new TestResolver();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
/** Untracked, no field security: the OldValues shortcut is open unless the row filter closes it. */
|
|
514
|
+
function entityWithInternFilter(filter: { UpdateRLSFilterID?: string; ReadRLSFilterID?: string }): EntityInfo {
|
|
515
|
+
const init = employeeEntityInit(false);
|
|
516
|
+
init['TrackRecordChanges'] = false;
|
|
517
|
+
init['Permissions'] = [
|
|
518
|
+
{ EntityID: ENTITY_ID, RoleID: HR_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true },
|
|
519
|
+
{ EntityID: ENTITY_ID, RoleID: INTERN_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true, ...filter },
|
|
520
|
+
];
|
|
521
|
+
return new EntityInfo(init);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const claimsNewStatusAsOld = (): ClientInput => ({
|
|
525
|
+
ID: '1',
|
|
526
|
+
Status: 'Published',
|
|
527
|
+
OldValues___: [
|
|
528
|
+
{ Key: 'ID', Value: '1' },
|
|
529
|
+
{ Key: 'Status', Value: 'Published' },
|
|
530
|
+
],
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('loads the stored row for a caller under an Update row filter, whatever OldValues claim', () => {
|
|
534
|
+
const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
|
|
535
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID]))).toBe(true);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('keeps the shortcut for a caller with no Update row filter, on the same entity', () => {
|
|
539
|
+
const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
|
|
540
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([HR_ROLE_ID]))).toBe(false);
|
|
541
|
+
// A role without the filter exempts the user from it (roles are additive).
|
|
542
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID, HR_ROLE_ID]))).toBe(false);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('loads the stored row when the user is missing, instead of throwing or skipping the filter', () => {
|
|
546
|
+
const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
|
|
547
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, null)).toBe(true);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('loads the stored row under an API-key Update row filter, even for a role-exempt user', () => {
|
|
551
|
+
const entity = entityWithInternFilter({});
|
|
552
|
+
const keyBound = (permissionType: 'Read' | 'Update') => {
|
|
553
|
+
const user = buildUser([HR_ROLE_ID]); // no role filter: only the key binding narrows this caller
|
|
554
|
+
user.APIKeyRowFilters = [{ EntityID: ENTITY_ID, PermissionType: permissionType, FilterID: DRAFTS_ONLY }];
|
|
555
|
+
return user;
|
|
556
|
+
};
|
|
557
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([HR_ROLE_ID]))).toBe(false);
|
|
558
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, keyBound('Update'))).toBe(true);
|
|
559
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, keyBound('Read'))).toBe(false);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('ignores a Read-only row filter: only the Update filter gates this save', () => {
|
|
563
|
+
const entity = entityWithInternFilter({ ReadRLSFilterID: DRAFTS_ONLY });
|
|
564
|
+
expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID]))).toBe(false);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
@@ -90,6 +90,8 @@ describe('coreReservedServerExtensionRoots', () => {
|
|
|
90
90
|
|
|
91
91
|
const knownConstants: Record<string, string> = {
|
|
92
92
|
REALTIME_SDP_EXCHANGE_PATH: '/realtime',
|
|
93
|
+
// Public verify-link routes for realtime identity verification; under the reserved '/realtime' root.
|
|
94
|
+
REALTIME_VERIFY_MOUNT_PATH: '/realtime/verify',
|
|
93
95
|
MAGIC_LINK_MOUNT_PATH: '/magic-link',
|
|
94
96
|
WIDGET_MOUNT_PATH: '/widget',
|
|
95
97
|
AUTH_CATALOG_MOUNT_PATH: '/auth',
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
/**
|
|
3
|
+
* Routing of shared-cache events in MJAPI: metadata notices trigger a metadata check,
|
|
4
|
+
* everything reaches LocalCacheManager, and only RunView slot changes reach browsers.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import type { CacheChangedEvent, ProviderBase } from '@memberjunction/core';
|
|
8
|
+
|
|
9
|
+
const dispatch = vi.fn();
|
|
10
|
+
const publish = vi.fn();
|
|
11
|
+
const startSweeper = vi.fn();
|
|
12
|
+
|
|
13
|
+
/** Intervals StartUserCacheChecks handed to the user cache. */
|
|
14
|
+
const userCacheInterval: number[] = [];
|
|
15
|
+
|
|
16
|
+
vi.mock('@memberjunction/core', () => ({
|
|
17
|
+
CacheCategory: { RunViewCache: 'RunViewCache', RunQueryCache: 'RunQueryCache', DatasetCache: 'DatasetCache', Metadata: 'Metadata', Default: 'default' },
|
|
18
|
+
LocalCacheManager: { Instance: {
|
|
19
|
+
DispatchCacheChange: (e: CacheChangedEvent) => dispatch(e),
|
|
20
|
+
// The metadata sweep takes a fleet lease before doing any work; grant it in tests.
|
|
21
|
+
TryAcquireSharedLease: vi.fn(async () => true),
|
|
22
|
+
} },
|
|
23
|
+
LogStatusEx: vi.fn(),
|
|
24
|
+
LogError: vi.fn(),
|
|
25
|
+
BaseEngineSweeper: { Instance: { Start: (ms: number) => startSweeper(ms) } },
|
|
26
|
+
}));
|
|
27
|
+
vi.mock('@memberjunction/redis-provider', () => ({ RedisLocalStorageProvider: vi.fn(function (this: Record<string, unknown>, cfg: unknown) { this.cfg = cfg; }) }));
|
|
28
|
+
vi.mock('../generic/PubSubManager.js', () => ({ PubSubManager: { Instance: { Publish: (...a: unknown[]) => publish(...a) } } }));
|
|
29
|
+
vi.mock('../generic/CacheInvalidationResolver.js', () => ({ CACHE_INVALIDATION_TOPIC: 'CACHE_INVALIDATION' }));
|
|
30
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({
|
|
31
|
+
UserCache: { Instance: { StartStalenessChecks: (ms: number) => userCacheInterval.push(ms) } },
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
import {
|
|
35
|
+
CacheManagerConfigFromSettings, CreateSharedCacheFromEnvironment, MJAPI_PUBLISH_MODES, ResolveSharedCacheTTLSeconds, RouteSharedCacheEvent,
|
|
36
|
+
StartEngineSweeper,
|
|
37
|
+
StartUserCacheChecks,
|
|
38
|
+
StartMetadataSweep,
|
|
39
|
+
WirePushStatusFanOut,
|
|
40
|
+
} from '../sharedCache.js';
|
|
41
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
42
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
43
|
+
import { PublishStatusUpdate, SetPushStatusPublishHook } from '../generic/PushStatusResolver.js';
|
|
44
|
+
import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
45
|
+
import type { CacheSettingsConfig } from '../config.js';
|
|
46
|
+
|
|
47
|
+
function settings(overrides: Partial<CacheSettingsConfig>): CacheSettingsConfig {
|
|
48
|
+
return { maxMemoryMB: 150, maxPercentOfCachePerEntity: 50, defaultTTLSeconds: 0, evictionSweepIntervalSeconds: 300, verboseLogging: false, engineSweepIntervalSeconds: 300, ...overrides };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function event(overrides: Partial<CacheChangedEvent>): CacheChangedEvent {
|
|
52
|
+
return { CacheKey: 'k', Category: 'RunViewCache', Action: 'set', Timestamp: 1, SourceServerId: 'peer-server', ...overrides };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function provider(isNotice: boolean): ProviderBase {
|
|
56
|
+
return { HandlePeerMetadataNotice: vi.fn(() => isNotice) } as unknown as ProviderBase;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('shared cache event routing', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
dispatch.mockReset();
|
|
62
|
+
publish.mockReset();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('publishes RunView rows in full, metadata snapshots as notices, and nothing else', () => {
|
|
66
|
+
expect(MJAPI_PUBLISH_MODES).toEqual({ RunViewCache: 'full', default: 'notice' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('hands a metadata notice to the provider and stops there', () => {
|
|
70
|
+
const p = provider(true);
|
|
71
|
+
RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_Timestamps' }), p);
|
|
72
|
+
expect(p.HandlePeerMetadataNotice).toHaveBeenCalledOnce();
|
|
73
|
+
expect(dispatch).not.toHaveBeenCalled();
|
|
74
|
+
expect(publish).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('dispatches a RunView change and relays it to browsers as an entity invalidation', () => {
|
|
78
|
+
RouteSharedCacheEvent(event({ CacheKey: 'MJ: AI Models|_|_|-1|0|_|_|imr:1' }), provider(false));
|
|
79
|
+
expect(dispatch).toHaveBeenCalledOnce();
|
|
80
|
+
expect(publish).toHaveBeenCalledWith('CACHE_INVALIDATION', expect.objectContaining({ entityName: 'MJ: AI Models', action: 'set', primaryKeyValues: null }));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('does not relay other categories or category clears to browsers', () => {
|
|
84
|
+
RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_AllMetadata' }), provider(false));
|
|
85
|
+
RouteSharedCacheEvent(event({ Category: 'DatasetCache', CacheKey: 'ds' }), provider(false));
|
|
86
|
+
RouteSharedCacheEvent(event({ Category: 'RunViewCache', CacheKey: 'RunViewCache', Action: 'category_cleared' }), provider(false));
|
|
87
|
+
expect(dispatch).toHaveBeenCalledTimes(3);
|
|
88
|
+
expect(publish).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('ignores notices that arrive before the metadata provider exists', () => {
|
|
92
|
+
RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_Timestamps' }), undefined);
|
|
93
|
+
expect(dispatch).toHaveBeenCalledOnce();
|
|
94
|
+
expect(publish).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('shared cache configuration', () => {
|
|
99
|
+
it('builds the provider only when REDIS_URL is set, with MJAPI publish modes', () => {
|
|
100
|
+
expect(CreateSharedCacheFromEnvironment(undefined, {})).toBeNull();
|
|
101
|
+
const provider = CreateSharedCacheFromEnvironment(settings({ verboseLogging: true, sharedCacheTTLSeconds: 120 }),
|
|
102
|
+
{ REDIS_URL: 'redis://h:1', REDIS_KEY_PREFIX: 'fleet' }) as unknown as { cfg: Record<string, unknown> };
|
|
103
|
+
expect(provider).toBeInstanceOf(RedisLocalStorageProvider);
|
|
104
|
+
expect(provider.cfg).toMatchObject({
|
|
105
|
+
url: 'redis://h:1', keyPrefix: 'fleet', defaultTTLSeconds: 120, enablePubSub: true, enableLogging: true,
|
|
106
|
+
publishModes: { RunViewCache: 'full', default: 'notice' }, defaultPublishMode: 'none',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('lets REDIS_TTL_SECONDS override the configured TTL, and ignores junk', () => {
|
|
111
|
+
expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: '60' })).toBe(60);
|
|
112
|
+
expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: '0' })).toBe(0);
|
|
113
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
114
|
+
expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: 'soon' })).toBe(120);
|
|
115
|
+
warn.mockRestore();
|
|
116
|
+
expect(ResolveSharedCacheTTLSeconds(undefined, {})).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('maps cacheSettings onto LocalCacheManager settings', () => {
|
|
120
|
+
expect(CacheManagerConfigFromSettings(settings({ maxMemoryMB: 10, maxPercentOfCachePerEntity: 20, defaultTTLSeconds: 5, evictionSweepIntervalSeconds: 7, verboseLogging: true })))
|
|
121
|
+
.toEqual({ maxSizeBytes: 10 * 1024 * 1024, maxPercentOfCachePerEntity: 20, defaultTTLMs: 5000, evictionSweepIntervalMs: 7000, verboseLogging: true });
|
|
122
|
+
expect(CacheManagerConfigFromSettings(undefined).maxSizeBytes).toBe(150 * 1024 * 1024);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('starts the engine sweep at the configured interval, off when 0', () => {
|
|
126
|
+
startSweeper.mockReset();
|
|
127
|
+
expect(StartEngineSweeper(settings({ engineSweepIntervalSeconds: 60 }))).toBe(60_000);
|
|
128
|
+
expect(startSweeper).toHaveBeenLastCalledWith(60_000);
|
|
129
|
+
expect(StartEngineSweeper(undefined)).toBe(300_000);
|
|
130
|
+
expect(StartEngineSweeper(settings({ engineSweepIntervalSeconds: 0 }))).toBe(0);
|
|
131
|
+
expect(startSweeper).toHaveBeenLastCalledWith(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('starts the user-cache staleness check at the configured interval, off when 0', () => {
|
|
135
|
+
userCacheInterval.length = 0;
|
|
136
|
+
expect(StartUserCacheChecks(settings({ userCacheCheckIntervalSeconds: 60 }))).toBe(60_000);
|
|
137
|
+
expect(StartUserCacheChecks(undefined)).toBe(300_000);
|
|
138
|
+
expect(StartUserCacheChecks(settings({ userCacheCheckIntervalSeconds: 0 }))).toBe(0);
|
|
139
|
+
expect(userCacheInterval).toEqual([60_000, 300_000, 0]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The metadata sweep is the backstop for metadata written outside MJ — and nothing else.
|
|
145
|
+
*
|
|
146
|
+
* Metadata staleness is otherwise event-driven: a `BaseEntity` write to a member entity schedules a
|
|
147
|
+
* refresh, a peer's snapshot write publishes a notice, a CLI write clears the cache. None of those
|
|
148
|
+
* fire for raw SQL, so metadata edited directly in the database is never noticed by a running
|
|
149
|
+
* process.
|
|
150
|
+
*
|
|
151
|
+
* It must not become an unconditional poll. A recurring query prevents a serverless database from
|
|
152
|
+
* pausing, and `TrustServerCacheCompletely = true` (every MJ entity, by default) states that the
|
|
153
|
+
* event paths already cover the entity — so there is nothing a query could find.
|
|
154
|
+
*/
|
|
155
|
+
describe('StartMetadataSweep', () => {
|
|
156
|
+
afterEach(() => {
|
|
157
|
+
StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 0 }), () => undefined);
|
|
158
|
+
vi.useRealTimers();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('returns the configured interval, and 0 disables it', () => {
|
|
162
|
+
expect(StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 45 }), () => undefined)).toBe(45_000);
|
|
163
|
+
expect(StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 0 }), () => undefined)).toBe(0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('asks the provider NOTHING when no metadata entity declares drift', async () => {
|
|
167
|
+
vi.useFakeTimers();
|
|
168
|
+
const sweep = vi.fn();
|
|
169
|
+
const provider = {
|
|
170
|
+
MetadataMembersDeclaringDrift: () => [],
|
|
171
|
+
SweepMetadataAgainstDatabase: sweep,
|
|
172
|
+
} as unknown as ProviderBase;
|
|
173
|
+
|
|
174
|
+
StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 10 }), () => provider);
|
|
175
|
+
await vi.advanceTimersByTimeAsync(10_000 * 3 + 100);
|
|
176
|
+
|
|
177
|
+
expect(sweep).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('sweeps when a metadata entity declares drift', async () => {
|
|
181
|
+
vi.useFakeTimers();
|
|
182
|
+
const sweep = vi.fn().mockResolvedValue({ Declared: ['MJ: Entities'], Checked: true, Refreshed: true });
|
|
183
|
+
const provider = {
|
|
184
|
+
MetadataMembersDeclaringDrift: () => ['MJ: Entities'],
|
|
185
|
+
SweepMetadataAgainstDatabase: sweep,
|
|
186
|
+
} as unknown as ProviderBase;
|
|
187
|
+
|
|
188
|
+
StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 10 }), () => provider);
|
|
189
|
+
await vi.advanceTimersByTimeAsync(10_000 + 100);
|
|
190
|
+
|
|
191
|
+
expect(sweep).toHaveBeenCalled();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The push-status fan-out's WIRING (MJ #4222).
|
|
197
|
+
*
|
|
198
|
+
* `pushStatusFanOut.test.ts` covers the pieces — what is worth replicating, that every publish is
|
|
199
|
+
* stamped with the publishing instance, and every branch of `ParseReplicatedStatusUpdate`. What
|
|
200
|
+
* nothing covered is the wiring that joins them to Redis, which is the code that moved into this
|
|
201
|
+
* module: that a channel is actually subscribed, that inbound traffic is parsed against THIS
|
|
202
|
+
* process's id and handed to `PubSubManager`, that a local publish is forwarded on the same
|
|
203
|
+
* channel, and that a Redis failure degrades the feature instead of stopping the server.
|
|
204
|
+
*
|
|
205
|
+
* The parser and the publisher here are the real ones; mocking them would make these tests of the
|
|
206
|
+
* mock rather than of the wiring.
|
|
207
|
+
*/
|
|
208
|
+
describe('push-status fan-out wiring', () => {
|
|
209
|
+
/** A stand-in for the shared cache provider, capturing what the fan-out does to it. */
|
|
210
|
+
class FakeSharedCache {
|
|
211
|
+
public SubscribedChannel: string | undefined;
|
|
212
|
+
public Published: Array<{ channel: string; payload: string }> = [];
|
|
213
|
+
private inboundHandler: ((raw: string) => void) | undefined;
|
|
214
|
+
constructor(private subscribeFails = false) {}
|
|
215
|
+
|
|
216
|
+
public SubscribeToChannel = vi.fn(async (channel: string, handler: (raw: string) => void) => {
|
|
217
|
+
if (this.subscribeFails) {
|
|
218
|
+
throw new Error('redis is down');
|
|
219
|
+
}
|
|
220
|
+
this.SubscribedChannel = channel;
|
|
221
|
+
this.inboundHandler = handler;
|
|
222
|
+
return () => undefined;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
public PublishMessage = vi.fn((channel: string, payload: string) => {
|
|
226
|
+
this.Published.push({ channel, payload });
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/** Simulates Redis delivering a message on the subscribed channel. */
|
|
230
|
+
public Deliver(raw: string): void {
|
|
231
|
+
this.inboundHandler?.(raw);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public AsProvider(): RedisLocalStorageProvider {
|
|
235
|
+
return this as unknown as RedisLocalStorageProvider;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** A PubSubEngine the real publisher can publish locally on, which these tests ignore. */
|
|
240
|
+
function localTopic(): PubSubEngine {
|
|
241
|
+
return { publish: vi.fn(async () => undefined) } as unknown as PubSubEngine;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
beforeEach(() => {
|
|
245
|
+
publish.mockReset();
|
|
246
|
+
SetPushStatusPublishHook(undefined);
|
|
247
|
+
});
|
|
248
|
+
afterEach(() => {
|
|
249
|
+
SetPushStatusPublishHook(undefined);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('subscribes to the push-status channel on the shared connection', async () => {
|
|
253
|
+
const redis = new FakeSharedCache();
|
|
254
|
+
await WirePushStatusFanOut(redis.AsProvider());
|
|
255
|
+
|
|
256
|
+
expect(redis.SubscribeToChannel).toHaveBeenCalledTimes(1);
|
|
257
|
+
expect(redis.SubscribedChannel).toBe('push-status-updates');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("hands a PEER's update to this process's local topic, identity fields intact", async () => {
|
|
261
|
+
const redis = new FakeSharedCache();
|
|
262
|
+
await WirePushStatusFanOut(redis.AsProvider());
|
|
263
|
+
|
|
264
|
+
redis.Deliver(JSON.stringify({
|
|
265
|
+
sessionId: 'session-1', ownerUserId: 'user-1', message: 'done', SourceServerId: 'some-other-replica',
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
269
|
+
expect(publish).toHaveBeenCalledWith('PUSH_STATUS_UPDATES', {
|
|
270
|
+
sessionId: 'session-1', ownerUserId: 'user-1', message: 'done', SourceServerId: 'some-other-replica',
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("drops this process's own update echoed back, so a push is not delivered twice", async () => {
|
|
275
|
+
// The parser owns the comparison; this pins that the wiring gives it THIS process's id.
|
|
276
|
+
const redis = new FakeSharedCache();
|
|
277
|
+
await WirePushStatusFanOut(redis.AsProvider());
|
|
278
|
+
|
|
279
|
+
redis.Deliver(JSON.stringify({
|
|
280
|
+
sessionId: 'session-1', ownerUserId: 'user-1', message: 'done',
|
|
281
|
+
SourceServerId: MJGlobal.Instance.ProcessUUID,
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
expect(publish).not.toHaveBeenCalled();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('forwards a real local publish onto the same channel', async () => {
|
|
288
|
+
const redis = new FakeSharedCache();
|
|
289
|
+
await WirePushStatusFanOut(redis.AsProvider());
|
|
290
|
+
|
|
291
|
+
PublishStatusUpdate(localTopic(), { sessionId: 'session-9', ownerUserId: 'user-9', message: 'agent finished' });
|
|
292
|
+
|
|
293
|
+
expect(redis.Published).toHaveLength(1);
|
|
294
|
+
expect(redis.Published[0].channel).toBe('push-status-updates');
|
|
295
|
+
expect(JSON.parse(redis.Published[0].payload)).toEqual({
|
|
296
|
+
sessionId: 'session-9', ownerUserId: 'user-9', message: 'agent finished',
|
|
297
|
+
SourceServerId: MJGlobal.Instance.ProcessUUID,
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('degrades instead of stopping the server when Redis cannot subscribe', async () => {
|
|
302
|
+
const redis = new FakeSharedCache(true);
|
|
303
|
+
|
|
304
|
+
await expect(WirePushStatusFanOut(redis.AsProvider())).resolves.toBeUndefined();
|
|
305
|
+
|
|
306
|
+
// No hook may be left pointing at a connection it could not subscribe on.
|
|
307
|
+
PublishStatusUpdate(localTopic(), { sessionId: 's', ownerUserId: 'u', message: 'x' });
|
|
308
|
+
expect(redis.PublishMessage).not.toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated resolvers emit SQL Server bracket-quoted identifiers; on PostgreSQL they become
|
|
3
|
+
* double-quoted. Only identifiers change: brackets inside a string literal are data.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { TranslateBracketsToPG } from '../postgresqlCompat.js';
|
|
7
|
+
|
|
8
|
+
describe('TranslateBracketsToPG', () => {
|
|
9
|
+
it('turns bracket-quoted identifiers into double-quoted ones', () => {
|
|
10
|
+
expect(TranslateBracketsToPG('SELECT [ID], [Name] FROM [__mj].[vwUsers]')).toBe('SELECT "ID", "Name" FROM "__mj"."vwUsers"');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('leaves brackets inside a string literal alone', () => {
|
|
14
|
+
expect(TranslateBracketsToPG("SELECT [ID] FROM [t] WHERE [Note] = '[draft] copy'")).toBe(`SELECT "ID" FROM "t" WHERE "Note" = '[draft] copy'`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('unescapes ]] and escapes a double quote in the name', () => {
|
|
18
|
+
expect(TranslateBracketsToPG('SELECT [a]]b], [say "hi"] FROM t')).toBe('SELECT "a]b", "say ""hi""" FROM t');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
ResolveWidgetGuestRunContext,
|
|
17
17
|
ElevateUserPayload,
|
|
18
18
|
ResolveScopedAnonymousRunUser,
|
|
19
|
+
ResolveRecordingStoreUser,
|
|
19
20
|
} from '../realtimeWidget/widgetGuestElevation.js';
|
|
20
21
|
import type { UserPayload } from '../types.js';
|
|
21
22
|
|
|
@@ -99,6 +100,49 @@ describe('widgetGuestElevation — ResolveScopedAnonymousRunUser (issue #3371)',
|
|
|
99
100
|
});
|
|
100
101
|
});
|
|
101
102
|
|
|
103
|
+
describe('widgetGuestElevation — ResolveRecordingStoreUser (#5195: UI-role users lost every recording)', () => {
|
|
104
|
+
const systemUser = { ID: 'system-1', Email: 'system@system.org' } as UserInfo;
|
|
105
|
+
|
|
106
|
+
function userWith(flags: Partial<UserInfo>): UserInfo {
|
|
107
|
+
return { ID: 'user-1', Email: 'someone@example.com', ...flags } as UserInfo;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
getSystemUserMock.mockReset();
|
|
112
|
+
getSystemUserMock.mockReturnValue(systemUser);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('runs a signed-in owner’s recording store as the SYSTEM user (the stock UI role cannot create MJ: Files)', () => {
|
|
116
|
+
expect(ResolveRecordingStoreUser(userWith({ IsMagicLinkAnonymous: false }))).toBe(systemUser);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('runs a scoped anonymous magic-link session as the SYSTEM user, exactly as before', () => {
|
|
120
|
+
const scopedAnon = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'res-1' } });
|
|
121
|
+
expect(ResolveRecordingStoreUser(scopedAnon)).toBe(systemUser);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('keeps an UNSCOPED anonymous session on the caller, exactly as before', () => {
|
|
125
|
+
const unscoped = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: undefined });
|
|
126
|
+
expect(ResolveRecordingStoreUser(unscoped)).toBe(unscoped);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('keeps a PUBLIC WEB-WIDGET guest on the caller (their RLS read filter depends on it)', () => {
|
|
130
|
+
const widgetGuest = userWith({
|
|
131
|
+
IsMagicLinkAnonymous: true,
|
|
132
|
+
MagicLinkScope: { ResourceID: 'res-1' },
|
|
133
|
+
WidgetGuestContext: { WidgetID: 'widget-1' },
|
|
134
|
+
});
|
|
135
|
+
expect(ResolveRecordingStoreUser(widgetGuest)).toBe(widgetGuest);
|
|
136
|
+
expect(getSystemUserMock).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('FAILS CLOSED to the caller when no system user is available', () => {
|
|
140
|
+
getSystemUserMock.mockReturnValue(undefined);
|
|
141
|
+
const named = userWith({ IsMagicLinkAnonymous: false });
|
|
142
|
+
expect(ResolveRecordingStoreUser(named)).toBe(named);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
102
146
|
describe('widgetGuestElevation — elevateUserPayload', () => {
|
|
103
147
|
it('swaps in the elevated principal while preserving the guest sessionId for PubSub routing', () => {
|
|
104
148
|
const guestPayload = payloadWith({ IsMagicLinkAnonymous: true });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
2
|
+
import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
|
|
3
|
+
import { GetCurrentHostInstance } from './HostInstance.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gives the realtime bridge engine (`AIBridgeEngine`) this MJAPI instance's identity, the one its agent-session rows
|
|
7
|
+
* carry: `hostname:port:pid:bootId` ({@link GetCurrentHostInstance}). The engine stamps it into the `HostInstanceID`
|
|
8
|
+
* of each `MJ: AI Agent Session Bridges` row it creates, and its orphan reconcile closes only the bridges an earlier
|
|
9
|
+
* boot of this instance left, never another MJAPI's (#5310). Without it the engine's fallback identity applies, which
|
|
10
|
+
* names this process alone, so the reconcile would never find what an earlier boot left.
|
|
11
|
+
*
|
|
12
|
+
* `Serve` calls it right after `SetHostInstancePort`, before anything can start a bridge: a server extension may start
|
|
13
|
+
* a scheduled meeting bridge before the server listens.
|
|
14
|
+
*/
|
|
15
|
+
export function BindBridgeEngineHostInstance(): void {
|
|
16
|
+
AIBridgeEngine.Instance.SetHostInstanceIdentity(GetCurrentHostInstance());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Starts the realtime bridge engine's orphan reconcile: now, then every 10 minutes, it closes the bridges an earlier
|
|
21
|
+
* boot of this instance left `Connecting` or `Connected` (reason `Janitor`) and finalizes their co-agent runs.
|
|
22
|
+
*
|
|
23
|
+
* `Serve` calls it once the server listens on its port, where it starts the session janitor. Holding the port is what
|
|
24
|
+
* makes another boot of this instance a process that has ended; before that, an MJAPI started on a port another MJAPI
|
|
25
|
+
* still serves would take that one's live bridges for its own orphans.
|
|
26
|
+
*
|
|
27
|
+
* @param provider The server's metadata provider, for the reads and writes.
|
|
28
|
+
* @param systemUser The user the writes run as.
|
|
29
|
+
*/
|
|
30
|
+
export function StartBridgeOrphanReconciliation(provider: IMetadataProvider, systemUser: UserInfo): void {
|
|
31
|
+
AIBridgeEngine.Instance.StartOrphanReconciliation(systemUser, provider);
|
|
32
|
+
}
|