@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The `workQueue` configuration section. Kept out of config.ts so it can be tested without
|
|
3
|
+
* config.ts's module-load database validation.
|
|
4
|
+
* @module MJServer/services
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
export const WorkQueueSubscriptionEntrySchema = z.object({
|
|
9
|
+
/** A subscription name, or '*' for every MJWorker subscription. */
|
|
10
|
+
name: z.string().min(1),
|
|
11
|
+
/** Deliveries this instance processes concurrently for the subscription. */
|
|
12
|
+
concurrency: z.number().int().positive().optional().default(4),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Durable work-queue host. When enabled, this process runs its share of MJWorker subscriptions. Every claim is atomic
|
|
17
|
+
* against shared state, so any number of instances may enable it against one database.
|
|
18
|
+
*/
|
|
19
|
+
export const WorkQueueSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
enabled: z.boolean().optional().default(false),
|
|
22
|
+
systemUserEmail: z.string().optional().default('system@memberjunction.org'),
|
|
23
|
+
subscriptions: z.array(WorkQueueSubscriptionEntrySchema).optional().default([{ name: '*', concurrency: 4 }]),
|
|
24
|
+
idlePollMinMs: z.number().int().positive().optional().default(250),
|
|
25
|
+
idlePollMaxMs: z.number().int().positive().optional().default(5000),
|
|
26
|
+
shutdownDrainMs: z.number().int().nonnegative().optional().default(8000),
|
|
27
|
+
sweeperEnabled: z.boolean().optional().default(true),
|
|
28
|
+
sweeperIntervalMs: z.number().int().positive().optional().default(60000),
|
|
29
|
+
reconcileIntervalMs: z.number().int().nonnegative().optional().default(30000),
|
|
30
|
+
})
|
|
31
|
+
.refine(config => config.idlePollMaxMs >= config.idlePollMinMs, {
|
|
32
|
+
message: 'workQueue.idlePollMaxMs must be >= workQueue.idlePollMinMs',
|
|
33
|
+
path: ['idlePollMaxMs'],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export type WorkQueueConfig = z.infer<typeof WorkQueueSchema>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The merge base for mj.config.cjs. Like scheduledJobs and integrationSyncWorker, the EFFECTIVE default system user
|
|
40
|
+
* is the placeholder: set workQueue.systemUserEmail explicitly, or StartWorkQueueHost fails fast and says so.
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_WORK_QUEUE_CONFIG: WorkQueueConfig = {
|
|
43
|
+
enabled: false,
|
|
44
|
+
systemUserEmail: 'not.set@nowhere.com',
|
|
45
|
+
subscriptions: [{ name: '*', concurrency: 4 }],
|
|
46
|
+
idlePollMinMs: 250,
|
|
47
|
+
idlePollMaxMs: 5000,
|
|
48
|
+
shutdownDrainMs: 8000,
|
|
49
|
+
sweeperEnabled: true,
|
|
50
|
+
sweeperIntervalMs: 60000,
|
|
51
|
+
reconcileIntervalMs: 30000,
|
|
52
|
+
};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything an MJAPI process does with its shared (Redis) connection: which provider to build,
|
|
3
|
+
* what it publishes, what it does with events from other servers, and which periodic jobs the
|
|
4
|
+
* connection's leases coordinate.
|
|
5
|
+
*
|
|
6
|
+
* Two independent consumers share the one connection and its one subscriber:
|
|
7
|
+
* {@link WireSharedCacheEvents} for cache invalidation, and {@link WirePushStatusFanOut} for
|
|
8
|
+
* cross-instance push-status delivery (MJ #4222). Both live here rather than at the call site so
|
|
9
|
+
* that "what is our Redis used for" has a single answer.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
import { BaseEngineSweeper, CacheCategory, LocalCacheManager, LogError, LogStatusEx } from '@memberjunction/core';
|
|
13
|
+
import type { CacheChangedEvent, LocalCacheManagerConfig, ProviderBase } from '@memberjunction/core';
|
|
14
|
+
import type { CacheSettingsConfig } from './config.js';
|
|
15
|
+
import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
|
|
16
|
+
import type { CachePublishMode } from '@memberjunction/redis-provider';
|
|
17
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
18
|
+
import { PubSubManager } from './generic/PubSubManager.js';
|
|
19
|
+
import { PUSH_STATUS_UPDATES_TOPIC, SetPushStatusPublishHook, ParseReplicatedStatusUpdate } from './generic/PushStatusResolver.js';
|
|
20
|
+
import { CACHE_INVALIDATION_TOPIC } from './generic/CacheInvalidationResolver.js';
|
|
21
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* What an MJAPI process publishes, per category.
|
|
25
|
+
*
|
|
26
|
+
* - RunView slots carry their rows: engines on other servers apply them directly.
|
|
27
|
+
* - Writes without a category (the provider's metadata snapshot, ~2 MB compressed) publish a
|
|
28
|
+
* notice only: other servers re-check their metadata against the database and adopt the shared
|
|
29
|
+
* snapshot, so the value never needs to travel.
|
|
30
|
+
* - Everything else (dataset slots, query slots, registry) has no subscriber and publishes nothing.
|
|
31
|
+
* Category clears are always published.
|
|
32
|
+
*/
|
|
33
|
+
export const MJAPI_PUBLISH_MODES: Readonly<Record<string, CachePublishMode>> = {
|
|
34
|
+
[CacheCategory.RunViewCache]: 'full',
|
|
35
|
+
[CacheCategory.Default]: 'notice',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Options for {@link CreateSharedCacheProvider}. */
|
|
39
|
+
export interface SharedCacheProviderOptions {
|
|
40
|
+
Url: string;
|
|
41
|
+
KeyPrefix: string;
|
|
42
|
+
DefaultTTLSeconds?: number;
|
|
43
|
+
EnableLogging: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Expiry for the shared Redis cache: `REDIS_TTL_SECONDS` when it holds a non-negative integer,
|
|
48
|
+
* otherwise `cacheSettings.sharedCacheTTLSeconds`, otherwise `undefined` (the provider's default).
|
|
49
|
+
*/
|
|
50
|
+
export function ResolveSharedCacheTTLSeconds(configured: number | undefined, env: NodeJS.ProcessEnv = process.env): number | undefined {
|
|
51
|
+
const fromEnv = env.REDIS_TTL_SECONDS;
|
|
52
|
+
if (fromEnv !== undefined && fromEnv.trim() !== '') {
|
|
53
|
+
const parsed = Number(fromEnv);
|
|
54
|
+
if (Number.isInteger(parsed) && parsed >= 0) {
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
console.warn(`REDIS_TTL_SECONDS="${fromEnv}" is not a non-negative integer; ignoring it`);
|
|
58
|
+
}
|
|
59
|
+
return configured;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The shared cache the environment asks for (`REDIS_URL`, `REDIS_KEY_PREFIX`, `REDIS_TTL_SECONDS`),
|
|
64
|
+
* or null. Built before the database provider so metadata and startup engines use it from the
|
|
65
|
+
* first read.
|
|
66
|
+
*/
|
|
67
|
+
export function CreateSharedCacheFromEnvironment(settings: CacheSettingsConfig | undefined, env: NodeJS.ProcessEnv = process.env): RedisLocalStorageProvider | null {
|
|
68
|
+
if (!env.REDIS_URL) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return CreateSharedCacheProvider({
|
|
72
|
+
Url: env.REDIS_URL,
|
|
73
|
+
KeyPrefix: env.REDIS_KEY_PREFIX || 'mj',
|
|
74
|
+
DefaultTTLSeconds: ResolveSharedCacheTTLSeconds(settings?.sharedCacheTTLSeconds, env),
|
|
75
|
+
EnableLogging: settings?.verboseLogging ?? false,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The warm-up lease MJAPI hands to `StartupManager`, in milliseconds, from
|
|
81
|
+
* `cacheSettings.startupWarmupLeaseSeconds` (default 30).
|
|
82
|
+
*/
|
|
83
|
+
export function WarmupLeaseMsFromSettings(settings: CacheSettingsConfig | undefined): number {
|
|
84
|
+
return (settings?.startupWarmupLeaseSeconds ?? 30) * 1000;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** `LocalCacheManager` settings from `cacheSettings` in mj.config.cjs. */
|
|
88
|
+
export function CacheManagerConfigFromSettings(settings: CacheSettingsConfig | undefined): Partial<LocalCacheManagerConfig> {
|
|
89
|
+
return {
|
|
90
|
+
maxSizeBytes: (settings?.maxMemoryMB ?? 150) * 1024 * 1024,
|
|
91
|
+
maxPercentOfCachePerEntity: settings?.maxPercentOfCachePerEntity ?? 50,
|
|
92
|
+
defaultTTLMs: (settings?.defaultTTLSeconds ?? 0) * 1000,
|
|
93
|
+
evictionSweepIntervalMs: (settings?.evictionSweepIntervalSeconds ?? 300) * 1000,
|
|
94
|
+
verboseLogging: settings?.verboseLogging ?? false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Starts the periodic engine-vs-database sweep (plan Phase 3.1) at
|
|
100
|
+
* `cacheSettings.engineSweepIntervalSeconds` (default 300; 0 leaves it off).
|
|
101
|
+
* @returns The interval in milliseconds, 0 when disabled.
|
|
102
|
+
*/
|
|
103
|
+
export function StartEngineSweeper(settings: CacheSettingsConfig | undefined): number {
|
|
104
|
+
const intervalMs = (settings?.engineSweepIntervalSeconds ?? 300) * 1000;
|
|
105
|
+
BaseEngineSweeper.Instance.Start(intervalMs);
|
|
106
|
+
return intervalMs;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The periodic metadata sweep, so a restart or a config change can replace it. @internal */
|
|
110
|
+
let metadataSweepTimer: ReturnType<typeof setInterval> | null = null;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Starts the periodic metadata-vs-database sweep at `cacheSettings.metadataSweepIntervalSeconds`
|
|
114
|
+
* (default 300; 0 leaves it off).
|
|
115
|
+
*
|
|
116
|
+
* The sweep asks the database **nothing** unless one of the entities the metadata is built from
|
|
117
|
+
* declares `TrustServerCacheCompletely = false` — see `ProviderBase.SweepMetadataAgainstDatabase`.
|
|
118
|
+
* On a stock installation no metadata entity declares it, so the timer ticks and costs nothing. It
|
|
119
|
+
* exists for installations that write metadata tables directly, where no event is ever raised and
|
|
120
|
+
* nothing else would notice.
|
|
121
|
+
*
|
|
122
|
+
* One process per interval does the work, via a shared lease: a process that refreshes writes a new
|
|
123
|
+
* snapshot and publishes a notice, which the others act on, so there is no value in each of them
|
|
124
|
+
* asking the database the same question.
|
|
125
|
+
*
|
|
126
|
+
* @returns The interval in milliseconds, 0 when disabled.
|
|
127
|
+
*/
|
|
128
|
+
export function StartMetadataSweep(settings: CacheSettingsConfig | undefined, metadataProvider: () => ProviderBase | undefined): number {
|
|
129
|
+
if (metadataSweepTimer) {
|
|
130
|
+
clearInterval(metadataSweepTimer);
|
|
131
|
+
metadataSweepTimer = null;
|
|
132
|
+
}
|
|
133
|
+
const intervalMs = (settings?.metadataSweepIntervalSeconds ?? 300) * 1000;
|
|
134
|
+
if (intervalMs <= 0) {
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
const leaseMs = Math.max(5_000, intervalMs - 5_000);
|
|
138
|
+
const timer = setInterval(() => void runMetadataSweep(metadataProvider(), leaseMs), intervalMs);
|
|
139
|
+
if (typeof timer === 'object' && timer !== null && 'unref' in timer) {
|
|
140
|
+
(timer as { unref(): void }).unref();
|
|
141
|
+
}
|
|
142
|
+
metadataSweepTimer = timer;
|
|
143
|
+
return intervalMs;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** One sweep: take the fleet lease only when there is something to check. @internal */
|
|
147
|
+
async function runMetadataSweep(provider: ProviderBase | undefined, leaseMs: number): Promise<void> {
|
|
148
|
+
if (!provider) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
// The lease is taken only when an entity declares drift, so a stock installation costs no
|
|
153
|
+
// Redis round trip either — the provider answers from its own membership set.
|
|
154
|
+
const declared = provider.MetadataMembersDeclaringDrift();
|
|
155
|
+
if (declared.length === 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (!(await LocalCacheManager.Instance.TryAcquireSharedLease('metadata-sweep', leaseMs))) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const result = await provider.SweepMetadataAgainstDatabase();
|
|
162
|
+
if (result.Refreshed) {
|
|
163
|
+
LogStatusEx({ message: `[MJAPI] metadata sweep reloaded metadata; entities declaring drift: ${result.Declared.join(', ')}`, verboseOnly: false });
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
LogError(`[MJAPI] metadata sweep failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Starts the periodic user-cache staleness check at `cacheSettings.userCacheCheckIntervalSeconds`
|
|
172
|
+
* (default 300; 0 leaves it off). The cache refreshes on MJ writes by itself — this covers users
|
|
173
|
+
* and roles changed outside MJ.
|
|
174
|
+
* @returns The interval in milliseconds, 0 when disabled.
|
|
175
|
+
*/
|
|
176
|
+
export function StartUserCacheChecks(settings: CacheSettingsConfig | undefined): number {
|
|
177
|
+
const intervalMs = (settings?.userCacheCheckIntervalSeconds ?? 300) * 1000;
|
|
178
|
+
UserCache.Instance.StartStalenessChecks(intervalMs);
|
|
179
|
+
return intervalMs;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Builds the Redis provider MJAPI uses as its shared cache. Pub/sub starts with {@link WireSharedCacheEvents}. */
|
|
183
|
+
export function CreateSharedCacheProvider(options: SharedCacheProviderOptions): RedisLocalStorageProvider {
|
|
184
|
+
// The provider's own config keys stay as the package declares them.
|
|
185
|
+
return new RedisLocalStorageProvider({
|
|
186
|
+
url: options.Url,
|
|
187
|
+
keyPrefix: options.KeyPrefix,
|
|
188
|
+
defaultTTLSeconds: options.DefaultTTLSeconds,
|
|
189
|
+
enablePubSub: true,
|
|
190
|
+
enableLogging: options.EnableLogging,
|
|
191
|
+
publishModes: MJAPI_PUBLISH_MODES,
|
|
192
|
+
defaultPublishMode: 'none',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Subscribes to other servers' cache events and routes them. Called before the database provider
|
|
198
|
+
* exists (so engines hear peers while they load); `metadataProvider` is resolved per event.
|
|
199
|
+
* 1. a metadata-snapshot notice → a metadata staleness check on this process's provider, and
|
|
200
|
+
* nothing further (a consumed notice returns here);
|
|
201
|
+
* 2. every other event → `LocalCacheManager.DispatchCacheChange` (engines, the user cache, and
|
|
202
|
+
* RunView callbacks);
|
|
203
|
+
* 3. RunView slot changes → an entity-level invalidation to connected browsers.
|
|
204
|
+
*/
|
|
205
|
+
export async function WireSharedCacheEvents(redis: RedisLocalStorageProvider, metadataProvider: () => ProviderBase | undefined): Promise<void> {
|
|
206
|
+
await redis.StartListening();
|
|
207
|
+
redis.OnCacheChanged((event) => RouteSharedCacheEvent(event, metadataProvider()));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** One incoming event, routed as described on {@link WireSharedCacheEvents}. */
|
|
211
|
+
export function RouteSharedCacheEvent(event: CacheChangedEvent, metadataProvider: ProviderBase | undefined): void {
|
|
212
|
+
const source = event.SourceServerId ? event.SourceServerId.substring(0, 8) : 'unknown';
|
|
213
|
+
LogStatusEx({ message: `[MJAPI] shared cache event: ${event.Category}/${event.Action} "${event.CacheKey}" from ${source}`, verboseOnly: true });
|
|
214
|
+
|
|
215
|
+
if (metadataProvider?.HandlePeerMetadataNotice(event)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
LocalCacheManager.Instance.DispatchCacheChange(event);
|
|
219
|
+
relayRunViewChangeToBrowsers(event);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Browsers only care about entity data. A RunView slot key starts with the entity name
|
|
224
|
+
* (`Entity|Filter|…`); other categories' keys are not entity names and used to reach browsers as
|
|
225
|
+
* invalidations for "entities" such as `___MJCore_Metadata_AllMetadata`.
|
|
226
|
+
*/
|
|
227
|
+
function relayRunViewChangeToBrowsers(event: CacheChangedEvent): void {
|
|
228
|
+
if (event.Category !== CacheCategory.RunViewCache || event.Action === 'category_cleared') {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const entityName = event.CacheKey ? event.CacheKey.split('|')[0] : '';
|
|
232
|
+
if (!entityName) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
PubSubManager.Instance.Publish(CACHE_INVALIDATION_TOPIC, {
|
|
236
|
+
entityName,
|
|
237
|
+
primaryKeyValues: null, // entity-level invalidation
|
|
238
|
+
action: event.Action,
|
|
239
|
+
sourceServerId: event.SourceServerId || 'unknown',
|
|
240
|
+
timestamp: new Date(),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Redis channel carrying replicated push-status updates between server instances. */
|
|
245
|
+
const PUSH_STATUS_FANOUT_CHANNEL = 'push-status-updates';
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Replicate push-status updates across server instances over Redis (MJ #4222).
|
|
249
|
+
*
|
|
250
|
+
* Outbound: every locally-published update is forwarded on a shared channel. Inbound: a message
|
|
251
|
+
* from another instance is republished onto THIS instance's local topic, where the normal
|
|
252
|
+
* subscription filter decides who receives it — so the identity gate (`ownerUserId` vs. the
|
|
253
|
+
* connection's authenticated user) still applies to a replicated message exactly as it does to a
|
|
254
|
+
* local one. The replica has no say in who sees what.
|
|
255
|
+
*
|
|
256
|
+
* Republishing goes straight to `PubSubManager`, never back through `publishStatusUpdate`, so an
|
|
257
|
+
* inbound message cannot be re-broadcast and loop. `SourceServerId` guards the remaining case: a
|
|
258
|
+
* publisher also receives its own message from Redis.
|
|
259
|
+
*
|
|
260
|
+
* The channel rides the same connection and subscriber as cache invalidation, and is namespaced
|
|
261
|
+
* with the provider's key prefix — so replicas configured with different `REDIS_KEY_PREFIX`
|
|
262
|
+
* values stop replicating to each other, exactly as their caches do.
|
|
263
|
+
*
|
|
264
|
+
* @param redis The shared cache provider, already built by {@link CreateSharedCacheFromEnvironment}.
|
|
265
|
+
*/
|
|
266
|
+
export async function WirePushStatusFanOut(redis: RedisLocalStorageProvider): Promise<void> {
|
|
267
|
+
try {
|
|
268
|
+
await redis.SubscribeToChannel(PUSH_STATUS_FANOUT_CHANNEL, (raw: string) => {
|
|
269
|
+
try {
|
|
270
|
+
const payload = ParseReplicatedStatusUpdate(raw, MJGlobal.Instance.ProcessUUID);
|
|
271
|
+
if (!payload) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// Rebuilt as a plain record: the topic's publish signature takes an index-signature
|
|
275
|
+
// type, and listing the fields keeps the wire shape explicit at the one place it
|
|
276
|
+
// crosses hosts.
|
|
277
|
+
PubSubManager.Instance.Publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
278
|
+
sessionId: payload.sessionId,
|
|
279
|
+
ownerUserId: payload.ownerUserId,
|
|
280
|
+
message: payload.message,
|
|
281
|
+
SourceServerId: payload.SourceServerId,
|
|
282
|
+
});
|
|
283
|
+
} catch (err) {
|
|
284
|
+
// A malformed message on a shared channel must not take down the subscriber.
|
|
285
|
+
LogError('Error processing push-status fan-out message', undefined, err);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
SetPushStatusPublishHook((payload) => {
|
|
290
|
+
redis.PublishMessage(PUSH_STATUS_FANOUT_CHANNEL, JSON.stringify(payload));
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Printed unconditionally, not verbose-gated. "Is fan-out actually on?" is the first
|
|
294
|
+
// question anyone debugging a hung conversation behind a load balancer asks, and a silent
|
|
295
|
+
// default left no way to answer it.
|
|
296
|
+
console.log('[MJAPI] Push-status updates: cross-instance fan-out enabled via Redis');
|
|
297
|
+
} catch (err) {
|
|
298
|
+
// Single-instance delivery still works, and the durable tail query covers the rest.
|
|
299
|
+
// Degraded, not broken — so this must not stop the server from starting.
|
|
300
|
+
console.warn(`Push-status fan-out unavailable: ${(err as Error).message}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -54,6 +54,13 @@ export type AppContext = {
|
|
|
54
54
|
* Per-request DatabaseProviderBase instances
|
|
55
55
|
*/
|
|
56
56
|
providers: Array<ProviderInfo>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The caller's network address as Express resolved it (`req.ip`, which honours the app's
|
|
60
|
+
* `trust proxy` setting). Present for HTTP requests; absent on WebSocket (subscription) contexts.
|
|
61
|
+
* Used only to key abuse limits (e.g. realtime verification sends) — never as an identity.
|
|
62
|
+
*/
|
|
63
|
+
ClientIp?: string;
|
|
57
64
|
};
|
|
58
65
|
|
|
59
66
|
export class ProviderInfo {
|
|
@@ -121,6 +128,11 @@ export type RunViewGenericParams = {
|
|
|
121
128
|
* Propagated to `RunViewParams.DataSource`.
|
|
122
129
|
*/
|
|
123
130
|
dataSource?: 'Live' | 'Materialized';
|
|
131
|
+
/**
|
|
132
|
+
* When true, binary fields (`varbinary` / `bytea`, as base64 strings) are returned.
|
|
133
|
+
* Propagated to `RunViewParams.IncludeBinaryFields`.
|
|
134
|
+
*/
|
|
135
|
+
includeBinaryFields?: boolean;
|
|
124
136
|
};
|
|
125
137
|
|
|
126
138
|
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free helpers for {@link AdhocQueryResolver}. Kept in their own
|
|
3
|
-
* module (no type-graphql / mssql imports) so the total-row-count boundary logic is
|
|
4
|
-
* unit-testable without standing up the GraphQL resolver or a database.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Determines the exact total row count from the page alone, when it can be known
|
|
8
|
-
* without a separate `COUNT(*)`:
|
|
9
|
-
*
|
|
10
|
-
* - **Not paging** (`maxRows == null`): the query ran uncapped, so every row came
|
|
11
|
-
* back — the total is just the number returned.
|
|
12
|
-
* - **Short page** (`pageLength < maxRows`): a page shorter than the page size means
|
|
13
|
-
* there are no rows beyond it, so the total is `startRow + pageLength` exactly.
|
|
14
|
-
* - **Full page** (`pageLength >= maxRows`): more rows may exist — returns `null` to
|
|
15
|
-
* signal that a `COUNT(*)` is required to know the true total.
|
|
16
|
-
*/
|
|
17
|
-
export declare function ExactTotalFromPage(startRow: number, pageLength: number, maxRows: number | null): number | null;
|
|
18
|
-
/** @deprecated Use {@link ExactTotalFromPage}. */
|
|
19
|
-
export declare function exactTotalFromPage(startRow: number, pageLength: number, maxRows: number | null): number | null;
|
|
20
|
-
/**
|
|
21
|
-
* Reads the total row count from a `COUNT(*)` recordset (`[{ TotalRowCount }]`),
|
|
22
|
-
* falling back to `fallback` when the count is absent, non-numeric, or negative — so
|
|
23
|
-
* an unexpected count shape never yields a misleading total. `fallback` should be a
|
|
24
|
-
* safe lower bound (e.g. `startRow + rowsReturned`).
|
|
25
|
-
*/
|
|
26
|
-
export declare function ResolveAdhocTotalRowCount(countRows: ReadonlyArray<{
|
|
27
|
-
TotalRowCount?: unknown;
|
|
28
|
-
}> | null | undefined, fallback: number): number;
|
|
29
|
-
/** @deprecated Use {@link ResolveAdhocTotalRowCount}. */
|
|
30
|
-
export declare function resolveAdhocTotalRowCount(countRows: ReadonlyArray<{
|
|
31
|
-
TotalRowCount?: unknown;
|
|
32
|
-
}> | null | undefined, fallback: number): number;
|
|
33
|
-
//# sourceMappingURL=adhoc-query-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adhoc-query-helpers.d.ts","sourceRoot":"","sources":["../../src/resolvers/adhoc-query-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,IAAI,GACvB,MAAM,GAAG,IAAI,CAQf;AAED,kDAAkD;AAClD,wBAAgB,kBAAkB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,IAAI,GACvB,MAAM,GAAG,IAAI,CAEf;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,aAAa,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EACxE,QAAQ,EAAE,MAAM,GACjB,MAAM,CASR;AAED,yDAAyD;AACzD,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,aAAa,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EACxE,QAAQ,EAAE,MAAM,GACjB,MAAM,CAER"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free helpers for {@link AdhocQueryResolver}. Kept in their own
|
|
3
|
-
* module (no type-graphql / mssql imports) so the total-row-count boundary logic is
|
|
4
|
-
* unit-testable without standing up the GraphQL resolver or a database.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Determines the exact total row count from the page alone, when it can be known
|
|
8
|
-
* without a separate `COUNT(*)`:
|
|
9
|
-
*
|
|
10
|
-
* - **Not paging** (`maxRows == null`): the query ran uncapped, so every row came
|
|
11
|
-
* back — the total is just the number returned.
|
|
12
|
-
* - **Short page** (`pageLength < maxRows`): a page shorter than the page size means
|
|
13
|
-
* there are no rows beyond it, so the total is `startRow + pageLength` exactly.
|
|
14
|
-
* - **Full page** (`pageLength >= maxRows`): more rows may exist — returns `null` to
|
|
15
|
-
* signal that a `COUNT(*)` is required to know the true total.
|
|
16
|
-
*/
|
|
17
|
-
export function ExactTotalFromPage(startRow, pageLength, maxRows) {
|
|
18
|
-
if (maxRows == null) {
|
|
19
|
-
return pageLength;
|
|
20
|
-
}
|
|
21
|
-
if (pageLength < maxRows) {
|
|
22
|
-
return startRow + pageLength;
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
/** @deprecated Use {@link ExactTotalFromPage}. */
|
|
27
|
-
export function exactTotalFromPage(startRow, pageLength, maxRows) {
|
|
28
|
-
return ExactTotalFromPage(startRow, pageLength, maxRows);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Reads the total row count from a `COUNT(*)` recordset (`[{ TotalRowCount }]`),
|
|
32
|
-
* falling back to `fallback` when the count is absent, non-numeric, or negative — so
|
|
33
|
-
* an unexpected count shape never yields a misleading total. `fallback` should be a
|
|
34
|
-
* safe lower bound (e.g. `startRow + rowsReturned`).
|
|
35
|
-
*/
|
|
36
|
-
export function ResolveAdhocTotalRowCount(countRows, fallback) {
|
|
37
|
-
const raw = countRows?.[0]?.TotalRowCount;
|
|
38
|
-
if (raw != null) {
|
|
39
|
-
const n = Number(raw);
|
|
40
|
-
if (Number.isFinite(n) && n >= 0) {
|
|
41
|
-
return Math.floor(n);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return fallback;
|
|
45
|
-
}
|
|
46
|
-
/** @deprecated Use {@link ResolveAdhocTotalRowCount}. */
|
|
47
|
-
export function resolveAdhocTotalRowCount(countRows, fallback) {
|
|
48
|
-
return ResolveAdhocTotalRowCount(countRows, fallback);
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=adhoc-query-helpers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adhoc-query-helpers.js","sourceRoot":"","sources":["../../src/resolvers/adhoc-query-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAC9B,QAAgB,EAChB,UAAkB,EAClB,OAAsB;IAEtB,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,UAAU,CAAC;IACtB,CAAC;IACD,IAAI,UAAU,GAAG,OAAO,EAAE,CAAC;QACvB,OAAO,QAAQ,GAAG,UAAU,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,kBAAkB,CAC9B,QAAgB,EAChB,UAAkB,EAClB,OAAsB;IAEtB,OAAO,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACrC,SAAwE,EACxE,QAAgB;IAEhB,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC;IAC1C,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,yBAAyB,CACrC,SAAwE,EACxE,QAAgB;IAEhB,OAAO,yBAAyB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { ExactTotalFromPage, ResolveAdhocTotalRowCount } from '../resolvers/adhoc-query-helpers';
|
|
3
|
-
|
|
4
|
-
describe('exactTotalFromPage', () => {
|
|
5
|
-
it('returns the row count when not paging (maxRows null) — the query was uncapped', () => {
|
|
6
|
-
expect(ExactTotalFromPage(0, 47, null)).toBe(47);
|
|
7
|
-
expect(ExactTotalFromPage(0, 0, null)).toBe(0);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('returns startRow + pageLength for a SHORT page (fewer rows than the page size)', () => {
|
|
11
|
-
expect(ExactTotalFromPage(0, 47, 100)).toBe(47); // page 1, short → exact total
|
|
12
|
-
expect(ExactTotalFromPage(200, 30, 100)).toBe(230); // page 3, short last page
|
|
13
|
-
expect(ExactTotalFromPage(100, 0, 100)).toBe(100); // empty page past the end
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('returns null for a FULL page — a COUNT is required because more rows may exist', () => {
|
|
17
|
-
expect(ExactTotalFromPage(0, 100, 100)).toBeNull();
|
|
18
|
-
expect(ExactTotalFromPage(500, 100, 100)).toBeNull();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('treats an over-full page defensively as "needs count" (should not occur with OFFSET/FETCH)', () => {
|
|
22
|
-
expect(ExactTotalFromPage(0, 150, 100)).toBeNull();
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('resolveAdhocTotalRowCount', () => {
|
|
27
|
-
it('reads TotalRowCount from the count recordset', () => {
|
|
28
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 2087 }], 100)).toBe(2087);
|
|
29
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 0 }], 100)).toBe(0);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('floors a fractional/stringified count', () => {
|
|
33
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: 42.9 }], 0)).toBe(42);
|
|
34
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: '2087' as unknown as number }], 0)).toBe(2087);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('falls back when the count is absent, empty, null, or malformed', () => {
|
|
38
|
-
expect(ResolveAdhocTotalRowCount(null, 100)).toBe(100);
|
|
39
|
-
expect(ResolveAdhocTotalRowCount(undefined, 100)).toBe(100);
|
|
40
|
-
expect(ResolveAdhocTotalRowCount([], 100)).toBe(100);
|
|
41
|
-
expect(ResolveAdhocTotalRowCount([{}], 100)).toBe(100);
|
|
42
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: null }], 100)).toBe(100);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('falls back on non-finite or negative counts (never reports a misleading total)', () => {
|
|
46
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: Number.NaN }], 100)).toBe(100);
|
|
47
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: -5 }], 100)).toBe(100);
|
|
48
|
-
expect(ResolveAdhocTotalRowCount([{ TotalRowCount: Infinity }], 100)).toBe(100);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free helpers for {@link AdhocQueryResolver}. Kept in their own
|
|
3
|
-
* module (no type-graphql / mssql imports) so the total-row-count boundary logic is
|
|
4
|
-
* unit-testable without standing up the GraphQL resolver or a database.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Determines the exact total row count from the page alone, when it can be known
|
|
9
|
-
* without a separate `COUNT(*)`:
|
|
10
|
-
*
|
|
11
|
-
* - **Not paging** (`maxRows == null`): the query ran uncapped, so every row came
|
|
12
|
-
* back — the total is just the number returned.
|
|
13
|
-
* - **Short page** (`pageLength < maxRows`): a page shorter than the page size means
|
|
14
|
-
* there are no rows beyond it, so the total is `startRow + pageLength` exactly.
|
|
15
|
-
* - **Full page** (`pageLength >= maxRows`): more rows may exist — returns `null` to
|
|
16
|
-
* signal that a `COUNT(*)` is required to know the true total.
|
|
17
|
-
*/
|
|
18
|
-
export function ExactTotalFromPage(
|
|
19
|
-
startRow: number,
|
|
20
|
-
pageLength: number,
|
|
21
|
-
maxRows: number | null,
|
|
22
|
-
): number | null {
|
|
23
|
-
if (maxRows == null) {
|
|
24
|
-
return pageLength;
|
|
25
|
-
}
|
|
26
|
-
if (pageLength < maxRows) {
|
|
27
|
-
return startRow + pageLength;
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** @deprecated Use {@link ExactTotalFromPage}. */
|
|
33
|
-
export function exactTotalFromPage(
|
|
34
|
-
startRow: number,
|
|
35
|
-
pageLength: number,
|
|
36
|
-
maxRows: number | null,
|
|
37
|
-
): number | null {
|
|
38
|
-
return ExactTotalFromPage(startRow, pageLength, maxRows);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Reads the total row count from a `COUNT(*)` recordset (`[{ TotalRowCount }]`),
|
|
43
|
-
* falling back to `fallback` when the count is absent, non-numeric, or negative — so
|
|
44
|
-
* an unexpected count shape never yields a misleading total. `fallback` should be a
|
|
45
|
-
* safe lower bound (e.g. `startRow + rowsReturned`).
|
|
46
|
-
*/
|
|
47
|
-
export function ResolveAdhocTotalRowCount(
|
|
48
|
-
countRows: ReadonlyArray<{ TotalRowCount?: unknown }> | null | undefined,
|
|
49
|
-
fallback: number,
|
|
50
|
-
): number {
|
|
51
|
-
const raw = countRows?.[0]?.TotalRowCount;
|
|
52
|
-
if (raw != null) {
|
|
53
|
-
const n = Number(raw);
|
|
54
|
-
if (Number.isFinite(n) && n >= 0) {
|
|
55
|
-
return Math.floor(n);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return fallback;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** @deprecated Use {@link ResolveAdhocTotalRowCount}. */
|
|
62
|
-
export function resolveAdhocTotalRowCount(
|
|
63
|
-
countRows: ReadonlyArray<{ TotalRowCount?: unknown }> | null | undefined,
|
|
64
|
-
fallback: number,
|
|
65
|
-
): number {
|
|
66
|
-
return ResolveAdhocTotalRowCount(countRows, fallback);
|
|
67
|
-
}
|