Speed up startup by persisting some queries (#9594)

* persist startup queries

* Use IDB for query storage (#9687)

* Add storage abstraction for persisted query data

Introduce a platform-specific storage abstraction layer for react-query
persistence:
- Native: Uses MMKV for high-performance synchronous storage
- Web: Uses IndexedDB via the `idb` library for efficient async storage

This replaces the previous AsyncStorage implementation with more performant
platform-native solutions. The abstraction maintains API compatibility with
@tanstack/query-async-storage-persister.

* Refactor storage abstraction to use factory pattern

Change createPersistedQueryStorage to a factory function that accepts a
storage ID, allowing multiple isolated storage instances:
- Native: Each instance gets its own MMKV store
- Web: Each instance gets its own IndexedDB database

Adopt the factory pattern in:
- react-query.tsx: Uses 'persisted_queries' storage
- ageAssurance/data.tsx: Uses 'age_assurance' storage

This provides better separation between different query client caches
and allows each to be managed independently.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Refactor to use archival storage

(cherry picked from commit a773b40e41c96f821cd32260919ce1437c0fc3ab)

* Improve archive db types

(cherry picked from commit 80e4959ba2aa00c984c26aed2f7dfae1095720b0)

* rm idb

* clear on logout, bust on app version

* create abstraction for persisting queries, make gcTime infinite

* Rm abstraction

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Samuel Newman
2026-02-02 19:55:01 +02:00
committed by GitHub
parent bfd5ed480f
commit 4f1d4821c5
16 changed files with 304 additions and 89 deletions
+24 -19
View File
@@ -8,11 +8,11 @@ import {
moderateFeedGenerator,
RichText,
} from '@atproto/api'
import {t} from '@lingui/macro'
import {
type InfiniteData,
keepPreviousData,
type QueryClient,
type QueryKey,
useInfiniteQuery,
useMutation,
useQuery,
@@ -22,7 +22,11 @@ import {
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {STALE} from '#/state/queries'
import {
PERSISTED_QUERY_GCTIME,
PERSISTED_QUERY_ROOT,
STALE,
} from '#/state/queries'
import {RQKEY as listQueryKey} from '#/state/queries/list'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent, useSession} from '#/state/session'
@@ -114,7 +118,7 @@ export function hydrateFeedGenerator(
avatar: view.avatar,
displayName: view.displayName
? sanitizeDisplayName(view.displayName)
: `Feed by ${sanitizeHandle(view.creator.handle, '@')}`,
: t`Feed by ${sanitizeHandle(view.creator.handle, '@')}`,
description: new RichText({
text: view.description || '',
facets: (view.descriptionFacets || [])?.slice(),
@@ -155,7 +159,7 @@ export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo {
creatorHandle: view.creator.handle,
displayName: view.name
? sanitizeDisplayName(view.name)
: `User List by ${sanitizeHandle(view.creator.handle, '@')}`,
: t`User List by ${sanitizeHandle(view.creator.handle, '@')}`,
contentMode: undefined,
}
}
@@ -238,13 +242,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
)
const lastPageCountRef = useRef(0)
const query = useInfiniteQuery<
AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema,
Error,
InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
QueryKey,
string | undefined
>({
const query = useInfiniteQuery({
enabled: Boolean(moderationOpts) && options?.enabled !== false,
queryKey: createGetPopularFeedsQueryKey(options),
queryFn: async ({pageParam}) => {
@@ -261,7 +259,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
return res.data
},
initialPageParam: undefined,
initialPageParam: undefined as string | undefined,
getNextPageParam: lastPage => lastPage.cursor,
select: useCallback(
(
@@ -418,7 +416,10 @@ const PWI_DISCOVER_FEED_STUB: SavedFeedSourceInfo = {
contentMode: undefined,
}
const pinnedFeedInfosQueryKeyRoot = 'pinnedFeedsInfos'
const createPinnedFeedInfosQueryKeyRoot = (
kind: 'pinned' | 'saved',
feedUris: string[],
) => [PERSISTED_QUERY_ROOT, 'feed-info', kind, feedUris]
export function usePinnedFeedsInfos() {
const {hasSession} = useSession()
@@ -427,13 +428,13 @@ export function usePinnedFeedsInfos() {
const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? []
return useQuery({
queryKey: createPinnedFeedInfosQueryKeyRoot(
'pinned',
pinnedItems.map(f => f.value),
),
gcTime: PERSISTED_QUERY_GCTIME,
staleTime: STALE.INFINITY,
enabled: !isLoadingPrefs,
queryKey: [
pinnedFeedInfosQueryKeyRoot,
(hasSession ? 'authed:' : 'unauthed:') +
pinnedItems.map(f => f.value).join(','),
],
queryFn: async () => {
if (!hasSession) {
return [PWI_DISCOVER_FEED_STUB]
@@ -535,9 +536,13 @@ export function useSavedFeeds() {
const queryClient = useQueryClient()
return useQuery({
queryKey: createPinnedFeedInfosQueryKeyRoot(
'saved',
savedItems.map(f => f.value),
),
gcTime: PERSISTED_QUERY_GCTIME,
staleTime: STALE.INFINITY,
enabled: !isLoadingPrefs,
queryKey: [pinnedFeedInfosQueryKeyRoot, ...savedItems],
placeholderData: previousData => {
return (
previousData || {