|  | 
| 1 | 1 | import { InMemoryCache, type InMemoryCacheConfig } from '@apollo/client/core/index.js'; | 
| 2 |  | -import { | 
| 3 |  | -  getNotifications, | 
| 4 |  | -  NOTIFICATION_FRAGMENT, | 
| 5 |  | -} from '~/components/Notifications/graphql/notification.query'; | 
|  | 2 | +import { getNotifications } from '~/components/Notifications/graphql/notification.query'; | 
| 6 | 3 | import { NotificationType, type NotificationOverview } from '~/composables/gql/graphql'; | 
| 7 | 4 | import { NotificationType as NotificationCacheType } from '../../composables/gql/typename'; | 
| 8 | 5 | import { mergeAndDedup } from './merge'; | 
| @@ -112,47 +109,46 @@ const defaultCacheConfig: InMemoryCacheConfig = { | 
| 112 | 109 |         }, | 
| 113 | 110 |         archiveNotification: { | 
| 114 | 111 |           /** | 
| 115 |  | -           * Ensures newly archived notifications appear in the archive list immediately. | 
|  | 112 | +           * Ensures newly archived notifications appear in the archive list without a page reload. | 
| 116 | 113 |            * | 
| 117 |  | -           * When a notification is archived, we need to manually prepend it to the archive list | 
| 118 |  | -           * in the cache if that list has already been queried. Otherwise, the archived notification | 
| 119 |  | -           * won't appear until the next refetch (usually a page refresh). | 
| 120 |  | -           * Note: the prepended notification may not be in the correct order. This is probably ok. | 
|  | 114 | +           * When a notification is archived, we need to evict the cached archive list to force a refetch. | 
|  | 115 | +           * This ensures the archived notification appears in the correct sorted position. | 
|  | 116 | +           * | 
|  | 117 | +           * If the archive list is empty, we evict the entire notifications cache since evicting an empty | 
|  | 118 | +           * list has no effect. This forces a full refetch of all notification data. | 
|  | 119 | +           * Note: This may cause temporary jitter with infinite scroll. | 
| 121 | 120 |            * | 
| 122 | 121 |            * This function: | 
| 123 |  | -           * 1. Gets the archived notification's data from the cache using its ID | 
| 124 |  | -           * 2. If the archive list exists in the cache, adds the notification to the beginning of that list | 
| 125 |  | -           * 3. Returns the original mutation result | 
|  | 122 | +           * 1. Checks if the cache has an archive list. If not, this function is a no-op. | 
|  | 123 | +           * 2. If the list has items, evicts just the archive list | 
|  | 124 | +           * 3. If it is empty, evicts the entire notifications cache | 
|  | 125 | +           * 4. Runs garbage collection to clean up orphaned references | 
|  | 126 | +           * 5. Returns the original mutation result | 
| 126 | 127 |            * | 
| 127 | 128 |            * @param _ - Existing cache value (unused) | 
| 128 | 129 |            * @param incoming - Result (i.e. the archived notification) from the server after archiving | 
| 129 | 130 |            * @param cache - Apollo cache instance | 
| 130 |  | -           * @param args - Mutation arguments containing the notification ID | 
| 131 | 131 |            * @returns The incoming result to be cached | 
| 132 | 132 |            */ | 
| 133 |  | -          merge(_, incoming, { cache, args }) { | 
| 134 |  | -            if (!args?.id) return incoming; | 
| 135 |  | -            const id = cache.identify({ id: args.id, __typename: NotificationCacheType }); | 
| 136 |  | -            if (!id) return incoming; | 
| 137 |  | - | 
| 138 |  | -            const notification = cache.readFragment({ id, fragment: NOTIFICATION_FRAGMENT }); | 
| 139 |  | -            if (!notification) return incoming; | 
|  | 133 | +          merge(_, incoming, { cache }) { | 
|  | 134 | +            const archiveQuery = cache.readQuery({ | 
|  | 135 | +              query: getNotifications, | 
|  | 136 | +              // @ts-expect-error the cache only uses the filter type; the limit & offset are superfluous. | 
|  | 137 | +              variables: { filter: { type: NotificationType.Archive } }, | 
|  | 138 | +            }); | 
|  | 139 | +            if (!archiveQuery) return incoming; | 
| 140 | 140 | 
 | 
| 141 |  | -            cache.updateQuery( | 
| 142 |  | -              { | 
| 143 |  | -                query: getNotifications, | 
| 144 |  | -                // @ts-expect-error the cache only uses the filter type; the limit & offset are superfluous. | 
| 145 |  | -                variables: { filter: { type: NotificationType.Archive } }, | 
| 146 |  | -              }, | 
| 147 |  | -              (data) => { | 
| 148 |  | -                // no data means the archive hasn't been queried yet, in which case this operation is unnecessary | 
| 149 |  | -                if (!data) return; | 
| 150 |  | -                const updated = structuredClone(data); | 
| 151 |  | -                updated.notifications.list.unshift(notification); | 
| 152 |  | -                return updated; | 
| 153 |  | -              } | 
| 154 |  | -            ); | 
|  | 141 | +            if (archiveQuery.notifications.list.length === 0) { | 
|  | 142 | +              cache.evict({ fieldName: 'notifications' }); | 
|  | 143 | +            } else { | 
|  | 144 | +              cache.evict({ | 
|  | 145 | +                id: archiveQuery.notifications.id, | 
|  | 146 | +                fieldName: 'list', | 
|  | 147 | +                args: { filter: { type: NotificationType.Archive } }, | 
|  | 148 | +              }); | 
|  | 149 | +            } | 
| 155 | 150 | 
 | 
|  | 151 | +            cache.gc(); | 
| 156 | 152 |             return incoming; | 
| 157 | 153 |           }, | 
| 158 | 154 |         }, | 
|  | 
0 commit comments