Merge pull request #884 from BlankParticle/feat/idb-caching

feat: add local query cache for faster load times
This commit is contained in:
Adam
2025-05-06 00:27:45 -07:00
committed by GitHub
7 changed files with 69 additions and 17 deletions

View File

@@ -895,6 +895,7 @@ export const MailList = memo(({ isCompact }: MailListProps) => {
<div className="flex h-[calc(100vh-4rem)] w-full items-center justify-center">
<div className="flex flex-col items-center justify-center gap-2 text-center">
<Image
suppressHydrationWarning
src={resolvedTheme === 'dark' ? '/empty-state.svg' : '/empty-state-light.svg'}
alt="Empty Inbox"
width={200}

View File

@@ -1,5 +1,12 @@
'use client';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
HelpCircle,
LogIn,
@@ -12,28 +19,20 @@ import {
} from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { CircleCheck, ThreeDots } from '../icons/icons';
import { SunIcon } from '../icons/animated/sun';
import Link from 'next/link';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Popover, PopoverContent, PopoverTrigger } from './popover';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import { useConnections } from '@/hooks/use-connections';
import { signOut, useSession } from '@/lib/auth-client';
import { AddConnectionDialog } from '../connection/add';
import { CircleCheck, ThreeDots } from '../icons/icons';
import { useTRPC } from '@/providers/query-provider';
import { useSidebar } from '@/components/ui/sidebar';
import { useMutation } from '@tanstack/react-query';
import { useBrainState } from '@/hooks/use-summary';
import { useBilling } from '@/hooks/use-billing';
import { SunIcon } from '../icons/animated/sun';
import { clear as idbClear } from 'idb-keyval';
import { Gauge } from '@/components/ui/gauge';
import { useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
@@ -43,6 +42,7 @@ import { Progress } from './progress';
import { Button } from './button';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import Link from 'next/link';
export function NavUser() {
const { data: session, refetch } = useSession();
@@ -61,6 +61,7 @@ export function NavUser() {
const { chatMessages, brainActivity } = useBilling();
const pathname = usePathname();
const searchParams = useSearchParams();
const queryClient = useQueryClient();
const getSettingsHref = useCallback(() => {
const category = searchParams.get('category');
@@ -71,7 +72,11 @@ export function NavUser() {
}, [pathname, searchParams]);
const handleClearCache = useCallback(async () => {
queryClient.clear();
await idbClear();
toast.success('Cache cleared successfully');
// Reload the page after clearing the cache
setTimeout(() => window.location.reload(), 500);
}, []);
const handleCopyConnectionId = useCallback(async () => {

View File

@@ -72,7 +72,7 @@ export const useThread = (threadId: string | null) => {
},
{
enabled: !!id && !!session?.user.id,
staleTime: 1000 * 60 * 60 * 12, // 12 hour
staleTime: 1000 * 60 * 60, // 60 minutes
},
),
);

View File

@@ -8,6 +8,7 @@ export const SIDEBAR_WIDTH_ICON = '3rem';
export const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
export const BASE_URL = process.env.NEXT_PUBLIC_APP_URL;
export const MAX_URL_LENGTH = 2000;
export const CACHE_BURST_KEY = 'cache-burst:v0.0.1';
export const emailProviders = [
{

View File

@@ -55,7 +55,9 @@
"@react-email/components": "^0.0.36",
"@react-email/html": "^0.0.11",
"@react-email/render": "^1.0.6",
"@tanstack/query-sync-storage-persister": "^5.75.0",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-persist-client": "^5.75.2",
"@tiptap/core": "2.11.5",
"@tiptap/extension-bold": "2.11.5",
"@tiptap/extension-document": "2.11.5",
@@ -98,6 +100,7 @@
"he": "1.2.0",
"hono": "^4.7.7",
"husky": "9.1.7",
"idb-keyval": "^6.2.1",
"input-otp": "1.4.2",
"jotai": "2.12.1",
"jsonrepair": "^3.12.0",

View File

@@ -1,13 +1,34 @@
'use client';
import { QueryCache, QueryClient, QueryClientProvider, hashKey } from '@tanstack/react-query';
import {
PersistQueryClientProvider,
type PersistedClient,
type Persister,
} from '@tanstack/react-query-persist-client';
import { createTRPCClient, httpBatchLink, loggerLink } from '@trpc/client';
import { QueryCache, QueryClient, hashKey } from '@tanstack/react-query';
import { createTRPCContext } from '@trpc/tanstack-react-query';
import { useSession, type Session } from '@/lib/auth-client';
import { CACHE_BURST_KEY } from '@/lib/constants';
import type { PropsWithChildren } from 'react';
import { get, set, del } from 'idb-keyval';
import type { AppRouter } from '@/trpc';
import superjson from 'superjson';
import { toast } from 'sonner';
function createIDBPersister(idbValidKey: IDBValidKey = 'zero-query-cache') {
return {
persistClient: async (client: PersistedClient) => {
await set(idbValidKey, client);
},
restoreClient: async () => {
return await get<PersistedClient>(idbValidKey);
},
removeClient: async () => {
await del(idbValidKey);
},
} satisfies Persister;
}
export const makeQueryClient = (session: Session | null) =>
new QueryClient({
queryCache: new QueryCache({
@@ -26,6 +47,7 @@ export const makeQueryClient = (session: Session | null) =>
session ? { userId: session.user.id, connectionId: session.connectionId } : undefined,
...queryKey,
]),
gcTime: 1000 * 60 * 60 * 24,
},
mutations: {
onError: (err) => toast.error(err.message),
@@ -67,10 +89,17 @@ export function QueryProvider({ children }: PropsWithChildren) {
const queryClient = getQueryClient(data ?? null);
return (
<QueryClientProvider client={queryClient}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{
persister: createIDBPersister(),
buster: CACHE_BURST_KEY,
maxAge: 1000 * 60 * 60 * 24, // 24 hours
}}
>
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
{children}
</TRPCProvider>
</QueryClientProvider>
</PersistQueryClientProvider>
);
}

View File

@@ -63,7 +63,9 @@
"@react-email/components": "^0.0.36",
"@react-email/html": "^0.0.11",
"@react-email/render": "^1.0.6",
"@tanstack/query-sync-storage-persister": "^5.75.0",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-persist-client": "^5.75.2",
"@tiptap/core": "2.11.5",
"@tiptap/extension-bold": "2.11.5",
"@tiptap/extension-document": "2.11.5",
@@ -106,6 +108,7 @@
"he": "1.2.0",
"hono": "^4.7.7",
"husky": "9.1.7",
"idb-keyval": "^6.2.1",
"input-otp": "1.4.2",
"jotai": "2.12.1",
"jsonrepair": "^3.12.0",
@@ -728,10 +731,16 @@
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.16", "", { "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA=="],
"@tanstack/query-core": ["@tanstack/query-core@5.74.9", "", {}, "sha512-qmjXpWyigDw4SfqdSBy24FzRvpBPXlaSbl92N77lcrL+yvVQLQkf0T6bQNbTxl9IEB/SvVFhhVZoIlQvFnNuuw=="],
"@tanstack/query-core": ["@tanstack/query-core@5.75.0", "", {}, "sha512-rk8KQuCdhoRkzjRVF3QxLgAfFUyS0k7+GCQjlGEpEGco+qazJ0eMH6aO1DjDjibH7/ik383nnztua3BG+lOnwg=="],
"@tanstack/query-persist-client-core": ["@tanstack/query-persist-client-core@5.75.0", "", { "dependencies": { "@tanstack/query-core": "5.75.0" } }, "sha512-/EeURZCen5hw1u4B7s/UbbwrtvNBX/VNWhR8rNtXvXmY9020YtvRd95SCblUVd8kcia4knPM+T+UThskowKmVA=="],
"@tanstack/query-sync-storage-persister": ["@tanstack/query-sync-storage-persister@5.75.0", "", { "dependencies": { "@tanstack/query-core": "5.75.0", "@tanstack/query-persist-client-core": "5.75.0" } }, "sha512-RudT5n121Rvj6KS8zAtF4dsnuuAfbY/H7XARW/kwqKN5R1yCEajf2oOViFCOSgvjm66aRGS2xpiOM8f98mO5Zg=="],
"@tanstack/react-query": ["@tanstack/react-query@5.74.9", "", { "dependencies": { "@tanstack/query-core": "5.74.9" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-F8xCXDQRDgsPzLzX9+d6ycNoITAIU2bycc1idZd06bt/GjN1quEJDjHvEDWZGoVn0A/ZmntVrYv6TE0kR7c7LA=="],
"@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.75.2", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.75.0" }, "peerDependencies": { "@tanstack/react-query": "^5.75.2", "react": "^18 || ^19" } }, "sha512-XcUKk95LLsHpPFcnE7jP1tXOsjIvM61bvkICmRC+yzg4DAb4aGCO11aqHiBgHfnli5w218DwNXG0RGd731UAUg=="],
"@tiptap/core": ["@tiptap/core@2.11.5", "", { "peerDependencies": { "@tiptap/pm": "^2.7.0" } }, "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw=="],
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@2.11.7", "", { "peerDependencies": { "@tiptap/core": "^2.7.0" } }, "sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw=="],
@@ -1498,6 +1507,8 @@
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"idb-keyval": ["idb-keyval@6.2.1", "", {}, "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
@@ -2740,6 +2751,8 @@
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
"@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.74.9", "", {}, "sha512-qmjXpWyigDw4SfqdSBy24FzRvpBPXlaSbl92N77lcrL+yvVQLQkf0T6bQNbTxl9IEB/SvVFhhVZoIlQvFnNuuw=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],