@tanstack/start-server-core 1.20.3-alpha.1

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/cjs/createRequestHandler.cjs +50 -0
  4. package/dist/cjs/createRequestHandler.cjs.map +1 -0
  5. package/dist/cjs/createRequestHandler.d.cts +8 -0
  6. package/dist/cjs/createStartHandler.cjs +254 -0
  7. package/dist/cjs/createStartHandler.cjs.map +1 -0
  8. package/dist/cjs/createStartHandler.d.cts +10 -0
  9. package/dist/cjs/h3.cjs +355 -0
  10. package/dist/cjs/h3.cjs.map +1 -0
  11. package/dist/cjs/h3.d.cts +109 -0
  12. package/dist/cjs/handlerCallback.cjs +7 -0
  13. package/dist/cjs/handlerCallback.cjs.map +1 -0
  14. package/dist/cjs/handlerCallback.d.cts +9 -0
  15. package/dist/cjs/index.cjs +245 -0
  16. package/dist/cjs/index.cjs.map +1 -0
  17. package/dist/cjs/index.d.cts +12 -0
  18. package/dist/cjs/router-manifest.cjs +44 -0
  19. package/dist/cjs/router-manifest.cjs.map +1 -0
  20. package/dist/cjs/router-manifest.d.cts +17 -0
  21. package/dist/cjs/server-functions-handler.cjs +154 -0
  22. package/dist/cjs/server-functions-handler.cjs.map +1 -0
  23. package/dist/cjs/server-functions-handler.d.cts +4 -0
  24. package/dist/cjs/serverRoute.cjs +100 -0
  25. package/dist/cjs/serverRoute.cjs.map +1 -0
  26. package/dist/cjs/serverRoute.d.cts +115 -0
  27. package/dist/cjs/ssr-server.cjs +246 -0
  28. package/dist/cjs/ssr-server.cjs.map +1 -0
  29. package/dist/cjs/ssr-server.d.cts +29 -0
  30. package/dist/cjs/transformStreamWithRouter.cjs +183 -0
  31. package/dist/cjs/transformStreamWithRouter.cjs.map +1 -0
  32. package/dist/cjs/transformStreamWithRouter.d.cts +6 -0
  33. package/dist/cjs/tsrScript.cjs +4 -0
  34. package/dist/cjs/tsrScript.cjs.map +1 -0
  35. package/dist/cjs/tsrScript.d.cts +1 -0
  36. package/dist/cjs/undici.cjs +14 -0
  37. package/dist/cjs/undici.cjs.map +1 -0
  38. package/dist/cjs/undici.d.cts +43 -0
  39. package/dist/esm/createRequestHandler.d.ts +8 -0
  40. package/dist/esm/createRequestHandler.js +50 -0
  41. package/dist/esm/createRequestHandler.js.map +1 -0
  42. package/dist/esm/createStartHandler.d.ts +10 -0
  43. package/dist/esm/createStartHandler.js +232 -0
  44. package/dist/esm/createStartHandler.js.map +1 -0
  45. package/dist/esm/h3.d.ts +109 -0
  46. package/dist/esm/h3.js +248 -0
  47. package/dist/esm/h3.js.map +1 -0
  48. package/dist/esm/handlerCallback.d.ts +9 -0
  49. package/dist/esm/handlerCallback.js +7 -0
  50. package/dist/esm/handlerCallback.js.map +1 -0
  51. package/dist/esm/index.d.ts +12 -0
  52. package/dist/esm/index.js +137 -0
  53. package/dist/esm/index.js.map +1 -0
  54. package/dist/esm/router-manifest.d.ts +17 -0
  55. package/dist/esm/router-manifest.js +44 -0
  56. package/dist/esm/router-manifest.js.map +1 -0
  57. package/dist/esm/server-functions-handler.d.ts +4 -0
  58. package/dist/esm/server-functions-handler.js +154 -0
  59. package/dist/esm/server-functions-handler.js.map +1 -0
  60. package/dist/esm/serverRoute.d.ts +115 -0
  61. package/dist/esm/serverRoute.js +100 -0
  62. package/dist/esm/serverRoute.js.map +1 -0
  63. package/dist/esm/ssr-server.d.ts +29 -0
  64. package/dist/esm/ssr-server.js +246 -0
  65. package/dist/esm/ssr-server.js.map +1 -0
  66. package/dist/esm/transformStreamWithRouter.d.ts +6 -0
  67. package/dist/esm/transformStreamWithRouter.js +183 -0
  68. package/dist/esm/transformStreamWithRouter.js.map +1 -0
  69. package/dist/esm/tsrScript.d.ts +1 -0
  70. package/dist/esm/tsrScript.js +5 -0
  71. package/dist/esm/tsrScript.js.map +1 -0
  72. package/dist/esm/undici.d.ts +43 -0
  73. package/dist/esm/undici.js +14 -0
  74. package/dist/esm/undici.js.map +1 -0
  75. package/package.json +68 -0
  76. package/src/createRequestHandler.ts +73 -0
  77. package/src/createStartHandler.ts +348 -0
  78. package/src/h3.ts +492 -0
  79. package/src/handlerCallback.ts +15 -0
  80. package/src/index.tsx +24 -0
  81. package/src/router-manifest.ts +79 -0
  82. package/src/server-functions-handler.ts +273 -0
  83. package/src/serverRoute.ts +661 -0
  84. package/src/ssr-server.ts +350 -0
  85. package/src/tanstack-start.d.ts +5 -0
  86. package/src/transformStreamWithRouter.ts +258 -0
  87. package/src/tsrScript.ts +91 -0
  88. package/src/undici.ts +60 -0
  89. package/src/vite-env.d.ts +4 -0
