diff --git a/web/helpers/apollo-cache/index.ts b/web/helpers/apollo-cache/index.ts index c47909151..7e918b673 100644 --- a/web/helpers/apollo-cache/index.ts +++ b/web/helpers/apollo-cache/index.ts @@ -1,6 +1,10 @@ import { InMemoryCache, type InMemoryCacheConfig } from '@apollo/client/core/index.js'; -import type { NotificationOverview } from '~/composables/gql/graphql'; -import { NotificationType } from '../../composables/gql/typename'; +import { + getNotifications, + NOTIFICATION_FRAGMENT, +} from '~/components/Notifications/graphql/notification.query'; +import { NotificationType, type NotificationOverview } from '~/composables/gql/graphql'; +import { NotificationType as NotificationCacheType } from '../../composables/gql/typename'; import { mergeAndDedup } from './merge'; /**------------------------------------------------------------------------ @@ -60,9 +64,9 @@ const defaultCacheConfig: InMemoryCacheConfig = { overview: { /** * Busts notification cache when new unread notifications are detected. - * + * * This allows incoming notifications to appear in the sidebar without reloading the page. - * + * * @param existing - Existing overview data in cache * @param incoming - New overview data from server * @param context - Apollo context containing cache instance @@ -106,6 +110,52 @@ const defaultCacheConfig: InMemoryCacheConfig = { return incoming; // Return the incoming data so Apollo knows the result of the mutation }, }, + archiveNotification: { + /** + * Ensures newly archived notifications appear in the archive list immediately. + * + * When a notification is archived, we need to manually prepend it to the archive list + * in the cache if that list has already been queried. Otherwise, the archived notification + * won't appear until the next refetch (usually a page refresh). + * Note: the prepended notification may not be in the correct order. This is probably ok. + * + * This function: + * 1. Gets the archived notification's data from the cache using its ID + * 2. If the archive list exists in the cache, adds the notification to the beginning of that list + * 3. Returns the original mutation result + * + * @param _ - Existing cache value (unused) + * @param incoming - Result (i.e. the archived notification) from the server after archiving + * @param cache - Apollo cache instance + * @param args - Mutation arguments containing the notification ID + * @returns The incoming result to be cached + */ + merge(_, incoming, { cache, args }) { + if (!args?.id) return incoming; + const id = cache.identify({ id: args.id, __typename: NotificationCacheType }); + if (!id) return incoming; + + const notification = cache.readFragment({ id, fragment: NOTIFICATION_FRAGMENT }); + if (!notification) return incoming; + + cache.updateQuery( + { + query: getNotifications, + // @ts-expect-error the cache only uses the filter type; the limit & offset are superfluous. + variables: { filter: { type: NotificationType.Archive } }, + }, + (data) => { + // no data means the archive hasn't been queried yet, in which case this operation is unnecessary + if (!data) return; + const updated = structuredClone(data); + updated.notifications.list.unshift(notification); + return updated; + } + ); + + return incoming; + }, + }, deleteNotification: { /** * Ensures that a deleted notification is removed from the cache + @@ -119,10 +169,7 @@ const defaultCacheConfig: InMemoryCacheConfig = { */ merge(_, incoming, { cache, args }) { if (args?.id) { - const id = cache.identify({ - id: args.id, - __typename: NotificationType, - }); + const id = cache.identify({ id: args.id, __typename: NotificationCacheType }); cache.evict({ id }); } // Removes references to evicted notification, preventing dangling references