Initial commit
This commit is contained in:
43
skills/tanstack-patterns/reference/INDEX.md
Normal file
43
skills/tanstack-patterns/reference/INDEX.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# TanStack Patterns Reference
|
||||
|
||||
Configuration and pattern references for TanStack ecosystem.
|
||||
|
||||
## Available References
|
||||
|
||||
### [router-config.md](router-config.md)
|
||||
Router setup and configuration.
|
||||
- File structure conventions
|
||||
- Root route setup
|
||||
- Route naming conventions
|
||||
- beforeLoad vs loader
|
||||
|
||||
### [query-config.md](query-config.md)
|
||||
QueryClient configuration.
|
||||
- Default configuration
|
||||
- Query options reference
|
||||
- Mutation options reference
|
||||
- Per-query overrides
|
||||
- DevTools setup
|
||||
|
||||
### [caching-strategy.md](caching-strategy.md)
|
||||
Detailed caching patterns.
|
||||
- Grey Haven staleTime standards
|
||||
- Fresh vs stale behavior
|
||||
- Cache invalidation strategies
|
||||
- Manual cache updates
|
||||
- Cache persistence
|
||||
|
||||
### [multi-tenant.md](multi-tenant.md)
|
||||
Multi-tenant patterns with RLS.
|
||||
- Server function patterns
|
||||
- Query key patterns
|
||||
- Tenant context hook
|
||||
- Row Level Security
|
||||
- Best practices
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Need router setup?** → [router-config.md](router-config.md)
|
||||
**Need query config?** → [query-config.md](query-config.md)
|
||||
**Need caching strategy?** → [caching-strategy.md](caching-strategy.md)
|
||||
**Need multi-tenant patterns?** → [multi-tenant.md](multi-tenant.md)
|
||||
98
skills/tanstack-patterns/reference/caching-strategy.md
Normal file
98
skills/tanstack-patterns/reference/caching-strategy.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# TanStack Query Caching Strategy
|
||||
|
||||
Detailed caching patterns for Grey Haven projects.
|
||||
|
||||
## Grey Haven staleTime Standards
|
||||
|
||||
```typescript
|
||||
const STALE_TIMES = {
|
||||
auth: 5 * 60 * 1000, // 5 minutes (auth data)
|
||||
user: 1 * 60 * 1000, // 1 minute (user profiles)
|
||||
list: 1 * 60 * 1000, // 1 minute (lists)
|
||||
static: 10 * 60 * 1000, // 10 minutes (static/config data)
|
||||
realtime: 0, // 0 (always refetch, e.g., notifications)
|
||||
};
|
||||
```
|
||||
|
||||
## Cache Behavior
|
||||
|
||||
### Fresh vs Stale
|
||||
|
||||
```typescript
|
||||
// Data is "fresh" for staleTime duration
|
||||
const { data } = useQuery({
|
||||
queryKey: ["user", userId],
|
||||
queryFn: () => getUserById(userId),
|
||||
staleTime: 60000, // Fresh for 60 seconds
|
||||
});
|
||||
|
||||
// During "fresh" period:
|
||||
// - No background refetch
|
||||
// - Instant return from cache
|
||||
// - New component mounts get cached data immediately
|
||||
|
||||
// After "stale" period:
|
||||
// - Background refetch on mount
|
||||
// - Cached data shown while refetching
|
||||
// - UI updates when new data arrives
|
||||
```
|
||||
|
||||
## Cache Invalidation
|
||||
|
||||
```typescript
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Invalidate all user queries
|
||||
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||
|
||||
// Invalidate specific user
|
||||
queryClient.invalidateQueries({ queryKey: ["user", userId] });
|
||||
|
||||
// Invalidate and refetch immediately
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["users"],
|
||||
refetchType: "active" // Only refetch active queries
|
||||
});
|
||||
```
|
||||
|
||||
## Manual Cache Updates
|
||||
|
||||
```typescript
|
||||
// Update cache directly (optimistic update)
|
||||
queryClient.setQueryData(["user", userId], (old: User) => ({
|
||||
...old,
|
||||
name: "New Name"
|
||||
}));
|
||||
|
||||
// Get cached data
|
||||
const cachedUser = queryClient.getQueryData<User>(["user", userId]);
|
||||
```
|
||||
|
||||
## Cache Persistence
|
||||
|
||||
TanStack Query cache is in-memory only. For persistence:
|
||||
|
||||
```typescript
|
||||
import { persistQueryClient } from "@tanstack/react-query-persist-client";
|
||||
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
||||
|
||||
const persister = createSyncStoragePersister({
|
||||
storage: window.localStorage,
|
||||
});
|
||||
|
||||
persistQueryClient({
|
||||
queryClient,
|
||||
persister,
|
||||
maxAge: 1000 * 60 * 60 * 24, // 24 hours
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use staleTime**: Always set appropriate staleTime
|
||||
2. **Invalidate after mutations**: Use `onSuccess` to invalidate
|
||||
3. **Specific keys**: Use specific query keys for targeted invalidation
|
||||
4. **Prefetch**: Prefetch data on hover/navigation
|
||||
5. **Background refetch**: Let queries refetch in background
|
||||
97
skills/tanstack-patterns/reference/multi-tenant.md
Normal file
97
skills/tanstack-patterns/reference/multi-tenant.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Multi-Tenant Patterns with TanStack
|
||||
|
||||
Multi-tenant isolation patterns for TanStack Start applications.
|
||||
|
||||
## Server Function Pattern
|
||||
|
||||
**ALWAYS include tenant_id parameter:**
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT
|
||||
export const getUsers = createServerFn("GET", async (tenantId: string) => {
|
||||
return await db.query.users.findMany({
|
||||
where: eq(users.tenant_id, tenantId),
|
||||
});
|
||||
});
|
||||
|
||||
// ❌ WRONG - Missing tenant_id
|
||||
export const getUsers = createServerFn("GET", async () => {
|
||||
return await db.query.users.findMany();
|
||||
});
|
||||
```
|
||||
|
||||
## Query Key Pattern
|
||||
|
||||
Include tenant_id in query keys:
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT
|
||||
const { data } = useQuery({
|
||||
queryKey: ["users", tenantId],
|
||||
queryFn: () => getUsers(tenantId),
|
||||
});
|
||||
|
||||
// ❌ WRONG - Missing tenant_id in key
|
||||
const { data } = useQuery({
|
||||
queryKey: ["users"],
|
||||
queryFn: () => getUsers(tenantId),
|
||||
});
|
||||
```
|
||||
|
||||
## Tenant Context Hook
|
||||
|
||||
```typescript
|
||||
// src/lib/hooks/use-tenant.ts
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getAuthenticatedUser } from "~/lib/server/functions/auth";
|
||||
|
||||
export function useTenant() {
|
||||
const { data: authData } = useQuery({
|
||||
queryKey: ["auth", "current-user"],
|
||||
queryFn: () => getAuthenticatedUser(),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
});
|
||||
|
||||
return {
|
||||
tenantId: authData?.tenantId,
|
||||
user: authData?.user,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Usage in Components
|
||||
|
||||
```typescript
|
||||
function UsersList() {
|
||||
const { tenantId } = useTenant();
|
||||
|
||||
const { data: users } = useQuery({
|
||||
queryKey: ["users", tenantId],
|
||||
queryFn: () => getUsers(tenantId!),
|
||||
enabled: !!tenantId, // Only run when tenantId is available
|
||||
});
|
||||
|
||||
return <div>...</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Row Level Security (RLS)
|
||||
|
||||
With RLS, tenant_id filtering is automatic:
|
||||
|
||||
```typescript
|
||||
// Server function with RLS-enabled connection
|
||||
export const getUsers = createServerFn("GET", async () => {
|
||||
// Uses authenticated database connection
|
||||
// RLS policies automatically filter by tenant_id
|
||||
return await db.query.users.findMany();
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always include tenant_id**: In server functions and query keys
|
||||
2. **Use tenant context**: Create `useTenant()` hook for consistency
|
||||
3. **Enable guards**: Use `enabled: !!tenantId` for queries
|
||||
4. **RLS when possible**: Prefer RLS over manual filtering
|
||||
5. **Test isolation**: Verify tenant isolation in tests
|
||||
67
skills/tanstack-patterns/reference/query-config.md
Normal file
67
skills/tanstack-patterns/reference/query-config.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# TanStack Query Configuration
|
||||
|
||||
QueryClient configuration reference for Grey Haven projects.
|
||||
|
||||
## Default Configuration
|
||||
|
||||
```typescript
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60000, // 1 minute
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Query Options
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `staleTime` | 60000ms | How long data stays fresh |
|
||||
| `retry` | 1 | Number of retry attempts |
|
||||
| `refetchOnWindowFocus` | false | Refetch when window gains focus |
|
||||
| `refetchInterval` | false | Auto-refetch interval |
|
||||
| `enabled` | true | Whether query runs automatically |
|
||||
|
||||
### Mutation Options
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `retry` | 0 | Number of retry attempts |
|
||||
| `onSuccess` | undefined | Success callback |
|
||||
| `onError` | undefined | Error callback |
|
||||
| `onSettled` | undefined | Always runs after success/error |
|
||||
|
||||
## Per-Query Configuration
|
||||
|
||||
```typescript
|
||||
const { data } = useQuery({
|
||||
queryKey: ["user", userId],
|
||||
queryFn: () => getUserById(userId),
|
||||
staleTime: 300000, // Override default (5 minutes)
|
||||
retry: 3, // Override default
|
||||
refetchOnWindowFocus: true, // Override default
|
||||
});
|
||||
```
|
||||
|
||||
## DevTools Setup
|
||||
|
||||
```typescript
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
66
skills/tanstack-patterns/reference/router-config.md
Normal file
66
skills/tanstack-patterns/reference/router-config.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# TanStack Router Configuration
|
||||
|
||||
Router setup and configuration reference.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/routes/
|
||||
├── __root.tsx # Root layout (required)
|
||||
├── index.tsx # Homepage
|
||||
├── _layout/ # Route group (underscore prefix)
|
||||
│ ├── _layout.tsx # Group layout
|
||||
│ └── page.tsx # /page
|
||||
└── $param.tsx # Dynamic route
|
||||
```
|
||||
|
||||
## Root Route Setup
|
||||
|
||||
```typescript
|
||||
// src/routes/__root.tsx
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Outlet />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Route Naming Conventions
|
||||
|
||||
| File | URL | Description |
|
||||
|------|-----|-------------|
|
||||
| `index.tsx` | `/` | Homepage |
|
||||
| `about.tsx` | `/about` | Static route |
|
||||
| `_layout.tsx` | - | Layout wrapper (no URL) |
|
||||
| `$userId.tsx` | `/:userId` | Dynamic param |
|
||||
| `_authenticated/` | - | Route group (no URL) |
|
||||
|
||||
## beforeLoad vs loader
|
||||
|
||||
- **beforeLoad**: Auth checks, redirects, context setup
|
||||
- **loader**: Data fetching
|
||||
|
||||
```typescript
|
||||
export const Route = createFileRoute("/_authenticated/_layout")({
|
||||
beforeLoad: async () => {
|
||||
const session = await getSession();
|
||||
if (!session) throw redirect({ to: "/login" });
|
||||
return { session };
|
||||
},
|
||||
});
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
loader: async ({ context }) => {
|
||||
return await getDashboardData(context.session.tenantId);
|
||||
},
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user