@tanstack/start-server-core 1.132.0-alpha.8 → 1.132.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 (38) hide show
  1. package/dist/esm/createStartHandler.d.ts +3 -6
  2. package/dist/esm/createStartHandler.js +261 -220
  3. package/dist/esm/createStartHandler.js.map +1 -1
  4. package/dist/esm/index.d.ts +1 -3
  5. package/dist/esm/index.js +0 -4
  6. package/dist/esm/index.js.map +1 -1
  7. package/dist/esm/loadVirtualModule.js +0 -2
  8. package/dist/esm/loadVirtualModule.js.map +1 -1
  9. package/dist/esm/request-handler.d.ts +19 -0
  10. package/dist/esm/request-response.d.ts +2 -2
  11. package/dist/esm/request-response.js +5 -2
  12. package/dist/esm/request-response.js.map +1 -1
  13. package/dist/esm/serializer/ServerFunctionSerializationAdapter.js +1 -1
  14. package/dist/esm/serializer/ServerFunctionSerializationAdapter.js.map +1 -1
  15. package/dist/esm/server-functions-handler.d.ts +2 -1
  16. package/dist/esm/server-functions-handler.js +16 -13
  17. package/dist/esm/server-functions-handler.js.map +1 -1
  18. package/dist/esm/virtual-modules.d.ts +0 -2
  19. package/dist/esm/virtual-modules.js +0 -1
  20. package/dist/esm/virtual-modules.js.map +1 -1
  21. package/package.json +7 -7
  22. package/src/createStartHandler.ts +353 -326
  23. package/src/index.tsx +2 -42
  24. package/src/loadVirtualModule.ts +0 -2
  25. package/src/request-handler.ts +31 -0
  26. package/src/request-response.ts +8 -5
  27. package/src/serializer/ServerFunctionSerializationAdapter.ts +1 -1
  28. package/src/server-functions-handler.ts +21 -16
  29. package/src/tanstack-start.d.ts +0 -2
  30. package/src/virtual-modules.ts +0 -2
  31. package/dist/esm/serializer/getSerovalPlugins.d.ts +0 -3
  32. package/dist/esm/serializer/getSerovalPlugins.js +0 -13
  33. package/dist/esm/serializer/getSerovalPlugins.js.map +0 -1
  34. package/dist/esm/serverRoute.d.ts +0 -124
  35. package/dist/esm/serverRoute.js +0 -103
  36. package/dist/esm/serverRoute.js.map +0 -1
  37. package/src/serializer/getSerovalPlugins.ts +0 -10
  38. package/src/serverRoute.ts +0 -736
@@ -1,7 +1,4 @@
1
- import { RequestHandler } from './request-response.js';
2
- import { AnyRouter, Awaitable } from '@tanstack/router-core';
1
+ import { RequestHandler } from './request-handler.js';
2
+ import { AnyRouter, Register } from '@tanstack/router-core';
3
3
  import { HandlerCallback } from '@tanstack/router-core/ssr/server';
4
- export type CustomizeStartHandler<TRouter extends AnyRouter> = (cb: HandlerCallback<TRouter>) => RequestHandler;
5
- export declare function createStartHandler<TRouter extends AnyRouter>({ createRouter, }: {
6
- createRouter: () => Awaitable<TRouter>;
7
- }): CustomizeStartHandler<TRouter>;
4
+ export declare function createStartHandler<TRegister = Register>(cb: HandlerCallback<AnyRouter>): RequestHandler<TRegister>;
@@ -1,13 +1,11 @@
1
1
  import { createMemoryHistory } from "@tanstack/history";
2
- import { json, mergeHeaders, flattenMiddlewares } from "@tanstack/start-client-core";
3
- import { joinPaths, trimPath, processRouteTree, isRedirect, isResolvedRedirect, getMatchedRoutes } from "@tanstack/router-core";
2
+ import { flattenMiddlewares, json, mergeHeaders } from "@tanstack/start-client-core";
3
+ import { joinPaths, trimPath, isRedirect, isResolvedRedirect, executeRewriteInput } from "@tanstack/router-core";
4
4
  import { attachRouterServerSsrUtils } from "@tanstack/router-core/ssr/server";
