diff --git a/crates/yaak-models/guest-js/store.ts b/crates/yaak-models/guest-js/store.ts index 2ac19a25..6cd4d8cb 100644 --- a/crates/yaak-models/guest-js/store.ts +++ b/crates/yaak-models/guest-js/store.ts @@ -209,12 +209,24 @@ export function replaceModelsInStore< export function mergeModelsInStore< M extends AnyModel['model'], T extends Extract, ->(model: M, models: T[]) { +>(model: M, models: T[], filter?: (model: T) => boolean) { mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => { const existingModels = { ...prev[model] } as Record; + + // Merge in new models first for (const m of models) { existingModels[m.id] = m; } + + // Then filter out unwanted models + if (filter) { + for (const [id, m] of Object.entries(existingModels)) { + if (!filter(m)) { + delete existingModels[id]; + } + } + } + return { ...prev, [model]: existingModels, diff --git a/src-web/hooks/useHttpResponseEvents.ts b/src-web/hooks/useHttpResponseEvents.ts index 582bff11..e439132e 100644 --- a/src-web/hooks/useHttpResponseEvents.ts +++ b/src-web/hooks/useHttpResponseEvents.ts @@ -6,7 +6,7 @@ import { replaceModelsInStore, } from '@yaakapp-internal/models'; import { useAtomValue } from 'jotai'; -import { useEffect, useMemo } from 'react'; +import { useEffect } from 'react'; export function useHttpResponseEvents(response: HttpResponse | null) { const allEvents = useAtomValue(httpResponseEventsAtom); @@ -17,18 +17,13 @@ export function useHttpResponseEvents(response: HttpResponse | null) { return; } - // Use merge instead of replace to preserve events that came in via model_write - // while we were fetching from the database + // Fetch events from database, filtering out events from other responses and merging atomically invoke('cmd_get_http_response_events', { responseId: response.id }).then( - (events) => mergeModelsInStore('http_response_event', events), + (events) => + mergeModelsInStore('http_response_event', events, (e) => e.responseId === response.id), ); }, [response?.id]); - // Filter events for the current response - const events = useMemo( - () => allEvents.filter((e) => e.responseId === response?.id), - [allEvents, response?.id], - ); - + const events = allEvents.filter((e) => e.responseId === response?.id); return { data: events, error: null, isLoading: false }; } diff --git a/src-web/hooks/usePinnedGrpcConnection.ts b/src-web/hooks/usePinnedGrpcConnection.ts index bd88a69a..8e655d33 100644 --- a/src-web/hooks/usePinnedGrpcConnection.ts +++ b/src-web/hooks/usePinnedGrpcConnection.ts @@ -7,7 +7,7 @@ import { replaceModelsInStore, } from '@yaakapp-internal/models'; import { atom, useAtomValue } from 'jotai'; -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { activeRequestIdAtom } from './useActiveRequestId'; @@ -60,7 +60,7 @@ export const activeGrpcConnectionAtom = atom((get) => { }); export function useGrpcEvents(connectionId: string | null) { - const events = useAtomValue(grpcEventsAtom); + const allEvents = useAtomValue(grpcEventsAtom); useEffect(() => { if (connectionId == null) { @@ -68,12 +68,14 @@ export function useGrpcEvents(connectionId: string | null) { return; } - // Use merge instead of replace to preserve events that came in via model_write - // while we were fetching from the database - invoke('models_grpc_events', { connectionId }).then((events) => { - mergeModelsInStore('grpc_event', events); - }); + // Fetch events from database, filtering out events from other connections and merging atomically + invoke('models_grpc_events', { connectionId }).then((events) => + mergeModelsInStore('grpc_event', events, (e) => e.connectionId === connectionId), + ); }, [connectionId]); - return events; + return useMemo( + () => allEvents.filter((e) => e.connectionId === connectionId), + [allEvents, connectionId], + ); } diff --git a/src-web/hooks/usePinnedWebsocketConnection.ts b/src-web/hooks/usePinnedWebsocketConnection.ts index d36ed6d5..a1a4dceb 100644 --- a/src-web/hooks/usePinnedWebsocketConnection.ts +++ b/src-web/hooks/usePinnedWebsocketConnection.ts @@ -7,7 +7,7 @@ import { websocketEventsAtom, } from '@yaakapp-internal/models'; import { atom, useAtomValue } from 'jotai'; -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage'; import { jotaiStore } from '../lib/jotai'; import { activeRequestIdAtom } from './useActiveRequestId'; @@ -47,7 +47,7 @@ export function setPinnedWebsocketConnectionId(id: string | null) { } export function useWebsocketEvents(connectionId: string | null) { - const events = useAtomValue(websocketEventsAtom); + const allEvents = useAtomValue(websocketEventsAtom); useEffect(() => { if (connectionId == null) { @@ -55,12 +55,14 @@ export function useWebsocketEvents(connectionId: string | null) { return; } - // Use merge instead of replace to preserve events that came in via model_write - // while we were fetching from the database - invoke('models_websocket_events', { connectionId }).then( - (events) => mergeModelsInStore('websocket_event', events), + // Fetch events from database, filtering out events from other connections and merging atomically + invoke('models_websocket_events', { connectionId }).then((events) => + mergeModelsInStore('websocket_event', events, (e) => e.connectionId === connectionId), ); }, [connectionId]); - return events; + return useMemo( + () => allEvents.filter((e) => e.connectionId === connectionId), + [allEvents, connectionId], + ); }