doover-js 0.11.0 → 0.13.0
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 +106 -0
- package/dist/apis/_args.js +5 -5
- package/dist/apis/agents-api.d.ts +4 -4
- package/dist/apis/agents-api.js +2 -2
- package/dist/apis/aggregates-api.d.ts +4 -4
- package/dist/apis/aggregates-api.js +5 -5
- package/dist/apis/alarms-api.d.ts +2 -2
- package/dist/apis/alarms-api.js +3 -3
- package/dist/apis/channels-api.d.ts +3 -3
- package/dist/apis/channels-api.js +6 -6
- package/dist/apis/connections-api.d.ts +2 -2
- package/dist/apis/connections-api.js +6 -6
- package/dist/apis/messages-api.d.ts +4 -4
- package/dist/apis/messages-api.js +11 -11
- package/dist/apis/notifications-api.d.ts +2 -2
- package/dist/apis/notifications-api.js +4 -4
- package/dist/apis/organisations-api.d.ts +2 -2
- package/dist/apis/permissions-api.d.ts +2 -2
- package/dist/apis/permissions-api.js +3 -3
- package/dist/apis/processors-api.d.ts +2 -2
- package/dist/apis/turn-api.d.ts +2 -2
- package/dist/apis/users-api.d.ts +2 -2
- package/dist/auth/auth-store.d.ts +1 -1
- package/dist/auth/build-auth.d.ts +3 -3
- package/dist/auth/build-auth.js +8 -8
- package/dist/auth/cookie-auth.d.ts +1 -1
- package/dist/auth/cookie-auth.js +2 -2
- package/dist/auth/doover-auth.d.ts +2 -2
- package/dist/auth/doover-token-auth.d.ts +3 -3
- package/dist/auth/doover-token-auth.js +13 -13
- package/dist/auth/index.d.ts +10 -0
- package/dist/auth/index.js +15 -0
- package/dist/client/data-client.d.ts +17 -14
- package/dist/client/doover-client.d.ts +9 -7
- package/dist/client/doover-client.js +44 -42
- package/dist/client/errors.d.ts +2 -2
- package/dist/client/errors.js +4 -4
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +12 -0
- package/dist/client/local-agent-client.d.ts +3 -3
- package/dist/client/local-agent-client.js +19 -19
- package/dist/client/multiplex-client.d.ts +5 -5
- package/dist/client/multiplex-client.js +25 -25
- package/dist/client/multiplex-gateway.d.ts +3 -3
- package/dist/client/multiplex-merge.d.ts +1 -1
- package/dist/client/network-status.d.ts +17 -0
- package/dist/client/network-status.js +70 -0
- package/dist/client/offline-cache.d.ts +9 -4
- package/dist/client/offline-cache.js +34 -22
- package/dist/client/provenance.d.ts +1 -1
- package/dist/client/request-options.d.ts +1 -1
- package/dist/client/singleton.d.ts +2 -2
- package/dist/client/singleton.js +2 -2
- package/dist/client/status-tracker.d.ts +1 -1
- package/dist/esm/apis/_args.d.ts +16 -0
- package/dist/esm/apis/_args.js +27 -0
- package/dist/esm/apis/agents-api.d.ts +196 -0
- package/dist/esm/apis/agents-api.js +327 -0
- package/dist/esm/apis/aggregates-api.d.ts +67 -0
- package/dist/esm/apis/aggregates-api.js +64 -0
- package/dist/esm/apis/alarms-api.d.ts +48 -0
- package/dist/esm/apis/alarms-api.js +75 -0
- package/dist/esm/apis/channels-api.d.ts +83 -0
- package/dist/esm/apis/channels-api.js +65 -0
- package/dist/esm/apis/connections-api.d.ts +45 -0
- package/dist/esm/apis/connections-api.js +44 -0
- package/dist/esm/apis/messages-api.d.ts +128 -0
- package/dist/esm/apis/messages-api.js +179 -0
- package/dist/esm/apis/notifications-api.d.ts +94 -0
- package/dist/esm/apis/notifications-api.js +153 -0
- package/dist/esm/apis/organisations-api.d.ts +26 -0
- package/dist/esm/apis/organisations-api.js +41 -0
- package/dist/esm/apis/permissions-api.d.ts +17 -0
- package/dist/esm/apis/permissions-api.js +23 -0
- package/dist/esm/apis/processors-api.d.ts +56 -0
- package/dist/esm/apis/processors-api.js +97 -0
- package/dist/esm/apis/turn-api.d.ts +7 -0
- package/dist/esm/apis/turn-api.js +8 -0
- package/dist/esm/apis/users-api.d.ts +8 -0
- package/dist/esm/apis/users-api.js +9 -0
- package/dist/esm/auth/auth-profile.d.ts +45 -0
- package/dist/esm/auth/auth-profile.js +68 -0
- package/dist/esm/auth/auth-store.d.ts +14 -0
- package/dist/esm/auth/build-auth.d.ts +38 -0
- package/dist/esm/auth/build-auth.js +92 -0
- package/dist/esm/auth/cookie-auth.d.ts +91 -0
- package/dist/esm/auth/cookie-auth.js +196 -0
- package/dist/esm/auth/doover-auth.d.ts +52 -0
- package/dist/esm/auth/doover-auth.js +28 -0
- package/dist/esm/auth/doover-token-auth.d.ts +39 -0
- package/dist/esm/auth/doover-token-auth.js +217 -0
- package/dist/esm/auth/errors.d.ts +3 -0
- package/dist/esm/auth/errors.js +6 -0
- package/dist/esm/auth/index.d.ts +10 -0
- package/dist/esm/auth/index.js +6 -0
- package/dist/esm/auth/jwt.d.ts +6 -0
- package/dist/esm/auth/jwt.js +26 -0
- package/dist/esm/client/capabilities.d.ts +10 -0
- package/dist/esm/client/capabilities.js +42 -0
- package/dist/esm/client/data-client.d.ts +107 -0
- package/dist/esm/client/doover-client.d.ts +48 -0
- package/dist/esm/client/doover-client.js +96 -0
- package/dist/esm/client/errors.d.ts +32 -0
- package/dist/esm/client/errors.js +64 -0
- package/dist/esm/client/index.d.ts +6 -0
- package/dist/esm/client/index.js +3 -0
- package/dist/esm/client/local-agent-client.d.ts +65 -0
- package/dist/esm/client/local-agent-client.js +225 -0
- package/dist/esm/client/multiplex-client.d.ts +165 -0
- package/dist/esm/client/multiplex-client.js +842 -0
- package/dist/esm/client/multiplex-gateway.d.ts +49 -0
- package/dist/esm/client/multiplex-gateway.js +110 -0
- package/dist/esm/client/multiplex-merge.d.ts +12 -0
- package/dist/esm/client/multiplex-merge.js +38 -0
- package/dist/esm/client/network-status.d.ts +17 -0
- package/dist/esm/client/network-status.js +66 -0
- package/dist/esm/client/offline-cache.d.ts +167 -0
- package/dist/esm/client/offline-cache.js +769 -0
- package/dist/esm/client/provenance.d.ts +60 -0
- package/dist/esm/client/provenance.js +108 -0
- package/dist/esm/client/request-options.d.ts +23 -0
- package/dist/esm/client/request-options.js +23 -0
- package/dist/esm/client/singleton.d.ts +26 -0
- package/dist/esm/client/singleton.js +54 -0
- package/dist/esm/client/stats.d.ts +79 -0
- package/dist/esm/client/stats.js +155 -0
- package/dist/esm/client/status-tracker.d.ts +24 -0
- package/dist/esm/client/status-tracker.js +86 -0
- package/dist/esm/gateway/gateway-client.d.ts +86 -0
- package/dist/esm/gateway/gateway-client.js +463 -0
- package/dist/esm/gateway/index.d.ts +3 -0
- package/dist/esm/gateway/index.js +1 -0
- package/dist/esm/gateway/types.d.ts +142 -0
- package/dist/esm/http/errors.d.ts +19 -0
- package/dist/esm/http/errors.js +22 -0
- package/dist/esm/http/index.d.ts +3 -0
- package/dist/esm/http/index.js +2 -0
- package/dist/esm/http/rest-client.d.ts +64 -0
- package/dist/esm/http/rest-client.js +181 -0
- package/dist/esm/index.d.ts +64 -0
- package/dist/esm/index.js +38 -0
- package/dist/esm/node/config-manager.d.ts +34 -0
- package/dist/esm/node/config-manager.js +140 -0
- package/dist/esm/node/index.d.ts +1 -0
- package/dist/esm/node/index.js +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/react/context.d.ts +23 -0
- package/dist/esm/react/context.js +28 -0
- package/dist/esm/react/index.d.ts +38 -0
- package/dist/esm/react/index.js +21 -0
- package/dist/esm/react/messageRangeStore.d.ts +93 -0
- package/dist/esm/react/messageRangeStore.js +140 -0
- package/dist/esm/react/sharedQueryClient.d.ts +21 -0
- package/dist/esm/react/sharedQueryClient.js +35 -0
- package/dist/esm/react/useAgentChannel.d.ts +8 -0
- package/dist/esm/react/useAgentChannel.js +9 -0
- package/dist/esm/react/useAgentConnections.d.ts +8 -0
- package/dist/esm/react/useAgentConnections.js +20 -0
- package/dist/esm/react/useAgentTokenState.d.ts +70 -0
- package/dist/esm/react/useAgentTokenState.js +127 -0
- package/dist/esm/react/useChannelAggregate.d.ts +64 -0
- package/dist/esm/react/useChannelAggregate.js +77 -0
- package/dist/esm/react/useChannelMessage.d.ts +45 -0
- package/dist/esm/react/useChannelMessage.js +79 -0
- package/dist/esm/react/useChannelMessages.d.ts +83 -0
- package/dist/esm/react/useChannelMessages.js +214 -0
- package/dist/esm/react/useChannelSubscription.d.ts +27 -0
- package/dist/esm/react/useChannelSubscription.js +30 -0
- package/dist/esm/react/useClientStatus.d.ts +10 -0
- package/dist/esm/react/useClientStatus.js +19 -0
- package/dist/esm/react/useConnectionState.d.ts +26 -0
- package/dist/esm/react/useConnectionState.js +45 -0
- package/dist/esm/react/useDeviceMap.d.ts +109 -0
- package/dist/esm/react/useDeviceMap.js +59 -0
- package/dist/esm/react/useInvocationLogs.d.ts +23 -0
- package/dist/esm/react/useInvocationLogs.js +37 -0
- package/dist/esm/react/useMultiAgentAggregates.d.ts +38 -0
- package/dist/esm/react/useMultiAgentAggregates.js +104 -0
- package/dist/esm/react/useMultiAgentChannelMessages.d.ts +79 -0
- package/dist/esm/react/useMultiAgentChannelMessages.js +169 -0
- package/dist/esm/react/useOfflineStatus.d.ts +8 -0
- package/dist/esm/react/useOfflineStatus.js +37 -0
- package/dist/esm/react/useSendMessage.d.ts +16 -0
- package/dist/esm/react/useSendMessage.js +26 -0
- package/dist/esm/react/useSendRpc.d.ts +68 -0
- package/dist/esm/react/useSendRpc.js +164 -0
- package/dist/esm/react/useTurnCredentials.d.ts +12 -0
- package/dist/esm/react/useTurnCredentials.js +22 -0
- package/dist/esm/react/useUpdateAggregate.d.ts +27 -0
- package/dist/esm/react/useUpdateAggregate.js +39 -0
- package/dist/esm/react/useUpdateMessage.d.ts +38 -0
- package/dist/esm/react/useUpdateMessage.js +36 -0
- package/dist/esm/refine/index.d.ts +72 -0
- package/dist/esm/refine/index.js +215 -0
- package/dist/esm/rpc/errors.d.ts +10 -0
- package/dist/esm/rpc/errors.js +19 -0
- package/dist/esm/rpc/index.d.ts +3 -0
- package/dist/esm/rpc/index.js +2 -0
- package/dist/esm/rpc/rpc-dispatcher.d.ts +41 -0
- package/dist/esm/rpc/rpc-dispatcher.js +141 -0
- package/dist/esm/types/audit.d.ts +53 -0
- package/dist/esm/types/audit.js +11 -0
- package/dist/esm/types/batch.d.ts +85 -0
- package/dist/esm/types/batch.js +42 -0
- package/dist/esm/types/common.d.ts +250 -0
- package/dist/esm/types/connection.d.ts +72 -0
- package/dist/esm/types/connection.js +7 -0
- package/dist/esm/types/index.d.ts +9 -0
- package/dist/esm/types/openapi.d.ts +195 -0
- package/dist/esm/types/provenance.d.ts +45 -0
- package/dist/esm/types/viewer.d.ts +122 -0
- package/dist/esm/utils/snowflake.d.ts +21 -0
- package/dist/esm/utils/snowflake.js +33 -0
- package/dist/esm/viewer/doover-data-provider.d.ts +90 -0
- package/dist/esm/viewer/doover-data-provider.js +189 -0
- package/dist/esm/viewer/path-parsing.d.ts +5 -0
- package/dist/esm/viewer/path-parsing.js +35 -0
- package/dist/gateway/gateway-client.d.ts +5 -5
- package/dist/gateway/gateway-client.js +5 -5
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.js +5 -0
- package/dist/gateway/types.d.ts +1 -1
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.js +9 -0
- package/dist/http/rest-client.d.ts +5 -3
- package/dist/http/rest-client.js +2 -2
- package/dist/index.d.ts +64 -62
- package/dist/index.js +92 -89
- package/dist/node/config-manager.d.ts +2 -2
- package/dist/node/config-manager.js +5 -5
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +2 -2
- package/dist/react/context.d.ts +1 -1
- package/dist/react/index.d.ts +38 -38
- package/dist/react/index.js +59 -59
- package/dist/react/messageRangeStore.d.ts +1 -1
- package/dist/react/useAgentChannel.d.ts +3 -3
- package/dist/react/useAgentChannel.js +2 -2
- package/dist/react/useAgentConnections.d.ts +1 -1
- package/dist/react/useAgentConnections.js +2 -2
- package/dist/react/useAgentTokenState.d.ts +3 -3
- package/dist/react/useAgentTokenState.js +8 -8
- package/dist/react/useChannelAggregate.d.ts +3 -3
- package/dist/react/useChannelAggregate.js +8 -8
- package/dist/react/useChannelMessage.d.ts +2 -2
- package/dist/react/useChannelMessage.js +4 -4
- package/dist/react/useChannelMessages.d.ts +3 -3
- package/dist/react/useChannelMessages.js +10 -10
- package/dist/react/useChannelSubscription.d.ts +2 -2
- package/dist/react/useChannelSubscription.js +2 -2
- package/dist/react/useClientStatus.d.ts +1 -1
- package/dist/react/useClientStatus.js +2 -2
- package/dist/react/useConnectionState.js +2 -2
- package/dist/react/useDeviceMap.js +2 -2
- package/dist/react/useInvocationLogs.d.ts +1 -1
- package/dist/react/useInvocationLogs.js +2 -2
- package/dist/react/useMultiAgentAggregates.d.ts +1 -1
- package/dist/react/useMultiAgentAggregates.js +5 -5
- package/dist/react/useMultiAgentChannelMessages.d.ts +1 -1
- package/dist/react/useMultiAgentChannelMessages.js +2 -2
- package/dist/react/useOfflineStatus.d.ts +2 -2
- package/dist/react/useOfflineStatus.js +31 -17
- package/dist/react/useSendMessage.d.ts +2 -2
- package/dist/react/useSendMessage.js +2 -2
- package/dist/react/useSendRpc.d.ts +2 -2
- package/dist/react/useSendRpc.js +2 -2
- package/dist/react/useTurnCredentials.d.ts +2 -2
- package/dist/react/useTurnCredentials.js +2 -2
- package/dist/react/useUpdateAggregate.d.ts +3 -3
- package/dist/react/useUpdateAggregate.js +4 -4
- package/dist/react/useUpdateMessage.d.ts +2 -2
- package/dist/react/useUpdateMessage.js +2 -2
- package/dist/refine/index.d.ts +1 -1
- package/dist/rpc/errors.d.ts +1 -1
- package/dist/rpc/index.d.ts +3 -0
- package/dist/rpc/index.js +7 -0
- package/dist/rpc/rpc-dispatcher.d.ts +4 -4
- package/dist/rpc/rpc-dispatcher.js +2 -2
- package/dist/types/common.d.ts +1 -1
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +2 -0
- package/dist/types/openapi.d.ts +1 -1
- package/dist/types/viewer.d.ts +2 -2
- package/dist/viewer/doover-data-provider.d.ts +10 -10
- package/dist/viewer/doover-data-provider.js +25 -25
- package/dist/viewer/path-parsing.d.ts +1 -1
- package/package.json +795 -8
- package/dist/test/api-args.test.js +0 -48
- package/dist/test/api-overloads.test.d.ts +0 -9
- package/dist/test/api-overloads.test.js +0 -330
- package/dist/test/apis.test.js +0 -408
- package/dist/test/auth.test.js +0 -708
- package/dist/test/batch-apis.test.js +0 -183
- package/dist/test/capabilities.test.js +0 -27
- package/dist/test/config-manager.test.js +0 -160
- package/dist/test/data-client-errors.test.js +0 -24
- package/dist/test/data-client-shape.test.js +0 -28
- package/dist/test/doover-client-dataclient.test.d.ts +0 -1
- package/dist/test/doover-client-dataclient.test.js +0 -58
- package/dist/test/doover-client-provenance.test.d.ts +0 -1
- package/dist/test/doover-client-provenance.test.js +0 -32
- package/dist/test/doover-client.test.d.ts +0 -1
- package/dist/test/doover-client.test.js +0 -42
- package/dist/test/doover-data-provider.test.d.ts +0 -1
- package/dist/test/doover-data-provider.test.js +0 -657
- package/dist/test/exports.test.d.ts +0 -1
- package/dist/test/exports.test.js +0 -80
- package/dist/test/gateway-client.test.d.ts +0 -1
- package/dist/test/gateway-client.test.js +0 -474
- package/dist/test/gateway-provenance.test.d.ts +0 -1
- package/dist/test/gateway-provenance.test.js +0 -29
- package/dist/test/gateway-subscriptions.test.d.ts +0 -1
- package/dist/test/gateway-subscriptions.test.js +0 -95
- package/dist/test/helpers.d.ts +0 -32
- package/dist/test/helpers.js +0 -115
- package/dist/test/local-agent-client.test.d.ts +0 -1
- package/dist/test/local-agent-client.test.js +0 -202
- package/dist/test/message-range-store.test.d.ts +0 -1
- package/dist/test/message-range-store.test.js +0 -183
- package/dist/test/multiplex-capabilities.test.d.ts +0 -1
- package/dist/test/multiplex-capabilities.test.js +0 -37
- package/dist/test/multiplex-conflicts.test.d.ts +0 -1
- package/dist/test/multiplex-conflicts.test.js +0 -35
- package/dist/test/multiplex-gateway.test.d.ts +0 -1
- package/dist/test/multiplex-gateway.test.js +0 -103
- package/dist/test/multiplex-merge.test.d.ts +0 -1
- package/dist/test/multiplex-merge.test.js +0 -19
- package/dist/test/multiplex-noncore.test.d.ts +0 -1
- package/dist/test/multiplex-noncore.test.js +0 -82
- package/dist/test/multiplex-passthrough.test.d.ts +0 -1
- package/dist/test/multiplex-passthrough.test.js +0 -35
- package/dist/test/multiplex-reads.test.d.ts +0 -1
- package/dist/test/multiplex-reads.test.js +0 -87
- package/dist/test/multiplex-registry.test.d.ts +0 -1
- package/dist/test/multiplex-registry.test.js +0 -81
- package/dist/test/multiplex-routing.test.d.ts +0 -1
- package/dist/test/multiplex-routing.test.js +0 -45
- package/dist/test/multiplex-status.test.d.ts +0 -1
- package/dist/test/multiplex-status.test.js +0 -63
- package/dist/test/multiplex-writes.test.d.ts +0 -1
- package/dist/test/multiplex-writes.test.js +0 -93
- package/dist/test/offline-cache.test.d.ts +0 -1
- package/dist/test/offline-cache.test.js +0 -289
- package/dist/test/provenance-regression.test.d.ts +0 -1
- package/dist/test/provenance-regression.test.js +0 -45
- package/dist/test/provenance-stamper.test.d.ts +0 -1
- package/dist/test/provenance-stamper.test.js +0 -41
- package/dist/test/provenance-type.test.d.ts +0 -1
- package/dist/test/provenance-type.test.js +0 -15
- package/dist/test/react-channel-messages-forward.test.d.ts +0 -1
- package/dist/test/react-channel-messages-forward.test.js +0 -139
- package/dist/test/react-channel-messages-range.test.d.ts +0 -1
- package/dist/test/react-channel-messages-range.test.js +0 -109
- package/dist/test/react-client-status.test.d.ts +0 -1
- package/dist/test/react-client-status.test.js +0 -35
- package/dist/test/react-dataclient.test.d.ts +0 -1
- package/dist/test/react-dataclient.test.js +0 -28
- package/dist/test/react-exports.test.d.ts +0 -1
- package/dist/test/react-exports.test.js +0 -46
- package/dist/test/react-multiagent-messages.test.d.ts +0 -1
- package/dist/test/react-multiagent-messages.test.js +0 -113
- package/dist/test/react-sources-option.test.d.ts +0 -1
- package/dist/test/react-sources-option.test.js +0 -41
- package/dist/test/react.test.d.ts +0 -1
- package/dist/test/react.test.js +0 -482
- package/dist/test/refine.test.d.ts +0 -1
- package/dist/test/refine.test.js +0 -71
- package/dist/test/rest-client.test.d.ts +0 -1
- package/dist/test/rest-client.test.js +0 -197
- package/dist/test/rpc-dispatcher.test.d.ts +0 -1
- package/dist/test/rpc-dispatcher.test.js +0 -251
- package/dist/test/singleton.test.d.ts +0 -1
- package/dist/test/singleton.test.js +0 -62
- package/dist/test/stats-rpc.test.d.ts +0 -1
- package/dist/test/stats-rpc.test.js +0 -69
- package/dist/test/status-tracker.test.d.ts +0 -1
- package/dist/test/status-tracker.test.js +0 -85
- package/dist/test/users-api.test.d.ts +0 -1
- package/dist/test/users-api.test.js +0 -19
- package/dist/test/utils.test.d.ts +0 -1
- package/dist/test/utils.test.js +0 -64
- /package/dist/{test/api-args.test.d.ts → esm/auth/auth-store.js} +0 -0
- /package/dist/{test/apis.test.d.ts → esm/client/data-client.js} +0 -0
- /package/dist/{test/auth.test.d.ts → esm/gateway/types.js} +0 -0
- /package/dist/{test/batch-apis.test.d.ts → esm/types/common.js} +0 -0
- /package/dist/{test/capabilities.test.d.ts → esm/types/index.js} +0 -0
- /package/dist/{test/config-manager.test.d.ts → esm/types/openapi.js} +0 -0
- /package/dist/{test/data-client-errors.test.d.ts → esm/types/provenance.js} +0 -0
- /package/dist/{test/data-client-shape.test.d.ts → esm/types/viewer.js} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { Aggregate } from "../types/common.js";
|
|
3
|
+
import type { ChannelIdentifier } from "../types/viewer.js";
|
|
4
|
+
import { type OfflineStatusSnapshot } from "./useOfflineStatus.js";
|
|
5
|
+
export declare function channelAggregateQueryKey(agentId: string | undefined, channelName: string | undefined, sources?: string[]): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "src", string];
|
|
6
|
+
/**
|
|
7
|
+
* Shape returned by `useChannelAggregate`. Shadows the react-query
|
|
8
|
+
* `data` field with the unwrapped aggregate payload (`TData`) so the
|
|
9
|
+
* common call-site pattern — `const { data } = useChannelAggregate(…)` —
|
|
10
|
+
* just works without a secondary `.data` access or cast. The
|
|
11
|
+
* `attachments` and `last_updated` slots are hoisted alongside for the
|
|
12
|
+
* same reason; everything else (isLoading, isError, refetch, status,
|
|
13
|
+
* …) passes through from react-query unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export interface UseChannelAggregateResult<TData> extends Omit<UseQueryResult<Aggregate<TData> | undefined>, "data"> {
|
|
16
|
+
/** The aggregate payload. `undefined` until the first fetch lands. */
|
|
17
|
+
data: TData | undefined;
|
|
18
|
+
/** Attachments (files/blobs) on the aggregate. */
|
|
19
|
+
attachments: Aggregate["attachments"] | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Server timestamp (epoch **milliseconds**) of the last aggregate update —
|
|
22
|
+
* matches the `Date` / `dayjs()` constructor. Use `dayjs(last_updated)`, not
|
|
23
|
+
* `dayjs.unix(last_updated)` (the latter assumes seconds and lands ~58000
|
|
24
|
+
* years in the future).
|
|
25
|
+
*/
|
|
26
|
+
last_updated: number | null | undefined;
|
|
27
|
+
/** Offline/cache status when the provided client supports offline caching. */
|
|
28
|
+
offline: OfflineStatusSnapshot;
|
|
29
|
+
}
|
|
30
|
+
export interface UseChannelAggregateOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Whether to fetch the initial aggregate over REST on mount.
|
|
33
|
+
*
|
|
34
|
+
* Defaults to `true` — the hook does a one-shot HTTP GET to seed the cache
|
|
35
|
+
* before the gateway WebSocket has connected, so the first paint isn't
|
|
36
|
+
* empty.
|
|
37
|
+
*
|
|
38
|
+
* Set to `false` for purely WS-driven consumers that don't want the REST
|
|
39
|
+
* round-trip (e.g. when the gateway's upsert-on-subscribe semantics make
|
|
40
|
+
* the REST call redundant or actively wrong, like channels that don't yet
|
|
41
|
+
* exist). The cache is then populated only by `ChannelSync` /
|
|
42
|
+
* `AggregateUpdate` events.
|
|
43
|
+
*/
|
|
44
|
+
fetchInitial?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Restrict to these source ids on a `MultiplexClient`. Ignored for a plain
|
|
47
|
+
* `DooverClient` or `LocalAgentClient`. When set, the query key is
|
|
48
|
+
* source-dimensioned so data from different source subsets is cached
|
|
49
|
+
* independently.
|
|
50
|
+
*/
|
|
51
|
+
sources?: string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fetch a channel's aggregate and keep it live via the gateway. Incoming
|
|
55
|
+
* `channelSync` and `aggregateUpdate` events patch the react-query cache
|
|
56
|
+
* under `channelAggregateQueryKey(agentId, channelName)`.
|
|
57
|
+
*
|
|
58
|
+
* Typed on `TData` — the shape of `aggregate.data`. Defaults to the generic
|
|
59
|
+
* `Record<string, JSONValue>` used by `Aggregate`. No `extends` constraint
|
|
60
|
+
* on the generic: typed aggregate interfaces (e.g. `{ enabled: boolean }`)
|
|
61
|
+
* don't have the implicit index signature that `Aggregate["data"]` carries
|
|
62
|
+
* and enforcing compatibility there just forces `as` casts at call sites.
|
|
63
|
+
*/
|
|
64
|
+
export declare function useChannelAggregate<TData = Aggregate["data"]>(identifier: ChannelIdentifier, options?: UseChannelAggregateOptions): UseChannelAggregateResult<TData>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { DooverApiError } from "../http/errors.js";
|
|
4
|
+
import { useDooverClient } from "./context.js";
|
|
5
|
+
import { useChannelSubscription } from "./useChannelSubscription.js";
|
|
6
|
+
import { useOfflineStatus } from "./useOfflineStatus.js";
|
|
7
|
+
export function channelAggregateQueryKey(agentId, channelName, sources) {
|
|
8
|
+
const sourceDim = sources && sources.length ? [...sources].sort().join(",") : "*";
|
|
9
|
+
return ["doover", "agent", agentId, "channel", channelName, "src", sourceDim];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Fetch a channel's aggregate and keep it live via the gateway. Incoming
|
|
13
|
+
* `channelSync` and `aggregateUpdate` events patch the react-query cache
|
|
14
|
+
* under `channelAggregateQueryKey(agentId, channelName)`.
|
|
15
|
+
*
|
|
16
|
+
* Typed on `TData` — the shape of `aggregate.data`. Defaults to the generic
|
|
17
|
+
* `Record<string, JSONValue>` used by `Aggregate`. No `extends` constraint
|
|
18
|
+
* on the generic: typed aggregate interfaces (e.g. `{ enabled: boolean }`)
|
|
19
|
+
* don't have the implicit index signature that `Aggregate["data"]` carries
|
|
20
|
+
* and enforcing compatibility there just forces `as` casts at call sites.
|
|
21
|
+
*/
|
|
22
|
+
export function useChannelAggregate(identifier, options) {
|
|
23
|
+
const client = useDooverClient();
|
|
24
|
+
const offline = useOfflineStatus();
|
|
25
|
+
const queryClient = useQueryClient();
|
|
26
|
+
const { agentId, channelName } = identifier;
|
|
27
|
+
const sources = options?.sources;
|
|
28
|
+
const key = channelAggregateQueryKey(agentId, channelName, sources);
|
|
29
|
+
const fetchInitial = options?.fetchInitial ?? true;
|
|
30
|
+
const onAggregate = useCallback((aggregate) => {
|
|
31
|
+
queryClient.setQueryData(key, aggregate);
|
|
32
|
+
},
|
|
33
|
+
// The key array is structurally stable per (agentId, channelName, sourceDim).
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
[queryClient, agentId, channelName, sources?.join(",")]);
|
|
36
|
+
useChannelSubscription(identifier, { onAggregate });
|
|
37
|
+
const query = useQuery({
|
|
38
|
+
queryKey: key,
|
|
39
|
+
enabled: fetchInitial && !!agentId && !!channelName,
|
|
40
|
+
staleTime: Infinity,
|
|
41
|
+
queryFn: async () => {
|
|
42
|
+
if (!agentId || !channelName)
|
|
43
|
+
return undefined;
|
|
44
|
+
const id = { agentId, channelName };
|
|
45
|
+
// Pass `{ sources }` as a trailing bag only when set. The TypeScript
|
|
46
|
+
// overloads don't declare it, so we cast through `never` — the runtime
|
|
47
|
+
// implementation reads it via `...args: unknown[]`.
|
|
48
|
+
const sourcesArg = sources ? { sources } : undefined;
|
|
49
|
+
const channel = sourcesArg
|
|
50
|
+
? await client.channels.getChannel(id, sourcesArg)
|
|
51
|
+
: await client.channels.getChannel(id);
|
|
52
|
+
if (channel.aggregate)
|
|
53
|
+
return channel.aggregate;
|
|
54
|
+
return (sourcesArg
|
|
55
|
+
? await client.aggregates.getAggregate(id, sourcesArg)
|
|
56
|
+
: await client.aggregates.getAggregate(id));
|
|
57
|
+
},
|
|
58
|
+
// A 404 means the aggregate doesn't exist — retrying won't change that,
|
|
59
|
+
// and the caller needs the error promptly to render an empty/"not
|
|
60
|
+
// installed" state. Fall back to the react-query default (3 retries) for
|
|
61
|
+
// every other failure.
|
|
62
|
+
retry: (failureCount, error) => {
|
|
63
|
+
if (error instanceof DooverApiError && error.status === 404)
|
|
64
|
+
return false;
|
|
65
|
+
return failureCount < 3;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const aggregate = query.data;
|
|
69
|
+
const { data: _ignored, ...rest } = query;
|
|
70
|
+
return {
|
|
71
|
+
...rest,
|
|
72
|
+
data: aggregate?.data,
|
|
73
|
+
attachments: aggregate?.attachments,
|
|
74
|
+
last_updated: aggregate?.last_updated,
|
|
75
|
+
offline,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { JSONValue, MessageAttachment, MessageStructure } from "../types/common.js";
|
|
3
|
+
import type { ChannelIdentifier } from "../types/viewer.js";
|
|
4
|
+
export declare function channelMessageQueryKey(agentId: string | undefined, channelName: string | undefined, messageId: string | undefined, sources?: string[]): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "message", string | undefined, "src", string];
|
|
5
|
+
export interface UseChannelMessageOptions {
|
|
6
|
+
/** If false, skip subscribing for live message-update events. Defaults true. */
|
|
7
|
+
liveUpdates?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Restrict to these source ids on a `MultiplexClient`. Ignored for a plain
|
|
10
|
+
* `DooverClient` or `LocalAgentClient`. When set, the query key is
|
|
11
|
+
* source-dimensioned so data from different source subsets is cached
|
|
12
|
+
* independently.
|
|
13
|
+
*/
|
|
14
|
+
sources?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Shape returned by `useChannelMessage`. Mirrors `UseChannelAggregateResult`:
|
|
18
|
+
* `data` is unwrapped to the message payload (`message.data`) so the common
|
|
19
|
+
* `const { data } = useChannelMessage(…)` pattern works without a secondary
|
|
20
|
+
* access. The full `MessageStructure` and a couple of useful slots
|
|
21
|
+
* (`attachments`, `timestamp`) are hoisted alongside.
|
|
22
|
+
*/
|
|
23
|
+
export interface UseChannelMessageResult<TData> extends Omit<UseQueryResult<MessageStructure<TData> | undefined>, "data"> {
|
|
24
|
+
/** Unwrapped message payload (`message.data`). Undefined until first fetch. */
|
|
25
|
+
data: TData | undefined;
|
|
26
|
+
/** Full message structure, or undefined until first fetch. */
|
|
27
|
+
message: MessageStructure<TData> | undefined;
|
|
28
|
+
attachments: MessageAttachment[] | undefined;
|
|
29
|
+
/** Server timestamp reported by the message. */
|
|
30
|
+
timestamp: number | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Fetch a single channel message by id and keep it live via gateway
|
|
34
|
+
* `MessageUpdate` events. Mirrors `useChannelAggregate`'s pattern: REST seeds
|
|
35
|
+
* the cache, then `onMessageUpdate` patches it whenever the message body is
|
|
36
|
+
* mutated server-side — handy for watching long-running RPC-style messages
|
|
37
|
+
* where the receiver writes progress into a `response` field.
|
|
38
|
+
*
|
|
39
|
+
* Channel subscriptions deliver every message in the channel, so the
|
|
40
|
+
* handlers filter to the requested `messageId` before patching the cache.
|
|
41
|
+
*
|
|
42
|
+
* Typed on `TData` — the shape of `message.data`. Defaults to `JSONValue` to
|
|
43
|
+
* match `MessageStructure`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useChannelMessage<TData = JSONValue>(identifier: ChannelIdentifier, messageId: string | undefined, options?: UseChannelMessageOptions): UseChannelMessageResult<TData>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useQuery, useQueryClient, } from "@tanstack/react-query";
|
|
3
|
+
import { useDooverClient } from "./context.js";
|
|
4
|
+
import { useChannelSubscription } from "./useChannelSubscription.js";
|
|
5
|
+
export function channelMessageQueryKey(agentId, channelName, messageId, sources) {
|
|
6
|
+
const sourceDim = sources && sources.length ? [...sources].sort().join(",") : "*";
|
|
7
|
+
return [
|
|
8
|
+
"doover",
|
|
9
|
+
"agent",
|
|
10
|
+
agentId,
|
|
11
|
+
"channel",
|
|
12
|
+
channelName,
|
|
13
|
+
"message",
|
|
14
|
+
messageId,
|
|
15
|
+
"src",
|
|
16
|
+
sourceDim,
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Fetch a single channel message by id and keep it live via gateway
|
|
21
|
+
* `MessageUpdate` events. Mirrors `useChannelAggregate`'s pattern: REST seeds
|
|
22
|
+
* the cache, then `onMessageUpdate` patches it whenever the message body is
|
|
23
|
+
* mutated server-side — handy for watching long-running RPC-style messages
|
|
24
|
+
* where the receiver writes progress into a `response` field.
|
|
25
|
+
*
|
|
26
|
+
* Channel subscriptions deliver every message in the channel, so the
|
|
27
|
+
* handlers filter to the requested `messageId` before patching the cache.
|
|
28
|
+
*
|
|
29
|
+
* Typed on `TData` — the shape of `message.data`. Defaults to `JSONValue` to
|
|
30
|
+
* match `MessageStructure`.
|
|
31
|
+
*/
|
|
32
|
+
export function useChannelMessage(identifier, messageId, options) {
|
|
33
|
+
const client = useDooverClient();
|
|
34
|
+
const queryClient = useQueryClient();
|
|
35
|
+
const { agentId, channelName } = identifier;
|
|
36
|
+
const liveUpdates = options?.liveUpdates ?? true;
|
|
37
|
+
const sources = options?.sources;
|
|
38
|
+
const key = channelMessageQueryKey(agentId, channelName, messageId, sources);
|
|
39
|
+
const onMessageUpdate = useCallback((message) => {
|
|
40
|
+
if (message.id !== messageId)
|
|
41
|
+
return;
|
|
42
|
+
queryClient.setQueryData(key, message);
|
|
43
|
+
},
|
|
44
|
+
// The key array is structurally stable per (agentId, channelName, messageId, sourceDim).
|
|
45
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
+
[queryClient, agentId, channelName, messageId, sources?.join(",")]);
|
|
47
|
+
// Cover the case where the caller has the id before the create event has
|
|
48
|
+
// landed on this client — patch the cache when it shows up.
|
|
49
|
+
const onMessage = useCallback((message) => {
|
|
50
|
+
if (message.id !== messageId)
|
|
51
|
+
return;
|
|
52
|
+
queryClient.setQueryData(key, message);
|
|
53
|
+
},
|
|
54
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
+
[queryClient, agentId, channelName, messageId, sources?.join(",")]);
|
|
56
|
+
useChannelSubscription(liveUpdates && messageId ? identifier : undefined, { onMessage, onMessageUpdate });
|
|
57
|
+
const query = useQuery({
|
|
58
|
+
queryKey: key,
|
|
59
|
+
enabled: !!agentId && !!channelName && !!messageId,
|
|
60
|
+
staleTime: Infinity,
|
|
61
|
+
queryFn: () => {
|
|
62
|
+
// Pass `{ sources }` as a trailing bag only when set — cast through
|
|
63
|
+
// `never` since the TypeScript overloads don't declare it.
|
|
64
|
+
const sourcesArg = sources ? { sources } : undefined;
|
|
65
|
+
return (sourcesArg
|
|
66
|
+
? client.messages.getMessage(agentId, channelName, messageId, sourcesArg)
|
|
67
|
+
: client.messages.getMessage(agentId, channelName, messageId));
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const message = query.data;
|
|
71
|
+
const { data: _ignored, ...rest } = query;
|
|
72
|
+
return {
|
|
73
|
+
...rest,
|
|
74
|
+
data: message?.data,
|
|
75
|
+
message,
|
|
76
|
+
attachments: message?.attachments,
|
|
77
|
+
timestamp: message?.timestamp,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type InfiniteData, type UseInfiniteQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { MessageStructure } from "../types/common.js";
|
|
3
|
+
import type { ChannelIdentifier } from "../types/viewer.js";
|
|
4
|
+
import { type OfflineStatusSnapshot } from "./useOfflineStatus.js";
|
|
5
|
+
export declare function channelMessagesQueryKey(agentId: string | undefined, channelName: string | undefined, fields?: readonly string[], sources?: string[], anchor?: string): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages", "src", string] | readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages", "src", string, {
|
|
6
|
+
readonly anchor: string;
|
|
7
|
+
}] | readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages", "src", string, {
|
|
8
|
+
readonly fields: string[];
|
|
9
|
+
}] | readonly ["doover", "agent", string | undefined, "channel", string | undefined, "messages", "src", string, {
|
|
10
|
+
readonly anchor: string;
|
|
11
|
+
}, {
|
|
12
|
+
readonly fields: string[];
|
|
13
|
+
}];
|
|
14
|
+
export interface UseChannelMessagesOptions {
|
|
15
|
+
limit?: number;
|
|
16
|
+
/** If false, skip subscribing for live message-create updates. Defaults true. */
|
|
17
|
+
liveUpdates?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Restrict the returned messages to those whose payload contains any of
|
|
20
|
+
* these top-level field names. Passed through as `field_name` on the
|
|
21
|
+
* REST call.
|
|
22
|
+
*/
|
|
23
|
+
fields?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Optional first-page `before` cursor (snowflake id). Defaults to
|
|
26
|
+
* unset, which returns the latest messages. Use this when you need to
|
|
27
|
+
* guard against client-side clock skew (seed with a slightly-future
|
|
28
|
+
* snowflake so server-stamped messages don't get missed).
|
|
29
|
+
*/
|
|
30
|
+
initialBefore?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Pin the window to a point in history (snowflake id), for "jump to this
|
|
33
|
+
* date" navigation. Changing it re-anchors and refetches.
|
|
34
|
+
*
|
|
35
|
+
* Distinct from `initialBefore`, which is a clock-skew seed regenerated per
|
|
36
|
+
* mount and therefore must NOT dimension the cache key. An anchor identifies
|
|
37
|
+
* a stream the caller means to look at, so it does. Pass a stable value — a
|
|
38
|
+
* per-render `now` would refetch on every render.
|
|
39
|
+
*
|
|
40
|
+
* Cheaper than it looks: anchored windows share the range cache with every
|
|
41
|
+
* other window on the channel, so re-anchoring over already-fetched history
|
|
42
|
+
* costs no requests.
|
|
43
|
+
*/
|
|
44
|
+
anchor?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional lower-bound snowflake id. The server only returns messages
|
|
47
|
+
* newer than this on every page, so paginating older eventually returns
|
|
48
|
+
* an empty page and `hasNextPage` flips to false — useful for fetching
|
|
49
|
+
* a bounded time window (e.g. last 24h) without a manual cutoff loop on
|
|
50
|
+
* the consumer side.
|
|
51
|
+
*/
|
|
52
|
+
after?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Keep paging older automatically until `hasNextPage` is false. Almost
|
|
55
|
+
* always combined with `after` (or a small channel) — without a lower
|
|
56
|
+
* bound this will walk the channel back to its first message.
|
|
57
|
+
*/
|
|
58
|
+
autoPaginate?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Restrict to these source ids on a `MultiplexClient`. Ignored for a plain
|
|
61
|
+
* `DooverClient` or `LocalAgentClient`. When set, the query key is
|
|
62
|
+
* source-dimensioned so data from different source subsets is cached
|
|
63
|
+
* independently.
|
|
64
|
+
*/
|
|
65
|
+
sources?: string[];
|
|
66
|
+
}
|
|
67
|
+
type Page<TData> = MessageStructure<TData>[];
|
|
68
|
+
export interface UseChannelMessagesResult<TData> extends Omit<UseInfiniteQueryResult<InfiniteData<Page<TData>>>, "data"> {
|
|
69
|
+
/** Flat chronological list across all loaded pages. */
|
|
70
|
+
messages: MessageStructure<TData>[];
|
|
71
|
+
/** Raw react-query data (pages). */
|
|
72
|
+
data: InfiniteData<Page<TData>> | undefined;
|
|
73
|
+
/** Offline/cache status when the provided client supports offline caching. */
|
|
74
|
+
offline: OfflineStatusSnapshot;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Paginated infinite query over `DooverDataProvider.getMessages`, with live
|
|
78
|
+
* `messageCreate` pushes are appended to the newest page and `messageUpdate`
|
|
79
|
+
* pushes replace matching cached messages. The "next" page fetches older
|
|
80
|
+
* messages (cursor = oldest-loaded message id).
|
|
81
|
+
*/
|
|
82
|
+
export declare function useChannelMessages<TData = unknown>(identifier: ChannelIdentifier, options?: UseChannelMessagesOptions): UseChannelMessagesResult<TData>;
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import { useInfiniteQuery, useQueryClient, } from "@tanstack/react-query";
|
|
3
|
+
import { generateSnowflakeIdAtTime } from "../utils/snowflake.js";
|
|
4
|
+
import { getChannelRangeStore } from "./messageRangeStore.js";
|
|
5
|
+
import { useDooverClient } from "./context.js";
|
|
6
|
+
import { useChannelSubscription } from "./useChannelSubscription.js";
|
|
7
|
+
import { useOfflineStatus } from "./useOfflineStatus.js";
|
|
8
|
+
export function channelMessagesQueryKey(agentId, channelName, fields, sources, anchor) {
|
|
9
|
+
// Source-dimension the key (multiplex source subsets cache independently);
|
|
10
|
+
// "*" means "all/unspecified" for a plain DooverClient/LocalAgentClient.
|
|
11
|
+
const sourceDim = sources && sources.length ? [...sources].sort().join(",") : "*";
|
|
12
|
+
const base = ["doover", "agent", agentId, "channel", channelName, "messages", "src", sourceDim];
|
|
13
|
+
// An anchored window is its own chain of cursors, so it must not share a cache
|
|
14
|
+
// entry with the live one — same reasoning as `fields` below. Note this is
|
|
15
|
+
// deliberately absent from the range store's key: sharing ranges across
|
|
16
|
+
// anchors is the point of it.
|
|
17
|
+
const key = anchor ? [...base, { anchor }] : base;
|
|
18
|
+
if (!fields || fields.length === 0)
|
|
19
|
+
return key;
|
|
20
|
+
// A `field_name`-filtered request returns a different message stream than
|
|
21
|
+
// the unfiltered (or differently-filtered) one, so it must live under its
|
|
22
|
+
// own cache entry — otherwise one filter's pagination cursor leaks into
|
|
23
|
+
// another's `getNextPageParam`, and the "next page" fetch ends up anchored
|
|
24
|
+
// to an unrelated (often much older) message id. Sort so call-site argument
|
|
25
|
+
// order doesn't fragment the cache.
|
|
26
|
+
return [...key, { fields: [...fields].sort() }];
|
|
27
|
+
}
|
|
28
|
+
/** Mirrors `MessagesApi.listMessages`'s own default. */
|
|
29
|
+
const DEFAULT_PAGE_LIMIT = 10;
|
|
30
|
+
/**
|
|
31
|
+
* Paginated infinite query over `DooverDataProvider.getMessages`, with live
|
|
32
|
+
* `messageCreate` pushes are appended to the newest page and `messageUpdate`
|
|
33
|
+
* pushes replace matching cached messages. The "next" page fetches older
|
|
34
|
+
* messages (cursor = oldest-loaded message id).
|
|
35
|
+
*/
|
|
36
|
+
export function useChannelMessages(identifier, options) {
|
|
37
|
+
const client = useDooverClient();
|
|
38
|
+
const offline = useOfflineStatus();
|
|
39
|
+
const queryClient = useQueryClient();
|
|
40
|
+
const { agentId, channelName } = identifier;
|
|
41
|
+
const limit = options?.limit;
|
|
42
|
+
const liveUpdates = options?.liveUpdates ?? true;
|
|
43
|
+
const fields = options?.fields;
|
|
44
|
+
const initialBefore = options?.initialBefore;
|
|
45
|
+
const anchor = options?.anchor;
|
|
46
|
+
const after = options?.after;
|
|
47
|
+
const autoPaginate = options?.autoPaginate ?? false;
|
|
48
|
+
const sources = options?.sources;
|
|
49
|
+
const key = channelMessagesQueryKey(agentId, channelName, fields, sources, anchor);
|
|
50
|
+
// Keyed per stream, NOT per anchor — every window on this channel shares it.
|
|
51
|
+
const storeKey = JSON.stringify(channelMessagesQueryKey(agentId, channelName, fields, sources));
|
|
52
|
+
const store = getChannelRangeStore(storeKey);
|
|
53
|
+
// `after`/`sources` change what a page means (a short page proves a bound was
|
|
54
|
+
// hit, not that history ran out; multiplex merges several streams), so those
|
|
55
|
+
// requests bypass the range cache rather than record unsound coverage.
|
|
56
|
+
const rangeCacheable = after === undefined && !sources;
|
|
57
|
+
// A dropped socket may have swallowed messages, so the live feed stops being
|
|
58
|
+
// proof of coverage until a fetch re-establishes it.
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const onClose = () => store.sealTips();
|
|
61
|
+
client.gateway.on("close", onClose);
|
|
62
|
+
return () => {
|
|
63
|
+
client.gateway.off("close", onClose);
|
|
64
|
+
};
|
|
65
|
+
}, [client, store]);
|
|
66
|
+
const onMessage = useCallback((message) => {
|
|
67
|
+
// This cache entry is scoped to `fields`; the gateway delivers every
|
|
68
|
+
// message on the channel, so drop live pushes whose payload doesn't
|
|
69
|
+
// carry one of those fields rather than letting them pollute it.
|
|
70
|
+
if (fields && fields.length > 0) {
|
|
71
|
+
const data = message.data;
|
|
72
|
+
if (!data ||
|
|
73
|
+
typeof data !== "object" ||
|
|
74
|
+
!fields.some((f) => f in data)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (rangeCacheable)
|
|
79
|
+
store.recordLive(message);
|
|
80
|
+
queryClient.setQueryData(key, (current) => {
|
|
81
|
+
if (!current)
|
|
82
|
+
return current;
|
|
83
|
+
const typed = message;
|
|
84
|
+
// Newest page is index 0 — prepend there.
|
|
85
|
+
const [firstPage, ...rest] = current.pages;
|
|
86
|
+
return {
|
|
87
|
+
...current,
|
|
88
|
+
pages: [[...(firstPage ?? []), typed], ...rest],
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
+
[queryClient, agentId, channelName, fields?.join(","), sources?.join(",")]);
|
|
94
|
+
const onMessageUpdate = useCallback((message) => {
|
|
95
|
+
// A filtered stream must not accept an update that does not belong to it.
|
|
96
|
+
// Newly qualifying messages are left for a refetch because inserting one
|
|
97
|
+
// into an arbitrary cursor page could invalidate that page's boundaries.
|
|
98
|
+
if (fields && fields.length > 0) {
|
|
99
|
+
const data = message.data;
|
|
100
|
+
if (!data ||
|
|
101
|
+
typeof data !== "object" ||
|
|
102
|
+
!fields.some((field) => field in data)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (rangeCacheable)
|
|
107
|
+
store.recordUpdate(message);
|
|
108
|
+
queryClient.setQueryData(key, (current) => {
|
|
109
|
+
if (!current)
|
|
110
|
+
return current;
|
|
111
|
+
let changed = false;
|
|
112
|
+
const typed = message;
|
|
113
|
+
const pages = current.pages.map((page) => page.map((item) => {
|
|
114
|
+
if (item.id !== typed.id)
|
|
115
|
+
return item;
|
|
116
|
+
changed = true;
|
|
117
|
+
return typed;
|
|
118
|
+
}));
|
|
119
|
+
return changed ? { ...current, pages } : current;
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
123
|
+
[queryClient, agentId, channelName, fields?.join(","), sources?.join(",")]);
|
|
124
|
+
useChannelSubscription(liveUpdates ? identifier : undefined, {
|
|
125
|
+
onMessage,
|
|
126
|
+
onMessageUpdate,
|
|
127
|
+
});
|
|
128
|
+
const query = useInfiniteQuery({
|
|
129
|
+
queryKey: key,
|
|
130
|
+
enabled: !!agentId && !!channelName,
|
|
131
|
+
staleTime: Infinity,
|
|
132
|
+
initialPageParam: { before: anchor ?? initialBefore },
|
|
133
|
+
// Older (down): the next cursor is the oldest id currently held.
|
|
134
|
+
getNextPageParam: (lastPage) => lastPage && lastPage.length > 0
|
|
135
|
+
? { before: lastPage[0]?.id }
|
|
136
|
+
: undefined,
|
|
137
|
+
// Newer (up): only for an anchored window — the live window already begins
|
|
138
|
+
// at "now", so it has nothing above to page into. `after` = newest id held;
|
|
139
|
+
// the server returns the oldest messages above it, walking up toward now.
|
|
140
|
+
// An empty page whose own cursor was already a forward `after` means the
|
|
141
|
+
// top is reached; an empty *initial* page (nothing below the anchor) still
|
|
142
|
+
// seeds the walk from the anchor, since messages may sit above it.
|
|
143
|
+
getPreviousPageParam: (firstPage, _all, firstPageParam) => {
|
|
144
|
+
if (!anchor)
|
|
145
|
+
return undefined;
|
|
146
|
+
if (firstPage && firstPage.length > 0) {
|
|
147
|
+
return { after: firstPage[firstPage.length - 1]?.id };
|
|
148
|
+
}
|
|
149
|
+
if (firstPageParam?.after !== undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
return { after: anchor };
|
|
152
|
+
},
|
|
153
|
+
queryFn: async ({ pageParam }) => {
|
|
154
|
+
if (!agentId || !channelName)
|
|
155
|
+
return [];
|
|
156
|
+
const id = { agentId, channelName };
|
|
157
|
+
const param = (pageParam ?? {});
|
|
158
|
+
// Mirrors the API's own default; the store needs the limit actually applied
|
|
159
|
+
const effectiveLimit = limit ?? DEFAULT_PAGE_LIMIT;
|
|
160
|
+
// Pass `{ sources }` as a trailing bag only when set — cast through
|
|
161
|
+
// `unknown` since the TypeScript overloads don't declare it.
|
|
162
|
+
const runList = (params) => sources
|
|
163
|
+
? client.messages.listMessages(id, params, { sources })
|
|
164
|
+
: client.messages.listMessages(id, params);
|
|
165
|
+
// Forward (newer) page: `after` alone pages oldest-first upward. Bypasses
|
|
166
|
+
// the range store, which models before-anchored windows only.
|
|
167
|
+
if (param.after !== undefined && param.before === undefined) {
|
|
168
|
+
const page = await runList({
|
|
169
|
+
after: param.after,
|
|
170
|
+
...(limit !== undefined ? { limit } : {}),
|
|
171
|
+
...(fields && fields.length > 0 ? { field_name: fields } : {}),
|
|
172
|
+
order: "asc",
|
|
173
|
+
});
|
|
174
|
+
return page;
|
|
175
|
+
}
|
|
176
|
+
// Older page. Resolve `before` here rather than letting the API default
|
|
177
|
+
// it, so the range store records the exact bound the request proved.
|
|
178
|
+
const before = typeof param.before === "string"
|
|
179
|
+
? param.before
|
|
180
|
+
: generateSnowflakeIdAtTime(new Date());
|
|
181
|
+
if (rangeCacheable) {
|
|
182
|
+
const cached = store.read(BigInt(before), effectiveLimit);
|
|
183
|
+
if (cached)
|
|
184
|
+
return cached;
|
|
185
|
+
}
|
|
186
|
+
const requestedAt = Date.now();
|
|
187
|
+
const page = await runList({
|
|
188
|
+
before,
|
|
189
|
+
...(limit !== undefined ? { limit } : {}),
|
|
190
|
+
...(fields && fields.length > 0 ? { field_name: fields } : {}),
|
|
191
|
+
...(after !== undefined ? { after } : {}),
|
|
192
|
+
order: "asc",
|
|
193
|
+
});
|
|
194
|
+
if (rangeCacheable) {
|
|
195
|
+
store.record({
|
|
196
|
+
before: BigInt(before),
|
|
197
|
+
limit: effectiveLimit,
|
|
198
|
+
page: page,
|
|
199
|
+
at: requestedAt,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return page;
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!autoPaginate)
|
|
207
|
+
return;
|
|
208
|
+
if (query.isFetching || !query.hasNextPage)
|
|
209
|
+
return;
|
|
210
|
+
query.fetchNextPage();
|
|
211
|
+
}, [autoPaginate, query.isFetching, query.hasNextPage, query.fetchNextPage]);
|
|
212
|
+
const messages = useMemo(() => (query.data?.pages ?? []).flat(), [query.data]);
|
|
213
|
+
return { ...query, messages, offline };
|
|
214
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Aggregate, JSONValue, MessageStructure } from "../types/common.js";
|
|
2
|
+
import type { ChannelIdentifier } from "../types/viewer.js";
|
|
3
|
+
export interface ChannelSubscriptionHandlers {
|
|
4
|
+
onMessage?: (message: MessageStructure) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Fires on server-side MessageUpdate events. `message` is the full updated
|
|
7
|
+
* MessageStructure; `request_data` (optional) is the diff of what changed
|
|
8
|
+
* in this specific update. Most consumers only need `message`.
|
|
9
|
+
*/
|
|
10
|
+
onMessageUpdate?: (message: MessageStructure, request_data?: JSONValue) => void;
|
|
11
|
+
onAggregate?: (aggregate: Aggregate) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Reserved for future use. The composite gateway already routes by
|
|
14
|
+
* `channel.agent_id`, so this option is a no-op in v1. Pass it for
|
|
15
|
+
* forward-compatibility; it has no effect on the subscription.
|
|
16
|
+
*/
|
|
17
|
+
sources?: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Attach live-update handlers to a channel for the lifetime of the host
|
|
21
|
+
* component. Wraps `DooverDataProvider.subscribeToChannel` with ref-counted
|
|
22
|
+
* subscribe/unsubscribe and handler refs so changes to the handler closures
|
|
23
|
+
* don't re-trigger the underlying subscribe.
|
|
24
|
+
*
|
|
25
|
+
* Passing `undefined` (or an identifier without both fields) is a no-op.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useChannelSubscription(identifier: ChannelIdentifier | undefined, handlers: ChannelSubscriptionHandlers): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useDooverClient } from "./context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Attach live-update handlers to a channel for the lifetime of the host
|
|
5
|
+
* component. Wraps `DooverDataProvider.subscribeToChannel` with ref-counted
|
|
6
|
+
* subscribe/unsubscribe and handler refs so changes to the handler closures
|
|
7
|
+
* don't re-trigger the underlying subscribe.
|
|
8
|
+
*
|
|
9
|
+
* Passing `undefined` (or an identifier without both fields) is a no-op.
|
|
10
|
+
*/
|
|
11
|
+
export function useChannelSubscription(identifier, handlers) {
|
|
12
|
+
const client = useDooverClient();
|
|
13
|
+
const handlersRef = useRef(handlers);
|
|
14
|
+
handlersRef.current = handlers;
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!identifier?.agentId || !identifier?.channelName)
|
|
17
|
+
return;
|
|
18
|
+
const channel = { agent_id: identifier.agentId, name: identifier.channelName };
|
|
19
|
+
const off = client.gateway.subscribeToChannel(channel, {
|
|
20
|
+
onMessage: (msg) => handlersRef.current.onMessage?.(msg),
|
|
21
|
+
onMessageUpdate: (msg, rd) => handlersRef.current.onMessageUpdate?.(msg, rd),
|
|
22
|
+
onAggregate: (agg) => handlersRef.current.onAggregate?.(agg),
|
|
23
|
+
});
|
|
24
|
+
void client.gateway.connect();
|
|
25
|
+
return () => off();
|
|
26
|
+
// Identifier fields are the real deps; we intentionally leave `handlers`
|
|
27
|
+
// out because it's read through handlersRef.
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, [client, identifier?.agentId, identifier?.channelName]);
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DataClientStatus } from "../client/data-client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the live `DataClientStatus` for the client in context. Seeds with
|
|
4
|
+
* `client.getStatus()`, subscribes via `client.onStatusChange(...)` for the
|
|
5
|
+
* component's lifetime. Works for a `DooverClient` (single-source) or a
|
|
6
|
+
* `MultiplexClient` (rolled-up status with a `members` breakdown). Prefer this
|
|
7
|
+
* over `useConnectionState` for new code — it carries session, latency, last
|
|
8
|
+
* error and per-source state.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useClientStatus(): DataClientStatus;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useDooverClient } from "./context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the live `DataClientStatus` for the client in context. Seeds with
|
|
5
|
+
* `client.getStatus()`, subscribes via `client.onStatusChange(...)` for the
|
|
6
|
+
* component's lifetime. Works for a `DooverClient` (single-source) or a
|
|
7
|
+
* `MultiplexClient` (rolled-up status with a `members` breakdown). Prefer this
|
|
8
|
+
* over `useConnectionState` for new code — it carries session, latency, last
|
|
9
|
+
* error and per-source state.
|
|
10
|
+
*/
|
|
11
|
+
export function useClientStatus() {
|
|
12
|
+
const client = useDooverClient();
|
|
13
|
+
const [status, setStatus] = useState(() => client.getStatus());
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setStatus(client.getStatus());
|
|
16
|
+
return client.onStatusChange(setStatus);
|
|
17
|
+
}, [client]);
|
|
18
|
+
return status;
|
|
19
|
+
}
|