5
5
  import { runWithStartContext } from "@tanstack/start-storage-context";
6
6
  import { requestHandler, getResponseHeaders } from "./request-response.js";
7
7
  import { getStartManifest } from "./router-manifest.js";
8
8
  import { handleServerAction } from "./server-functions-handler.js";
9
- import { VIRTUAL_MODULES } from "./virtual-modules.js";
10
- import { loadVirtualModule } from "./loadVirtualModule.js";
11
9
  import { HEADERS } from "./constants.js";
12
10
  import { ServerFunctionSerializationAdapter } from "./serializer/ServerFunctionSerializationAdapter.js";
13
11
  function getStartResponseHeaders(opts) {
@@ -22,172 +20,180 @@ function getStartResponseHeaders(opts) {
22
20
  );
23
21
  return headers;
24
22
  }
25
- function createStartHandler({
26
- createRouter
27
- }) {
28
- let routeTreeModule = null;
23
+ function createStartHandler(cb) {
24
+ if (!process.env.TSS_SERVER_FN_BASE) {
25
+ throw new Error(
26
+ "tanstack/start-server-core: TSS_SERVER_FN_BASE must be defined in your environment for createStartHandler()"
27
+ );
28
+ }
29
+ const APP_BASE = process.env.TSS_APP_BASE || "/";
30
+ const serverFnBase = joinPaths([
31
+ APP_BASE,
32
+ trimPath(process.env.TSS_SERVER_FN_BASE),
33
+ "/"
34
+ ]);
29
35
  let startRoutesManifest = null;
30
- let processedServerRouteTree = void 0;
31
- return (cb) => {
32
- const originalFetch = globalThis.fetch;
33
- const startRequestResolver = async (request) => {
34
- globalThis.fetch = async function(input, init) {
35
- function resolve(url2, requestOptions) {
36
- const fetchRequest = new Request(url2, requestOptions);
37
- return startRequestResolver(fetchRequest);
38
- }
39
- function getOrigin() {
40
- return request.headers.get("Origin") || request.headers.get("Referer") || "http://localhost";
41
- }
42
- if (typeof input === "string" && input.startsWith("/")) {
43
- const url2 = new URL(input, getOrigin());
44
- return resolve(url2, init);
45
- } else if (typeof input === "object" && "url" in input && typeof input.url === "string" && input.url.startsWith("/")) {
46
- const url2 = new URL(input.url, getOrigin());
47
- return resolve(url2, init);
48
- }
49
- return originalFetch(input, init);
50
- };
51
- const url = new URL(request.url);
52
- const href = url.href.replace(url.origin, "");
53
- const APP_BASE = process.env.TSS_APP_BASE || "/";
54
- const router = await createRouter();
55
- const history = createMemoryHistory({
56
- initialEntries: [href]
57
- });
36
+ let startEntry = null;
37
+ let routerEntry = null;
38
+ const getEntries = async () => {
39
+ if (routerEntry === null) {
40
+ routerEntry = await import("#tanstack-router-entry");
41
+ }
42
+ if (startEntry === null) {
43
+ startEntry = await import("#tanstack-start-entry");
44
+ }
45
+ return {
46
+ startEntry,
47
+ routerEntry
48
+ };
49
+ };
50
+ const originalFetch = globalThis.fetch;
51
+ const startRequestResolver = async (request, requestOpts) => {
52
+ function getOrigin() {
53
+ const originHeader = request.headers.get("Origin");
54
+ if (originHeader) {
55
+ return originHeader;
56
+ }
57
+ try {
58
+ return new URL(request.url).origin;
59
+ } catch {
60
+ }
61
+ return "http://localhost";
62
+ }
63
+ globalThis.fetch = async function(input, init) {
64
+ function resolve(url2, requestOptions) {
65
+ const fetchRequest = new Request(url2, requestOptions);
66
+ return startRequestResolver(fetchRequest, requestOpts);
67
+ }
68
+ if (typeof input === "string" && input.startsWith("/")) {
69
+ const url2 = new URL(input, getOrigin());
70
+ return resolve(url2, init);
71
+ } else if (typeof input === "object" && "url" in input && typeof input.url === "string" && input.url.startsWith("/")) {
72
+ const url2 = new URL(input.url, getOrigin());
73
+ return resolve(url2, init);
74
+ }
75
+ return originalFetch(input, init);
76
+ };
77
+ const url = new URL(request.url);
78
+ const href = url.href.replace(url.origin, "");
79
+ let router = null;
80
+ const getRouter = async () => {
81
+ if (router) return router;
82
+ router = await (await getEntries()).routerEntry.getRouter();
58
83
  const isPrerendering = process.env.TSS_PRERENDERING === "true";
59
84
  let isShell = process.env.TSS_SHELL === "true";
60
85
  if (isPrerendering && !isShell) {
61
86
  isShell = request.headers.get(HEADERS.TSS_SHELL) === "true";
62
87
  }
63
- const serializationAdapters = (router.options.serializationAdapters ?? []).concat(ServerFunctionSerializationAdapter);
88
+ const history = createMemoryHistory({
89
+ initialEntries: [href]
90
+ });
91
+ const origin = router.options.origin ?? getOrigin();
64
92
  router.update({
65
93
  history,
66
94
  isShell,
67
95
  isPrerendering,
68
- serializationAdapters
96
+ origin,
97
+ ...{
98
+ defaultSsr: startOptions.defaultSsr,
99
+ serializationAdapters: startOptions.serializationAdapters
100
+ }
69
101
  });
70
- const response = await runWithStartContext({ router }, async () => {
71
- try {
72
- if (!process.env.TSS_SERVER_FN_BASE) {
73
- throw new Error(
74
- "tanstack/start-server-core: TSS_SERVER_FN_BASE must be defined in your environment for createStartHandler()"
75
- );
76
- }
77
- const serverFnBase = joinPaths([
78
- APP_BASE,
79
- trimPath(process.env.TSS_SERVER_FN_BASE),
80
- "/"
81
- ]);
82
- if (href.startsWith(serverFnBase)) {
83
- return await handleServerAction({ request });
84
- }
85
- if (routeTreeModule === null) {
102
+ return router;
103
+ };
104
+ const startOptions = await (await getEntries()).startEntry.startInstance?.getOptions() || {};
105
+ startOptions.serializationAdapters = startOptions.serializationAdapters || [];
106
+ startOptions.serializationAdapters.push(ServerFunctionSerializationAdapter);
107
+ const requestHandlerMiddleware = handlerToMiddleware(
108
+ async ({ context }) => {
109
+ const response2 = await runWithStartContext(
110
+ {
111
+ getRouter,
112
+ startOptions,
113
+ contextAfterGlobalMiddlewares: context
114
+ },
115
+ async () => {
86
116
  try {
87
- routeTreeModule = await loadVirtualModule(
88
- VIRTUAL_MODULES.routeTree
89
- );
90
- if (routeTreeModule.serverRouteTree) {
91
- processedServerRouteTree = processRouteTree({
92
- routeTree: routeTreeModule.serverRouteTree,
93
- initRoute: (route, i) => {
94
- route.init({
95
- originalIndex: i
96
- });
97
- }
117
+ if (href.startsWith(serverFnBase)) {
118
+ return await handleServerAction({
119
+ request,
120
+ context: requestOpts?.context
98
121
  });
99
122
  }
100
- } catch (e) {
101
- console.log(e);
102
- }
103
- }
104
- const executeRouter = async () => {
105
- const requestAcceptHeader = request.headers.get("Accept") || "*/*";
106
- const splitRequestAcceptHeader = requestAcceptHeader.split(",");
107
- const supportedMimeTypes = ["*/*", "text/html"];
108
- const isRouterAcceptSupported = supportedMimeTypes.some(
109
- (mimeType) => splitRequestAcceptHeader.some(
110
- (acceptedMimeType) => acceptedMimeType.trim().startsWith(mimeType)
111
- )
112
- );
113
- if (!isRouterAcceptSupported) {
114
- return json(
115
- {
116
- error: "Only HTML requests are supported here"
117
- },
118
- {
119
- status: 500
123
+ const executeRouter = async ({
124
+ serverContext
125
+ }) => {
126
+ const requestAcceptHeader = request.headers.get("Accept") || "*/*";
127
+ const splitRequestAcceptHeader = requestAcceptHeader.split(",");
128
+ const supportedMimeTypes = ["*/*", "text/html"];
129
+ const isRouterAcceptSupported = supportedMimeTypes.some(
130
+ (mimeType) => splitRequestAcceptHeader.some(
131
+ (acceptedMimeType) => acceptedMimeType.trim().startsWith(mimeType)
132
+ )
133
+ );
134
+ if (!isRouterAcceptSupported) {
135
+ return json(
136
+ {
137
+ error: "Only HTML requests are supported here"
138
+ },
139
+ {
140
+ status: 500
141
+ }
142
+ );
120
143
  }
121
- );
122
- }
123
- if (startRoutesManifest === null) {
124
- startRoutesManifest = await getStartManifest({
125
- basePath: APP_BASE
144
+ if (startRoutesManifest === null) {
145
+ startRoutesManifest = await getStartManifest({
146
+ basePath: APP_BASE
147
+ });
148
+ }
149
+ const router2 = await getRouter();
150
+ attachRouterServerSsrUtils({
151
+ router: router2,
152
+ manifest: startRoutesManifest
153
+ });
154
+ router2.update({ additionalContext: { serverContext } });
155
+ await router2.load();
156
+ if (router2.state.redirect) {
157
+ return router2.state.redirect;
158
+ }
159
+ await router2.serverSsr.dehydrate();
160
+ const responseHeaders = getStartResponseHeaders({ router: router2 });
161
+ const response4 = await cb({
162
+ request,
163
+ router: router2,
164
+ responseHeaders
165
+ });
166
+ return response4;
167
+ };
168
+ const response3 = await handleServerRoutes({
169
+ getRouter,
170
+ request,
171
+ executeRouter
126
172
  });
127
- }
128
- attachRouterServerSsrUtils(router, startRoutesManifest);
129
- await router.load();
130
- if (router.state.redirect) {
131
- return router.state.redirect;
132
- }
133
- await router.serverSsr.dehydrate();
134
- const responseHeaders = getStartResponseHeaders({ router });
135
- const response2 = await cb({
136
- request,
137
- router,
138
- responseHeaders
139
- });
140
- return response2;
141
- };
142
- if (processedServerRouteTree) {
143
- const [_matchedRoutes, response2] = await handleServerRoutes({
144
- processedServerRouteTree,
145
- router,
146
- request,
147
- basePath: APP_BASE,
148
- executeRouter
149
- });
150
- if (response2) return response2;
151
- }
152
- const routerResponse = await executeRouter();
153
- return routerResponse;
154
- } catch (err) {
155
- if (err instanceof Response) {
156
- return err;
157
- }
158
- throw err;
159
- }
160
- });
161
- if (isRedirect(response)) {
162
- if (isResolvedRedirect(response)) {
163
- if (request.headers.get("x-tsr-redirect") === "manual") {
164
- return json(
165
- {
166
- ...response.options,
167
- isSerializedRedirect: true
168
- },
169
- {
170
- headers: response.headers
173
+ return response3;
174
+ } catch (err) {
175
+ if (err instanceof Response) {
176
+ return err;
171
177
  }
172
- );
178
+ throw err;
179
+ }
173
180
  }
174
- return response;
175
- }
176
- if (response.options.to && typeof response.options.to === "string" && !response.options.to.startsWith("/")) {
177
- throw new Error(
178
- `Server side redirects must use absolute paths via the 'href' or 'to' options. The redirect() method's "to" property accepts an internal path only. Use the "href" property to provide an external URL. Received: ${JSON.stringify(response.options)}`
179
- );
180
- }
181
- if (["params", "search", "hash"].some(
182
- (d) => typeof response.options[d] === "function"
183
- )) {
184
- throw new Error(
185
- `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(
186
- response.options
187
- ).filter((d) => typeof response.options[d] === "function").map((d) => `"${d}"`).join(", ")}`
188
- );
189
- }
190
- const redirect = router.resolveRedirect(response);
181
+ );
182
+ return response2;
183
+ }
184
+ );
185
+ const flattenedMiddlewares = startOptions.requestMiddleware ? flattenMiddlewares(startOptions.requestMiddleware) : [];
186
+ const middlewares = flattenedMiddlewares.map((d) => d.options.server);
187
+ const ctx = await executeMiddleware(
188
+ [...middlewares, requestHandlerMiddleware],
189
+ {
190
+ request,
191
+ context: requestOpts?.context || {}
192
+ }
193
+ );
194
+ const response = ctx.response;
195
+ if (isRedirect(response)) {
196
+ if (isResolvedRedirect(response)) {
191
197
  if (request.headers.get("x-tsr-redirect") === "manual") {
192
198
  return json(
193
199
  {
@@ -199,96 +205,131 @@ function createStartHandler({
199
205
  }
200
206
  );
201
207
  }
202
- return redirect;
208
+ return response;
203
209
  }
204
- return response;
205
- };
206
- return requestHandler(startRequestResolver);
210
+ if (response.options.to && typeof response.options.to === "string" && !response.options.to.startsWith("/")) {
211
+ throw new Error(
212
+ `Server side redirects must use absolute paths via the 'href' or 'to' options. The redirect() method's "to" property accepts an internal path only. Use the "href" property to provide an external URL. Received: ${JSON.stringify(response.options)}`
213
+ );
214
+ }
215
+ if (["params", "search", "hash"].some(
216
+ (d) => typeof response.options[d] === "function"
217
+ )) {
218
+ throw new Error(
219
+ `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(
220
+ response.options
221
+ ).filter((d) => typeof response.options[d] === "function").map((d) => `"${d}"`).join(", ")}`
222
+ );
223
+ }
224
+ const router2 = await getRouter();
225
+ const redirect = router2.resolveRedirect(response);
226
+ if (request.headers.get("x-tsr-redirect") === "manual") {
227
+ return json(
228
+ {
229
+ ...response.options,
230
+ isSerializedRedirect: true
231
+ },
232
+ {
233
+ headers: response.headers
234
+ }
235
+ );
236
+ }
237
+ return redirect;
238
+ }
239
+ return response;
207
240
  };
241
+ return requestHandler(startRequestResolver);
208
242
  }
209
- async function handleServerRoutes(opts) {
210
- const url = new URL(opts.request.url);
243
+ async function handleServerRoutes({
244
+ getRouter,
245
+ request,
246
+ executeRouter
247
+ }) {
248
+ const router = await getRouter();
249
+ let url = new URL(request.url);
250
+ url = executeRewriteInput(router.rewrite, url);
211
251
  const pathname = url.pathname;
212
- const serverTreeResult = getMatchedRoutes({
252
+ const { matchedRoutes, foundRoute, routeParams } = router.getMatchedRoutes(
213
253
  pathname,
214
- basepath: opts.basePath,
215
- caseSensitive: true,
216
- routesByPath: opts.processedServerRouteTree.routesByPath,
217
- routesById: opts.processedServerRouteTree.routesById,
218
- flatRoutes: opts.processedServerRouteTree.flatRoutes
219
- });
220
- const routeTreeResult = opts.router.getMatchedRoutes(pathname, void 0);
221
- let response;
222
- let matchedRoutes = [];
223
- matchedRoutes = serverTreeResult.matchedRoutes;
224
- if (routeTreeResult.foundRoute) {
225
- if (serverTreeResult.matchedRoutes.length < routeTreeResult.matchedRoutes.length) {
226
- const closestCommon = [...routeTreeResult.matchedRoutes].reverse().find((r) => {
227
- return opts.processedServerRouteTree.routesById[r.id] !== void 0;
228
- });
229
- if (closestCommon) {
230
- let routeId = closestCommon.id;
231
- matchedRoutes = [];
232
- do {
233
- const route = opts.processedServerRouteTree.routesById[routeId];
234
- if (!route) {
235
- break;
236
- }
237
- matchedRoutes.push(route);
238
- routeId = route.parentRoute?.id;
239
- } while (routeId);
240
- matchedRoutes.reverse();
241
- }
242
- }
243
- }
244
- if (matchedRoutes.length) {
245
- const middlewares = flattenMiddlewares(
246
- matchedRoutes.flatMap((r) => r.options.middleware).filter(Boolean)
247
- ).map((d) => d.options.server);
248
- if (serverTreeResult.foundRoute?.options.methods) {
249
- const method = Object.keys(
250
- serverTreeResult.foundRoute.options.methods
251
- ).find(
252
- (method2) => method2.toLowerCase() === opts.request.method.toLowerCase()
254
+ void 0
255
+ );
256
+ const middlewares = flattenMiddlewares(
257
+ matchedRoutes.flatMap((r) => r.options.server?.middleware).filter(Boolean)
258
+ ).map((d) => d.options.server);
259
+ const server = foundRoute?.options.server;
260
+ if (server) {
261
+ if (server.handlers) {
262
+ const handlers = typeof server.handlers === "function" ? server.handlers({
263
+ createHandlers: (d) => d
264
+ }) : server.handlers;
265
+ const requestMethod = request.method.toLowerCase();
266
+ let method = Object.keys(handlers).find(
267
+ (method2) => method2.toLowerCase() === requestMethod
253
268
  );
269
+ if (!method) {
270
+ method = Object.keys(handlers).find(
271
+ (method2) => method2.toLowerCase() === "all"
272
+ ) ? "all" : void 0;
273
+ }
254
274
  if (method) {
255
- const handler = serverTreeResult.foundRoute.options.methods[method];
275
+ const handler = handlers[method];
256
276
  if (handler) {
277
+ const mayDefer = !!foundRoute.options.component;
257
278
  if (typeof handler === "function") {
258
- middlewares.push(handlerToMiddleware(handler));
279
+ middlewares.push(handlerToMiddleware(handler, mayDefer));
259
280
  } else {
260
- if (handler._options.middlewares && handler._options.middlewares.length) {
281
+ const { middleware } = handler;
282
+ if (middleware && middleware.length) {
261
283
  middlewares.push(
262
- ...flattenMiddlewares(handler._options.middlewares).map(
263
- (d) => d.options.server
264
- )
284
+ ...flattenMiddlewares(middleware).map((d) => d.options.server)
265
285
  );
266
286
  }
267
- if (handler._options.handler) {
268
- middlewares.push(handlerToMiddleware(handler._options.handler));
287
+ if (handler.handler) {
288
+ middlewares.push(handlerToMiddleware(handler.handler, mayDefer));
269
289
  }
270
290
  }
271
291
  }
272
292
  }
273
293
  }
274
- middlewares.push(handlerToMiddleware(opts.executeRouter));
275
- const ctx = await executeMiddleware(middlewares, {
276
- request: opts.request,
277
- context: {},
278
- params: serverTreeResult.routeParams,
279
- pathname
280
- });
281
- response = ctx.response;
282
294
  }
283
- return [matchedRoutes, response];
295
+ middlewares.push(
296
+ handlerToMiddleware((ctx2) => executeRouter({ serverContext: ctx2.context }))
297
+ );
298
+ const ctx = await executeMiddleware(middlewares, {
299
+ request,
300
+ context: {},
301
+ params: routeParams,
302
+ pathname
303
+ });
304
+ const response = ctx.response;
305
+ return response;
284
306
  }
285
- function handlerToMiddleware(handler) {
307
+ function throwRouteHandlerError() {
308
+ if (process.env.NODE_ENV === "development") {
309
+ throw new Error(
310
+ `It looks like you forgot to return a response from your server route handler. If you want to defer to the app router, make sure to have a component set in this route.`
311
+ );
312
+ }
313
+ throw new Error("Internal Server Error");
314
+ }
315
+ function throwIfMayNotDefer() {
316
+ if (process.env.NODE_ENV === "development") {
317
+ throw new Error(
318
+ `You cannot defer to the app router if there is no component defined on this route.`
319
+ );
320
+ }
321
+ throw new Error("Internal Server Error");
322
+ }
323
+ function handlerToMiddleware(handler, mayDefer = false) {
324
+ if (mayDefer) {
325
+ return handler;
326
+ }
286
327
  return async ({ next: _next, ...rest }) => {
287
- const response = await handler(rest);
288
- if (response) {
289
- return { response };
328
+ const response = await handler({ ...rest, next: throwIfMayNotDefer });
329
+ if (!response) {
330
+ throwRouteHandlerError();
290
331
  }
291
- return _next(rest);
332
+ return response;
292
333
  };
293
334
  }
294
335
  function executeMiddleware(middlewares, ctx) {