@benjavicente/start-client-core 1.167.9

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/bin/intent.js +25 -0
  4. package/dist/esm/client/ServerFunctionSerializationAdapter.d.ts +7 -0
  5. package/dist/esm/client/ServerFunctionSerializationAdapter.js +18 -0
  6. package/dist/esm/client/ServerFunctionSerializationAdapter.js.map +1 -0
  7. package/dist/esm/client/hydrateStart.d.ts +2 -0
  8. package/dist/esm/client/hydrateStart.js +31 -0
  9. package/dist/esm/client/hydrateStart.js.map +1 -0
  10. package/dist/esm/client/index.d.ts +2 -0
  11. package/dist/esm/client/index.js +2 -0
  12. package/dist/esm/client-rpc/createClientRpc.d.ts +6 -0
  13. package/dist/esm/client-rpc/createClientRpc.js +21 -0
  14. package/dist/esm/client-rpc/createClientRpc.js.map +1 -0
  15. package/dist/esm/client-rpc/frame-decoder.d.ts +23 -0
  16. package/dist/esm/client-rpc/frame-decoder.js +231 -0
  17. package/dist/esm/client-rpc/frame-decoder.js.map +1 -0
  18. package/dist/esm/client-rpc/index.d.ts +1 -0
  19. package/dist/esm/client-rpc/index.js +2 -0
  20. package/dist/esm/client-rpc/serverFnFetcher.d.ts +1 -0
  21. package/dist/esm/client-rpc/serverFnFetcher.js +231 -0
  22. package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -0
  23. package/dist/esm/constants.d.ts +53 -0
  24. package/dist/esm/constants.js +46 -0
  25. package/dist/esm/constants.js.map +1 -0
  26. package/dist/esm/createMiddleware.d.ts +195 -0
  27. package/dist/esm/createMiddleware.js +26 -0
  28. package/dist/esm/createMiddleware.js.map +1 -0
  29. package/dist/esm/createServerFn.d.ts +131 -0
  30. package/dist/esm/createServerFn.js +200 -0
  31. package/dist/esm/createServerFn.js.map +1 -0
  32. package/dist/esm/createStart.d.ts +50 -0
  33. package/dist/esm/createStart.js +29 -0
  34. package/dist/esm/createStart.js.map +1 -0
  35. package/dist/esm/fake-start-entry.d.ts +2 -0
  36. package/dist/esm/fake-start-entry.js +7 -0
  37. package/dist/esm/fake-start-entry.js.map +1 -0
  38. package/dist/esm/getDefaultSerovalPlugins.d.ts +1 -0
  39. package/dist/esm/getDefaultSerovalPlugins.js +10 -0
  40. package/dist/esm/getDefaultSerovalPlugins.js.map +1 -0
  41. package/dist/esm/getGlobalStartContext.d.ts +3 -0
  42. package/dist/esm/getGlobalStartContext.js +12 -0
  43. package/dist/esm/getGlobalStartContext.js.map +1 -0
  44. package/dist/esm/getRouterInstance.d.ts +2 -0
  45. package/dist/esm/getRouterInstance.js +8 -0
  46. package/dist/esm/getRouterInstance.js.map +1 -0
  47. package/dist/esm/getStartContextServerOnly.d.ts +2 -0
  48. package/dist/esm/getStartContextServerOnly.js +8 -0
  49. package/dist/esm/getStartContextServerOnly.js.map +1 -0
  50. package/dist/esm/getStartOptions.d.ts +2 -0
  51. package/dist/esm/getStartOptions.js +8 -0
  52. package/dist/esm/getStartOptions.js.map +1 -0
  53. package/dist/esm/global.d.ts +7 -0
  54. package/dist/esm/index.d.ts +20 -0
  55. package/dist/esm/index.js +12 -0
  56. package/dist/esm/safeObjectMerge.d.ts +10 -0
  57. package/dist/esm/safeObjectMerge.js +30 -0
  58. package/dist/esm/safeObjectMerge.js.map +1 -0
  59. package/dist/esm/serverRoute.d.ts +65 -0
  60. package/dist/esm/startEntry.d.ts +8 -0
  61. package/dist/esm/tests/createServerFn.test-d.d.ts +1 -0
  62. package/dist/esm/tests/createServerMiddleware.test-d.d.ts +1 -0
  63. package/package.json +98 -0
  64. package/skills/start-core/SKILL.md +210 -0
  65. package/skills/start-core/deployment/SKILL.md +306 -0
  66. package/skills/start-core/execution-model/SKILL.md +302 -0
  67. package/skills/start-core/middleware/SKILL.md +365 -0
  68. package/skills/start-core/server-functions/SKILL.md +335 -0
  69. package/skills/start-core/server-routes/SKILL.md +280 -0
  70. package/src/client/ServerFunctionSerializationAdapter.ts +16 -0
  71. package/src/client/hydrateStart.ts +43 -0
  72. package/src/client/index.ts +2 -0
  73. package/src/client-rpc/createClientRpc.ts +20 -0
  74. package/src/client-rpc/frame-decoder.ts +389 -0
  75. package/src/client-rpc/index.ts +1 -0
  76. package/src/client-rpc/serverFnFetcher.ts +416 -0
  77. package/src/constants.ts +90 -0
  78. package/src/createMiddleware.ts +824 -0
  79. package/src/createServerFn.ts +813 -0
  80. package/src/createStart.ts +166 -0
  81. package/src/fake-start-entry.ts +2 -0
  82. package/src/getDefaultSerovalPlugins.ts +17 -0
  83. package/src/getGlobalStartContext.ts +18 -0
  84. package/src/getRouterInstance.ts +8 -0
  85. package/src/getStartContextServerOnly.ts +4 -0
  86. package/src/getStartOptions.ts +8 -0
  87. package/src/global.ts +9 -0
  88. package/src/index.tsx +119 -0
  89. package/src/safeObjectMerge.ts +38 -0
  90. package/src/serverRoute.ts +509 -0
  91. package/src/start-entry.d.ts +11 -0
  92. package/src/startEntry.ts +10 -0
  93. package/src/tests/createServerFn.test-d.ts +866 -0
  94. package/src/tests/createServerMiddleware.test-d.ts +810 -0
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: start-core/server-routes
3
+ description: >-
4
+ Server-side API endpoints using the server property on
5
+ createFileRoute, HTTP method handlers (GET, POST, PUT, DELETE),
6
+ createHandlers for per-handler middleware, handler context
7
+ (request, params, context), request body parsing, response
8
+ helpers, file naming for API routes.
9
+ type: sub-skill
10
+ library: tanstack-start
11
+ library_version: '1.166.2'
12
+ requires:
13
+ - start-core
14
+ sources:
15
+ - TanStack/router:docs/start/framework/react/guide/server-routes.md
16
+ ---
17
+
18
+ # Server Routes
19
+
20
+ Server routes are API endpoints defined alongside app routes in the `src/routes` directory. They use the `server` property on `createFileRoute` and handle raw HTTP requests.
21
+
22
+ ## Basic Server Route
23
+
24
+ ```ts
25
+ // src/routes/api/hello.ts
26
+ import { createFileRoute } from '@benjavicente/react-router'
27
+
28
+ export const Route = createFileRoute('/api/hello')({
29
+ server: {
30
+ handlers: {
31
+ GET: async ({ request }) => {
32
+ return new Response('Hello, World!')
33
+ },
34
+ },
35
+ },
36
+ })
37
+ ```
38
+
39
+ ## Combining Server Route and App Route
40
+
41
+ The same file can define both a server route and a UI route:
42
+
43
+ ```tsx
44
+ // src/routes/hello.tsx
45
+ import { createFileRoute } from '@benjavicente/react-router'
46
+ import { useState } from 'react'
47
+
48
+ export const Route = createFileRoute('/hello')({
49
+ server: {
50
+ handlers: {
51
+ POST: async ({ request }) => {
52
+ const body = await request.json()
53
+ return Response.json({ message: `Hello, ${body.name}!` })
54
+ },
55
+ },
56
+ },
57
+ component: HelloComponent,
58
+ })
59
+
60
+ function HelloComponent() {
61
+ const [reply, setReply] = useState('')
62
+ return (
63
+ <button
64
+ onClick={() => {
65
+ fetch('/hello', {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify({ name: 'Tanner' }),
69
+ })
70
+ .then((res) => res.json())
71
+ .then((data) => setReply(data.message))
72
+ }}
73
+ >
74
+ Say Hello {reply && `- ${reply}`}
75
+ </button>
76
+ )
77
+ }
78
+ ```
79
+
80
+ ## File Route Conventions
81
+
82
+ Server routes follow TanStack Router file-based routing conventions:
83
+
84
+ | File | Route |
85
+ | --------------------------- | ----------------------------- |
86
+ | `routes/users.ts` | `/users` |
87
+ | `routes/users/$id.ts` | `/users/$id` |
88
+ | `routes/users/$id/posts.ts` | `/users/$id/posts` |
89
+ | `routes/api/file/$.ts` | `/api/file/$` (splat) |
90
+ | `routes/my-script[.]js.ts` | `/my-script.js` (escaped dot) |
91
+
92
+ ## Unique Route Paths
93
+
94
+ Each route can only have a single handler file. These would conflict:
95
+
96
+ - `routes/users.ts`
97
+ - `routes/users.index.ts`
98
+ - `routes/users/index.ts`
99
+
100
+ ## Handler Context
101
+
102
+ Each handler receives:
103
+
104
+ - `request` — the incoming [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
105
+ - `params` — dynamic path parameters
106
+ - `context` — context from middleware
107
+ - `pathname` — the matched pathname
108
+ - `next` — call to fall through to SSR (returns a `Response`)
109
+
110
+ ## Dynamic Path Params
111
+
112
+ ```ts
113
+ // routes/users/$id.ts
114
+ import { createFileRoute } from '@benjavicente/react-router'
115
+
116
+ export const Route = createFileRoute('/users/$id')({
117
+ server: {
118
+ handlers: {
119
+ GET: async ({ params }) => {
120
+ return new Response(`User ID: ${params.id}`)
121
+ },
122
+ },
123
+ },
124
+ })
125
+ ```
126
+
127
+ ## Splat/Wildcard Params
128
+
129
+ ```ts
130
+ // routes/file/$.ts
131
+ import { createFileRoute } from '@benjavicente/react-router'
132
+
133
+ export const Route = createFileRoute('/file/$')({
134
+ server: {
135
+ handlers: {
136
+ GET: async ({ params }) => {
137
+ return new Response(`File: ${params._splat}`)
138
+ },
139
+ },
140
+ },
141
+ })
142
+ ```
143
+
144
+ ## Request Body Handling
145
+
146
+ ```ts
147
+ export const Route = createFileRoute('/api/users')({
148
+ server: {
149
+ handlers: {
150
+ POST: async ({ request }) => {
151
+ const body = await request.json()
152
+ return Response.json({ created: body.name })
153
+ },
154
+ },
155
+ },
156
+ })
157
+ ```
158
+
159
+ Other body methods: `request.text()`, `request.formData()`.
160
+
161
+ ## JSON Responses
162
+
163
+ ```ts
164
+ // Using Response.json helper
165
+ handlers: {
166
+ GET: async () => {
167
+ return Response.json({ message: 'Hello!' })
168
+ },
169
+ }
170
+ ```
171
+
172
+ ## Status Codes and Headers
173
+
174
+ ```ts
175
+ handlers: {
176
+ GET: async ({ params }) => {
177
+ const user = await findUser(params.id)
178
+ if (!user) {
179
+ return new Response('Not found', { status: 404 })
180
+ }
181
+ return Response.json(user)
182
+ },
183
+ }
184
+ ```
185
+
186
+ ```ts
187
+ handlers: {
188
+ GET: async () => {
189
+ return new Response('Hello', {
190
+ headers: { 'Content-Type': 'text/plain' },
191
+ })
192
+ },
193
+ }
194
+ ```
195
+
196
+ ## Middleware on Server Routes
197
+
198
+ ### All handlers
199
+
200
+ ```tsx
201
+ export const Route = createFileRoute('/api/admin')({
202
+ server: {
203
+ middleware: [authMiddleware, loggerMiddleware],
204
+ handlers: {
205
+ GET: async ({ context }) => Response.json(context.user),
206
+ POST: async ({ request, context }) => {
207
+ /* ... */
208
+ },
209
+ },
210
+ },
211
+ })
212
+ ```
213
+
214
+ ### Specific handlers with createHandlers
215
+
216
+ ```tsx
217
+ export const Route = createFileRoute('/api/data')({
218
+ server: {
219
+ handlers: ({ createHandlers }) =>
220
+ createHandlers({
221
+ GET: async () => Response.json({ public: true }),
222
+ POST: {
223
+ middleware: [authMiddleware],
224
+ handler: async ({ context }) => {
225
+ return Response.json({ user: context.session.user })
226
+ },
227
+ },
228
+ }),
229
+ },
230
+ })
231
+ ```
232
+
233
+ ### Combined route-level and handler-specific
234
+
235
+ ```tsx
236
+ export const Route = createFileRoute('/api/posts')({
237
+ server: {
238
+ middleware: [authMiddleware], // runs first for all
239
+ handlers: ({ createHandlers }) =>
240
+ createHandlers({
241
+ GET: async () => Response.json([]),
242
+ POST: {
243
+ middleware: [validationMiddleware], // runs after auth, POST only
244
+ handler: async ({ request }) => {
245
+ const body = await request.json()
246
+ return Response.json({ created: true })
247
+ },
248
+ },
249
+ }),
250
+ },
251
+ })
252
+ ```
253
+
254
+ ## Common Mistakes
255
+
256
+ ### 1. MEDIUM: Duplicate route paths
257
+
258
+ ```text
259
+ # WRONG — both resolve to /users, causes error
260
+ routes/users.ts
261
+ routes/users/index.ts
262
+
263
+ # CORRECT — pick one
264
+ routes/users.ts
265
+ ```
266
+
267
+ ### 2. MEDIUM: Forgetting to await request body methods
268
+
269
+ ```ts
270
+ // WRONG — body is a Promise, not the actual data
271
+ const body = request.json()
272
+
273
+ // CORRECT — await the promise
274
+ const body = await request.json()
275
+ ```
276
+
277
+ ## Cross-References
278
+
279
+ - [start-core/middleware](../middleware/SKILL.md) — middleware for server routes
280
+ - [start-core/server-functions](../server-functions/SKILL.md) — alternative for RPC-style calls
@@ -0,0 +1,16 @@
1
+ import { createSerializationAdapter } from '@benjavicente/router-core'
2
+ import { TSS_SERVER_FUNCTION } from '../constants'
3
+ import { createClientRpc } from '../client-rpc/createClientRpc'
4
+
5
+ export const ServerFunctionSerializationAdapter = createSerializationAdapter({
6
+ key: '$TSS/serverfn',
7
+ test: (v): v is { serverFnMeta: { id: string } } => {
8
+ if (typeof v !== 'function') return false
9
+
10
+ if (!(TSS_SERVER_FUNCTION in v)) return false
11
+
12
+ return !!v[TSS_SERVER_FUNCTION]
13
+ },
14
+ toSerializable: ({ serverFnMeta }) => ({ functionId: serverFnMeta.id }),
15
+ fromSerializable: ({ functionId }) => createClientRpc(functionId),
16
+ })
@@ -0,0 +1,43 @@
1
+ import { hydrate } from '@benjavicente/router-core/ssr/client'
2
+
3
+ import { ServerFunctionSerializationAdapter } from './ServerFunctionSerializationAdapter'
4
+ import type { AnyStartInstanceOptions } from '../createStart'
5
+ import type { AnyRouter, AnySerializationAdapter } from '@benjavicente/router-core'
6
+ // eslint-disable-next-line import/no-duplicates,import/order
7
+ import { getRouter } from '#tanstack-router-entry'
8
+ // eslint-disable-next-line import/no-duplicates,import/order
9
+ import { startInstance } from '#tanstack-start-entry'
10
+
11
+ export async function hydrateStart(): Promise<AnyRouter> {
12
+ const router = await getRouter()
13
+
14
+ let serializationAdapters: Array<AnySerializationAdapter>
15
+ if (startInstance) {
16
+ const startOptions = await startInstance.getOptions()
17
+ startOptions.serializationAdapters =
18
+ startOptions.serializationAdapters ?? []
19
+ window.__TSS_START_OPTIONS__ = startOptions as AnyStartInstanceOptions
20
+ serializationAdapters = startOptions.serializationAdapters
21
+ router.options.defaultSsr = startOptions.defaultSsr
22
+ } else {
23
+ serializationAdapters = []
24
+ window.__TSS_START_OPTIONS__ = {
25
+ serializationAdapters,
26
+ } as AnyStartInstanceOptions
27
+ }
28
+
29
+ serializationAdapters.push(ServerFunctionSerializationAdapter)
30
+ if (router.options.serializationAdapters) {
31
+ serializationAdapters.push(...router.options.serializationAdapters)
32
+ }
33
+
34
+ router.update({
35
+ basepath: process.env.TSS_ROUTER_BASEPATH,
36
+ ...{ serializationAdapters },
37
+ })
38
+ if (!router.stores.matchesId.state.length) {
39
+ await hydrate(router)
40
+ }
41
+
42
+ return router
43
+ }
@@ -0,0 +1,2 @@
1
+ export { hydrateStart } from './hydrateStart'
2
+ export type * from '@benjavicente/router-core/ssr/client'
@@ -0,0 +1,20 @@
1
+ import { TSS_SERVER_FUNCTION } from '../constants'
2
+ import { getStartOptions } from '../getStartOptions'
3
+ import { serverFnFetcher } from './serverFnFetcher'
4
+ import type { ClientFnMeta } from '../constants'
5
+
6
+ export function createClientRpc(functionId: string) {
7
+ const url = process.env.TSS_SERVER_FN_BASE + functionId
8
+ const serverFnMeta: ClientFnMeta = { id: functionId }
9
+
10
+ const clientFn = (...args: Array<any>) => {
11
+ const startFetch = getStartOptions()?.serverFns?.fetch
12
+ return serverFnFetcher(url, args, startFetch ?? fetch)
13
+ }
14
+
15
+ return Object.assign(clientFn, {
16
+ url,
17
+ serverFnMeta,
18
+ [TSS_SERVER_FUNCTION]: true,
19
+ })
20
+ }