kaddidlehopper 0.1.0

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.
Files changed (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,445 @@
1
+ ---
2
+ name: tanstack-start-server-functions
3
+ description: Create server functions in TanStack Start for server-side logic callable from anywhere. Use for database access, API calls with secrets, mutations, or any server-only code that needs to be called from components or loaders.
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: tanstack
7
+ version: "1.0"
8
+ ---
9
+
10
+ # TanStack Start Server Functions
11
+
12
+ Server functions are the primary way to run server-side code in TanStack Start. They provide type-safe RPC calls from client to server.
13
+
14
+ ## When to Use
15
+
16
+ - Database queries and mutations
17
+ - API calls requiring secrets
18
+ - Server-only business logic
19
+ - Any code that needs server capabilities
20
+ - Called from loaders, components, or other server functions
21
+
22
+ ## Basic Server Function
23
+
24
+ ```typescript
25
+ // src/server/posts.functions.ts
26
+ import { createServerFn } from '@tanstack/react-start';
27
+
28
+ // Simple GET function
29
+ export const getPosts = createServerFn().handler(async () => {
30
+ const posts = await db.query('SELECT * FROM posts');
31
+ return posts;
32
+ });
33
+
34
+ // Call it anywhere
35
+ const posts = await getPosts();
36
+ ```
37
+
38
+ ## Function with Input
39
+
40
+ ```typescript
41
+ // src/server/posts.functions.ts
42
+ import { createServerFn } from '@tanstack/react-start';
43
+
44
+ export const getPost = createServerFn({ method: 'GET' })
45
+ .inputValidator((data: { id: string }) => data)
46
+ .handler(async ({ data }) => {
47
+ const post = await db.query('SELECT * FROM posts WHERE id = $1', [data.id]);
48
+ return post[0] || null;
49
+ });
50
+
51
+ // Call with input
52
+ const post = await getPost({ data: { id: '123' } });
53
+ ```
54
+
55
+ ## HTTP Methods
56
+
57
+ ```typescript
58
+ // GET - for reading data (default)
59
+ export const getData = createServerFn().handler(async () => {
60
+ return { data: 'value' };
61
+ });
62
+
63
+ // GET with explicit method
64
+ export const getUser = createServerFn({ method: 'GET' })
65
+ .inputValidator((data: { id: string }) => data)
66
+ .handler(async ({ data }) => {
67
+ return await findUser(data.id);
68
+ });
69
+
70
+ // POST - for mutations
71
+ export const createPost = createServerFn({ method: 'POST' })
72
+ .inputValidator((data: { title: string; content: string }) => data)
73
+ .handler(async ({ data }) => {
74
+ const post = await db.insert('posts', data);
75
+ return post;
76
+ });
77
+ ```
78
+
79
+ ## Input Validation with Zod
80
+
81
+ ```typescript
82
+ import { createServerFn } from '@tanstack/react-start';
83
+ import { z } from 'zod';
84
+
85
+ const CreateUserSchema = z.object({
86
+ email: z.string().email(),
87
+ name: z.string().min(1).max(100),
88
+ age: z.number().int().min(0).optional(),
89
+ });
90
+
91
+ export const createUser = createServerFn({ method: 'POST' })
92
+ .inputValidator(CreateUserSchema)
93
+ .handler(async ({ data }) => {
94
+ // data is fully typed and validated
95
+ // { email: string, name: string, age?: number }
96
+
97
+ const user = await db.insert('users', data);
98
+ return user;
99
+ });
100
+
101
+ // Usage - type errors if invalid
102
+ await createUser({
103
+ data: {
104
+ email: 'alice@example.com',
105
+ name: 'Alice',
106
+ },
107
+ });
108
+ ```
109
+
110
+ ## Organized File Structure
111
+
112
+ ```
113
+ src/
114
+ ├── server/
115
+ │ ├── posts.functions.ts # Server function wrappers
116
+ │ ├── posts.server.ts # Server-only helpers (DB queries)
117
+ │ ├── users.functions.ts
118
+ │ ├── users.server.ts
119
+ │ └── schemas.ts # Shared validation schemas
120
+ ├── routes/
121
+ │ └── posts.tsx
122
+ └── ...
123
+ ```
124
+
125
+ ### Server-Only Helpers
126
+
127
+ ```typescript
128
+ // src/server/posts.server.ts
129
+ // These are server-only - NEVER import in client code
130
+
131
+ export async function findPostById(id: string) {
132
+ return db.query('SELECT * FROM posts WHERE id = $1', [id]);
133
+ }
134
+
135
+ export async function insertPost(data: { title: string; content: string }) {
136
+ return db.query(
137
+ 'INSERT INTO posts (title, content) VALUES ($1, $2) RETURNING *',
138
+ [data.title, data.content]
139
+ );
140
+ }
141
+ ```
142
+
143
+ ### Server Function Wrappers
144
+
145
+ ```typescript
146
+ // src/server/posts.functions.ts
147
+ // These are the public API - safe to import anywhere
148
+
149
+ import { createServerFn } from '@tanstack/react-start';
150
+ import { findPostById, insertPost } from './posts.server';
151
+ import { z } from 'zod';
152
+
153
+ export const getPost = createServerFn({ method: 'GET' })
154
+ .inputValidator((data: { id: string }) => data)
155
+ .handler(async ({ data }) => {
156
+ return findPostById(data.id);
157
+ });
158
+
159
+ export const createPost = createServerFn({ method: 'POST' })
160
+ .inputValidator(z.object({
161
+ title: z.string().min(1),
162
+ content: z.string(),
163
+ }))
164
+ .handler(async ({ data }) => {
165
+ return insertPost(data);
166
+ });
167
+ ```
168
+
169
+ ## Using in Components
170
+
171
+ ```tsx
172
+ // src/routes/posts.tsx
173
+ import { createFileRoute } from '@tanstack/react-router';
174
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
175
+ import { getPosts, createPost } from '../server/posts.functions';
176
+ import { useServerFn } from '@tanstack/react-start';
177
+
178
+ export const Route = createFileRoute('/posts')({
179
+ loader: async () => {
180
+ // Call server function in loader
181
+ const posts = await getPosts();
182
+ return { posts };
183
+ },
184
+ component: PostsComponent,
185
+ });
186
+
187
+ function PostsComponent() {
188
+ const { posts } = Route.useLoaderData();
189
+ const queryClient = useQueryClient();
190
+
191
+ // Wrap for use with TanStack Query
192
+ const createPostFn = useServerFn(createPost);
193
+
194
+ const mutation = useMutation({
195
+ mutationFn: (data: { title: string; content: string }) =>
196
+ createPostFn({ data }),
197
+ onSuccess: () => {
198
+ // Invalidate and refetch
199
+ queryClient.invalidateQueries({ queryKey: ['posts'] });
200
+ },
201
+ });
202
+
203
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
204
+ e.preventDefault();
205
+ const formData = new FormData(e.currentTarget);
206
+ mutation.mutate({
207
+ title: formData.get('title') as string,
208
+ content: formData.get('content') as string,
209
+ });
210
+ };
211
+
212
+ return (
213
+ <div>
214
+ <form onSubmit={handleSubmit}>
215
+ <input name="title" placeholder="Title" required />
216
+ <textarea name="content" placeholder="Content" />
217
+ <button type="submit" disabled={mutation.isPending}>
218
+ {mutation.isPending ? 'Creating...' : 'Create Post'}
219
+ </button>
220
+ </form>
221
+
222
+ <ul>
223
+ {posts.map((post) => (
224
+ <li key={post.id}>{post.title}</li>
225
+ ))}
226
+ </ul>
227
+ </div>
228
+ );
229
+ }
230
+ ```
231
+
232
+ ## Form Data Handling
233
+
234
+ ```typescript
235
+ import { createServerFn } from '@tanstack/react-start';
236
+
237
+ export const uploadFile = createServerFn({ method: 'POST' })
238
+ .inputValidator((formData: FormData) => formData)
239
+ .handler(async ({ data: formData }) => {
240
+ const file = formData.get('file') as File;
241
+ const name = formData.get('name') as string;
242
+
243
+ // Process file...
244
+ const buffer = await file.arrayBuffer();
245
+
246
+ return { filename: file.name, size: file.size };
247
+ });
248
+
249
+ // Usage in component
250
+ function UploadForm() {
251
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
252
+ e.preventDefault();
253
+ const formData = new FormData(e.currentTarget);
254
+
255
+ const result = await uploadFile({ data: formData });
256
+ console.log('Uploaded:', result.filename);
257
+ };
258
+
259
+ return (
260
+ <form onSubmit={handleSubmit}>
261
+ <input type="file" name="file" />
262
+ <input type="text" name="name" />
263
+ <button type="submit">Upload</button>
264
+ </form>
265
+ );
266
+ }
267
+ ```
268
+
269
+ ## Middleware
270
+
271
+ ```typescript
272
+ import { createServerFn, createMiddleware } from '@tanstack/react-start';
273
+
274
+ // Create reusable middleware
275
+ const authMiddleware = createMiddleware().handler(async ({ next }) => {
276
+ const user = await getCurrentUser();
277
+
278
+ if (!user) {
279
+ throw new Error('Unauthorized');
280
+ }
281
+
282
+ // Pass data to next middleware/handler
283
+ return next({ user });
284
+ });
285
+
286
+ const loggingMiddleware = createMiddleware().handler(async ({ next }) => {
287
+ const start = Date.now();
288
+ const result = await next();
289
+ console.log(`Request took ${Date.now() - start}ms`);
290
+ return result;
291
+ });
292
+
293
+ // Use middleware in server function
294
+ export const getSecretData = createServerFn({ method: 'GET' })
295
+ .middleware([loggingMiddleware, authMiddleware])
296
+ .handler(async ({ context }) => {
297
+ // context.user is available from authMiddleware
298
+ return { secret: 'data', user: context.user };
299
+ });
300
+ ```
301
+
302
+ ## Error Handling
303
+
304
+ ```typescript
305
+ import { createServerFn } from '@tanstack/react-start';
306
+
307
+ export const riskyOperation = createServerFn({ method: 'POST' })
308
+ .inputValidator((data: { value: number }) => data)
309
+ .handler(async ({ data }) => {
310
+ if (data.value < 0) {
311
+ throw new Error('Value must be positive');
312
+ }
313
+
314
+ try {
315
+ const result = await dangerousOperation(data.value);
316
+ return { success: true, result };
317
+ } catch (error) {
318
+ // Log server-side
319
+ console.error('Operation failed:', error);
320
+
321
+ // Return safe error to client
322
+ throw new Error('Operation failed. Please try again.');
323
+ }
324
+ });
325
+
326
+ // In component - handle errors
327
+ function MyComponent() {
328
+ const [error, setError] = useState<string | null>(null);
329
+
330
+ const handleClick = async () => {
331
+ try {
332
+ await riskyOperation({ data: { value: -1 } });
333
+ } catch (e) {
334
+ setError(e instanceof Error ? e.message : 'Unknown error');
335
+ }
336
+ };
337
+
338
+ return (
339
+ <div>
340
+ <button onClick={handleClick}>Do Thing</button>
341
+ {error && <p className="error">{error}</p>}
342
+ </div>
343
+ );
344
+ }
345
+ ```
346
+
347
+ ## Redirects from Server Functions
348
+
349
+ ```typescript
350
+ import { createServerFn } from '@tanstack/react-start';
351
+ import { redirect } from '@tanstack/react-router';
352
+
353
+ export const loginUser = createServerFn({ method: 'POST' })
354
+ .inputValidator((data: { email: string; password: string }) => data)
355
+ .handler(async ({ data }) => {
356
+ const user = await authenticate(data.email, data.password);
357
+
358
+ if (!user) {
359
+ return { error: 'Invalid credentials' };
360
+ }
361
+
362
+ // Set session cookie, etc.
363
+ await createSession(user.id);
364
+
365
+ // Redirect after successful login
366
+ throw redirect({ to: '/dashboard' });
367
+ });
368
+ ```
369
+
370
+ ## Environment Variables
371
+
372
+ ```typescript
373
+ import { createServerFn } from '@tanstack/react-start';
374
+
375
+ export const callExternalApi = createServerFn({ method: 'GET' })
376
+ .handler(async () => {
377
+ // Access secrets safely on server
378
+ const apiKey = process.env.EXTERNAL_API_KEY;
379
+
380
+ const response = await fetch('https://api.example.com/data', {
381
+ headers: { Authorization: `Bearer ${apiKey}` },
382
+ });
383
+
384
+ return response.json();
385
+ });
386
+ ```
387
+
388
+ ## Common Patterns
389
+
390
+ ### CRUD Operations
391
+
392
+ ```typescript
393
+ // src/server/items.functions.ts
394
+ import { createServerFn } from '@tanstack/react-start';
395
+ import { z } from 'zod';
396
+ import { db, items } from '../db';
397
+ import { eq } from 'drizzle-orm';
398
+
399
+ const ItemSchema = z.object({
400
+ name: z.string().min(1),
401
+ price: z.number().positive(),
402
+ });
403
+
404
+ export const getItems = createServerFn().handler(async () => {
405
+ return db.select().from(items);
406
+ });
407
+
408
+ export const getItem = createServerFn({ method: 'GET' })
409
+ .inputValidator((data: { id: string }) => data)
410
+ .handler(async ({ data }) => {
411
+ const result = await db.select().from(items).where(eq(items.id, data.id));
412
+ return result[0] || null;
413
+ });
414
+
415
+ export const createItem = createServerFn({ method: 'POST' })
416
+ .inputValidator(ItemSchema)
417
+ .handler(async ({ data }) => {
418
+ const result = await db.insert(items).values(data).returning();
419
+ return result[0];
420
+ });
421
+
422
+ export const updateItem = createServerFn({ method: 'POST' })
423
+ .inputValidator(z.object({ id: z.string() }).extend(ItemSchema.shape))
424
+ .handler(async ({ data }) => {
425
+ const { id, ...values } = data;
426
+ const result = await db.update(items).set(values).where(eq(items.id, id)).returning();
427
+ return result[0];
428
+ });
429
+
430
+ export const deleteItem = createServerFn({ method: 'POST' })
431
+ .inputValidator((data: { id: string }) => data)
432
+ .handler(async ({ data }) => {
433
+ await db.delete(items).where(eq(items.id, data.id));
434
+ return { success: true };
435
+ });
436
+ ```
437
+
438
+ ## Key Points
439
+
440
+ 1. **Server functions run ONLY on the server** - Safe for secrets and DB access
441
+ 2. **Type-safe across the network** - Input and output are fully typed
442
+ 3. **Use `.functions.ts` suffix** - Clear convention for importable functions
443
+ 4. **Validate input** - Use Zod or custom validators
444
+ 5. **Keep `.server.ts` files private** - Never import directly in client code
445
+ 6. **Use `useServerFn` hook** - When integrating with TanStack Query