@memberjunction/server 6.2.0-edge.3 → 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/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 +2378 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +123 -0
- 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 +14 -11
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +706 -0
- 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 +20 -4
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +39 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +4 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +120 -131
- 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/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 +14 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +21 -5
- 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/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/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
- 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/RealtimeBridgeResolver.d.ts +54 -2
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
- 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/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/meetingMutations.d.ts +6 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +21 -0
- package/dist/resolvers/meetings/meetingMutations.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/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 +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -95
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- 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__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
- package/src/__tests__/MeetingResolver.test.ts +75 -1
- package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
- 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.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- 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__/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__/actionAuthorization.test.ts +130 -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__/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__/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 +125 -0
- package/src/context.ts +15 -10
- package/src/generated/graphql-schemas/__mj.ts +496 -1
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +39 -9
- package/src/generic/RunViewResolver.ts +4 -0
- package/src/index.ts +132 -143
- package/src/isolatedProvider.ts +22 -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 +24 -6
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
- 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/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/meetingMutations.ts +21 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +7 -0
|
@@ -40,7 +40,7 @@ import { DeleteOptionsInput } from './DeleteOptionsInput.js';
|
|
|
40
40
|
import { MJEvent, MJEventType, MJGlobal, ENCRYPTED_SENTINEL, ReplaceByteArraysWithBase64, EscapeSQLString, IsValueEncrypted, IsOnlyTimezoneShift } from '@memberjunction/global';
|
|
41
41
|
import { SQLParser } from '@memberjunction/sql-parser';
|
|
42
42
|
import { PostgreSQLDialect, SQLServerDialect, type SQLParserDialect } from '@memberjunction/sql-dialect';
|
|
43
|
-
import { EncryptionEngine } from '@memberjunction/encryption';
|
|
43
|
+
import { EncryptionEngine, IsEncryptionKeyUnavailable, ReportEncryptionKeyUnavailable } from '@memberjunction/encryption';
|
|
44
44
|
import { PUSH_STATUS_UPDATES_TOPIC, PublishStatusUpdate } from './PushStatusResolver.js';
|
|
45
45
|
import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData } from './CacheInvalidationResolver.js';
|
|
46
46
|
import { PubSubManager } from './PubSubManager.js';
|
|
@@ -199,7 +199,11 @@ export class ResolverBase {
|
|
|
199
199
|
const decryptedValue = await engine.Decrypt(value, contextUser);
|
|
200
200
|
dataObject[fieldName] = decryptedValue;
|
|
201
201
|
} catch (err) {
|
|
202
|
-
|
|
202
|
+
if (IsEncryptionKeyUnavailable(err)) {
|
|
203
|
+
ReportEncryptionKeyUnavailable(err);
|
|
204
|
+
} else {
|
|
205
|
+
LogError(`Failed to decrypt field ${fieldName} for API response: ${err}`);
|
|
206
|
+
}
|
|
203
207
|
// On decryption failure, use sentinel for safety
|
|
204
208
|
dataObject[fieldName] = ENCRYPTED_SENTINEL;
|
|
205
209
|
}
|
|
@@ -932,6 +936,7 @@ export class ResolverBase {
|
|
|
932
936
|
label: string,
|
|
933
937
|
provider?: IMetadataProvider,
|
|
934
938
|
user?: UserInfo,
|
|
939
|
+
options?: { LeaveUnparseableToProvider?: boolean },
|
|
935
940
|
): void {
|
|
936
941
|
if (!clause?.trim()) return;
|
|
937
942
|
|
|
@@ -945,6 +950,9 @@ export class ResolverBase {
|
|
|
945
950
|
? `SELECT 1 FROM __mj_clause_screen ORDER BY ${clause}`
|
|
946
951
|
: `SELECT 1 FROM __mj_clause_screen WHERE (${clause})`;
|
|
947
952
|
const parser = new SQLParser(wrapped, dialect);
|
|
953
|
+
// A clause the provider checks again on its own (an aggregate expression) leaves text the parser
|
|
954
|
+
// cannot read to that per-item check instead of failing the whole request.
|
|
955
|
+
if (!parser.IsValid && options?.LeaveUnparseableToProvider) return;
|
|
948
956
|
if (!parser.IsValid || parser.HasWriteStatement || parser.StatementKind !== 'select') {
|
|
949
957
|
throw new Error(
|
|
950
958
|
`Invalid ${label}: not a safe read-only filter fragment — refusing under uncertainty`,
|
|
@@ -1062,6 +1070,11 @@ export class ResolverBase {
|
|
|
1062
1070
|
* The single place that is not true is a field carrying `UserSearchParamFormatAPI`, whose
|
|
1063
1071
|
* admin-authored format may splice the term in unquoted; `createViewUserSearchSQL` re-applies
|
|
1064
1072
|
* `ValidateUserProvidedSQLClause` for exactly those entities.
|
|
1073
|
+
*
|
|
1074
|
+
* Each `Aggregates[].expression` gets the same screen, except that text the parser cannot read
|
|
1075
|
+
* goes on to the provider, which refuses it per aggregate. The provider never runs the caller's
|
|
1076
|
+
* aggregate text: it runs the SQL that `CheckAggregateExpression` rebuilds from a single
|
|
1077
|
+
* aggregate call over the entity's columns, and refuses everything else per aggregate.
|
|
1065
1078
|
*/
|
|
1066
1079
|
protected screenClientViewClauses(
|
|
1067
1080
|
clauses: {
|
|
@@ -1069,6 +1082,7 @@ export class ResolverBase {
|
|
|
1069
1082
|
orderBy?: string | null;
|
|
1070
1083
|
userSearchString?: string | null;
|
|
1071
1084
|
overrideExcludeFilter?: string | null;
|
|
1085
|
+
aggregates?: AggregateExpression[] | null;
|
|
1072
1086
|
},
|
|
1073
1087
|
provider?: IMetadataProvider,
|
|
1074
1088
|
user?: UserInfo,
|
|
@@ -1076,6 +1090,9 @@ export class ResolverBase {
|
|
|
1076
1090
|
this.assertClientClauseUsesEntityBaseViews(clauses.extraFilter, 'ExtraFilter', provider, user);
|
|
1077
1091
|
this.assertClientClauseUsesEntityBaseViews(clauses.orderBy, 'OrderBy', provider, user);
|
|
1078
1092
|
this.assertClientClauseUsesEntityBaseViews(clauses.overrideExcludeFilter, 'OverrideExcludeFilter', provider, user);
|
|
1093
|
+
for (const aggregate of clauses.aggregates ?? []) {
|
|
1094
|
+
this.assertClientClauseUsesEntityBaseViews(aggregate?.expression, 'Aggregate', provider, user, { LeaveUnparseableToProvider: true });
|
|
1095
|
+
}
|
|
1079
1096
|
}
|
|
1080
1097
|
|
|
1081
1098
|
/** Same write-node walk as EDS `sqlReadOnlyScreen.astContainsWriteNode`. */
|
|
@@ -1223,7 +1240,7 @@ export class ResolverBase {
|
|
|
1223
1240
|
// Screen at this boundary: parse, reject writes, allow only entity BaseViews the
|
|
1224
1241
|
// acting user can read (subqueries do not inherit the top entity's permission check).
|
|
1225
1242
|
this.screenClientViewClauses(
|
|
1226
|
-
{ extraFilter, orderBy, userSearchString, overrideExcludeFilter },
|
|
1243
|
+
{ extraFilter, orderBy, userSearchString, overrideExcludeFilter, aggregates },
|
|
1227
1244
|
provider as unknown as IMetadataProvider,
|
|
1228
1245
|
user,
|
|
1229
1246
|
);
|
|
@@ -1390,6 +1407,7 @@ export class ResolverBase {
|
|
|
1390
1407
|
orderBy: param.orderBy,
|
|
1391
1408
|
userSearchString: param.userSearchString,
|
|
1392
1409
|
overrideExcludeFilter: param.overrideExcludeFilter,
|
|
1410
|
+
aggregates: param.aggregates,
|
|
1393
1411
|
},
|
|
1394
1412
|
md,
|
|
1395
1413
|
contextUser ?? undefined,
|
|
@@ -1818,9 +1836,10 @@ export class ResolverBase {
|
|
|
1818
1836
|
// that hydrates from those values — never loading what the database actually holds.
|
|
1819
1837
|
const hasNarrowedAuditPayload = this.StripRecordChangePayloadFromClientInput(entityInfo, userInfo, input, clientNewValues);
|
|
1820
1838
|
|
|
1821
|
-
if (this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload)) {
|
|
1839
|
+
if (this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload, userInfo)) {
|
|
1822
1840
|
// We get here because the entity tracks record changes, OR the client did not provide OldValues,
|
|
1823
|
-
// OR field-level security is in play — in every case we need the true
|
|
1841
|
+
// OR field-level security or an Update row filter is in play — in every case we need the true
|
|
1842
|
+
// old values from the DB
|
|
1824
1843
|
const cKey = new CompositeKey(
|
|
1825
1844
|
entityInfo.PrimaryKeys.map((pk) => {
|
|
1826
1845
|
return {
|
|
@@ -1918,19 +1937,28 @@ export class ResolverBase {
|
|
|
1918
1937
|
* it never applies them to the entity. An update-denied field can then only become dirty by being
|
|
1919
1938
|
* named in the mutation input itself, which is precisely the case that check does catch.
|
|
1920
1939
|
*
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1940
|
+
* The flag comes after the cheap terms, so the extra load lands only on entities that have the
|
|
1941
|
+
* feature switched on, which is almost none of them.
|
|
1923
1942
|
*
|
|
1924
1943
|
* `MJ: Record Changes` always loads from the database: its server class allows an update only when
|
|
1925
1944
|
* Comments is the one dirty field, and "dirty" compared against client-supplied OldValues lets a
|
|
1926
1945
|
* caller pin forged audit columns as both old and new values. It doesn't track its own changes, so
|
|
1927
1946
|
* nothing else forces the load.
|
|
1947
|
+
*
|
|
1948
|
+
* A caller under an Update row filter (a role's Update RLS filter or an API-key row filter) always
|
|
1949
|
+
* loads from the database too (#4919). The save's post-image check skips its query when no column
|
|
1950
|
+
* the filter reads is dirty, and dirtiness measured against client OldValues is the caller's to
|
|
1951
|
+
* choose: claiming the new value of a filter column as its old value made it clean, so the update
|
|
1952
|
+
* moved the row outside the caller's scope without the check ever running. Evaluated last: it
|
|
1953
|
+
* builds the filter clause, and only callers with such a filter pay for the load. A missing user
|
|
1954
|
+
* (a user-cache miss) also loads: with no user the filter can't be ruled out.
|
|
1928
1955
|
*/
|
|
1929
1956
|
protected MustLoadTruthFromDatabase(
|
|
1930
1957
|
entityInfo: EntityInfo,
|
|
1931
1958
|
input: { OldValues___?: Array<{ Key: string; Value: unknown }> },
|
|
1932
1959
|
hasDeniedReadFields: boolean,
|
|
1933
|
-
hasNarrowedAuditPayload: boolean
|
|
1960
|
+
hasNarrowedAuditPayload: boolean,
|
|
1961
|
+
user: UserInfo | undefined
|
|
1934
1962
|
): boolean {
|
|
1935
1963
|
return (
|
|
1936
1964
|
entityInfo.TrackRecordChanges ||
|
|
@@ -1938,7 +1966,9 @@ export class ResolverBase {
|
|
|
1938
1966
|
!input.OldValues___ ||
|
|
1939
1967
|
hasDeniedReadFields ||
|
|
1940
1968
|
hasNarrowedAuditPayload ||
|
|
1941
|
-
entityInfo.EnableFieldLevelSecurity
|
|
1969
|
+
entityInfo.EnableFieldLevelSecurity ||
|
|
1970
|
+
!user ||
|
|
1971
|
+
entityInfo.GetEffectiveRowFilterWhereClause(user, EntityPermissionType.Update, '').length > 0
|
|
1942
1972
|
);
|
|
1943
1973
|
}
|
|
1944
1974
|
|
|
@@ -1248,6 +1248,10 @@ export class RunViewResolver extends ResolverBase {
|
|
|
1248
1248
|
// otherwise a CacheLocal batch's DataSource:'Materialized' request silently reads the live view.
|
|
1249
1249
|
DataSource: item.params.DataSource,
|
|
1250
1250
|
IncludeBinaryFields: item.params.IncludeBinaryFields,
|
|
1251
|
+
// BypassCache likewise — the input type (RunDynamicViewInput) has always declared it, and its
|
|
1252
|
+
// own description promises "the pre-check cache lookup is skipped". Dropping it here left the
|
|
1253
|
+
// provider free to answer a deliberate database read from the server cache.
|
|
1254
|
+
BypassCache: item.params.BypassCache,
|
|
1251
1255
|
AfterKey: item.params.AfterKey
|
|
1252
1256
|
? CompositeKey.FromKeyValuePairs(item.params.AfterKey.KeyValuePairs)
|
|
1253
1257
|
: undefined,
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ dotenv.config({ quiet: true });
|
|
|
4
4
|
|
|
5
5
|
import { expressMiddleware } from '@as-integrations/express5';
|
|
6
6
|
import { mergeSchemas } from '@graphql-tools/schema';
|
|
7
|
-
import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ResolveStartupMode } from '@memberjunction/core';
|
|
7
|
+
import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ProviderBase, ResolveStartupMode } from '@memberjunction/core';
|
|
8
8
|
import { UserCache, resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
|
|
9
9
|
import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
|
|
10
10
|
import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
|
|
@@ -19,9 +19,10 @@ import { default as fg } from 'fast-glob';
|
|
|
19
19
|
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
20
20
|
import { createServer } from 'node:http';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
23
24
|
import { Session as InspectorSession } from 'node:inspector';
|
|
24
|
-
import { sep } from 'node:path';
|
|
25
|
+
import { join, sep } from 'node:path';
|
|
25
26
|
import 'reflect-metadata';
|
|
26
27
|
import { ReplaySubject } from 'rxjs';
|
|
27
28
|
import { BuildSchemaOptions, buildSchemaSync, getMetadataStorage, GraphQLTimestamp, PubSubEngine } from 'type-graphql';
|
|
@@ -34,7 +35,7 @@ import { configInfo, configFilePath, dbDatabase, dbHost, dbPort, dbUsername, gra
|
|
|
34
35
|
import { TranslateBracketsToPG } from './postgresqlCompat.js';
|
|
35
36
|
import { BuildPostgreSQLConnectionConfig, DescribeReadOnlyLoginOverreach, PostgreSQLReadOnlyPool, ResolvePostgreSQLEndpoint, ResolvePostgreSQLReadOnlyCredentials, ToPGPoolConfig } from './postgresqlPoolSettings.js';
|
|
36
37
|
import { default as jwt } from 'jsonwebtoken';
|
|
37
|
-
import { contextFunction, CreateUnifiedAuthMiddleware, getUserPayload } from './context.js';
|
|
38
|
+
import { contextFunction, CreateReadOnlyProvider, CreateUnifiedAuthMiddleware, getUserPayload } from './context.js';
|
|
38
39
|
import { UserPayload } from './types.js';
|
|
39
40
|
import { requireSystemUserDirective, publicDirective } from './directives/index.js';
|
|
40
41
|
import { variablesLoggingMiddleware } from './logging/variablesLoggingMiddleware.js';
|
|
@@ -50,6 +51,14 @@ import { createRealtimeSdpBrokerRouter } from './rest/RealtimeSdpBrokerHandler.j
|
|
|
50
51
|
import { REALTIME_SDP_EXCHANGE_PATH } from '@memberjunction/ai';
|
|
51
52
|
import { createMagicLinkHandler, createMagicLinkJwksRouter, registerMagicLinkAuthProvider, MAGIC_LINK_MOUNT_PATH } from './auth/magicLink/index.js';
|
|
52
53
|
import { createWidgetHandler, WIDGET_MOUNT_PATH } from './realtimeWidget/index.js';
|
|
54
|
+
import {
|
|
55
|
+
CreateRealtimeVerifyRouter,
|
|
56
|
+
REALTIME_VERIFY_MOUNT_PATH,
|
|
57
|
+
RealtimeSessionEventService,
|
|
58
|
+
RealtimeSessionVerificationService,
|
|
59
|
+
ParseReplicatedRealtimeSessionEvent,
|
|
60
|
+
type RealtimeSessionEventTopicPayload,
|
|
61
|
+
} from './realtimeSessions/index.js';
|
|
53
62
|
import { resolve } from 'node:path';
|
|
54
63
|
import { DataSourceInfo, raiseEvent } from './types.js';
|
|
55
64
|
|
|
@@ -60,15 +69,11 @@ import { LocalCacheManager, StartupManager, TelemetryManager, TelemetryLevel, Lo
|
|
|
60
69
|
import { getSystemUser, validateAuthProvidersRegistered } from './auth/index.js';
|
|
61
70
|
import { createAuthProviderCatalogRouter, AUTH_CATALOG_MOUNT_PATH } from './auth/AuthProviderCatalogRouter.js';
|
|
62
71
|
import { GetAPIKeyEngine } from '@memberjunction/api-keys';
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
72
|
+
import { ActionEngineServer } from '@memberjunction/actions';
|
|
73
|
+
import { CreateAdhocSQLAuthorizer } from './resolvers/AdhocQueryResolver.js';
|
|
74
|
+
import { CacheManagerConfigFromSettings, CreateSharedCacheFromEnvironment, StartEngineSweeper, StartMetadataSweep, StartUserCacheChecks, WarmupLeaseMsFromSettings, WirePushStatusFanOut, WireSharedCacheEvents } from './sharedCache.js';
|
|
65
75
|
import { PubSubManager } from './generic/PubSubManager.js';
|
|
66
76
|
import { ReconcileOrphanedConversationDetails } from './generic/OrphanedConversationDetailReconciler.js';
|
|
67
|
-
import {
|
|
68
|
-
PUSH_STATUS_UPDATES_TOPIC,
|
|
69
|
-
SetPushStatusPublishHook,
|
|
70
|
-
ParseReplicatedStatusUpdate,
|
|
71
|
-
} from './generic/PushStatusResolver.js';
|
|
72
77
|
import {
|
|
73
78
|
HANDOFF_OFFER_FANOUT_CHANNEL,
|
|
74
79
|
HANDOFF_OFFER_TOPIC,
|
|
@@ -76,11 +81,14 @@ import {
|
|
|
76
81
|
ParseReplicatedHandoffOfferUpdate,
|
|
77
82
|
} from './resolvers/HumanHandoffResolver.js';
|
|
78
83
|
import { RoomHandoffEngine } from '@memberjunction/livekit-room-server';
|
|
84
|
+
// Type-only: the provider is built in ./sharedCache.ts. Still needed here because the handoff-offer
|
|
85
|
+
// fan-out below stays in this file — see the note at its call site.
|
|
86
|
+
import type { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
79
87
|
import { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
|
|
80
88
|
import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
|
|
81
89
|
import { RegisterRSUProgressBridge } from './integration/RSUProgressBridge.js';
|
|
82
90
|
import { ClientToolRequestManager, AgentRunWatchdog } from '@memberjunction/ai-agents';
|
|
83
|
-
import { SessionJanitor } from './agentSessions/index.js';
|
|
91
|
+
import { BindBridgeEngineHostInstance, SessionJanitor, SetHostInstancePort, StartBridgeOrphanReconciliation } from './agentSessions/index.js';
|
|
84
92
|
import { StartTaskGraphDispatcher } from './services/StartTaskGraphDispatcher.js';
|
|
85
93
|
import { MJServerWorkQueueProviderSource, StartWorkQueueHost } from './services/WorkQueueHostService.js';
|
|
86
94
|
import { GetAttachmentService } from '@memberjunction/aiengine';
|
|
@@ -132,6 +140,7 @@ export * from 'type-graphql';
|
|
|
132
140
|
// later importers, which makes schema build fail on ViewResult.RowCount.
|
|
133
141
|
export { Int, Float, ID } from 'type-graphql';
|
|
134
142
|
export { NewUserBase } from './auth/newUsers.js';
|
|
143
|
+
export { CreateIsolatedProvider } from './isolatedProvider.js';
|
|
135
144
|
export { configInfo, DEFAULT_SERVER_CONFIG } from './config.js';
|
|
136
145
|
export { ServerExtensionLoader, BaseServerExtension, DefaultServerExtensionServiceRegistry } from '@memberjunction/server-extensions-core';
|
|
137
146
|
export type {
|
|
@@ -196,6 +205,8 @@ export * from './resolvers/GenerateSeedTaxonomyResolver.js';
|
|
|
196
205
|
export * from './resolvers/PipelineProgressResolver.js';
|
|
197
206
|
export * from './resolvers/IntegrationProgressResolver.js';
|
|
198
207
|
export * from './resolvers/IdentityClaimRedemptionResolver.js';
|
|
208
|
+
export * from './resolvers/UserAvatarResolver.js';
|
|
209
|
+
export * from './resolvers/avatarInputValidation.js';
|
|
199
210
|
export * from './resolvers/ClientToolRequestResolver.js';
|
|
200
211
|
export * from './resolvers/AutotagPipelineResolver.js';
|
|
201
212
|
export * from './resolvers/TagGovernanceResolver.js';
|
|
@@ -253,6 +264,9 @@ export * from './resolvers/CurrentUserContextResolver.js';
|
|
|
253
264
|
export * from './resolvers/RSUResolver.js';
|
|
254
265
|
export * from './resolvers/AgentSessionResolver.js';
|
|
255
266
|
export * from './resolvers/RealtimeClientSessionResolver.js';
|
|
267
|
+
export * from './resolvers/RealtimeSessionEventsResolver.js';
|
|
268
|
+
export * from './resolvers/RealtimeSessionVerificationResolver.js';
|
|
269
|
+
export * from './realtimeSessions/index.js';
|
|
256
270
|
export * from './resolvers/MeetingResolver.js';
|
|
257
271
|
export * from './resolvers/RemoteBrowserActionResolver.js';
|
|
258
272
|
export * from './agentSessions/index.js';
|
|
@@ -316,59 +330,6 @@ function resolveServerVersion(): string | undefined {
|
|
|
316
330
|
/** How often to re-check for conversation details left behind by finished runs. */
|
|
317
331
|
const ORPHAN_DETAIL_SWEEP_INTERVAL_MS = 5 * 60_000;
|
|
318
332
|
|
|
319
|
-
/** Redis channel carrying replicated push-status updates between server instances. */
|
|
320
|
-
const PUSH_STATUS_FANOUT_CHANNEL = 'push-status-updates';
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Replicate push-status updates across server instances over Redis (MJ #4222).
|
|
324
|
-
*
|
|
325
|
-
* Outbound: every locally-published update is forwarded on a shared channel. Inbound: a message
|
|
326
|
-
* from another instance is republished onto THIS instance's local topic, where the normal
|
|
327
|
-
* subscription filter decides who receives it — so the identity gate (`ownerUserId` vs. the
|
|
328
|
-
* connection's authenticated user) still applies to a replicated message exactly as it does to a
|
|
329
|
-
* local one. The replica has no say in who sees what.
|
|
330
|
-
*
|
|
331
|
-
* Republishing goes straight to `PubSubManager`, never back through `publishStatusUpdate`, so an
|
|
332
|
-
* inbound message cannot be re-broadcast and loop. `SourceServerId` guards the remaining case: a
|
|
333
|
-
* publisher also receives its own message from Redis.
|
|
334
|
-
*/
|
|
335
|
-
async function wirePushStatusFanOut(redisProvider: RedisLocalStorageProvider, startupLog: StartupLogger): Promise<void> {
|
|
336
|
-
try {
|
|
337
|
-
await redisProvider.SubscribeToChannel(PUSH_STATUS_FANOUT_CHANNEL, (raw: string) => {
|
|
338
|
-
try {
|
|
339
|
-
const payload = ParseReplicatedStatusUpdate(raw, MJGlobal.Instance.ProcessUUID);
|
|
340
|
-
if (!payload) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
// Rebuilt as a plain record: the topic's publish signature takes an index-signature type,
|
|
344
|
-
// and listing the fields keeps the wire shape explicit at the one place it crosses hosts.
|
|
345
|
-
PubSubManager.Instance.Publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
346
|
-
sessionId: payload.sessionId,
|
|
347
|
-
ownerUserId: payload.ownerUserId,
|
|
348
|
-
message: payload.message,
|
|
349
|
-
SourceServerId: payload.SourceServerId,
|
|
350
|
-
});
|
|
351
|
-
} catch (err) {
|
|
352
|
-
// A malformed message on a shared channel must not take down the subscriber.
|
|
353
|
-
LogError('Error processing push-status fan-out message', undefined, err);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
SetPushStatusPublishHook((payload) => {
|
|
358
|
-
redisProvider.PublishMessage(PUSH_STATUS_FANOUT_CHANNEL, JSON.stringify(payload));
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// Printed unconditionally, not verbose-gated. "Is fan-out actually on?" is the first question
|
|
362
|
-
// anyone debugging a hung conversation behind a load balancer asks, and a silent default left
|
|
363
|
-
// no way to answer it.
|
|
364
|
-
console.log('[MJAPI] Push-status updates: cross-instance fan-out enabled via Redis');
|
|
365
|
-
} catch (err) {
|
|
366
|
-
// Single-instance delivery still works, and the durable tail query covers the rest. Degraded,
|
|
367
|
-
// not broken — so this must not stop the server from starting.
|
|
368
|
-
console.warn(`Push-status fan-out unavailable: ${(err as Error).message}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
333
|
/**
|
|
373
334
|
* Replicate handoff-offer updates across server instances over Redis.
|
|
374
335
|
*
|
|
@@ -408,6 +369,43 @@ async function wireHandoffOfferFanOut(redisProvider: RedisLocalStorageProvider,
|
|
|
408
369
|
}
|
|
409
370
|
}
|
|
410
371
|
|
|
372
|
+
/** Redis channel carrying replicated realtime session events between server instances. */
|
|
373
|
+
const REALTIME_SESSION_EVENTS_FANOUT_CHANNEL = 'realtime-session-events';
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Replicate realtime session events across server instances over Redis.
|
|
377
|
+
*
|
|
378
|
+
* Behind a load balancer the browser's WebSocket (the `RealtimeSessionEvents` subscription) lives on one
|
|
379
|
+
* replica while the request that raises the event — typically the public verify-link redemption — can be
|
|
380
|
+
* handled by another. The session-events topic is an in-process PubSub, so without this an
|
|
381
|
+
* `identity.verified` published on replica B never reaches the subscriber on replica A.
|
|
382
|
+
*
|
|
383
|
+
* Inbound messages are republished onto THIS instance's local topic only (never back through the
|
|
384
|
+
* replication hook, so there is no loop) and are still judged by the subscription filter against the
|
|
385
|
+
* connection's identity — a replica has no say in who sees what. The durable
|
|
386
|
+
* `RealtimeSessionVerificationStatus` query remains the backstop if Redis is down.
|
|
387
|
+
*/
|
|
388
|
+
async function wireRealtimeSessionEventFanOut(redisProvider: RedisLocalStorageProvider): Promise<void> {
|
|
389
|
+
try {
|
|
390
|
+
await redisProvider.SubscribeToChannel(REALTIME_SESSION_EVENTS_FANOUT_CHANNEL, (raw: string) => {
|
|
391
|
+
try {
|
|
392
|
+
const payload = ParseReplicatedRealtimeSessionEvent(raw, MJGlobal.Instance.ProcessUUID);
|
|
393
|
+
if (payload) {
|
|
394
|
+
RealtimeSessionEventService.Instance.PublishReplicated(payload);
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
LogError(`Realtime session event fan-out: dropping a malformed replicated event: ${(err as Error).message}`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
RealtimeSessionEventService.Instance.SetReplicationHook((payload: RealtimeSessionEventTopicPayload) => {
|
|
401
|
+
redisProvider.PublishMessage(REALTIME_SESSION_EVENTS_FANOUT_CHANNEL, JSON.stringify(payload));
|
|
402
|
+
});
|
|
403
|
+
console.log('[MJAPI] Realtime session events: cross-instance fan-out enabled via Redis');
|
|
404
|
+
} catch (err) {
|
|
405
|
+
// Single-instance delivery still works and the status query covers the rest: degraded, not broken.
|
|
406
|
+
console.warn(`Realtime session event fan-out unavailable: ${(err as Error).message}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
411
409
|
|
|
412
410
|
// Bind MJStorage as the conversation-attachment blob store. The attachment service itself no longer
|
|
413
411
|
// imports `@memberjunction/storage` — that dependency made it unusable from any browser or React
|
|
@@ -422,6 +420,12 @@ GetAttachmentService().BlobStore = new MJStorageBlobStore();
|
|
|
422
420
|
|
|
423
421
|
export const Serve = async (resolverPaths: Array<string>, app: Application = CreateApp(), options?: MJServerOptions): Promise<void> => {
|
|
424
422
|
const t0 = performance.now();
|
|
423
|
+
// Name this MJAPI instance by its port before anything stamps a session row with its identity, so the janitor's
|
|
424
|
+
// startup recovery closes only the sessions an earlier boot of this instance left, never those of another MJAPI on
|
|
425
|
+
// the same host (#5309).
|
|
426
|
+
SetHostInstancePort(graphqlPort);
|
|
427
|
+
// The realtime bridge engine stamps its bridge rows with the same identity and reconciles orphans by it (#5310).
|
|
428
|
+
BindBridgeEngineHostInstance();
|
|
425
429
|
// Level-gated startup logger. Resolves verbosity from telemetry.level (single
|
|
426
430
|
// operator knob). At `standard` (default), per-phase timings are collapsed into
|
|
427
431
|
// the one-line summary; at `verbose`+ each phase prints inline as before.
|
|
@@ -450,6 +454,25 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
450
454
|
const dbType = GetDbType();
|
|
451
455
|
const dataSources: DataSourceInfo[] = [];
|
|
452
456
|
|
|
457
|
+
// Shared cache first: the database provider loads metadata and startup engines through
|
|
458
|
+
// it, so the first server to start fills it and later servers read it instead of each querying
|
|
459
|
+
// the database and broadcasting what they loaded. Subscribed before engines load so they hear
|
|
460
|
+
// other servers while they start.
|
|
461
|
+
const sharedCache = CreateSharedCacheFromEnvironment(configInfo.cacheSettings);
|
|
462
|
+
if (sharedCache) {
|
|
463
|
+
await WireSharedCacheEvents(sharedCache, () => (Metadata.Provider instanceof ProviderBase ? Metadata.Provider : undefined)); // global-provider-ok: bootstrap
|
|
464
|
+
// The cross-instance fan-outs ride this connection. Push-status moved into sharedCache.ts
|
|
465
|
+
// with the rest of the Redis wiring; the handoff and realtime-session ones stay here deliberately —
|
|
466
|
+
// they reach into RoomHandoffEngine and RealtimeSessionEventService, and the cache module should not
|
|
467
|
+
// acquire a dependency on either.
|
|
468
|
+
await WirePushStatusFanOut(sharedCache);
|
|
469
|
+
await wireHandoffOfferFanOut(sharedCache, startupLog);
|
|
470
|
+
await wireRealtimeSessionEventFanOut(sharedCache);
|
|
471
|
+
startupLog.LogIf('verbose', `Redis cache provider connected: ${process.env.REDIS_URL}`);
|
|
472
|
+
}
|
|
473
|
+
const cacheManagerConfig = CacheManagerConfigFromSettings(configInfo.cacheSettings);
|
|
474
|
+
const warmupLeaseMs = WarmupLeaseMsFromSettings(configInfo.cacheSettings);
|
|
475
|
+
|
|
453
476
|
if (dbType === 'postgresql') {
|
|
454
477
|
// ─── PostgreSQL Path ───────────────────────────────────────────
|
|
455
478
|
startupLog.BeginPhase('Connecting to database');
|
|
@@ -509,6 +532,7 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
509
532
|
mj_core_schema,
|
|
510
533
|
cacheRefreshInterval / 1000, // convert ms to seconds
|
|
511
534
|
);
|
|
535
|
+
pgConfigData.LocalStorageProvider = sharedCache ?? undefined;
|
|
512
536
|
const provider = new PostgreSQLDataProvider();
|
|
513
537
|
await provider.Config(pgConfigData);
|
|
514
538
|
SetProvider(provider);
|
|
@@ -520,7 +544,7 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
520
544
|
const sysUser = UserCache.Instance.GetSystemUser();
|
|
521
545
|
const backupSysUser = UserCache.Instance.Users.find(u => u.IsActive && u.Type === 'Owner');
|
|
522
546
|
const pgStartupMode = ResolveStartupMode({ configValue: configInfo.startup?.mode, defaultMode: 'full' });
|
|
523
|
-
await StartupManagerImport.Instance.Startup(false, sysUser || backupSysUser, provider, { mode: pgStartupMode.mode });
|
|
547
|
+
await StartupManagerImport.Instance.Startup(false, sysUser || backupSysUser, provider, { mode: pgStartupMode.mode, cacheManagerConfig, warmupLeaseMs });
|
|
524
548
|
|
|
525
549
|
// Both provider sources have now had their turn — config/env at module load, metadata via
|
|
526
550
|
// AuthProviderEngine's startup hook — so "no providers at all" is finally a meaningful check.
|
|
@@ -678,10 +702,11 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
678
702
|
|
|
679
703
|
// cacheRefreshInterval is configured in ms; checkRefreshIntervalSeconds declares seconds — see providerConfigUnits.ts
|
|
680
704
|
const config = new SQLServerProviderConfigData(pool, mj_core_schema, MetadataCacheRefreshIntervalSeconds(cacheRefreshInterval));
|
|
705
|
+
config.LocalStorageProvider = sharedCache ?? undefined;
|
|
681
706
|
// MJAPI is a long-running server, so entry-point default is 'full' engine pre-warm;
|
|
682
707
|
// MJ_STARTUP_MODE / mj.config.cjs startup.mode can override per the shared precedence chain
|
|
683
708
|
const startupMode = ResolveStartupMode({ configValue: configInfo.startup?.mode, defaultMode: 'full' });
|
|
684
|
-
await setupSQLServerClient(config, { mode: startupMode.mode });
|
|
709
|
+
await setupSQLServerClient(config, { mode: startupMode.mode, cacheManagerConfig, warmupLeaseMs });
|
|
685
710
|
|
|
686
711
|
// See the note on the PostgreSQL path above: this is the first point at which both the
|
|
687
712
|
// config/env providers and the metadata catalog have been registered.
|
|
@@ -806,84 +831,31 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
806
831
|
startupLog.LogIf('verbose', 'Server telemetry disabled');
|
|
807
832
|
}
|
|
808
833
|
|
|
809
|
-
//
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
});
|
|
817
|
-
(Metadata.Provider as GenericDatabaseProvider).SetLocalStorageProvider(redisProvider); // global-provider-ok: bootstrap (Redis cache wiring)
|
|
818
|
-
await redisProvider.StartListening();
|
|
819
|
-
|
|
820
|
-
// Connect Redis pub/sub events to LocalCacheManager callback dispatch
|
|
821
|
-
// so cross-server cache invalidation messages are routed to registered callbacks
|
|
822
|
-
redisProvider.OnCacheChanged((event) => {
|
|
823
|
-
const sourceShort = event.SourceServerId ? event.SourceServerId.substring(0, 8) : 'unknown';
|
|
824
|
-
console.log(`[MJAPI] Redis pub/sub → DispatchCacheChange: ${event.Action} for "${event.CacheKey}" from server ${sourceShort}`);
|
|
825
|
-
LocalCacheManager.Instance.DispatchCacheChange(event);
|
|
826
|
-
|
|
827
|
-
// Also broadcast to connected browser clients via GraphQL subscription
|
|
828
|
-
// Extract entity name from the cache key (format: EntityName|Filter|OrderBy|...)
|
|
829
|
-
const entityName = event.CacheKey ? event.CacheKey.split('|')[0] : '';
|
|
830
|
-
if (entityName) {
|
|
831
|
-
PubSubManager.Instance.Publish(CACHE_INVALIDATION_TOPIC, {
|
|
832
|
-
entityName,
|
|
833
|
-
primaryKeyValues: null, // entity-level invalidation
|
|
834
|
-
action: event.Action || 'save',
|
|
835
|
-
sourceServerId: event.SourceServerId || 'unknown',
|
|
836
|
-
timestamp: new Date(),
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
// Fan push-status updates across server instances (MJ #4222).
|
|
842
|
-
//
|
|
843
|
-
// Behind a load balancer the browser's WebSocket lives on one replica while the mutation that
|
|
844
|
-
// drives the agent can be handled by another. The push topic is an in-process PubSub, so a
|
|
845
|
-
// completion published on replica B never reaches a subscriber on replica A — the browser waits
|
|
846
|
-
// forever for an event that was delivered to nobody. Replicating progress and completion over
|
|
847
|
-
// Redis closes that, and the durable tail query remains the backstop if Redis is down.
|
|
848
|
-
await wirePushStatusFanOut(redisProvider, startupLog);
|
|
849
|
-
await wireHandoffOfferFanOut(redisProvider, startupLog);
|
|
850
|
-
|
|
851
|
-
startupLog.LogIf('verbose', `Redis cache provider connected: ${process.env.REDIS_URL}`);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// If Redis is available, swap LocalCacheManager's storage provider to Redis.
|
|
855
|
-
// LocalCacheManager may have already been initialized (with in-memory provider)
|
|
856
|
-
// during engine loading. SetStorageProvider migrates cached data to Redis.
|
|
857
|
-
if (process.env.REDIS_URL) {
|
|
858
|
-
await LocalCacheManager.Instance.SetStorageProvider(Metadata.Provider.LocalStorageProvider); // global-provider-ok: bootstrap
|
|
859
|
-
startupLog.LogIf('verbose', 'LocalCacheManager: storage provider swapped to Redis');
|
|
860
|
-
}
|
|
861
|
-
// Ensure LocalCacheManager is initialized (no-op if already done during engine loading)
|
|
862
|
-
if (!LocalCacheManager.Instance.IsInitialized) {
|
|
863
|
-
// Build cache config from mj.config.cjs cacheSettings
|
|
864
|
-
const cs = configInfo.cacheSettings;
|
|
865
|
-
const cacheConfig = {
|
|
866
|
-
maxSizeBytes: (cs.maxMemoryMB ?? 150) * 1024 * 1024,
|
|
867
|
-
maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
|
|
868
|
-
defaultTTLMs: (cs.defaultTTLSeconds ?? 0) * 1000,
|
|
869
|
-
evictionSweepIntervalMs: (cs.evictionSweepIntervalSeconds ?? 300) * 1000,
|
|
870
|
-
verboseLogging: cs.verboseLogging ?? false,
|
|
871
|
-
};
|
|
872
|
-
await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheConfig); // global-provider-ok: bootstrap
|
|
873
|
-
if (startupLog.IsAtLeast('verbose')) {
|
|
874
|
-
// eslint-disable-next-line no-console
|
|
875
|
-
console.log('LocalCacheManager initialized with cache config:', JSON.stringify({
|
|
876
|
-
maxMemoryMB: cs.maxMemoryMB ?? 150,
|
|
877
|
-
maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
|
|
878
|
-
evictionSweepIntervalSeconds: cs.evictionSweepIntervalSeconds ?? 300,
|
|
879
|
-
}));
|
|
880
|
-
}
|
|
834
|
+
// The database provider already initialized LocalCacheManager from inside its own Config(), with
|
|
835
|
+
// no settings — this call is what makes `cacheSettings` take effect (Initialize merges a config
|
|
836
|
+
// handed to it later). Unconditional on purpose: the guard that used to sit here
|
|
837
|
+
// skipped exactly the case that needed it.
|
|
838
|
+
await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheManagerConfig); // global-provider-ok: bootstrap
|
|
839
|
+
if (sharedCache) {
|
|
840
|
+
await LocalCacheManager.Instance.SetStorageProvider(sharedCache);
|
|
881
841
|
}
|
|
842
|
+
const engineSweepMs = StartEngineSweeper(configInfo.cacheSettings);
|
|
843
|
+
startupLog.LogIf('verbose', engineSweepMs > 0 ? `Engine/database sweep every ${engineSweepMs / 1000}s` : 'Engine/database sweep disabled');
|
|
844
|
+
const userCacheCheckMs = StartUserCacheChecks(configInfo.cacheSettings);
|
|
845
|
+
startupLog.LogIf('verbose', userCacheCheckMs > 0 ? `User cache staleness check every ${userCacheCheckMs / 1000}s` : 'User cache staleness check disabled');
|
|
846
|
+
// Costs nothing unless a metadata entity declares TrustServerCacheCompletely = false.
|
|
847
|
+
const metadataSweepMs = StartMetadataSweep(configInfo.cacheSettings, () => (Metadata.Provider instanceof ProviderBase ? Metadata.Provider : undefined)); // global-provider-ok: bootstrap (this process's one provider)
|
|
848
|
+
startupLog.LogIf('verbose', metadataSweepMs > 0 ? `Metadata/database sweep every ${metadataSweepMs / 1000}s (only for entities declaring drift)` : 'Metadata/database sweep disabled');
|
|
882
849
|
|
|
883
850
|
// Initialize APIKeyEngine singleton — reads apiKeyGeneration from mj.config.cjs automatically
|
|
884
851
|
// This must happen before any request handler calls GetAPIKeyEngine()
|
|
885
852
|
GetAPIKeyEngine();
|
|
886
853
|
|
|
854
|
+
// Actions that run caller-supplied SQL (Run Ad-hoc Query) run only on the read-only login, with
|
|
855
|
+
// ExecuteAdhocQuery's table check and timeout limit.
|
|
856
|
+
ActionEngineServer.Instance.SetReadOnlyProviderFactory(() => CreateReadOnlyProvider(dataSources));
|
|
857
|
+
ActionEngineServer.Instance.SetAdhocSQLAuthorizer(CreateAdhocSQLAuthorizer(configInfo.databaseSettings.requestTimeout));
|
|
858
|
+
|
|
887
859
|
setupComplete$.next(true);
|
|
888
860
|
raiseEvent('setupComplete', dataSources, null, this);
|
|
889
861
|
|
|
@@ -1131,7 +1103,7 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1131
1103
|
const profile = await new Promise<unknown>((res, rej) =>
|
|
1132
1104
|
profSession!.post('Profiler.stop', (e, r) => (e ? rej(e) : res((r as { profile: unknown }).profile)))
|
|
1133
1105
|
);
|
|
1134
|
-
const outPath =
|
|
1106
|
+
const outPath = join(mkdtempSync(join(tmpdir(), 'mjapi-schema-')), `mjapi_schema_build_${process.pid}.cpuprofile`);
|
|
1135
1107
|
writeFileSync(outPath, JSON.stringify(profile));
|
|
1136
1108
|
profSession.disconnect();
|
|
1137
1109
|
// eslint-disable-next-line no-console
|
|
@@ -1414,6 +1386,19 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1414
1386
|
startupLog.LogIf('verbose', `[Widget] Public routes registered at ${WIDGET_MOUNT_PATH}/session and ${WIDGET_MOUNT_PATH}/session/refresh`);
|
|
1415
1387
|
}
|
|
1416
1388
|
|
|
1389
|
+
// ─── Realtime identity verification: PUBLIC verify-link routes (before auth mw) ───
|
|
1390
|
+
// The person opening the emailed link holds no MJ token — the single-use token is the capability.
|
|
1391
|
+
// GET only renders a confirm button; the POST redeems (mail scanners GET every link). Mounted only
|
|
1392
|
+
// when verification is enabled AND able to send mail AND able to protect its codes (see
|
|
1393
|
+
// realtime.identityVerification config: communicationProvider + hmacSecret).
|
|
1394
|
+
if (configInfo.realtime?.identityVerification?.enabled && !RealtimeSessionVerificationService.Instance.IsEnabled) {
|
|
1395
|
+
console.warn('[RealtimeVerify] realtime.identityVerification.enabled is true but verification is unavailable: it needs a communicationProvider and an hmacSecret of at least 16 characters.');
|
|
1396
|
+
}
|
|
1397
|
+
if (RealtimeSessionVerificationService.Instance.IsEnabled) {
|
|
1398
|
+
app.use(REALTIME_VERIFY_MOUNT_PATH, cors<cors.CorsRequest>(), CreateRealtimeVerifyRouter());
|
|
1399
|
+
startupLog.LogIf('verbose', `[RealtimeVerify] Public routes registered at ${REALTIME_VERIFY_MOUNT_PATH}/:token (GET) and ${REALTIME_VERIFY_MOUNT_PATH} (POST)`);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1417
1402
|
// ─── Server extensions loader & shared service registry ───────────────────
|
|
1418
1403
|
const extensionLoader = new ServerExtensionLoader();
|
|
1419
1404
|
_currentExtensionLoader = extensionLoader;
|
|
@@ -1757,12 +1742,16 @@ const setupComplete$ = new ReplaySubject(1);
|
|
|
1757
1742
|
});
|
|
1758
1743
|
}
|
|
1759
1744
|
|
|
1760
|
-
// Launch the AI Agent Session janitor: run own-host orphan recovery once at boot, then
|
|
1761
|
-
//
|
|
1762
|
-
//
|
|
1745
|
+
// Launch the AI Agent Session janitor: run own-host orphan recovery once at boot, then a periodic tick
|
|
1746
|
+
// that closes stale and over-deadline sessions and rebuilds lost realtime recordings from their
|
|
1747
|
+
// crash-recovery shards (storage reads and writes). Self-registers with ShutdownRegistry, so its timer
|
|
1748
|
+
// is cleared by the gracefulShutdown drain below (no explicit Stop() wiring needed here).
|
|
1763
1749
|
if (resumeUser && Metadata.Provider instanceof DatabaseProviderBase) { // global-provider-ok: server startup recovery — boot-time session janitor uses the server's own provider
|
|
1764
1750
|
SessionJanitor.Instance.Start(Metadata.Provider, resumeUser) // global-provider-ok: server-owned background reconciler runs under the server's provider + system user
|
|
1765
1751
|
.catch(err => console.warn(`[SessionJanitor] Startup failed: ${err}`));
|
|
1752
|
+
// The bridge engine's orphan reconcile likewise starts only now that this process holds its port, so it can't
|
|
1753
|
+
// take the live bridges of another MJAPI still serving that port for its own orphans (#5310).
|
|
1754
|
+
StartBridgeOrphanReconciliation(Metadata.Provider, resumeUser); // global-provider-ok: server-owned background reconciler runs under the server's provider + system user
|
|
1766
1755
|
}
|
|
1767
1756
|
|
|
1768
1757
|
// Launch the durable task-graph dispatcher: claim Pending tasks, execute them with a fresh
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DatabaseProviderBase, Metadata } from '@memberjunction/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A fresh Read-Write provider with its OWN transaction state, sharing the server's connection pool.
|
|
5
|
+
*
|
|
6
|
+
* A provider's ambient transaction (`BeginTransaction` / `CommitTransaction` / `RollbackTransaction`)
|
|
7
|
+
* is a field on the provider INSTANCE. Two units of work that open transactions on one instance nest
|
|
8
|
+
* into each other, and every write anyone else makes through that instance while one is open lands
|
|
9
|
+
* inside it (MJ#2140; `entityTransactionScope.ts`). The process-global `Metadata.Provider` is shared
|
|
10
|
+
* by every request, scheduled job and background service, so transactional work must never run on
|
|
11
|
+
* it: one transaction left open there silently swallows every later global-provider write until the
|
|
12
|
+
* process restarts.
|
|
13
|
+
*
|
|
14
|
+
* GraphQL resolvers already get a per-request provider from `AppContext.providers`. Code that runs
|
|
15
|
+
* outside one, such as the auth flow before the context exists, calls this.
|
|
16
|
+
*
|
|
17
|
+
* Cheap: it is the global provider's `CreateIndependentInstance()` (SQL Server and PostgreSQL), which
|
|
18
|
+
* reuses the already-loaded metadata and the shared pool.
|
|
19
|
+
*/
|
|
20
|
+
export async function CreateIsolatedProvider(): Promise<DatabaseProviderBase> {
|
|
21
|
+
return (Metadata.Provider as DatabaseProviderBase).CreateIndependentInstance(); // global-provider-ok: borrows only its pool and metadata, never its transaction state
|
|
22
|
+
}
|