@kood/claude-code 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/.claude/agents/code-reviewer.md +124 -124
- package/templates/.claude/agents/dependency-manager.md +85 -85
- package/templates/.claude/agents/deployment-validator.md +56 -56
- package/templates/.claude/agents/git-operator.md +64 -64
- package/templates/.claude/agents/implementation-executor.md +95 -95
- package/templates/.claude/agents/ko-to-en-translator.md +74 -0
- package/templates/.claude/agents/lint-fixer.md +78 -78
- package/templates/.claude/agents/refactor-advisor.md +122 -122
- package/templates/.claude/commands/agent-creator.md +185 -185
- package/templates/.claude/commands/bug-fix.md +193 -193
- package/templates/.claude/commands/command-creator.md +54 -54
- package/templates/.claude/commands/docs-creator.md +57 -57
- package/templates/.claude/commands/docs-refactor.md +26 -26
- package/templates/.claude/commands/execute.md +12 -12
- package/templates/.claude/commands/git-all.md +32 -32
- package/templates/.claude/commands/git-session.md +42 -42
- package/templates/.claude/commands/git.md +34 -34
- package/templates/.claude/commands/lint-fix.md +138 -138
- package/templates/.claude/commands/lint-init.md +61 -61
- package/templates/.claude/commands/plan.md +260 -260
- package/templates/.claude/commands/prd.md +24 -24
- package/templates/.claude/commands/pre-deploy.md +109 -109
- package/templates/.claude/commands/refactor.md +147 -147
- package/templates/.claude/commands/version-update.md +17 -17
- package/templates/hono/CLAUDE.md +27 -27
- package/templates/hono/docs/architecture.md +24 -24
- package/templates/hono/docs/deployment/cloudflare.md +18 -18
- package/templates/hono/docs/deployment/docker.md +13 -13
- package/templates/hono/docs/deployment/index.md +19 -19
- package/templates/hono/docs/deployment/railway.md +32 -32
- package/templates/hono/docs/deployment/vercel.md +29 -29
- package/templates/hono/docs/guides/conventions.md +57 -57
- package/templates/hono/docs/guides/env-setup.md +47 -47
- package/templates/hono/docs/guides/getting-started.md +27 -27
- package/templates/hono/docs/library/hono/error-handling.md +11 -11
- package/templates/hono/docs/library/hono/index.md +4 -4
- package/templates/hono/docs/library/hono/middleware.md +18 -18
- package/templates/hono/docs/library/hono/rpc.md +7 -7
- package/templates/hono/docs/library/hono/validation.md +6 -6
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +29 -29
- package/templates/hono/docs/library/prisma/config.md +16 -16
- package/templates/hono/docs/library/prisma/index.md +32 -32
- package/templates/hono/docs/library/t3-env/index.md +22 -22
- package/templates/hono/docs/library/zod/index.md +31 -31
- package/templates/nextjs/CLAUDE.md +228 -0
- package/templates/nextjs/docs/design.md +558 -0
- package/templates/nextjs/docs/guides/conventions.md +343 -0
- package/templates/nextjs/docs/guides/getting-started.md +367 -0
- package/templates/nextjs/docs/guides/routes.md +342 -0
- package/templates/nextjs/docs/library/better-auth/index.md +541 -0
- package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
- package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
- package/templates/nextjs/docs/library/nextjs/index.md +291 -0
- package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
- package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
- package/templates/nextjs/docs/library/prisma/config.md +77 -0
- package/templates/nextjs/docs/library/prisma/crud.md +90 -0
- package/templates/nextjs/docs/library/prisma/index.md +73 -0
- package/templates/nextjs/docs/library/prisma/relations.md +69 -0
- package/templates/nextjs/docs/library/prisma/schema.md +98 -0
- package/templates/nextjs/docs/library/prisma/setup.md +49 -0
- package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
- package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
- package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
- package/templates/nextjs/docs/library/zod/index.md +56 -0
- package/templates/nextjs/docs/library/zod/transforms.md +51 -0
- package/templates/nextjs/docs/library/zod/validation.md +70 -0
- package/templates/npx/CLAUDE.md +37 -37
- package/templates/npx/docs/library/commander/index.md +12 -12
- package/templates/npx/docs/library/fs-extra/index.md +9 -9
- package/templates/npx/docs/library/prompts/index.md +3 -3
- package/templates/npx/docs/references/patterns.md +12 -12
- package/templates/tanstack-start/CLAUDE.md +53 -49
- package/templates/tanstack-start/docs/architecture.md +128 -128
- package/templates/tanstack-start/docs/design.md +169 -169
- package/templates/tanstack-start/docs/guides/conventions.md +43 -43
- package/templates/tanstack-start/docs/guides/env-setup.md +35 -35
- package/templates/tanstack-start/docs/guides/getting-started.md +19 -19
- package/templates/tanstack-start/docs/guides/hooks.md +63 -35
- package/templates/tanstack-start/docs/guides/routes.md +61 -42
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- package/templates/tanstack-start/docs/library/better-auth/index.md +68 -68
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +19 -19
- package/templates/tanstack-start/docs/library/prisma/config.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/crud.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/index.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/relations.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/schema.md +23 -23
- package/templates/tanstack-start/docs/library/prisma/setup.md +6 -6
- package/templates/tanstack-start/docs/library/prisma/transactions.md +10 -10
- package/templates/tanstack-start/docs/library/t3-env/index.md +21 -160
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +19 -19
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +14 -14
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +21 -21
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +11 -11
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +17 -17
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +5 -5
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +8 -8
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +15 -15
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +4 -4
- package/templates/tanstack-start/docs/library/zod/complex-types.md +11 -11
- package/templates/tanstack-start/docs/library/zod/index.md +8 -8
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<patterns>
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
//
|
|
6
|
+
// Basic
|
|
7
7
|
const queryClient = useQueryClient()
|
|
8
8
|
const mutation = useMutation({
|
|
9
9
|
mutationFn: postTodo,
|
|
@@ -11,12 +11,12 @@ const mutation = useMutation({
|
|
|
11
11
|
})
|
|
12
12
|
mutation.mutate({ title: 'New Todo' })
|
|
13
13
|
|
|
14
|
-
//
|
|
14
|
+
// Callbacks
|
|
15
15
|
useMutation({
|
|
16
16
|
mutationFn: updateTodo,
|
|
17
17
|
onMutate: async (newTodo) => {
|
|
18
|
-
// mutation
|
|
19
|
-
return { previousData } // context
|
|
18
|
+
// Before mutation starts (for optimistic updates)
|
|
19
|
+
return { previousData } // Passed to context
|
|
20
20
|
},
|
|
21
21
|
onSuccess: (data, variables, context) => {},
|
|
22
22
|
onError: (error, variables, context) => {},
|
|
@@ -35,7 +35,7 @@ try {
|
|
|
35
35
|
const result = await mutation.mutateAsync(data)
|
|
36
36
|
} catch (error) { ... }
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Cache update
|
|
39
39
|
useMutation({
|
|
40
40
|
mutationFn: patchTodo,
|
|
41
41
|
onSuccess: (data) => {
|
|
@@ -49,15 +49,15 @@ useMutation({
|
|
|
49
49
|
|
|
50
50
|
<returns>
|
|
51
51
|
|
|
52
|
-
|
|
|
52
|
+
| Property | Description |
|
|
53
53
|
|------|------|
|
|
54
|
-
| data |
|
|
55
|
-
| error |
|
|
56
|
-
| isPending |
|
|
57
|
-
| isSuccess/isError |
|
|
58
|
-
| mutate |
|
|
59
|
-
| mutateAsync |
|
|
60
|
-
| reset |
|
|
61
|
-
| variables |
|
|
54
|
+
| data | Mutation result |
|
|
55
|
+
| error | Error object |
|
|
56
|
+
| isPending | Executing |
|
|
57
|
+
| isSuccess/isError | Status |
|
|
58
|
+
| mutate | Execute (async) |
|
|
59
|
+
| mutateAsync | Execute (Promise) |
|
|
60
|
+
| reset | Reset state |
|
|
61
|
+
| variables | Passed variables |
|
|
62
62
|
|
|
63
63
|
</returns>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<patterns>
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
//
|
|
6
|
+
// Basic
|
|
7
7
|
const { data, isLoading, error } = useQuery({
|
|
8
8
|
queryKey: ['todos'],
|
|
9
9
|
queryFn: getTodos,
|
|
@@ -11,28 +11,28 @@ const { data, isLoading, error } = useQuery({
|
|
|
11
11
|
if (isLoading) return <div>Loading...</div>
|
|
12
12
|
if (error) return <div>Error: {error.message}</div>
|
|
13
13
|
|
|
14
|
-
//
|
|
14
|
+
// Options
|
|
15
15
|
useQuery({
|
|
16
16
|
queryKey: ['todos'],
|
|
17
17
|
queryFn: fetchTodos,
|
|
18
|
-
staleTime: 1000 * 60 * 5, //
|
|
19
|
-
gcTime: 1000 * 60 * 30, //
|
|
20
|
-
refetchOnWindowFocus: true, //
|
|
21
|
-
refetchInterval: 1000 * 60, //
|
|
22
|
-
retry: 3, //
|
|
23
|
-
enabled: !!userId, //
|
|
24
|
-
initialData: [], //
|
|
25
|
-
select: (data) => data.filter(t => t.done), //
|
|
18
|
+
staleTime: 1000 * 60 * 5, // Time to stay fresh
|
|
19
|
+
gcTime: 1000 * 60 * 30, // Garbage collection time
|
|
20
|
+
refetchOnWindowFocus: true, // Refetch on focus
|
|
21
|
+
refetchInterval: 1000 * 60, // Auto refetch interval
|
|
22
|
+
retry: 3, // Retry count
|
|
23
|
+
enabled: !!userId, // Conditional execution
|
|
24
|
+
initialData: [], // Initial data
|
|
25
|
+
select: (data) => data.filter(t => t.done), // Data transformation
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// With parameters
|
|
29
29
|
useQuery({
|
|
30
30
|
queryKey: ['todo', todoId],
|
|
31
31
|
queryFn: () => fetchTodoById(todoId),
|
|
32
32
|
enabled: !!todoId,
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// Dependent queries
|
|
36
36
|
const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: ... })
|
|
37
37
|
const { data: posts } = useQuery({
|
|
38
38
|
queryKey: ['posts', user?.id],
|
|
@@ -40,11 +40,11 @@ const { data: posts } = useQuery({
|
|
|
40
40
|
enabled: !!user?.id,
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Parallel
|
|
44
44
|
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
|
|
45
45
|
const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// Dynamic parallel
|
|
48
48
|
const userQueries = useQueries({
|
|
49
49
|
queries: userIds.map((id) => ({
|
|
50
50
|
queryKey: ['user', id],
|
|
@@ -57,14 +57,14 @@ const userQueries = useQueries({
|
|
|
57
57
|
|
|
58
58
|
<returns>
|
|
59
59
|
|
|
60
|
-
|
|
|
60
|
+
| Property | Description |
|
|
61
61
|
|------|------|
|
|
62
|
-
| data |
|
|
63
|
-
| error |
|
|
64
|
-
| isLoading |
|
|
65
|
-
| isFetching |
|
|
66
|
-
| isError/isSuccess |
|
|
67
|
-
| refetch |
|
|
62
|
+
| data | Query result |
|
|
63
|
+
| error | Error object |
|
|
64
|
+
| isLoading | Initial loading |
|
|
65
|
+
| isFetching | Background fetching |
|
|
66
|
+
| isError/isSuccess | Status |
|
|
67
|
+
| refetch | Manual refetch |
|
|
68
68
|
| status | 'pending' \| 'error' \| 'success' |
|
|
69
69
|
|
|
70
70
|
</returns>
|
|
@@ -48,8 +48,8 @@ export const Route = createRootRoute({
|
|
|
48
48
|
export const Route = createFileRoute('/posts')({
|
|
49
49
|
loader: async () => fetchPosts(),
|
|
50
50
|
pendingComponent: () => <Spinner />,
|
|
51
|
-
pendingMs: 200, //
|
|
52
|
-
pendingMinMs: 500, //
|
|
51
|
+
pendingMs: 200, // Show after 200ms
|
|
52
|
+
pendingMinMs: 500, // Keep for at least 500ms (prevent flicker)
|
|
53
53
|
component: PostsPage,
|
|
54
54
|
})
|
|
55
55
|
|
|
@@ -61,7 +61,7 @@ export const Route = createFileRoute('/$')({
|
|
|
61
61
|
},
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// Error type discrimination
|
|
65
65
|
const CustomError = ({ error, reset }: ErrorComponentProps) => {
|
|
66
66
|
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
67
67
|
return <div><p>Network error</p><button onClick={reset}>Retry</button></div>
|
|
@@ -77,13 +77,13 @@ const CustomError = ({ error, reset }: ErrorComponentProps) => {
|
|
|
77
77
|
|
|
78
78
|
<priority>
|
|
79
79
|
|
|
80
|
-
|
|
|
80
|
+
| Priority | Component | Condition |
|
|
81
81
|
|---------|---------|------|
|
|
82
|
-
| 1 | `errorComponent` | loader/beforeLoad
|
|
83
|
-
| 2 | `notFoundComponent` | `notFound()`
|
|
84
|
-
| 3 | `pendingComponent` |
|
|
85
|
-
| 4 | `component` |
|
|
82
|
+
| 1 | `errorComponent` | Error thrown in loader/beforeLoad |
|
|
83
|
+
| 2 | `notFoundComponent` | `notFound()` thrown |
|
|
84
|
+
| 3 | `pendingComponent` | Loader executing (after pendingMs) |
|
|
85
|
+
| 4 | `component` | Normal rendering |
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Error propagation: Child → Parent (propagates to parent if no errorComponent)
|
|
88
88
|
|
|
89
89
|
</priority>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
```tsx
|
|
6
6
|
// Route-Scoped (Type-safe)
|
|
7
7
|
const PostPage = () => {
|
|
8
|
-
const { post } = Route.useLoaderData() // Loader
|
|
8
|
+
const { post } = Route.useLoaderData() // Loader return value
|
|
9
9
|
const { postId } = Route.useParams() // Path params
|
|
10
10
|
const { page, sort } = Route.useSearch() // Search params
|
|
11
11
|
const { user } = Route.useRouteContext() // Route context
|
|
@@ -20,10 +20,10 @@ const postMatch = useMatch({ from: '/posts/$postId', shouldThrow: false })
|
|
|
20
20
|
if (postMatch) return <span>Post: {postMatch.params.postId}</span>
|
|
21
21
|
|
|
22
22
|
const { postId } = useParams({ from: '/posts/$postId' })
|
|
23
|
-
const params = useParams({ strict: false }) //
|
|
23
|
+
const params = useParams({ strict: false }) // All params
|
|
24
24
|
|
|
25
25
|
const { page } = useSearch({ from: '/products' })
|
|
26
|
-
const search = useSearch({ strict: false }) //
|
|
26
|
+
const search = useSearch({ strict: false }) // Current search
|
|
27
27
|
|
|
28
28
|
const pathname = useRouterState({ select: state => state.location.pathname })
|
|
29
29
|
const isLoading = useRouterState({ select: state => state.isLoading })
|
|
@@ -37,17 +37,17 @@ console.log(location.search) // { page: 1 }
|
|
|
37
37
|
|
|
38
38
|
<reference>
|
|
39
39
|
|
|
40
|
-
| Hook | Scope | Type |
|
|
40
|
+
| Hook | Scope | Type | Purpose |
|
|
41
41
|
|------|-------|------|------|
|
|
42
|
-
| `Route.useLoaderData()` | Route | Auto | Loader
|
|
42
|
+
| `Route.useLoaderData()` | Route | Auto | Loader data |
|
|
43
43
|
| `Route.useParams()` | Route | Auto | Path params |
|
|
44
44
|
| `Route.useSearch()` | Route | Auto | Search params |
|
|
45
45
|
| `Route.useRouteContext()` | Route | Auto | Route context |
|
|
46
|
-
| `useParams({ from })` | Global | Manual |
|
|
47
|
-
| `useSearch({ from })` | Global | Manual |
|
|
48
|
-
| `useMatch({ from })` | Global | Manual |
|
|
49
|
-
| `useNavigate()` | Global | Auto |
|
|
50
|
-
| `useRouterState()` | Global | Manual |
|
|
51
|
-
| `useLocation()` | Global | Auto |
|
|
46
|
+
| `useParams({ from })` | Global | Manual | Other route params |
|
|
47
|
+
| `useSearch({ from })` | Global | Manual | Other route search |
|
|
48
|
+
| `useMatch({ from })` | Global | Manual | Route match info |
|
|
49
|
+
| `useNavigate()` | Global | Auto | Navigation |
|
|
50
|
+
| `useRouterState()` | Global | Manual | Router state |
|
|
51
|
+
| `useLocation()` | Global | Auto | Current location |
|
|
52
52
|
|
|
53
53
|
</reference>
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
<quick_reference>
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
//
|
|
16
|
+
// Basic route
|
|
17
17
|
export const Route = createFileRoute('/about')({ component: AboutPage })
|
|
18
18
|
|
|
19
|
-
// Loader +
|
|
19
|
+
// Loader + dynamic params
|
|
20
20
|
export const Route = createFileRoute('/posts/$postId')({
|
|
21
21
|
loader: async ({ params }) => ({ post: await getPost(params.postId) }),
|
|
22
22
|
component: PostPage,
|
|
@@ -78,10 +78,10 @@ routes/
|
|
|
78
78
|
└── $.tsx # Catch-all (404)
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
|
|
|
81
|
+
| Filename | Path |
|
|
82
82
|
|--------|------|
|
|
83
|
-
| `index.tsx` |
|
|
84
|
-
| `$param.tsx` |
|
|
83
|
+
| `index.tsx` | Directory root |
|
|
84
|
+
| `$param.tsx` | Dynamic segment |
|
|
85
85
|
| `_layout/` | Pathless layout |
|
|
86
86
|
| `$.tsx` | Catch-all |
|
|
87
87
|
|
|
@@ -89,27 +89,27 @@ routes/
|
|
|
89
89
|
|
|
90
90
|
<options>
|
|
91
91
|
|
|
92
|
-
|
|
|
92
|
+
| Option | Description |
|
|
93
93
|
|------|------|
|
|
94
|
-
| `component` |
|
|
95
|
-
| `loader` |
|
|
96
|
-
| `beforeLoad` |
|
|
97
|
-
| `validateSearch` | Search params
|
|
98
|
-
| `pendingComponent` |
|
|
99
|
-
| `errorComponent` |
|
|
100
|
-
| `notFoundComponent` | Not found
|
|
94
|
+
| `component` | Component to render |
|
|
95
|
+
| `loader` | Data loading function |
|
|
96
|
+
| `beforeLoad` | Run before loading (auth, etc) |
|
|
97
|
+
| `validateSearch` | Search params schema |
|
|
98
|
+
| `pendingComponent` | Loading state component |
|
|
99
|
+
| `errorComponent` | Error component |
|
|
100
|
+
| `notFoundComponent` | Not found component |
|
|
101
101
|
|
|
102
102
|
</options>
|
|
103
103
|
|
|
104
104
|
<hooks>
|
|
105
105
|
|
|
106
|
-
| Hook |
|
|
106
|
+
| Hook | Purpose |
|
|
107
107
|
|------|------|
|
|
108
|
-
| `Route.useLoaderData()` | Loader
|
|
109
|
-
| `Route.useParams()` | Path
|
|
108
|
+
| `Route.useLoaderData()` | Loader return value |
|
|
109
|
+
| `Route.useParams()` | Path parameters |
|
|
110
110
|
| `Route.useSearch()` | Search params |
|
|
111
111
|
| `Route.useRouteContext()` | Route context |
|
|
112
|
-
| `useNavigate()` |
|
|
113
|
-
| `useMatch({ from })` |
|
|
112
|
+
| `useNavigate()` | Programmatic navigation |
|
|
113
|
+
| `useMatch({ from })` | Route match info |
|
|
114
114
|
|
|
115
115
|
</hooks>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<Link to="/products" search={{ page: 1, sort: 'newest' }}>Products</Link>
|
|
10
10
|
<Link to="/products" search={prev => ({ ...prev, page: 2 })}>Next</Link>
|
|
11
11
|
|
|
12
|
-
// Active
|
|
12
|
+
// Active styles
|
|
13
13
|
<Link
|
|
14
14
|
to="/about"
|
|
15
15
|
activeProps={{ className: 'text-blue-500 font-bold' }}
|
|
@@ -33,11 +33,11 @@ const Component = () => {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Preloading
|
|
36
|
-
<Link to="/posts" preload="intent">Posts</Link> // hover
|
|
37
|
-
<Link to="/dashboard" preload="render">Dash</Link> //
|
|
38
|
-
<Link to="/products" preload="viewport">Prod</Link> // viewport
|
|
36
|
+
<Link to="/posts" preload="intent">Posts</Link> // On hover
|
|
37
|
+
<Link to="/dashboard" preload="render">Dash</Link> // On render
|
|
38
|
+
<Link to="/products" preload="viewport">Prod</Link> // On viewport entry
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// Conditional navigation
|
|
41
41
|
const SubmitButton = () => {
|
|
42
42
|
const navigate = useNavigate()
|
|
43
43
|
const [isPending, startTransition] = useTransition()
|
|
@@ -57,24 +57,24 @@ const SubmitButton = () => {
|
|
|
57
57
|
|
|
58
58
|
<options>
|
|
59
59
|
|
|
60
|
-
| Link Props |
|
|
60
|
+
| Link Props | Type | Description |
|
|
61
61
|
|------------|------|------|
|
|
62
|
-
| `to` | string |
|
|
63
|
-
| `params` | object | Path
|
|
62
|
+
| `to` | string | Destination path |
|
|
63
|
+
| `params` | object | Path parameters |
|
|
64
64
|
| `search` | object \| function | Search params |
|
|
65
65
|
| `hash` | string | Hash |
|
|
66
|
-
| `replace` | boolean | history replace |
|
|
67
|
-
| `preload` | 'intent' \| 'render' \| 'viewport' | Preload
|
|
68
|
-
| `activeProps` | object |
|
|
69
|
-
| `inactiveProps` | object |
|
|
66
|
+
| `replace` | boolean | Use history replace |
|
|
67
|
+
| `preload` | 'intent' \| 'render' \| 'viewport' | Preload strategy |
|
|
68
|
+
| `activeProps` | object | Props when active |
|
|
69
|
+
| `inactiveProps` | object | Props when inactive |
|
|
70
70
|
|
|
71
|
-
| navigate
|
|
71
|
+
| navigate Options | Type | Description |
|
|
72
72
|
|---------------|------|------|
|
|
73
|
-
| `to` | string |
|
|
74
|
-
| `params` | object | Path
|
|
73
|
+
| `to` | string | Destination path |
|
|
74
|
+
| `params` | object | Path parameters |
|
|
75
75
|
| `search` | object \| function | Search params |
|
|
76
76
|
| `hash` | string | Hash |
|
|
77
|
-
| `replace` | boolean | history.replace
|
|
78
|
-
| `resetScroll` | boolean |
|
|
77
|
+
| `replace` | boolean | Use history.replace |
|
|
78
|
+
| `resetScroll` | boolean | Reset scroll |
|
|
79
79
|
|
|
80
80
|
</options>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<patterns>
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
// beforeLoad:
|
|
6
|
+
// beforeLoad: auth check + add context
|
|
7
7
|
export const Route = createFileRoute('/dashboard')({
|
|
8
8
|
beforeLoad: async ({ context, location }) => {
|
|
9
9
|
if (!context.auth.isAuthenticated) {
|
|
@@ -30,7 +30,7 @@ export const Route = createFileRoute('/_authed/dashboard')({
|
|
|
30
30
|
component: DashboardPage,
|
|
31
31
|
})
|
|
32
32
|
const DashboardPage = () => {
|
|
33
|
-
const { user } = Route.useRouteContext() //
|
|
33
|
+
const { user } = Route.useRouteContext() // Context from _authed
|
|
34
34
|
return <h1>Welcome, {user.name}!</h1>
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -75,10 +75,10 @@ routes/
|
|
|
75
75
|
|
|
76
76
|
<usage>
|
|
77
77
|
|
|
78
|
-
|
|
|
78
|
+
| Location | Access Method |
|
|
79
79
|
|------|----------|
|
|
80
|
-
| beforeLoad | `{ context }`
|
|
81
|
-
| loader | `{ context }`
|
|
80
|
+
| beforeLoad | `{ context }` parameter |
|
|
81
|
+
| loader | `{ context }` parameter |
|
|
82
82
|
| component | `Route.useRouteContext()` |
|
|
83
83
|
|
|
84
84
|
</usage>
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
<patterns>
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
// Zod
|
|
6
|
+
// Zod schema + route
|
|
7
7
|
const searchSchema = z.object({
|
|
8
|
-
page: z.number().catch(1), //
|
|
9
|
-
search: z.string().optional(), //
|
|
8
|
+
page: z.number().catch(1), // Default value
|
|
9
|
+
search: z.string().optional(), // Optional
|
|
10
10
|
sort: z.enum(['newest', 'price']).catch('newest'),
|
|
11
|
-
tags: z.array(z.string()).catch([]), //
|
|
11
|
+
tags: z.array(z.string()).catch([]), // Array
|
|
12
12
|
inStock: z.boolean().catch(true), // Boolean
|
|
13
|
-
from: z.string().date().optional(), //
|
|
14
|
-
minPrice: z.number().min(0).catch(0), //
|
|
13
|
+
from: z.string().date().optional(), // Date
|
|
14
|
+
minPrice: z.number().min(0).catch(0), // Range
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
export const Route = createFileRoute('/products')({
|
|
18
18
|
validateSearch: searchSchema,
|
|
19
|
-
loaderDeps: ({ search }) => ({ search }), //
|
|
19
|
+
loaderDeps: ({ search }) => ({ search }), // Re-run loader on search change
|
|
20
20
|
loader: async ({ deps: { search } }) => fetchProducts(search),
|
|
21
21
|
component: ProductsPage,
|
|
22
22
|
})
|
|
@@ -26,11 +26,11 @@ const ProductsPage = () => {
|
|
|
26
26
|
return <div>Page: {page}, Sort: {sort}</div>
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Link
|
|
29
|
+
// Update via Link
|
|
30
30
|
<Link to="/products" search={{ page: 1, sort: 'newest' }}>Reset</Link>
|
|
31
31
|
<Link to="/products" search={prev => ({ ...prev, page: 2 })}>Next</Link>
|
|
32
32
|
|
|
33
|
-
// useNavigate
|
|
33
|
+
// Update via useNavigate
|
|
34
34
|
const Pagination = () => {
|
|
35
35
|
const navigate = useNavigate()
|
|
36
36
|
const { page } = Route.useSearch()
|
|
@@ -48,7 +48,7 @@ const Pagination = () => {
|
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Real-world: Filter + Sort + Pagination
|
|
52
52
|
const PostsPage = () => {
|
|
53
53
|
const { page, search, category, sort } = Route.useSearch()
|
|
54
54
|
const posts = Route.useLoaderData()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# TanStack Start -
|
|
1
|
+
# TanStack Start - Auth Patterns
|
|
2
2
|
|
|
3
3
|
<patterns>
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
|
-
//
|
|
6
|
+
// Login
|
|
7
7
|
export const loginFn = createServerFn({ method: 'POST' })
|
|
8
8
|
.inputValidator((data: { email: string; password: string }) => data)
|
|
9
9
|
.handler(async ({ data }) => {
|
|
@@ -14,7 +14,7 @@ export const loginFn = createServerFn({ method: 'POST' })
|
|
|
14
14
|
throw redirect({ to: '/dashboard' })
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// Logout
|
|
18
18
|
export const logoutFn = createServerFn({ method: 'POST' })
|
|
19
19
|
.handler(async () => {
|
|
20
20
|
const session = await useAppSession()
|
|
@@ -22,7 +22,7 @@ export const logoutFn = createServerFn({ method: 'POST' })
|
|
|
22
22
|
throw redirect({ to: '/' })
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Get current user
|
|
26
26
|
export const getCurrentUserFn = createServerFn({ method: 'GET' })
|
|
27
27
|
.handler(async () => {
|
|
28
28
|
const session = await useAppSession()
|
|
@@ -30,7 +30,7 @@ export const getCurrentUserFn = createServerFn({ method: 'GET' })
|
|
|
30
30
|
return getUserById(session.data.userId)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// Auth middleware
|
|
34
34
|
export const authMiddleware = createMiddleware({ type: 'function' })
|
|
35
35
|
.server(async ({ next }) => {
|
|
36
36
|
const session = await useAppSession()
|
|
@@ -39,12 +39,12 @@ export const authMiddleware = createMiddleware({ type: 'function' })
|
|
|
39
39
|
return next({ context: { user } })
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
// Server Function
|
|
42
|
+
// Apply to Server Function
|
|
43
43
|
export const protectedFn = createServerFn({ method: 'GET' })
|
|
44
44
|
.middleware([authMiddleware])
|
|
45
45
|
.handler(async ({ context }) => ({ user: context.user }))
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// Protected route
|
|
48
48
|
export const Route = createFileRoute('/dashboard')({
|
|
49
49
|
beforeLoad: async () => {
|
|
50
50
|
const user = await getCurrentUserFn()
|
|
@@ -57,7 +57,7 @@ export const Route = createFileRoute('/dashboard')({
|
|
|
57
57
|
},
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
// Better Auth
|
|
60
|
+
// Better Auth integration
|
|
61
61
|
export const auth = betterAuth({
|
|
62
62
|
database: prismaAdapter(prisma),
|
|
63
63
|
emailAndPassword: { enabled: true },
|
|
@@ -10,28 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## ⛔
|
|
13
|
+
## ⛔ Required Rules
|
|
14
14
|
|
|
15
|
-
|
|
|
15
|
+
| Forbidden | Use Instead |
|
|
16
16
|
|------|------|
|
|
17
|
-
| /api
|
|
18
|
-
|
|
|
19
|
-
|
|
|
17
|
+
| /api routes | Server Functions |
|
|
18
|
+
| Manual validation in handler | inputValidator |
|
|
19
|
+
| Manual auth in handler | middleware |
|
|
20
20
|
|
|
21
|
-
✅ POST/PUT/PATCH → inputValidator
|
|
22
|
-
✅
|
|
21
|
+
✅ POST/PUT/PATCH → inputValidator required
|
|
22
|
+
✅ Auth needed → middleware required
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
26
|
## Quick Reference
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
// GET +
|
|
29
|
+
// GET + Auth
|
|
30
30
|
export const getUsers = createServerFn({ method: 'GET' })
|
|
31
31
|
.middleware([authMiddleware])
|
|
32
32
|
.handler(async () => prisma.user.findMany())
|
|
33
33
|
|
|
34
|
-
// POST + Validation +
|
|
34
|
+
// POST + Validation + Auth
|
|
35
35
|
export const createUser = createServerFn({ method: 'POST' })
|
|
36
36
|
.middleware([authMiddleware])
|
|
37
37
|
.inputValidator(createUserSchema)
|
|
@@ -43,14 +43,14 @@ export const Route = createFileRoute('/users')({
|
|
|
43
43
|
loader: async () => ({ users: await getUsers() }),
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
// Loader
|
|
46
|
+
// Using Loader data
|
|
47
47
|
const UsersPage = (): JSX.Element => {
|
|
48
48
|
const { users } = Route.useLoaderData()
|
|
49
49
|
return <div>{/* render */}</div>
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
###
|
|
53
|
+
### Structure
|
|
54
54
|
|
|
55
55
|
```
|
|
56
56
|
routes/
|
|
@@ -59,9 +59,9 @@ routes/
|
|
|
59
59
|
├── users/
|
|
60
60
|
│ ├── index.tsx # /users
|
|
61
61
|
│ ├── $id.tsx # /users/:id
|
|
62
|
-
│ ├── -components/ #
|
|
63
|
-
│ └── -functions/ #
|
|
62
|
+
│ ├── -components/ # Page-specific
|
|
63
|
+
│ └── -functions/ # Page-specific Server Functions
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
Shared functions → @/functions/
|
|
66
|
+
Route-specific → routes/[path]/-functions/
|
|
67
67
|
```
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
# TanStack Start - Middleware
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Apply common logic to Server Functions and routes.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Basic Pattern
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
//
|
|
8
|
+
// Middleware definition
|
|
9
9
|
const loggingMiddleware = createMiddleware({ type: 'function' })
|
|
10
10
|
.server(({ next }) => {
|
|
11
11
|
console.log('Processing request')
|
|
12
12
|
return next()
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Apply
|
|
16
16
|
const fn = createServerFn()
|
|
17
17
|
.middleware([loggingMiddleware])
|
|
18
18
|
.handler(async () => ({ message: 'Hello' }))
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Auth Middleware
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
24
|
const authMiddleware = createMiddleware({ type: 'function' })
|
|
@@ -28,7 +28,7 @@ const authMiddleware = createMiddleware({ type: 'function' })
|
|
|
28
28
|
return next({ context: { user: session.user } })
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Usage
|
|
32
32
|
export const protectedFn = createServerFn({ method: 'GET' })
|
|
33
33
|
.middleware([authMiddleware])
|
|
34
34
|
.handler(async ({ context }) => ({ user: context.user }))
|
|
@@ -47,8 +47,8 @@ const workspaceMiddleware = createMiddleware({ type: 'function' })
|
|
|
47
47
|
```typescript
|
|
48
48
|
// src/start.ts
|
|
49
49
|
export const startInstance = createStart(() => ({
|
|
50
|
-
requestMiddleware: [globalMiddleware], //
|
|
51
|
-
functionMiddleware: [loggingMiddleware], //
|
|
50
|
+
requestMiddleware: [globalMiddleware], // All requests
|
|
51
|
+
functionMiddleware: [loggingMiddleware], // All Server Functions
|
|
52
52
|
}))
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@ export const startInstance = createStart(() => ({
|
|
|
57
57
|
```typescript
|
|
58
58
|
export const Route = createFileRoute('/hello')({
|
|
59
59
|
server: {
|
|
60
|
-
middleware: [authMiddleware], //
|
|
60
|
+
middleware: [authMiddleware], // All handlers
|
|
61
61
|
handlers: { GET: async ({ request }) => new Response('Hello') },
|
|
62
62
|
},
|
|
63
63
|
})
|