@tanstack/start-server-core 1.143.9 → 1.144.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.
@@ -1,5 +1,5 @@
1
1
  import { createMemoryHistory } from "@tanstack/history";
2
- import { flattenMiddlewares, mergeHeaders } from "@tanstack/start-client-core";
2
+ import { flattenMiddlewares, createNullProtoObject, mergeHeaders, safeObjectMerge } from "@tanstack/start-client-core";
3
3
  import { isRedirect, isResolvedRedirect, executeRewriteInput } from "@tanstack/router-core";
4
4
  import { getOrigin, attachRouterServerSsrUtils } from "@tanstack/router-core/ssr/server";
5
5
  import { runWithStartContext } from "@tanstack/start-storage-context";
@@ -19,31 +19,110 @@ function getStartResponseHeaders(opts) {
19
19
  );
20
20
  return headers;
21
21
  }
22
- function createStartHandler(cb) {
23
- const ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || "/";
24
- let startRoutesManifest = null;
25
- let startEntry = null;
26
- let routerEntry = null;
27
- const getEntries = async () => {
28
- if (routerEntry === null) {
29
- routerEntry = await import("#tanstack-router-entry");
22
+ let cachedStartEntry = null;
23
+ let cachedRouterEntry = null;
24
+ let cachedManifest = null;
25
+ async function getEntries() {
26
+ if (cachedRouterEntry === null) {
27
+ cachedRouterEntry = await import("#tanstack-router-entry");
28
+ }
29
+ if (cachedStartEntry === null) {
30
+ cachedStartEntry = await import("#tanstack-start-entry");
31
+ }
32
+ return {
33
+ startEntry: cachedStartEntry,
34
+ routerEntry: cachedRouterEntry
35
+ };
36
+ }
37
+ async function getManifest() {
38
+ if (cachedManifest === null) {
39
+ cachedManifest = await getStartManifest();
40
+ }
41
+ return cachedManifest;
42
+ }
43
+ const ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || "/";
44
+ const SERVER_FN_BASE = process.env.TSS_SERVER_FN_BASE;
45
+ const IS_PRERENDERING = process.env.TSS_PRERENDERING === "true";
46
+ const IS_SHELL_ENV = process.env.TSS_SHELL === "true";
47
+ const IS_DEV = process.env.NODE_ENV === "development";
48
+ const ERR_NO_RESPONSE = IS_DEV ? `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.` : "Internal Server Error";
49
+ const ERR_NO_DEFER = IS_DEV ? `You cannot defer to the app router if there is no component defined on this route.` : "Internal Server Error";
50
+ function throwRouteHandlerError() {
51
+ throw new Error(ERR_NO_RESPONSE);
52
+ }
53
+ function throwIfMayNotDefer() {
54
+ throw new Error(ERR_NO_DEFER);
55
+ }
56
+ function isSpecialResponse(value) {
57
+ return value instanceof Response || isRedirect(value);
58
+ }
59
+ function handleCtxResult(result) {
60
+ if (isSpecialResponse(result)) {
61
+ return { response: result };
62
+ }
63
+ return result;
64
+ }
65
+ function executeMiddleware(middlewares, ctx) {
66
+ let index = -1;
67
+ const next = async (nextCtx) => {
68
+ if (nextCtx) {
69
+ if (nextCtx.context) {
70
+ ctx.context = safeObjectMerge(ctx.context, nextCtx.context);
71
+ }
72
+ for (const key of Object.keys(nextCtx)) {
73
+ if (key !== "context") {
74
+ ctx[key] = nextCtx[key];
75
+ }
76
+ }
30
77
  }
31
- if (startEntry === null) {
32
- startEntry = await import("#tanstack-start-entry");
78
+ index++;
79
+ const middleware = middlewares[index];
80
+ if (!middleware) return ctx;
81
+ let result;
82
+ try {
83
+ result = await middleware({ ...ctx, next });
84
+ } catch (err) {
85
+ if (isSpecialResponse(err)) {
86
+ ctx.response = err;
87
+ return ctx;
88
+ }
89
+ throw err;
90
+ }
91
+ const normalized = handleCtxResult(result);
92
+ if (normalized) {
93
+ if (normalized.response !== void 0) {
94
+ ctx.response = normalized.response;
95
+ }
96
+ if (normalized.context) {
97
+ ctx.context = safeObjectMerge(ctx.context, normalized.context);
98
+ }
99
+ }
100
+ return ctx;
101
+ };
102
+ return next();
103
+ }
104
+ function handlerToMiddleware(handler, mayDefer = false) {
105
+ if (mayDefer) {
106
+ return handler;
107
+ }
108
+ return async (ctx) => {
109
+ const response = await handler({ ...ctx, next: throwIfMayNotDefer });
110
+ if (!response) {
111
+ throwRouteHandlerError();
33
112
  }
34
- return {
35
- startEntry,
36
- routerEntry
37
- };
113
+ return response;
38
114
  };
115
+ }
116
+ function createStartHandler(cb) {
39
117
  const startRequestResolver = async (request, requestOpts) => {
40
118
  let router = null;
41
119
  let cbWillCleanup = false;
42
120
  try {
43
- const origin = getOrigin(request);
44
121
  const url = new URL(request.url);
45
122
  const href = url.href.replace(url.origin, "");
46
- const startOptions = await (await getEntries()).startEntry.startInstance?.getOptions() || {};
123
+ const origin = getOrigin(request);
124
+ const entries = await getEntries();
125
+ const startOptions = await entries.startEntry.startInstance?.getOptions() || {};
47
126
  const serializationAdapters = [
48
127
  ...startOptions.serializationAdapters || [],
49
128
  ServerFunctionSerializationAdapter
@@ -52,12 +131,15 @@ function createStartHandler(cb) {
52
131
  ...startOptions,
53
132
  serializationAdapters
54
133
  };
134
+ const flattenedRequestMiddlewares = startOptions.requestMiddleware ? flattenMiddlewares(startOptions.requestMiddleware) : [];
135
+ const executedRequestMiddlewares = new Set(
136
+ flattenedRequestMiddlewares
137
+ );
55
138
  const getRouter = async () => {
56
139
  if (router) return router;
57
- router = await (await getEntries()).routerEntry.getRouter();
58
- const isPrerendering = process.env.TSS_PRERENDERING === "true";
59
- let isShell = process.env.TSS_SHELL === "true";
60
- if (isPrerendering && !isShell) {
140
+ router = await entries.routerEntry.getRouter();
141
+ let isShell = IS_SHELL_ENV;
142
+ if (IS_PRERENDERING && !isShell) {
61
143
  isShell = request.headers.get(HEADERS.TSS_SHELL) === "true";
62
144
  }
63
145
  const history = createMemoryHistory({
@@ -66,7 +148,7 @@ function createStartHandler(cb) {
66
148
  router.update({
67
149
  history,
68
150
  isShell,
69
- isPrerendering,
151
+ isPrerendering: IS_PRERENDERING,
70
152
  origin: router.options.origin ?? origin,
71
153
  ...{
72
154
  defaultSsr: requestStartOptions.defaultSsr,
@@ -79,140 +161,107 @@ function createStartHandler(cb) {
79
161
  });
80
162
  return router;
81
163
  };
82
- const requestHandlerMiddleware = handlerToMiddleware(
83
- async ({ context }) => {
84
- const response2 = await runWithStartContext(
164
+ if (SERVER_FN_BASE && url.pathname.startsWith(SERVER_FN_BASE)) {
165
+ const serverFnId = url.pathname.slice(SERVER_FN_BASE.length).split("/")[0];
166
+ if (!serverFnId) {
167
+ throw new Error("Invalid server action param for serverFnId");
168
+ }
169
+ const serverFnHandler = async ({ context }) => {
170
+ return runWithStartContext(
85
171
  {
86
172
  getRouter,
87
173
  startOptions: requestStartOptions,
88
174
  contextAfterGlobalMiddlewares: context,
89
- request
175
+ request,
176
+ executedRequestMiddlewares
90
177
  },
91
- async () => {
92
- try {
93
- if (href.startsWith(process.env.TSS_SERVER_FN_BASE)) {
94
- return await handleServerAction({
95
- request,
96
- context: requestOpts?.context
97
- });
98
- }
99
- const executeRouter = async ({
100
- serverContext
101
- }) => {
102
- const requestAcceptHeader = request.headers.get("Accept") || "*/*";
103
- const splitRequestAcceptHeader = requestAcceptHeader.split(",");
104
- const supportedMimeTypes = ["*/*", "text/html"];
105
- const isRouterAcceptSupported = supportedMimeTypes.some(
106
- (mimeType) => splitRequestAcceptHeader.some(
107
- (acceptedMimeType) => acceptedMimeType.trim().startsWith(mimeType)
108
- )
109
- );
110
- if (!isRouterAcceptSupported) {
111
- return Response.json(
112
- {
113
- error: "Only HTML requests are supported here"
114
- },
115
- {
116
- status: 500
117
- }
118
- );
119
- }
120
- if (startRoutesManifest === null) {
121
- startRoutesManifest = await getStartManifest();
122
- }
123
- const router2 = await getRouter();
124
- attachRouterServerSsrUtils({
125
- router: router2,
126
- manifest: startRoutesManifest
127
- });
128
- router2.update({ additionalContext: { serverContext } });
129
- await router2.load();
130
- if (router2.state.redirect) {
131
- return router2.state.redirect;
132
- }
133
- await router2.serverSsr.dehydrate();
134
- const responseHeaders = getStartResponseHeaders({ router: router2 });
135
- cbWillCleanup = true;
136
- const response4 = await cb({
137
- request,
138
- router: router2,
139
- responseHeaders
140
- });
141
- return response4;
142
- };
143
- const response3 = await handleServerRoutes({
144
- getRouter,
145
- request,
146
- executeRouter,
147
- context
148
- });
149
- return response3;
150
- } catch (err) {
151
- if (err instanceof Response) {
152
- return err;
153
- }
154
- throw err;
155
- }
156
- }
178
+ () => handleServerAction({
179
+ request,
180
+ context: requestOpts?.context,
181
+ serverFnId
182
+ })
157
183
  );
158
- return response2;
159
- }
160
- );
161
- const flattenedMiddlewares = startOptions.requestMiddleware ? flattenMiddlewares(startOptions.requestMiddleware) : [];
162
- const middlewares = flattenedMiddlewares.map((d) => d.options.server);
163
- const ctx = await executeMiddleware(
164
- [...middlewares, requestHandlerMiddleware],
165
- {
184
+ };
185
+ const middlewares2 = flattenedRequestMiddlewares.map(
186
+ (d) => d.options.server
187
+ );
188
+ const ctx2 = await executeMiddleware([...middlewares2, serverFnHandler], {
166
189
  request,
167
- context: requestOpts?.context || {}
168
- }
169
- );
170
- const response = ctx.response;
171
- if (isRedirect(response)) {
172
- if (isResolvedRedirect(response)) {
173
- if (request.headers.get("x-tsr-redirect") === "manual") {
174
- return Response.json(
175
- {
176
- ...response.options,
177
- isSerializedRedirect: true
178
- },
179
- {
180
- headers: response.headers
181
- }
182
- );
183
- }
184
- return response;
185
- }
186
- if (response.options.to && typeof response.options.to === "string" && !response.options.to.startsWith("/")) {
187
- throw new Error(
188
- `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)}`
190
+ context: createNullProtoObject(requestOpts?.context)
191
+ });
192
+ return handleRedirectResponse(ctx2.response, request, getRouter);
193
+ }
194
+ const executeRouter = async (serverContext) => {
195
+ const acceptHeader = request.headers.get("Accept") || "*/*";
196
+ const acceptParts = acceptHeader.split(",");
197
+ const supportedMimeTypes = ["*/*", "text/html"];
198
+ const isSupported = supportedMimeTypes.some(
199
+ (mimeType) => acceptParts.some((part) => part.trim().startsWith(mimeType))
200
+ );
201
+ if (!isSupported) {
202
+ return Response.json(
203
+ { error: "Only HTML requests are supported here" },
204
+ { status: 500 }
189
205
  );
190
206
  }
191
- if (["params", "search", "hash"].some(
192
- (d) => typeof response.options[d] === "function"
193
- )) {
194
- throw new Error(
195
- `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(
196
- response.options
197
- ).filter((d) => typeof response.options[d] === "function").map((d) => `"${d}"`).join(", ")}`
198
- );
207
+ const manifest = await getManifest();
208
+ const routerInstance = await getRouter();
209
+ attachRouterServerSsrUtils({
210
+ router: routerInstance,
211
+ manifest
212
+ });
213
+ routerInstance.update({ additionalContext: { serverContext } });
214
+ await routerInstance.load();
215
+ if (routerInstance.state.redirect) {
216
+ return routerInstance.state.redirect;
199
217
  }
200
- const router2 = await getRouter();
201
- const redirect = router2.resolveRedirect(response);
202
- if (request.headers.get("x-tsr-redirect") === "manual") {
203
- return Response.json(
204
- {
205
- ...response.options,
206
- isSerializedRedirect: true
207
- },
208
- {
209
- headers: response.headers
218
+ await routerInstance.serverSsr.dehydrate();
219
+ const responseHeaders = getStartResponseHeaders({
220
+ router: routerInstance
221
+ });
222
+ cbWillCleanup = true;
223
+ return cb({
224
+ request,
225
+ router: routerInstance,
226
+ responseHeaders
227
+ });
228
+ };
229
+ const requestHandlerMiddleware = async ({ context }) => {
230
+ return runWithStartContext(
231
+ {
232
+ getRouter,
233
+ startOptions: requestStartOptions,
234
+ contextAfterGlobalMiddlewares: context,
235
+ request,
236
+ executedRequestMiddlewares
237
+ },
238
+ async () => {
239
+ try {
240
+ return await handleServerRoutes({
241
+ getRouter,
242
+ request,
243
+ url,
244
+ executeRouter,
245
+ context,
246
+ executedRequestMiddlewares
247
+ });
248
+ } catch (err) {
249
+ if (err instanceof Response) {
250
+ return err;
251
+ }
252
+ throw err;
210
253
  }
211
- );
212
- }
213
- return redirect;
214
- }
215
- return response;
254
+ }
255
+ );
256
+ };
257
+ const middlewares = flattenedRequestMiddlewares.map(
258
+ (d) => d.options.server
259
+ );
260
+ const ctx = await executeMiddleware(
261
+ [...middlewares, requestHandlerMiddleware],
262
+ { request, context: createNullProtoObject(requestOpts?.context) }
263
+ );
264
+ return handleRedirectResponse(ctx.response, request, getRouter);
216
265
  } finally {
217
266
  if (router && !cbWillCleanup) {
218
267
  router.serverSsr?.cleanup();
@@ -222,137 +271,99 @@ function createStartHandler(cb) {
222
271
  };
223
272
  return requestHandler(startRequestResolver);
224
273
  }
274
+ async function handleRedirectResponse(response, request, getRouter) {
275
+ if (!isRedirect(response)) {
276
+ return response;
277
+ }
278
+ if (isResolvedRedirect(response)) {
279
+ if (request.headers.get("x-tsr-serverFn") === "true") {
280
+ return Response.json(
281
+ { ...response.options, isSerializedRedirect: true },
282
+ { headers: response.headers }
283
+ );
284
+ }
285
+ return response;
286
+ }
287
+ const opts = response.options;
288
+ if (opts.to && typeof opts.to === "string" && !opts.to.startsWith("/")) {
289
+ throw new Error(
290
+ `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(opts)}`
291
+ );
292
+ }
293
+ if (["params", "search", "hash"].some(
294
+ (d) => typeof opts[d] === "function"
295
+ )) {
296
+ throw new Error(
297
+ `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(
298
+ opts
299
+ ).filter((d) => typeof opts[d] === "function").map((d) => `"${d}"`).join(", ")}`
300
+ );
301
+ }
302
+ const router = await getRouter();
303
+ const redirect = router.resolveRedirect(response);
304
+ if (request.headers.get("x-tsr-serverFn") === "true") {
305
+ return Response.json(
306
+ { ...response.options, isSerializedRedirect: true },
307
+ { headers: response.headers }
308
+ );
309
+ }
310
+ return redirect;
311
+ }
225
312
  async function handleServerRoutes({
226
313
  getRouter,
227
314
  request,
315
+ url,
228
316
  executeRouter,
229
- context
317
+ context,
318
+ executedRequestMiddlewares
230
319
  }) {
231
320
  const router = await getRouter();
232
- let url = new URL(request.url);
233
- url = executeRewriteInput(router.rewrite, url);
234
- const pathname = url.pathname;
321
+ const rewrittenUrl = executeRewriteInput(router.rewrite, url);
322
+ const pathname = rewrittenUrl.pathname;
235
323
  const { matchedRoutes, foundRoute, routeParams } = router.getMatchedRoutes(pathname);
236
324
  const isExactMatch = foundRoute && routeParams["**"] === void 0;
237
- const middlewares = flattenMiddlewares(
238
- matchedRoutes.flatMap((r) => r.options.server?.middleware).filter(Boolean)
239
- ).map((d) => d.options.server);
325
+ const routeMiddlewares = [];
326
+ for (const route of matchedRoutes) {
327
+ const serverMiddleware = route.options.server?.middleware;
328
+ if (serverMiddleware) {
329
+ const flattened = flattenMiddlewares(serverMiddleware);
330
+ for (const m of flattened) {
331
+ if (!executedRequestMiddlewares.has(m)) {
332
+ routeMiddlewares.push(m.options.server);
333
+ }
334
+ }
335
+ }
336
+ }
240
337
  const server = foundRoute?.options.server;
241
- if (server && isExactMatch) {
242
- if (server.handlers) {
243
- const handlers = typeof server.handlers === "function" ? server.handlers({
244
- createHandlers: (d) => d
245
- }) : server.handlers;
246
- const requestMethod = request.method.toUpperCase();
247
- const handler = handlers[requestMethod] ?? handlers["ANY"];
248
- if (handler) {
249
- const mayDefer = !!foundRoute.options.component;
250
- if (typeof handler === "function") {
251
- middlewares.push(handlerToMiddleware(handler, mayDefer));
252
- } else {
253
- const { middleware } = handler;
254
- if (middleware && middleware.length) {
255
- middlewares.push(
256
- ...flattenMiddlewares(middleware).map((d) => d.options.server)
257
- );
258
- }
259
- if (handler.handler) {
260
- middlewares.push(handlerToMiddleware(handler.handler, mayDefer));
338
+ if (server?.handlers && isExactMatch) {
339
+ const handlers = typeof server.handlers === "function" ? server.handlers({ createHandlers: (d) => d }) : server.handlers;
340
+ const requestMethod = request.method.toUpperCase();
341
+ const handler = handlers[requestMethod] ?? handlers["ANY"];
342
+ if (handler) {
343
+ const mayDefer = !!foundRoute.options.component;
344
+ if (typeof handler === "function") {
345
+ routeMiddlewares.push(handlerToMiddleware(handler, mayDefer));
346
+ } else {
347
+ if (handler.middleware?.length) {
348
+ const handlerMiddlewares = flattenMiddlewares(handler.middleware);
349
+ for (const m of handlerMiddlewares) {
350
+ routeMiddlewares.push(m.options.server);
261
351
  }
262
352
  }
353
+ if (handler.handler) {
354
+ routeMiddlewares.push(handlerToMiddleware(handler.handler, mayDefer));
355
+ }
263
356
  }
264
357
  }
265
358
  }
266
- middlewares.push(
267
- handlerToMiddleware((ctx2) => executeRouter({ serverContext: ctx2.context }))
268
- );
269
- const ctx = await executeMiddleware(middlewares, {
359
+ routeMiddlewares.push((ctx2) => executeRouter(ctx2.context));
360
+ const ctx = await executeMiddleware(routeMiddlewares, {
270
361
  request,
271
362
  context,
272
363
  params: routeParams,
273
364
  pathname
274
365
  });
275
- const response = ctx.response;
276
- return response;
277
- }
278
- function throwRouteHandlerError() {
279
- if (process.env.NODE_ENV === "development") {
280
- throw new Error(
281
- `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.`
282
- );
283
- }
284
- throw new Error("Internal Server Error");
285
- }
286
- function throwIfMayNotDefer() {
287
- if (process.env.NODE_ENV === "development") {
288
- throw new Error(
289
- `You cannot defer to the app router if there is no component defined on this route.`
290
- );
291
- }
292
- throw new Error("Internal Server Error");
293
- }
294
- function handlerToMiddleware(handler, mayDefer = false) {
295
- if (mayDefer) {
296
- return handler;
297
- }
298
- return async ({ next: _next, ...rest }) => {
299
- const response = await handler({ ...rest, next: throwIfMayNotDefer });
300
- if (!response) {
301
- throwRouteHandlerError();
302
- }
303
- return response;
304
- };
305
- }
306
- function executeMiddleware(middlewares, ctx) {
307
- let index = -1;
308
- const next = async (ctx2) => {
309
- index++;
310
- const middleware = middlewares[index];
311
- if (!middleware) return ctx2;
312
- let result;
313
- try {
314
- result = await middleware({
315
- ...ctx2,
316
- // Allow the middleware to call the next middleware in the chain
317
- next: async (nextCtx) => {
318
- const nextResult = await next({
319
- ...ctx2,
320
- ...nextCtx,
321
- context: {
322
- ...ctx2.context,
323
- ...nextCtx?.context || {}
324
- }
325
- });
326
- return Object.assign(ctx2, handleCtxResult(nextResult));
327
- }
328
- // Allow the middleware result to extend the return context
329
- });
330
- } catch (err) {
331
- if (isSpecialResponse(err)) {
332
- result = {
333
- response: err
334
- };
335
- } else {
336
- throw err;
337
- }
338
- }
339
- return Object.assign(ctx2, handleCtxResult(result));
340
- };
341
- return handleCtxResult(next(ctx));
342
- }
343
- function handleCtxResult(result) {
344
- if (isSpecialResponse(result)) {
345
- return {
346
- response: result
347
- };
348
- }
349
- return result;
350
- }
351
- function isSpecialResponse(err) {
352
- return isResponse(err) || isRedirect(err);
353
- }
354
- function isResponse(response) {
355
- return response instanceof Response;
366
+ return ctx.response;
356
367
  }
357
368
  export {
358
369
  createStartHandler