Skip to content

Commit

Permalink
feat: better query persister
Browse files Browse the repository at this point in the history
  • Loading branch information
Dwynr committed Sep 10, 2024
1 parent ef22f31 commit c7e2cde
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 52 deletions.
52 changes: 33 additions & 19 deletions src/lib/queryPersister.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import { getItem, setItem, removeItem } from "@/lib/localForage"
import { type PersistedClient, type Persister } from "@tanstack/react-query-persist-client"
import localForage from "localforage"
// @ts-expect-error Not typed
import memoryStorageDriver from "localforage-memoryStorageDriver"

/**
* Persist all queries in IndexedDB.
* @date 3/13/2024 - 4:05:38 AM
*
* @export
* @param {IDBValidKey} [idbValidKey="reactQuery"]
* @returns {Persister}
*/
export function createIDBPersister(idbValidKey: IDBValidKey = "reactQuery"): Persister {
export const VERSION = 1
export const queryClientPersisterPrefix = "reactQueryV1"

export const store = localForage.createInstance({
name: "Filen_reactQuery",
version: 1.0,
storeName: "filen_reactQuery_v" + VERSION,
size: 1024 * 1024 * 1024
})

store.defineDriver(memoryStorageDriver).catch(console.error)
store.setDriver([store.INDEXEDDB, memoryStorageDriver._driver]).catch(console.error)

export function createIDBPersister() {
return {
persistClient: async (client: PersistedClient) => {
await setItem(idbValidKey as string, client)
getItem: (key: string) => {
return store.getItem(key)
},
restoreClient: async () => {
return (await getItem<PersistedClient>(idbValidKey as string)) as PersistedClient
setItem: (key: string, value: unknown) => {
return store.setItem(key, value)
},
removeClient: async () => {
await removeItem(idbValidKey as string)
removeItem: (key: string) => {
return store.removeItem(key)
},
keys: () => {
return store.keys()
},
clear: () => {
return store.clear()
}
} satisfies Persister
}
}

export default createIDBPersister
export const queryClientPersisterIDB = createIDBPersister()

export default queryClientPersisterIDB
3 changes: 2 additions & 1 deletion src/lib/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type FilenDesktopConfig } from "@filen/desktop/dist/types"
import { clear as clearLocalForage } from "@/lib/localForage"
import { connect as socketConnect } from "@/lib/socket"
import { localStorageKey as authedLocalStorageKey } from "@/hooks/useIsAuthed"
import queryClientPersisterIDB from "./queryPersister"

export const DEFAULT_SDK_CONFIG: FilenSDKConfig = {
email: "anonymous",
Expand Down Expand Up @@ -123,7 +124,7 @@ export async function setup(config?: FilenSDKConfig, connectToSocket: boolean =
* @returns {Promise<void>}
*/
export async function logout(): Promise<void> {
await clearLocalForage()
await Promise.all([clearLocalForage(), queryClientPersisterIDB.clear()])

if (IS_DESKTOP) {
await Promise.all([
Expand Down
93 changes: 61 additions & 32 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { ThemeProvider, useTheme } from "@/providers/themeProvider"
import { createRootRoute, Outlet } from "@tanstack/react-router"
import { memo, useEffect, useState, useRef, useCallback } from "react"
import { Toaster } from "@/components/ui/toaster"
import { QueryClient, focusManager, useIsRestoring } from "@tanstack/react-query"
import { PersistQueryClientProvider, type PersistQueryClientOptions, persistQueryClientRestore } from "@tanstack/react-query-persist-client"
import { QueryClient, focusManager, useIsRestoring, QueryClientProvider } from "@tanstack/react-query"
import { experimental_createPersister, type PersistedQuery } from "@tanstack/query-persist-client-core"
import useIsAuthed from "@/hooks/useIsAuthed"
import createIDBPersister from "@/lib/queryPersister"
import queryClientPersisterIDB, { queryClientPersisterPrefix } from "@/lib/queryPersister"
import DragSelect from "@/components/dragSelect"
import DropZone from "@/components/dropZone"
import ConfirmDialog from "@/components/dialogs/confirm"
Expand Down Expand Up @@ -41,6 +41,7 @@ import RemoteConfigHandler from "@/components/remoteConfigHandler"
import MaintenanceDialog from "@/components/dialogs/maintenance"
import LockDialog from "@/components/dialogs/lock"
import ExportReminder from "@/components/exportReminder"
import memoize from "lodash/memoize"

focusManager.setEventListener(handleFocus => {
const onFocus = () => {
Expand All @@ -60,32 +61,71 @@ focusManager.setEventListener(handleFocus => {
}
})

export const persistantQueryClient = new QueryClient({
const shouldPersistQuery = memoize(
(queryKey: unknown[]) => {
const shouldNotPersist = queryKey.some(queryKey => typeof queryKey === "string" && UNCACHED_QUERY_KEYS.includes(queryKey))

return !shouldNotPersist
},
queryKey => queryKey.join(":")
)

export const queryClientPersister = experimental_createPersister({
storage: queryClientPersisterIDB,
maxAge: 86400 * 1000 * 7,
buster: "",
serialize: query => {
if (query.state.status !== "success" || !shouldPersistQuery(query.queryKey as unknown[])) {
return undefined
}

return query
},
deserialize: query => query as PersistedQuery,
prefix: queryClientPersisterPrefix
})

export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnMount: "always",
refetchOnReconnect: "always",
refetchOnWindowFocus: "always",
staleTime: Infinity,
gcTime: Infinity
staleTime: 86400 * 1000 * 7,
gcTime: 86400 * 1000 * 7,
persister: queryClientPersister
}
}
})

export const queryClientPersister = createIDBPersister()
export async function restoreQueries(): Promise<void> {
const keys = await queryClientPersisterIDB.keys()

export const persistOptions: Omit<PersistQueryClientOptions, "queryClient"> = {
persister: queryClientPersister,
maxAge: Infinity,
dehydrateOptions: {
shouldDehydrateQuery(query) {
if (query.state.status !== "success" || query.state.error) {
return false
}
await Promise.all(
keys.map(async key => {
if (key.startsWith(queryClientPersisterPrefix)) {
const persistedQuery = (await queryClientPersisterIDB.getItem(key)) as unknown as PersistedQuery

return !query.queryKey.some(queryKey => typeof queryKey === "string" && UNCACHED_QUERY_KEYS.includes(queryKey))
}
}
if (!persistedQuery || !persistedQuery.state) {
await queryClientPersisterIDB.removeItem(key)

return
}

const shouldNotPersist = !shouldPersistQuery(persistedQuery.queryKey as unknown[])

if (persistedQuery.state.status === "success") {
if (!shouldNotPersist) {
queryClient.setQueryData(persistedQuery.queryKey, persistedQuery.state.data, {
updatedAt: persistedQuery.state.dataUpdatedAt
})
} else {
await queryClientPersisterIDB.removeItem(key)
}
}
}
})
)
}

export const Loading = memo(() => {
Expand Down Expand Up @@ -117,16 +157,8 @@ export const Root = memo(() => {
const isRestoring = useIsRestoring()

const setup = useCallback(async () => {
await persistQueryClientRestore({
queryClient: persistantQueryClient,
persister: queryClientPersister,
maxAge: Infinity,
buster: "",
hydrateOptions: undefined
}).catch(console.error)

try {
await setupApp()
await Promise.all([restoreQueries(), setupApp()])

console.log("Setup done")

Expand All @@ -147,10 +179,7 @@ export const Root = memo(() => {
return (
<main className="overflow-hidden">
<ThemeProvider>
<PersistQueryClientProvider
client={persistantQueryClient}
persistOptions={persistOptions}
>
<QueryClientProvider client={queryClient}>
{!ready || isRestoring ? (
<Loading />
) : (
Expand Down Expand Up @@ -201,7 +230,7 @@ export const Root = memo(() => {
<IsOnlineDialog />
<RemoteConfigHandler />
<MaintenanceDialog />
</PersistQueryClientProvider>
</QueryClientProvider>
</ThemeProvider>
<Toaster />
</main>
Expand Down

0 comments on commit c7e2cde

Please sign in to comment.