@@ -0,0 +1,73 @@
1
+ import { createMemoryHistory } from '@tanstack/history'
2
+ import { mergeHeaders } from '@tanstack/start-client-core'
3
+ import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server'
4
+ import type { HandlerCallback } from './handlerCallback'
5
+ import type { AnyRouter, Manifest } from '@tanstack/router-core'
6
+
7
+ export type RequestHandler<TRouter extends AnyRouter> = (
8
+ cb: HandlerCallback<TRouter>,
9
+ ) => Promise<Response>
10
+
11
+ export function createRequestHandler<TRouter extends AnyRouter>({
12
+ createRouter,
13
+ request,
14
+ getRouterManifest,
15
+ }: {
16
+ createRouter: () => TRouter
17
+ request: Request
18
+ getRouterManifest?: () => Manifest | Promise<Manifest>
19
+ }): RequestHandler<TRouter> {
20
+ return async (cb) => {
21
+ const router = createRouter()
22
+
23
+ attachRouterServerSsrUtils(router, await getRouterManifest?.())
24
+
25
+ const url = new URL(request.url, 'http://localhost')
26
+
27
+ const href = url.href.replace(url.origin, '')
28
+
29
+ // Create a history for the router
30
+ const history = createMemoryHistory({
31
+ initialEntries: [href],
32
+ })
33
+
34
+ // Update the router with the history and context
35
+ router.update({
36
+ history,
37
+ })
38
+
39
+ await router.load()
40
+
41
+ dehydrateRouter(router)
42
+
43
+ const responseHeaders = getRequestHeaders({
44
+ router,
45
+ })
46
+
47
+ return cb({
48
+ request,
49
+ router,
50
+ responseHeaders,
51
+ } as any)
52
+ }
53
+ }
54
+
55
+ function getRequestHeaders(opts: { router: AnyRouter }): Headers {
56
+ let headers = mergeHeaders(
57
+ {
58
+ 'Content-Type': 'text/html; charset=UTF-8',
59
+ },
60
+ ...opts.router.state.matches.map((match) => {
61
+ return match.headers
62
+ }),
63
+ )
64
+
65
+ // Handle Redirects
66
+ const { redirect } = opts.router.state
67
+
68
+ if (redirect) {
69
+ headers = mergeHeaders(headers, redirect.headers)
70
+ }
71
+
72
+ return headers
73
+ }
@@ -0,0 +1,348 @@
1
+ import path from 'node:path'
2
+ import { createMemoryHistory } from '@tanstack/history'
3
+ import {
4
+ flattenMiddlewares,
5
+ json,
6
+ mergeHeaders,
7
+ } from '@tanstack/start-client-core'
8
+ import {
9
+ getMatchedRoutes,
10
+ isRedirect,
11
+ processRouteTree,
12
+ rootRouteId,
13
+ tsrRedirectHeaderKey,
14
+ } from '@tanstack/router-core'
15
+ import { getResponseHeaders, requestHandler } from './h3'
16
+ import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server'
17
+ import { getStartManifest } from './router-manifest'
18
+ import { handleServerAction } from './server-functions-handler'
19
+ import type { AnyServerRoute, AnyServerRouteWithTypes } from './serverRoute'
20
+ import type { RequestHandler } from './h3'
21
+ import type { AnyRouter } from '@tanstack/router-core'
22
+ import type { HandlerCallback } from './handlerCallback'
23
+
24
+ type TODO = any
25
+
26
+ export type CustomizeStartHandler<TRouter extends AnyRouter> = (
27
+ cb: HandlerCallback<TRouter>,
28
+ ) => RequestHandler
29
+
30
+ export function getStartResponseHeaders(opts: { router: AnyRouter }) {
31
+ let headers = mergeHeaders(
32
+ getResponseHeaders(),
33
+ {
34
+ 'Content-Type': 'text/html; charset=UTF-8',
35
+ },
36
+ ...opts.router.state.matches.map((match) => {
37
+ return match.headers
38
+ }),
39
+ )
40
+ // Handle Redirects
41
+ const { redirect } = opts.router.state
42
+
43
+ if (redirect) {
44
+ headers = mergeHeaders(headers, redirect.headers)
45
+ }
46
+ return headers
47
+ }
48
+
49
+ export function createStartHandler<TRouter extends AnyRouter>({
50
+ createRouter,
51
+ }: {
52
+ createRouter: () => TRouter
53
+ }): CustomizeStartHandler<TRouter> {
54
+ return (cb) => {
55
+ return requestHandler(async ({ request }) => {
56
+ const url = new URL(request.url)
57
+ const href = url.href.replace(url.origin, '')
58
+
59
+ // Create a history for the client-side router
60
+ const history = createMemoryHistory({
61
+ initialEntries: [href],
62
+ })
63
+
64
+ // Create the client-side router
65
+ const router = createRouter()
66
+
67
+ // Attach the server-side SSR utils to the client-side router
68
+ attachRouterServerSsrUtils(router, getStartManifest())
69
+
70
+ // Update the client-side router with the history and context
71
+ router.update({
72
+ history,
73
+ })
74
+
75
+ const response = await (async () => {
76
+ try {
77
+ if (!process.env.TSS_SERVER_FN_BASE) {
78
+ throw new Error(
79
+ 'tanstack/start-server-core: TSS_SERVER_FN_BASE must be defined in your environment for createStartHandler()',
80
+ )
81
+ }
82
+
83
+ // First, let's attempt to handle server functions
84
+ if (
85
+ href.startsWith(path.join('/', process.env.TSS_SERVER_FN_BASE, '/'))
86
+ ) {
87
+ return await handleServerAction({ request })
88
+ }
89
+
90
+ // Then move on to attempting to load server routes
91
+ const serverRouteTreeModule = await (async () => {
92
+ try {
93
+ // @ts-expect-error
94
+ return (await import('tanstack:server-routes')) as {
95
+ routeTree: AnyServerRoute
96
+ }
97
+ } catch (e) {
98
+ console.log(e)
99
+ return undefined
100
+ }
101
+ })()
102
+
103
+ // If we have a server route tree, then we try matching to see if we have a
104
+ // server route that matches the request.
105
+ if (serverRouteTreeModule) {
106
+ const [matchedRoutes, response] = await handleServerRoutes({
107
+ routeTree: serverRouteTreeModule.routeTree,
108
+ request,
109
+ })
110
+
111
+ if (response) return response
112
+ }
113
+
114
+ const requestAcceptHeader = request.headers.get('Accept') || '*/*'
115
+ const splitRequestAcceptHeader = requestAcceptHeader.split(',')
116
+
117
+ const supportedMimeTypes = ['*/*', 'text/html']
118
+ const isRouterAcceptSupported = supportedMimeTypes.some((mimeType) =>
119
+ splitRequestAcceptHeader.some((acceptedMimeType) =>
120
+ acceptedMimeType.trim().startsWith(mimeType),
121
+ ),
122
+ )
123
+
124
+ if (!isRouterAcceptSupported) {
125
+ return json(
126
+ {
127
+ error: 'Only HTML requests are supported here',
128
+ },
129
+ {
130
+ status: 500,
131
+ },
132
+ )
133
+ }
134
+
135
+ // If no Server Routes were found, so fallback to normal SSR matching using
136
+ // the router
137
+
138
+ await router.load()
139
+
140
+ // If there was a redirect, skip rendering the page at all
141
+ if (router.state.redirect) return router.state.redirect
142
+
143
+ dehydrateRouter(router)
144
+
145
+ const responseHeaders = getStartResponseHeaders({ router })
146
+ const response = await cb({
147
+ request,
148
+ router,
149
+ responseHeaders,
150
+ })
151
+
152
+ return response
153
+ } catch (err) {
154
+ if (err instanceof Response) {
155
+ return err
156
+ }
157
+
158
+ throw err
159
+ }
160
+ })()
161
+
162
+ if (isRedirect(response)) {
163
+ if (
164
+ response.options.to &&
165
+ typeof response.options.to === 'string' &&
166
+ !response.options.to.startsWith('/')
167
+ ) {
168
+ throw new Error(
169
+ `Server side redirects must use absolute paths via the 'href' or 'to' options. Received: ${JSON.stringify(response.options)}`,
170
+ )
171
+ }
172
+
173
+ if (
174
+ ['params', 'search', 'hash'].some(
175
+ (d) => typeof (response.options as any)[d] === 'function',
176
+ )
177
+ ) {
178
+ throw new Error(
179
+ `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(
180
+ response.options,
181
+ )
182
+ .filter((d) => typeof (response.options as any)[d] === 'function')
183
+ .map((d) => `"${d}"`)
184
+ .join(', ')}`,
185
+ )
186
+ }
187
+
188
+ const redirect = router.resolveRedirect(response)
189
+
190
+ if (request.headers.get('x-tsr-redirect') === 'manual') {
191
+ return json(
192
+ {
193
+ ...response.options,
194
+ isSerializedRedirect: true,
195
+ },
196
+ {
197
+ headers: redirect.headers,
198
+ },
199
+ )
200
+ }
201
+
202
+ return redirect
203
+ }
204
+
205
+ response.headers.append(
206
+ 'Access-Control-Expose-Headers',
207
+ tsrRedirectHeaderKey,
208
+ )
209
+
210
+ return response
211
+ })
212
+ }
213
+ }
214
+
215
+ async function handleServerRoutes({
216
+ routeTree,
217
+ request,
218
+ }: {
219
+ routeTree: AnyServerRouteWithTypes
220
+ request: Request
221
+ }) {
222
+ const { flatRoutes, routesById, routesByPath } = processRouteTree({
223
+ routeTree,
224
+ initRoute: (route, i) => {
225
+ route.init({
226
+ originalIndex: i,
227
+ })
228
+ },
229
+ })
230
+
231
+ const url = new URL(request.url)
232
+ const pathname = url.pathname
233
+
234
+ const history = createMemoryHistory({
235
+ initialEntries: [pathname],
236
+ })
237
+
238
+ const { matchedRoutes, foundRoute, routeParams } =
239
+ getMatchedRoutes<AnyServerRouteWithTypes>({
240
+ pathname: history.location.pathname,
241
+ basepath: '/',
242
+ caseSensitive: true,
243
+ routesByPath,
244
+ routesById,
245
+ flatRoutes,
246
+ })
247
+
248
+ let response: Response | undefined
249
+
250
+ if (foundRoute && foundRoute.id !== rootRouteId) {
251
+ // We've found a server route that matches the request, so we can call it.
252
+ // TODO: Get the input type-signature correct
253
+ // TODO: Perform the middlewares?
254
+ // TODO: Error handling? What happens when its `throw redirect()` vs `throw new Error()`?
255
+
256
+ const method = Object.keys(foundRoute.options.methods).find(
257
+ (method) => method.toLowerCase() === request.method.toLowerCase(),
258
+ )
259
+
260
+ if (method) {
261
+ const handler = foundRoute.options.methods[method]
262
+
263
+ if (handler) {
264
+ const middlewares = flattenMiddlewares(
265
+ matchedRoutes.flatMap((r) => r.options.middleware).filter(Boolean),
266
+ ).map((d) => d.options.server)
267
+
268
+ middlewares.push(handlerToMiddleware(handler) as TODO)
269
+
270
+ // TODO: This is starting to feel too much like a server function
271
+ // Do generalize the existing middleware execution? Or do we need to
272
+ // build a new middleware execution system for server routes?
273
+ const ctx = await executeMiddleware(middlewares, {
274
+ request,
275
+ context: {},
276
+ params: routeParams,
277
+ pathname: history.location.pathname,
278
+ })
279
+
280
+ response = ctx.response
281
+ }
282
+ }
283
+ }
284
+
285
+ // We return the matched routes too so if
286
+ // the app router happens to match the same path,
287
+ // it can use any request middleware from server routes
288
+ return [matchedRoutes, response] as const
289
+ }
290
+
291
+ function handlerToMiddleware(
292
+ handler: AnyServerRouteWithTypes['options']['methods'][string],
293
+ ) {
294
+ return async ({ next, ...rest }: TODO) => ({
295
+ response: await handler(rest),
296
+ })
297
+ }
298
+
299
+ function executeMiddleware(middlewares: TODO, ctx: TODO) {
300
+ let index = -1
301
+
302
+ const next = async (ctx: TODO) => {
303
+ index++
304
+ const middleware = middlewares[index]
305
+ if (!middleware) return ctx
306
+
307
+ const result = await middleware({
308
+ ...ctx,
309
+ // Allow the middleware to call the next middleware in the chain
310
+ next: async (nextCtx: TODO) => {
311
+ // Allow the caller to extend the context for the next middleware
312
+ const nextResult = await next({ ...ctx, ...nextCtx })
313
+
314
+ // Merge the result into the context\
315
+ return Object.assign(ctx, handleCtxResult(nextResult))
316
+ },
317
+ // Allow the middleware result to extend the return context
318
+ }).catch((err: TODO) => {
319
+ if (isSpecialResponse(err)) {
320
+ return {
321
+ response: err,
322
+ }
323
+ }
324
+
325
+ throw err
326
+ })
327
+
328
+ // Merge the middleware result into the context, just in case it
329
+ // returns a partial context
330
+ return Object.assign(ctx, handleCtxResult(result))
331
+ }
332
+
333
+ return handleCtxResult(next(ctx))
334
+ }
335
+
336
+ function handleCtxResult(result: TODO) {
337
+ if (isSpecialResponse(result)) {
338
+ return {
339
+ response: result,
340
+ }
341
+ }
342
+
343
+ return result
344
+ }
345
+
346
+ function isSpecialResponse(err: TODO) {
347
+ return err instanceof Response || isRedirect(err)
348
+ }