create persister

This commit is contained in:
Nate Kelley 2025-08-15 15:27:57 -06:00
parent 9b9b0f0bc5
commit 7e5344ccc6
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
8 changed files with 397 additions and 215 deletions

View File

@ -2,6 +2,7 @@
"name": "@buster-app/web-tss",
"private": true,
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "vite dev --port 3000",
"dev:fast": "pnpm run build && pnpm run start",
@ -61,14 +62,18 @@
"@tailwindcss/vite": "^4.1.12",
"@tanstack/db": "^0.1.3",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/query-async-storage-persister": "^5.85.3",
"@tanstack/query-db-collection": "0.1.1",
"@tanstack/query-sync-storage-persister": "^5.85.3",
"@tanstack/react-db": "0.1.3",
"@tanstack/react-devtools": "^0.4.0",
"@tanstack/react-form": "^1.19.2",
"@tanstack/react-query": "^5.85.3",
"@tanstack/react-query-devtools": "^5.85.3",
"@tanstack/react-query-persist-client": "^5.85.3",
"@tanstack/react-router": "^1.131.14",
"@tanstack/react-router-devtools": "^1.131.14",
"@tanstack/react-router-ssr-query": "^1.131.14",
"@tanstack/react-router-with-query": "^1.130.17",
"@tanstack/react-start": "^1.131.15",
"@tanstack/react-store": "^0.7.3",

View File

@ -78,42 +78,39 @@ const topItems: ISidebarList = {
const yourStuff: ISidebarGroup = {
label: 'Your stuff',
id: 'your-stuff',
items: (
[
{
label: 'Metrics',
icon: <ASSET_ICONS.metrics />,
route: { to: '/app/metrics' },
id: '/app/metrics',
preload: 'intent',
preloadDelay: 1000,
},
{
label: 'Dashboards',
icon: <ASSET_ICONS.dashboards />,
route: { to: '/app/dashboards' },
id: '/app/dashboards/',
preload: 'intent',
preloadDelay: 1000,
},
{
label: 'Collections',
icon: <ASSET_ICONS.collections />,
route: { to: '/app/collections' },
id: '/app/collections/',
preload: 'intent',
preloadDelay: 1000,
},
{
label: 'Reports',
icon: <ASSET_ICONS.reports />,
route: { to: '/app/reports' },
id: '/app/reports/',
preload: 'intent',
show: process.env.NEXT_PUBLIC_ENABLE_REPORTS === 'true',
},
] satisfies (ISidebarItem & { show?: boolean })[]
).filter((x) => x.show !== false),
items: [
{
label: 'Metrics',
icon: <ASSET_ICONS.metrics />,
route: { to: '/app/metrics' },
id: '/app/metrics',
preload: 'intent',
preloadDelay: 1000,
},
{
label: 'Dashboards',
icon: <ASSET_ICONS.dashboards />,
route: { to: '/app/dashboards' },
id: '/app/dashboards/',
preload: 'intent',
preloadDelay: 1000,
},
{
label: 'Collections',
icon: <ASSET_ICONS.collections />,
route: { to: '/app/collections' },
id: '/app/collections/',
preload: 'intent',
preloadDelay: 1000,
},
{
label: 'Reports',
icon: <ASSET_ICONS.reports />,
route: { to: '/app/reports' },
id: '/app/reports/',
preload: 'intent',
},
] satisfies (ISidebarItem & { show?: boolean })[],
};
const adminTools: ISidebarGroup = {
@ -155,7 +152,10 @@ const tryGroup = (showInvitePeople: boolean): ISidebarGroup => ({
id: 'leave-feedback',
onClick: () => toggleContactSupportModal('feedback'),
},
].filter((x) => x.show !== false),
].reduce((acc, { show, ...item }) => {
if (show !== false) acc.push(item);
return acc;
}, [] as ISidebarItem[]),
});
export const SidebarPrimary = React.memo(() => {

View File

@ -1,10 +1,13 @@
import type { QueryClient } from '@tanstack/react-query';
import type React from 'react';
import type { PropsWithChildren } from 'react';
import { BusterStyleProvider } from './BusterStyles';
import { QueryPersister } from './Query/QueryProvider';
import {
SupabaseContextProvider,
type SupabaseContextType,
} from './Supabase/SupabaseContextProvider';
// import type { UseSupabaseUserContextType } from '@/lib/supabase';
// import { BusterAssetsProvider } from './Assets/BusterAssetsProvider';
// import { AppLayoutProvider } from './BusterAppLayout';
@ -21,15 +24,19 @@ import {
// clearLog: false // clears the console per group of renders (default: false)
// });
export const RootProviders: React.FC<PropsWithChildren<SupabaseContextType>> = ({
type RootProvidersProps = PropsWithChildren<SupabaseContextType & { queryClient: QueryClient }>;
export const RootProviders: React.FC<RootProvidersProps> = ({
children,
user,
accessToken,
queryClient,
}) => {
return (
<SupabaseContextProvider user={user} accessToken={accessToken}>
<BusterStyleProvider>{children}</BusterStyleProvider>
{/* <BusterReactQueryProvider>
<QueryPersister queryClient={queryClient}>
<SupabaseContextProvider user={user} accessToken={accessToken}>
<BusterStyleProvider>{children}</BusterStyleProvider>
{/* <BusterReactQueryProvider>
<HydrationBoundary state={dehydrate(queryClient)}>
<AppLayoutProvider>
<BusterUserConfigProvider>
@ -43,7 +50,8 @@ export const RootProviders: React.FC<PropsWithChildren<SupabaseContextType>> = (
</AppLayoutProvider>
</HydrationBoundary>
</BusterReactQueryProvider> */}
</SupabaseContextProvider>
</SupabaseContextProvider>
</QueryPersister>
);
};

View File

@ -0,0 +1,27 @@
import { isServer, type QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PersistQueryClientProvider as TanstackPersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import React from 'react';
import { persistOptions } from '@/integrations/tanstack-query/create-persister';
import { userQueryKeys } from '../../api/query_keys/users';
export const QueryPersister = ({
children,
queryClient,
}: {
children: React.ReactNode;
queryClient: QueryClient;
}) => {
const [mounted, setMounted] = React.useState(false);
return (
<TanstackPersistQueryClientProvider
client={queryClient}
persistOptions={persistOptions}
onSuccess={() => {
setMounted(true);
}}
>
{mounted ? children : null}
</TanstackPersistQueryClientProvider>
);
};

View File

@ -0,0 +1,60 @@
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
import { hashKey, isServer } from '@tanstack/react-query';
import type { PersistQueryClientProviderProps } from '@tanstack/react-query-persist-client';
import { dictionariesQueryKeys } from '@/api/query_keys/dictionaries';
import { slackQueryKeys } from '@/api/query_keys/slack';
import packageJson from '../../../package.json';
const buster = packageJson.version;
export const PERSIST_TIME = 1000 * 60 * 60 * 24 * 3; // 3 days
const PERSISTED_QUERIES = [slackQueryKeys.slackGetChannels.queryKey].map(hashKey);
export const PERMANENT_QUERIES = [
dictionariesQueryKeys.getCurrencies.queryKey,
dictionariesQueryKeys.colorPalettes.queryKey,
].map(hashKey);
const ALL_PERSISTED_QUERIES = [...PERSISTED_QUERIES, ...PERMANENT_QUERIES];
const persisterAsync = createAsyncStoragePersister({
key: 'buster-query-cache',
storage: isServer ? undefined : window.localStorage,
throttleTime: 1500, // 1.5 seconds,
serialize: (client) => {
/*
* Make persisted queries appear stale on first load by setting the dataUpdatedAt to 1 (NOT 0)
* This way the query will be refetched from the server when it is first mounted AND we
* don't have to deal with the flash of stale data that would otherwise happen.
*/
for (const query of client.clientState.queries) {
const isPermanentQuery = PERMANENT_QUERIES.includes(query.queryHash);
if (!isPermanentQuery) {
console.log('setting dataUpdatedAt to 1', query.queryHash);
query.state.dataUpdatedAt = 1;
}
}
return JSON.stringify(client);
},
});
export const persistOptions: PersistQueryClientProviderProps['persistOptions'] = {
maxAge: PERSIST_TIME,
dehydrateOptions: {
shouldDehydrateQuery: (query) => {
const isList =
query.queryKey[1] === 'list' || query.queryKey[query.queryKey.length - 1] === 'list';
return isList || ALL_PERSISTED_QUERIES.includes(query.queryHash);
},
},
hydrateOptions: {
defaultOptions: {
queries: {
initialDataUpdatedAt: 0,
},
},
},
buster,
persister: persisterAsync,
};

View File

@ -1,6 +1,8 @@
import type { User } from '@supabase/supabase-js';
import type { QueryClient } from '@tanstack/react-query';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
import { createRouter as createTanstackRouter } from '@tanstack/react-router';
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query';
import { routerWithQueryClient } from '@tanstack/react-router-with-query';
import * as TanstackQuery from './integrations/tanstack-query/query-client';
import { routeTree } from './routeTree.gen';
@ -14,7 +16,7 @@ export interface AppRouterContext {
export const createRouter = () => {
const queryClient = TanstackQuery.getQueryClient();
return routerWithQueryClient(
const router = routerWithQueryClient(
createTanstackRouter({
routeTree,
context: { queryClient, user: null }, //context is defined in the root route
@ -30,6 +32,15 @@ export const createRouter = () => {
}),
queryClient
);
// setupRouterSsrQueryIntegration({
// router,
// queryClient,
// // Disable auto-wrapping since we'll handle it ourselves
// wrapQueryClient: false,
// });
return router;
};
// Register the router instance for type safety

View File

@ -10,16 +10,9 @@ import appCss from '../styles/styles.css?url';
export const Route = createRootRouteWithContext<AppRouterContext>()({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'Buster',
},
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'Buster' },
],
links: [{ rel: 'stylesheet', href: appCss }],
}),
@ -33,7 +26,7 @@ export const Route = createRootRouteWithContext<AppRouterContext>()({
});
function RootDocument({ children }: { children: React.ReactNode }) {
const { user, accessToken } = Route.useRouteContext();
const { user, accessToken, queryClient } = Route.useRouteContext();
return (
<html lang="en">
@ -41,7 +34,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent />
</head>
<body>
<RootProviders user={user} accessToken={accessToken}>
<RootProviders user={user} accessToken={accessToken} queryClient={queryClient}>
{children}
</RootProviders>
<TanstackDevtools />

File diff suppressed because it is too large Load Diff