@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 +1 @@
1
- {"version":3,"file":"createStartHandler.js","sources":["../../src/createStartHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport { flattenMiddlewares, mergeHeaders } from '@tanstack/start-client-core'\nimport {\n executeRewriteInput,\n isRedirect,\n isResolvedRedirect,\n} from '@tanstack/router-core'\nimport {\n attachRouterServerSsrUtils,\n getOrigin,\n} from '@tanstack/router-core/ssr/server'\nimport { runWithStartContext } from '@tanstack/start-storage-context'\nimport { requestHandler } from './request-response'\nimport { getStartManifest } from './router-manifest'\nimport { handleServerAction } from './server-functions-handler'\n\nimport { HEADERS } from './constants'\nimport { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'\nimport type {\n AnyStartInstanceOptions,\n RouteMethod,\n RouteMethodHandlerFn,\n RouterEntry,\n StartEntry,\n} from '@tanstack/start-client-core'\nimport type { RequestHandler } from './request-handler'\nimport type {\n AnyRoute,\n AnyRouter,\n Awaitable,\n Manifest,\n Register,\n} from '@tanstack/router-core'\nimport type { HandlerCallback } from '@tanstack/router-core/ssr/server'\n\ntype TODO = any\n\nfunction getStartResponseHeaders(opts: { router: AnyRouter }) {\n const headers = mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=utf-8',\n },\n ...opts.router.state.matches.map((match) => {\n return match.headers\n }),\n )\n return headers\n}\n\nexport function createStartHandler<TRegister = Register>(\n cb: HandlerCallback<AnyRouter>,\n): RequestHandler<TRegister> {\n const ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'\n let startRoutesManifest: Manifest | null = null\n let startEntry: StartEntry | null = null\n let routerEntry: RouterEntry | null = null\n const getEntries = async (): Promise<{\n startEntry: StartEntry\n routerEntry: RouterEntry\n }> => {\n if (routerEntry === null) {\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n routerEntry = await import('#tanstack-router-entry')\n }\n if (startEntry === null) {\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n startEntry = await import('#tanstack-start-entry')\n }\n return {\n startEntry: startEntry as unknown as StartEntry,\n routerEntry: routerEntry as unknown as RouterEntry,\n }\n }\n\n const startRequestResolver: RequestHandler<Register> = async (\n request,\n requestOpts,\n ) => {\n let router: AnyRouter | null = null as AnyRouter | null\n // Track whether the callback will handle cleanup\n let cbWillCleanup = false as boolean\n try {\n const origin = getOrigin(request)\n\n const url = new URL(request.url)\n const href = url.href.replace(url.origin, '')\n\n const startOptions: AnyStartInstanceOptions =\n (await (await getEntries()).startEntry.startInstance?.getOptions()) ||\n ({} as AnyStartInstanceOptions)\n\n const serializationAdapters = [\n ...(startOptions.serializationAdapters || []),\n ServerFunctionSerializationAdapter,\n ]\n\n const requestStartOptions = {\n ...startOptions,\n serializationAdapters,\n }\n\n const getRouter = async () => {\n if (router) return router\n router = await (await getEntries()).routerEntry.getRouter()\n\n // Update the client-side router with the history\n const isPrerendering = process.env.TSS_PRERENDERING === 'true'\n // env var is set during dev is SPA mode is enabled\n let isShell = process.env.TSS_SHELL === 'true'\n if (isPrerendering && !isShell) {\n // only read the shell header if we are prerendering\n // to avoid runtime behavior changes by injecting this header\n // the header is set by the prerender plugin\n isShell = request.headers.get(HEADERS.TSS_SHELL) === 'true'\n }\n\n // Create a history for the client-side router\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n router.update({\n history,\n isShell,\n isPrerendering,\n origin: router.options.origin ?? origin,\n ...{\n defaultSsr: requestStartOptions.defaultSsr,\n serializationAdapters: [\n ...requestStartOptions.serializationAdapters,\n ...(router.options.serializationAdapters || []),\n ],\n },\n basepath: ROUTER_BASEPATH,\n })\n return router\n }\n\n const requestHandlerMiddleware = handlerToMiddleware(\n async ({ context }) => {\n const response = await runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n },\n async () => {\n try {\n // First, let's attempt to handle server functions\n if (href.startsWith(process.env.TSS_SERVER_FN_BASE)) {\n return await handleServerAction({\n request,\n context: requestOpts?.context,\n })\n }\n\n const executeRouter = async ({\n serverContext,\n }: {\n serverContext: any\n }) => {\n const requestAcceptHeader =\n request.headers.get('Accept') || '*/*'\n const splitRequestAcceptHeader =\n requestAcceptHeader.split(',')\n\n const supportedMimeTypes = ['*/*', 'text/html']\n const isRouterAcceptSupported = supportedMimeTypes.some(\n (mimeType) =>\n splitRequestAcceptHeader.some((acceptedMimeType) =>\n acceptedMimeType.trim().startsWith(mimeType),\n ),\n )\n\n if (!isRouterAcceptSupported) {\n return Response.json(\n {\n error: 'Only HTML requests are supported here',\n },\n {\n status: 500,\n },\n )\n }\n\n // if the startRoutesManifest is not loaded yet, load it once\n if (startRoutesManifest === null) {\n startRoutesManifest = await getStartManifest()\n }\n const router = await getRouter()\n attachRouterServerSsrUtils({\n router,\n manifest: startRoutesManifest,\n })\n\n router.update({ additionalContext: { serverContext } })\n await router.load()\n\n // If there was a redirect, skip rendering the page at all\n if (router.state.redirect) {\n return router.state.redirect\n }\n\n await router.serverSsr!.dehydrate()\n\n const responseHeaders = getStartResponseHeaders({ router })\n // Mark that the callback will handle cleanup\n cbWillCleanup = true\n const response = await cb({\n request,\n router,\n responseHeaders,\n })\n\n return response\n }\n\n const response = await handleServerRoutes({\n getRouter,\n request,\n executeRouter,\n context,\n })\n\n return response\n } catch (err) {\n if (err instanceof Response) {\n return err\n }\n\n throw err\n }\n },\n )\n return response\n },\n )\n\n const flattenedMiddlewares = startOptions.requestMiddleware\n ? flattenMiddlewares(startOptions.requestMiddleware)\n : []\n const middlewares = flattenedMiddlewares.map((d) => d.options.server)\n const ctx = await executeMiddleware(\n [...middlewares, requestHandlerMiddleware],\n {\n request,\n\n context: requestOpts?.context || {},\n },\n )\n\n const response: Response = ctx.response\n\n if (isRedirect(response)) {\n if (isResolvedRedirect(response)) {\n if (request.headers.get('x-tsr-redirect') === 'manual') {\n return Response.json(\n {\n ...response.options,\n isSerializedRedirect: true,\n },\n {\n headers: response.headers,\n },\n )\n }\n return response\n }\n if (\n response.options.to &&\n typeof response.options.to === 'string' &&\n !response.options.to.startsWith('/')\n ) {\n throw new Error(\n `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)}`,\n )\n }\n\n if (\n ['params', 'search', 'hash'].some(\n (d) => typeof (response.options as any)[d] === 'function',\n )\n ) {\n throw new Error(\n `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(\n response.options,\n )\n .filter((d) => typeof (response.options as any)[d] === 'function')\n .map((d) => `\"${d}\"`)\n .join(', ')}`,\n )\n }\n\n const router = await getRouter()\n const redirect = router.resolveRedirect(response)\n\n if (request.headers.get('x-tsr-redirect') === 'manual') {\n return Response.json(\n {\n ...response.options,\n isSerializedRedirect: true,\n },\n {\n headers: response.headers,\n },\n )\n }\n\n return redirect\n }\n\n return response\n } finally {\n if (router && !cbWillCleanup) {\n // Clean up router SSR state if it was set up but won't be cleaned up by the callback\n // (e.g., in redirect cases or early returns before the callback is invoked).\n // When the callback runs, it handles cleanup (either via transformStreamWithRouter\n // for streaming, or directly in renderRouterToString for non-streaming).\n router.serverSsr?.cleanup()\n }\n router = null\n }\n }\n\n return requestHandler(startRequestResolver)\n}\n\nasync function handleServerRoutes({\n getRouter,\n request,\n executeRouter,\n context,\n}: {\n getRouter: () => Awaitable<AnyRouter>\n request: Request\n executeRouter: ({\n serverContext,\n }: {\n serverContext: any\n }) => Promise<Response>\n context: any\n}) {\n const router = await getRouter()\n let url = new URL(request.url)\n url = executeRewriteInput(router.rewrite, url)\n const pathname = url.pathname\n // this will perform a fuzzy match, however for server routes we need an exact match\n // if the route is not an exact match, executeRouter will handle rendering the app router\n // the match will be cached internally, so no extra work is done during the app router render\n const { matchedRoutes, foundRoute, routeParams } =\n router.getMatchedRoutes(pathname)\n\n const isExactMatch = foundRoute && routeParams['**'] === undefined\n\n // TODO: Error handling? What happens when its `throw redirect()` vs `throw new Error()`?\n\n const middlewares = flattenMiddlewares(\n matchedRoutes.flatMap((r) => r.options.server?.middleware).filter(Boolean),\n ).map((d) => d.options.server)\n\n const server = foundRoute?.options.server\n if (server && isExactMatch) {\n if (server.handlers) {\n const handlers =\n typeof server.handlers === 'function'\n ? server.handlers({\n createHandlers: (d: any) => d,\n })\n : server.handlers\n\n const requestMethod = request.method.toUpperCase() as RouteMethod\n\n // Attempt to find the method in the handlers\n const handler = handlers[requestMethod] ?? handlers['ANY']\n\n // If a method is found, execute the handler\n if (handler) {\n const mayDefer = !!foundRoute.options.component\n if (typeof handler === 'function') {\n middlewares.push(handlerToMiddleware(handler, mayDefer))\n } else {\n const { middleware } = handler\n if (middleware && middleware.length) {\n middlewares.push(\n ...flattenMiddlewares(middleware).map((d) => d.options.server),\n )\n }\n if (handler.handler) {\n middlewares.push(handlerToMiddleware(handler.handler, mayDefer))\n }\n }\n }\n }\n }\n\n // eventually, execute the router\n middlewares.push(\n handlerToMiddleware((ctx) => executeRouter({ serverContext: ctx.context })),\n )\n\n const ctx = await executeMiddleware(middlewares, {\n request,\n context,\n params: routeParams,\n pathname,\n })\n\n const response: Response = ctx.response\n\n return response\n}\n\nfunction throwRouteHandlerError() {\n if (process.env.NODE_ENV === 'development') {\n throw new Error(\n `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.`,\n )\n }\n throw new Error('Internal Server Error')\n}\n\nfunction throwIfMayNotDefer() {\n if (process.env.NODE_ENV === 'development') {\n throw new Error(\n `You cannot defer to the app router if there is no component defined on this route.`,\n )\n }\n throw new Error('Internal Server Error')\n}\nfunction handlerToMiddleware(\n handler: RouteMethodHandlerFn<any, AnyRoute, any, any, any, any, any>,\n mayDefer: boolean = false,\n) {\n if (mayDefer) {\n return handler as TODO\n }\n return async ({ next: _next, ...rest }: TODO) => {\n const response = await handler({ ...rest, next: throwIfMayNotDefer })\n if (!response) {\n throwRouteHandlerError()\n }\n return response\n }\n}\n\nfunction executeMiddleware(middlewares: TODO, ctx: TODO) {\n let index = -1\n\n const next = async (ctx: TODO) => {\n index++\n const middleware = middlewares[index]\n if (!middleware) return ctx\n\n let result\n try {\n result = await middleware({\n ...ctx,\n // Allow the middleware to call the next middleware in the chain\n next: async (nextCtx: TODO) => {\n // Allow the caller to extend the context for the next middleware\n const nextResult = await next({\n ...ctx,\n ...nextCtx,\n context: {\n ...ctx.context,\n ...(nextCtx?.context || {}),\n },\n })\n\n // Merge the result into the context\\\n return Object.assign(ctx, handleCtxResult(nextResult))\n },\n // Allow the middleware result to extend the return context\n })\n } catch (err: TODO) {\n if (isSpecialResponse(err)) {\n result = {\n response: err,\n }\n } else {\n throw err\n }\n }\n\n // Merge the middleware result into the context, just in case it\n // returns a partial context\n return Object.assign(ctx, handleCtxResult(result))\n }\n\n return handleCtxResult(next(ctx))\n}\n\nfunction handleCtxResult(result: TODO) {\n if (isSpecialResponse(result)) {\n return {\n response: result,\n }\n }\n\n return result\n}\n\nfunction isSpecialResponse(err: TODO) {\n return isResponse(err) || isRedirect(err)\n}\n\nfunction isResponse(response: Response): response is Response {\n return response instanceof Response\n}\n"],"names":["response","router","ctx"],"mappings":";;;;;;;;;;AAqCA,SAAS,wBAAwB,MAA6B;AAC5D,QAAM,UAAU;AAAA,IACd;AAAA,MACE,gBAAgB;AAAA,IAAA;AAAA,IAElB,GAAG,KAAK,OAAO,MAAM,QAAQ,IAAI,CAAC,UAAU;AAC1C,aAAO,MAAM;AAAA,IACf,CAAC;AAAA,EAAA;AAEH,SAAO;AACT;AAEO,SAAS,mBACd,IAC2B;AAC3B,QAAM,kBAAkB,QAAQ,IAAI,uBAAuB;AAC3D,MAAI,sBAAuC;AAC3C,MAAI,aAAgC;AACpC,MAAI,cAAkC;AACtC,QAAM,aAAa,YAGb;AACJ,QAAI,gBAAgB,MAAM;AAExB,oBAAc,MAAM,OAAO,wBAAwB;AAAA,IACrD;AACA,QAAI,eAAe,MAAM;AAEvB,mBAAa,MAAM,OAAO,uBAAuB;AAAA,IACnD;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAEA,QAAM,uBAAiD,OACrD,SACA,gBACG;AACH,QAAI,SAA2B;AAE/B,QAAI,gBAAgB;AACpB,QAAI;AACF,YAAM,SAAS,UAAU,OAAO;AAEhC,YAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,YAAM,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,EAAE;AAE5C,YAAM,eACH,OAAO,MAAM,WAAA,GAAc,WAAW,eAAe,WAAA,KACrD,CAAA;AAEH,YAAM,wBAAwB;AAAA,QAC5B,GAAI,aAAa,yBAAyB,CAAA;AAAA,QAC1C;AAAA,MAAA;AAGF,YAAM,sBAAsB;AAAA,QAC1B,GAAG;AAAA,QACH;AAAA,MAAA;AAGF,YAAM,YAAY,YAAY;AAC5B,YAAI,OAAQ,QAAO;AACnB,iBAAS,OAAO,MAAM,WAAA,GAAc,YAAY,UAAA;AAGhD,cAAM,iBAAiB,QAAQ,IAAI,qBAAqB;AAExD,YAAI,UAAU,QAAQ,IAAI,cAAc;AACxC,YAAI,kBAAkB,CAAC,SAAS;AAI9B,oBAAU,QAAQ,QAAQ,IAAI,QAAQ,SAAS,MAAM;AAAA,QACvD;AAGA,cAAM,UAAU,oBAAoB;AAAA,UAClC,gBAAgB,CAAC,IAAI;AAAA,QAAA,CACtB;AAED,eAAO,OAAO;AAAA,UACZ;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ,OAAO,QAAQ,UAAU;AAAA,UACjC,GAAG;AAAA,YACD,YAAY,oBAAoB;AAAA,YAChC,uBAAuB;AAAA,cACrB,GAAG,oBAAoB;AAAA,cACvB,GAAI,OAAO,QAAQ,yBAAyB,CAAA;AAAA,YAAC;AAAA,UAC/C;AAAA,UAEF,UAAU;AAAA,QAAA,CACX;AACD,eAAO;AAAA,MACT;AAEA,YAAM,2BAA2B;AAAA,QAC/B,OAAO,EAAE,QAAA,MAAc;AACrB,gBAAMA,YAAW,MAAM;AAAA,YACrB;AAAA,cACE;AAAA,cACA,cAAc;AAAA,cACd,+BAA+B;AAAA,cAC/B;AAAA,YAAA;AAAA,YAEF,YAAY;AACV,kBAAI;AAEF,oBAAI,KAAK,WAAW,QAAQ,IAAI,kBAAkB,GAAG;AACnD,yBAAO,MAAM,mBAAmB;AAAA,oBAC9B;AAAA,oBACA,SAAS,aAAa;AAAA,kBAAA,CACvB;AAAA,gBACH;AAEA,sBAAM,gBAAgB,OAAO;AAAA,kBAC3B;AAAA,gBAAA,MAGI;AACJ,wBAAM,sBACJ,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AACnC,wBAAM,2BACJ,oBAAoB,MAAM,GAAG;AAE/B,wBAAM,qBAAqB,CAAC,OAAO,WAAW;AAC9C,wBAAM,0BAA0B,mBAAmB;AAAA,oBACjD,CAAC,aACC,yBAAyB;AAAA,sBAAK,CAAC,qBAC7B,iBAAiB,KAAA,EAAO,WAAW,QAAQ;AAAA,oBAAA;AAAA,kBAC7C;AAGJ,sBAAI,CAAC,yBAAyB;AAC5B,2BAAO,SAAS;AAAA,sBACd;AAAA,wBACE,OAAO;AAAA,sBAAA;AAAA,sBAET;AAAA,wBACE,QAAQ;AAAA,sBAAA;AAAA,oBACV;AAAA,kBAEJ;AAGA,sBAAI,wBAAwB,MAAM;AAChC,0CAAsB,MAAM,iBAAA;AAAA,kBAC9B;AACA,wBAAMC,UAAS,MAAM,UAAA;AACrB,6CAA2B;AAAA,oBACzB,QAAAA;AAAAA,oBACA,UAAU;AAAA,kBAAA,CACX;AAEDA,0BAAO,OAAO,EAAE,mBAAmB,EAAE,cAAA,GAAiB;AACtD,wBAAMA,QAAO,KAAA;AAGb,sBAAIA,QAAO,MAAM,UAAU;AACzB,2BAAOA,QAAO,MAAM;AAAA,kBACtB;AAEA,wBAAMA,QAAO,UAAW,UAAA;AAExB,wBAAM,kBAAkB,wBAAwB,EAAE,QAAAA,SAAQ;AAE1D,kCAAgB;AAChB,wBAAMD,YAAW,MAAM,GAAG;AAAA,oBACxB;AAAA,oBACA,QAAAC;AAAAA,oBACA;AAAA,kBAAA,CACD;AAED,yBAAOD;AAAAA,gBACT;AAEA,sBAAMA,YAAW,MAAM,mBAAmB;AAAA,kBACxC;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,gBAAA,CACD;AAED,uBAAOA;AAAAA,cACT,SAAS,KAAK;AACZ,oBAAI,eAAe,UAAU;AAC3B,yBAAO;AAAA,gBACT;AAEA,sBAAM;AAAA,cACR;AAAA,YACF;AAAA,UAAA;AAEF,iBAAOA;AAAAA,QACT;AAAA,MAAA;AAGF,YAAM,uBAAuB,aAAa,oBACtC,mBAAmB,aAAa,iBAAiB,IACjD,CAAA;AACJ,YAAM,cAAc,qBAAqB,IAAI,CAAC,MAAM,EAAE,QAAQ,MAAM;AACpE,YAAM,MAAM,MAAM;AAAA,QAChB,CAAC,GAAG,aAAa,wBAAwB;AAAA,QACzC;AAAA,UACE;AAAA,UAEA,SAAS,aAAa,WAAW,CAAA;AAAA,QAAC;AAAA,MACpC;AAGF,YAAM,WAAqB,IAAI;AAE/B,UAAI,WAAW,QAAQ,GAAG;AACxB,YAAI,mBAAmB,QAAQ,GAAG;AAChC,cAAI,QAAQ,QAAQ,IAAI,gBAAgB,MAAM,UAAU;AACtD,mBAAO,SAAS;AAAA,cACd;AAAA,gBACE,GAAG,SAAS;AAAA,gBACZ,sBAAsB;AAAA,cAAA;AAAA,cAExB;AAAA,gBACE,SAAS,SAAS;AAAA,cAAA;AAAA,YACpB;AAAA,UAEJ;AACA,iBAAO;AAAA,QACT;AACA,YACE,SAAS,QAAQ,MACjB,OAAO,SAAS,QAAQ,OAAO,YAC/B,CAAC,SAAS,QAAQ,GAAG,WAAW,GAAG,GACnC;AACA,gBAAM,IAAI;AAAA,YACR,oNAAoN,KAAK,UAAU,SAAS,OAAO,CAAC;AAAA,UAAA;AAAA,QAExP;AAEA,YACE,CAAC,UAAU,UAAU,MAAM,EAAE;AAAA,UAC3B,CAAC,MAAM,OAAQ,SAAS,QAAgB,CAAC,MAAM;AAAA,QAAA,GAEjD;AACA,gBAAM,IAAI;AAAA,YACR,+IAA+I,OAAO;AAAA,cACpJ,SAAS;AAAA,YAAA,EAER,OAAO,CAAC,MAAM,OAAQ,SAAS,QAAgB,CAAC,MAAM,UAAU,EAChE,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EACnB,KAAK,IAAI,CAAC;AAAA,UAAA;AAAA,QAEjB;AAEA,cAAMC,UAAS,MAAM,UAAA;AACrB,cAAM,WAAWA,QAAO,gBAAgB,QAAQ;AAEhD,YAAI,QAAQ,QAAQ,IAAI,gBAAgB,MAAM,UAAU;AACtD,iBAAO,SAAS;AAAA,YACd;AAAA,cACE,GAAG,SAAS;AAAA,cACZ,sBAAsB;AAAA,YAAA;AAAA,YAExB;AAAA,cACE,SAAS,SAAS;AAAA,YAAA;AAAA,UACpB;AAAA,QAEJ;AAEA,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,UAAA;AACE,UAAI,UAAU,CAAC,eAAe;AAK5B,eAAO,WAAW,QAAA;AAAA,MACpB;AACA,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO,eAAe,oBAAoB;AAC5C;AAEA,eAAe,mBAAmB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,QAAM,SAAS,MAAM,UAAA;AACrB,MAAI,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC7B,QAAM,oBAAoB,OAAO,SAAS,GAAG;AAC7C,QAAM,WAAW,IAAI;AAIrB,QAAM,EAAE,eAAe,YAAY,gBACjC,OAAO,iBAAiB,QAAQ;AAElC,QAAM,eAAe,cAAc,YAAY,IAAI,MAAM;AAIzD,QAAM,cAAc;AAAA,IAClB,cAAc,QAAQ,CAAC,MAAM,EAAE,QAAQ,QAAQ,UAAU,EAAE,OAAO,OAAO;AAAA,EAAA,EACzE,IAAI,CAAC,MAAM,EAAE,QAAQ,MAAM;AAE7B,QAAM,SAAS,YAAY,QAAQ;AACnC,MAAI,UAAU,cAAc;AAC1B,QAAI,OAAO,UAAU;AACnB,YAAM,WACJ,OAAO,OAAO,aAAa,aACvB,OAAO,SAAS;AAAA,QACd,gBAAgB,CAAC,MAAW;AAAA,MAAA,CAC7B,IACD,OAAO;AAEb,YAAM,gBAAgB,QAAQ,OAAO,YAAA;AAGrC,YAAM,UAAU,SAAS,aAAa,KAAK,SAAS,KAAK;AAGzD,UAAI,SAAS;AACX,cAAM,WAAW,CAAC,CAAC,WAAW,QAAQ;AACtC,YAAI,OAAO,YAAY,YAAY;AACjC,sBAAY,KAAK,oBAAoB,SAAS,QAAQ,CAAC;AAAA,QACzD,OAAO;AACL,gBAAM,EAAE,eAAe;AACvB,cAAI,cAAc,WAAW,QAAQ;AACnC,wBAAY;AAAA,cACV,GAAG,mBAAmB,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,MAAM;AAAA,YAAA;AAAA,UAEjE;AACA,cAAI,QAAQ,SAAS;AACnB,wBAAY,KAAK,oBAAoB,QAAQ,SAAS,QAAQ,CAAC;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,cAAY;AAAA,IACV,oBAAoB,CAACC,SAAQ,cAAc,EAAE,eAAeA,KAAI,SAAS,CAAC;AAAA,EAAA;AAG5E,QAAM,MAAM,MAAM,kBAAkB,aAAa;AAAA,IAC/C;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EAAA,CACD;AAED,QAAM,WAAqB,IAAI;AAE/B,SAAO;AACT;AAEA,SAAS,yBAAyB;AAChC,MAAI,QAAQ,IAAI,aAAa,eAAe;AAC1C,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AACA,QAAM,IAAI,MAAM,uBAAuB;AACzC;AAEA,SAAS,qBAAqB;AAC5B,MAAI,QAAQ,IAAI,aAAa,eAAe;AAC1C,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AACA,QAAM,IAAI,MAAM,uBAAuB;AACzC;AACA,SAAS,oBACP,SACA,WAAoB,OACpB;AACA,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,SAAO,OAAO,EAAE,MAAM,OAAO,GAAG,WAAiB;AAC/C,UAAM,WAAW,MAAM,QAAQ,EAAE,GAAG,MAAM,MAAM,oBAAoB;AACpE,QAAI,CAAC,UAAU;AACb,6BAAA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,kBAAkB,aAAmB,KAAW;AACvD,MAAI,QAAQ;AAEZ,QAAM,OAAO,OAAOA,SAAc;AAChC;AACA,UAAM,aAAa,YAAY,KAAK;AACpC,QAAI,CAAC,WAAY,QAAOA;AAExB,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,WAAW;AAAA,QACxB,GAAGA;AAAAA;AAAAA,QAEH,MAAM,OAAO,YAAkB;AAE7B,gBAAM,aAAa,MAAM,KAAK;AAAA,YAC5B,GAAGA;AAAAA,YACH,GAAG;AAAA,YACH,SAAS;AAAA,cACP,GAAGA,KAAI;AAAA,cACP,GAAI,SAAS,WAAW,CAAA;AAAA,YAAC;AAAA,UAC3B,CACD;AAGD,iBAAO,OAAO,OAAOA,MAAK,gBAAgB,UAAU,CAAC;AAAA,QACvD;AAAA;AAAA,MAAA,CAED;AAAA,IACH,SAAS,KAAW;AAClB,UAAI,kBAAkB,GAAG,GAAG;AAC1B,iBAAS;AAAA,UACP,UAAU;AAAA,QAAA;AAAA,MAEd,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAIA,WAAO,OAAO,OAAOA,MAAK,gBAAgB,MAAM,CAAC;AAAA,EACnD;AAEA,SAAO,gBAAgB,KAAK,GAAG,CAAC;AAClC;AAEA,SAAS,gBAAgB,QAAc;AACrC,MAAI,kBAAkB,MAAM,GAAG;AAC7B,WAAO;AAAA,MACL,UAAU;AAAA,IAAA;AAAA,EAEd;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,KAAW;AACpC,SAAO,WAAW,GAAG,KAAK,WAAW,GAAG;AAC1C;AAEA,SAAS,WAAW,UAA0C;AAC5D,SAAO,oBAAoB;AAC7B;"}
1
+ {"version":3,"file":"createStartHandler.js","sources":["../../src/createStartHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport {\n createNullProtoObject,\n flattenMiddlewares,\n mergeHeaders,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport {\n executeRewriteInput,\n isRedirect,\n isResolvedRedirect,\n} from '@tanstack/router-core'\nimport {\n attachRouterServerSsrUtils,\n getOrigin,\n} from '@tanstack/router-core/ssr/server'\nimport { runWithStartContext } from '@tanstack/start-storage-context'\nimport { requestHandler } from './request-response'\nimport { getStartManifest } from './router-manifest'\nimport { handleServerAction } from './server-functions-handler'\n\nimport { HEADERS } from './constants'\nimport { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'\nimport type {\n AnyFunctionMiddleware,\n AnyRequestMiddleware,\n AnyStartInstanceOptions,\n RouteMethod,\n RouteMethodHandlerFn,\n RouterEntry,\n StartEntry,\n} from '@tanstack/start-client-core'\nimport type { RequestHandler } from './request-handler'\nimport type {\n AnyRoute,\n AnyRouter,\n Manifest,\n Register,\n} from '@tanstack/router-core'\nimport type { HandlerCallback } from '@tanstack/router-core/ssr/server'\n\ntype TODO = any\n\ntype AnyMiddlewareServerFn =\n | AnyRequestMiddleware['options']['server']\n | AnyFunctionMiddleware['options']['server']\n\nfunction getStartResponseHeaders(opts: { router: AnyRouter }) {\n const headers = mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=utf-8',\n },\n ...opts.router.state.matches.map((match) => {\n return match.headers\n }),\n )\n return headers\n}\n\n// Cached entries - loaded once per process\nlet cachedStartEntry: StartEntry | null = null\nlet cachedRouterEntry: RouterEntry | null = null\nlet cachedManifest: Manifest | null = null\n\nasync function getEntries(): Promise<{\n startEntry: StartEntry\n routerEntry: RouterEntry\n}> {\n if (cachedRouterEntry === null) {\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n cachedRouterEntry = await import('#tanstack-router-entry')\n }\n if (cachedStartEntry === null) {\n // @ts-ignore when building, we currently don't respect tsconfig.ts' `include` so we are not picking up the .d.ts from start-client-core\n cachedStartEntry = await import('#tanstack-start-entry')\n }\n return {\n startEntry: cachedStartEntry as unknown as StartEntry,\n routerEntry: cachedRouterEntry as unknown as RouterEntry,\n }\n}\n\nasync function getManifest(): Promise<Manifest> {\n if (cachedManifest === null) {\n cachedManifest = await getStartManifest()\n }\n return cachedManifest\n}\n\n// Pre-computed constants\nconst ROUTER_BASEPATH = process.env.TSS_ROUTER_BASEPATH || '/'\nconst SERVER_FN_BASE = process.env.TSS_SERVER_FN_BASE\nconst IS_PRERENDERING = process.env.TSS_PRERENDERING === 'true'\nconst IS_SHELL_ENV = process.env.TSS_SHELL === 'true'\nconst IS_DEV = process.env.NODE_ENV === 'development'\n\n// Reusable error messages\nconst ERR_NO_RESPONSE = IS_DEV\n ? `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.`\n : 'Internal Server Error'\n\nconst ERR_NO_DEFER = IS_DEV\n ? `You cannot defer to the app router if there is no component defined on this route.`\n : 'Internal Server Error'\n\nfunction throwRouteHandlerError(): never {\n throw new Error(ERR_NO_RESPONSE)\n}\n\nfunction throwIfMayNotDefer(): never {\n throw new Error(ERR_NO_DEFER)\n}\n\n/**\n * Check if a value is a special response (Response or Redirect)\n */\nfunction isSpecialResponse(value: unknown): value is Response {\n return value instanceof Response || isRedirect(value)\n}\n\n/**\n * Normalize middleware result to context shape\n */\nfunction handleCtxResult(result: TODO) {\n if (isSpecialResponse(result)) {\n return { response: result }\n }\n return result\n}\n\n/**\n * Execute a middleware chain\n */\nfunction executeMiddleware(middlewares: Array<TODO>, ctx: TODO): Promise<TODO> {\n let index = -1\n\n const next = async (nextCtx?: TODO): Promise<TODO> => {\n // Merge context if provided using safeObjectMerge for prototype pollution prevention\n if (nextCtx) {\n if (nextCtx.context) {\n ctx.context = safeObjectMerge(ctx.context, nextCtx.context)\n }\n // Copy own properties except context (Object.keys returns only own enumerable properties)\n for (const key of Object.keys(nextCtx)) {\n if (key !== 'context') {\n ctx[key] = nextCtx[key]\n }\n }\n }\n\n index++\n const middleware = middlewares[index]\n if (!middleware) return ctx\n\n let result: TODO\n try {\n result = await middleware({ ...ctx, next })\n } catch (err) {\n if (isSpecialResponse(err)) {\n ctx.response = err\n return ctx\n }\n throw err\n }\n\n const normalized = handleCtxResult(result)\n if (normalized) {\n if (normalized.response !== undefined) {\n ctx.response = normalized.response\n }\n if (normalized.context) {\n ctx.context = safeObjectMerge(ctx.context, normalized.context)\n }\n }\n\n return ctx\n }\n\n return next()\n}\n\n/**\n * Wrap a route handler as middleware\n */\nfunction handlerToMiddleware(\n handler: RouteMethodHandlerFn<any, AnyRoute, any, any, any, any, any>,\n mayDefer: boolean = false,\n): TODO {\n if (mayDefer) {\n return handler\n }\n return async (ctx: TODO) => {\n const response = await handler({ ...ctx, next: throwIfMayNotDefer })\n if (!response) {\n throwRouteHandlerError()\n }\n return response\n }\n}\n\nexport function createStartHandler<TRegister = Register>(\n cb: HandlerCallback<AnyRouter>,\n): RequestHandler<TRegister> {\n const startRequestResolver: RequestHandler<Register> = async (\n request,\n requestOpts,\n ) => {\n let router: AnyRouter | null = null as AnyRouter | null\n let cbWillCleanup = false as boolean\n\n try {\n const url = new URL(request.url)\n const href = url.href.replace(url.origin, '')\n const origin = getOrigin(request)\n\n const entries = await getEntries()\n const startOptions: AnyStartInstanceOptions =\n (await entries.startEntry.startInstance?.getOptions()) ||\n ({} as AnyStartInstanceOptions)\n\n const serializationAdapters = [\n ...(startOptions.serializationAdapters || []),\n ServerFunctionSerializationAdapter,\n ]\n\n const requestStartOptions = {\n ...startOptions,\n serializationAdapters,\n }\n\n // Flatten request middlewares once\n const flattenedRequestMiddlewares = startOptions.requestMiddleware\n ? flattenMiddlewares(startOptions.requestMiddleware)\n : []\n\n // Create set for deduplication\n const executedRequestMiddlewares = new Set<TODO>(\n flattenedRequestMiddlewares,\n )\n\n // Memoized router getter\n const getRouter = async (): Promise<AnyRouter> => {\n if (router) return router\n\n router = await entries.routerEntry.getRouter()\n\n let isShell = IS_SHELL_ENV\n if (IS_PRERENDERING && !isShell) {\n isShell = request.headers.get(HEADERS.TSS_SHELL) === 'true'\n }\n\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n router.update({\n history,\n isShell,\n isPrerendering: IS_PRERENDERING,\n origin: router.options.origin ?? origin,\n ...{\n defaultSsr: requestStartOptions.defaultSsr,\n serializationAdapters: [\n ...requestStartOptions.serializationAdapters,\n ...(router.options.serializationAdapters || []),\n ],\n },\n basepath: ROUTER_BASEPATH,\n })\n\n return router\n }\n\n // Check for server function requests first (early exit)\n if (SERVER_FN_BASE && url.pathname.startsWith(SERVER_FN_BASE)) {\n const serverFnId = url.pathname\n .slice(SERVER_FN_BASE.length)\n .split('/')[0]\n\n if (!serverFnId) {\n throw new Error('Invalid server action param for serverFnId')\n }\n\n const serverFnHandler = async ({ context }: TODO) => {\n return runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n executedRequestMiddlewares,\n },\n () =>\n handleServerAction({\n request,\n context: requestOpts?.context,\n serverFnId,\n }),\n )\n }\n\n const middlewares = flattenedRequestMiddlewares.map(\n (d) => d.options.server,\n )\n const ctx = await executeMiddleware([...middlewares, serverFnHandler], {\n request,\n context: createNullProtoObject(requestOpts?.context),\n })\n\n return handleRedirectResponse(ctx.response, request, getRouter)\n }\n\n // Router execution function\n const executeRouter = async (serverContext: TODO): Promise<Response> => {\n const acceptHeader = request.headers.get('Accept') || '*/*'\n const acceptParts = acceptHeader.split(',')\n const supportedMimeTypes = ['*/*', 'text/html']\n\n const isSupported = supportedMimeTypes.some((mimeType) =>\n acceptParts.some((part) => part.trim().startsWith(mimeType)),\n )\n\n if (!isSupported) {\n return Response.json(\n { error: 'Only HTML requests are supported here' },\n { status: 500 },\n )\n }\n\n const manifest = await getManifest()\n const routerInstance = await getRouter()\n\n attachRouterServerSsrUtils({\n router: routerInstance,\n manifest,\n })\n\n routerInstance.update({ additionalContext: { serverContext } })\n await routerInstance.load()\n\n if (routerInstance.state.redirect) {\n return routerInstance.state.redirect\n }\n\n await routerInstance.serverSsr!.dehydrate()\n\n const responseHeaders = getStartResponseHeaders({\n router: routerInstance,\n })\n cbWillCleanup = true\n\n return cb({\n request,\n router: routerInstance,\n responseHeaders,\n })\n }\n\n // Main request handler\n const requestHandlerMiddleware = async ({ context }: TODO) => {\n return runWithStartContext(\n {\n getRouter,\n startOptions: requestStartOptions,\n contextAfterGlobalMiddlewares: context,\n request,\n executedRequestMiddlewares,\n },\n async () => {\n try {\n return await handleServerRoutes({\n getRouter,\n request,\n url,\n executeRouter,\n context,\n executedRequestMiddlewares,\n })\n } catch (err) {\n if (err instanceof Response) {\n return err\n }\n throw err\n }\n },\n )\n }\n\n const middlewares = flattenedRequestMiddlewares.map(\n (d) => d.options.server,\n )\n const ctx = await executeMiddleware(\n [...middlewares, requestHandlerMiddleware],\n { request, context: createNullProtoObject(requestOpts?.context) },\n )\n\n return handleRedirectResponse(ctx.response, request, getRouter)\n } finally {\n if (router && !cbWillCleanup) {\n // Clean up router SSR state if it was set up but won't be cleaned up by the callback\n // (e.g., in redirect cases or early returns before the callback is invoked).\n // When the callback runs, it handles cleanup (either via transformStreamWithRouter\n // for streaming, or directly in renderRouterToString for non-streaming).\n router.serverSsr?.cleanup()\n }\n router = null\n }\n }\n\n return requestHandler(startRequestResolver)\n}\n\nasync function handleRedirectResponse(\n response: Response,\n request: Request,\n getRouter: () => Promise<AnyRouter>,\n): Promise<Response> {\n if (!isRedirect(response)) {\n return response\n }\n\n if (isResolvedRedirect(response)) {\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return Response.json(\n { ...response.options, isSerializedRedirect: true },\n { headers: response.headers },\n )\n }\n return response\n }\n\n const opts = response.options\n if (opts.to && typeof opts.to === 'string' && !opts.to.startsWith('/')) {\n throw new Error(\n `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)}`,\n )\n }\n\n if (\n ['params', 'search', 'hash'].some(\n (d) => typeof (opts as TODO)[d] === 'function',\n )\n ) {\n throw new Error(\n `Server side redirects must use static search, params, and hash values and do not support functional values. Received functional values for: ${Object.keys(\n opts,\n )\n .filter((d) => typeof (opts as TODO)[d] === 'function')\n .map((d) => `\"${d}\"`)\n .join(', ')}`,\n )\n }\n\n const router = await getRouter()\n const redirect = router.resolveRedirect(response)\n\n if (request.headers.get('x-tsr-serverFn') === 'true') {\n return Response.json(\n { ...response.options, isSerializedRedirect: true },\n { headers: response.headers },\n )\n }\n\n return redirect\n}\n\nasync function handleServerRoutes({\n getRouter,\n request,\n url,\n executeRouter,\n context,\n executedRequestMiddlewares,\n}: {\n getRouter: () => Promise<AnyRouter>\n request: Request\n url: URL\n executeRouter: (serverContext: any) => Promise<Response>\n context: any\n executedRequestMiddlewares: Set<AnyRequestMiddleware>\n}): Promise<Response> {\n const router = await getRouter()\n const rewrittenUrl = executeRewriteInput(router.rewrite, url)\n const pathname = rewrittenUrl.pathname\n // this will perform a fuzzy match, however for server routes we need an exact match\n // if the route is not an exact match, executeRouter will handle rendering the app router\n // the match will be cached internally, so no extra work is done during the app router render\n const { matchedRoutes, foundRoute, routeParams } =\n router.getMatchedRoutes(pathname)\n\n const isExactMatch = foundRoute && routeParams['**'] === undefined\n\n // Collect and dedupe route middlewares\n const routeMiddlewares: Array<AnyMiddlewareServerFn> = []\n\n // Collect middleware from matched routes, filtering out those already executed\n // in the request phase\n for (const route of matchedRoutes) {\n const serverMiddleware = route.options.server?.middleware as\n | Array<AnyRequestMiddleware>\n | undefined\n if (serverMiddleware) {\n const flattened = flattenMiddlewares(serverMiddleware)\n for (const m of flattened) {\n if (!executedRequestMiddlewares.has(m)) {\n routeMiddlewares.push(m.options.server)\n }\n }\n }\n }\n\n // Add handler middleware if exact match\n const server = foundRoute?.options.server\n if (server?.handlers && isExactMatch) {\n const handlers =\n typeof server.handlers === 'function'\n ? server.handlers({ createHandlers: (d: any) => d })\n : server.handlers\n\n const requestMethod = request.method.toUpperCase() as RouteMethod\n const handler = handlers[requestMethod] ?? handlers['ANY']\n\n if (handler) {\n const mayDefer = !!foundRoute.options.component\n\n if (typeof handler === 'function') {\n routeMiddlewares.push(handlerToMiddleware(handler, mayDefer))\n } else {\n if (handler.middleware?.length) {\n const handlerMiddlewares = flattenMiddlewares(handler.middleware)\n for (const m of handlerMiddlewares) {\n routeMiddlewares.push(m.options.server)\n }\n }\n if (handler.handler) {\n routeMiddlewares.push(handlerToMiddleware(handler.handler, mayDefer))\n }\n }\n }\n }\n\n // Final middleware: execute router\n routeMiddlewares.push((ctx: TODO) => executeRouter(ctx.context))\n\n const ctx = await executeMiddleware(routeMiddlewares, {\n request,\n context,\n params: routeParams,\n pathname,\n })\n\n return ctx.response\n}\n"],"names":["middlewares","ctx"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { FrameType } from '@tanstack/start-client-core';
2
+ export { FRAME_HEADER_SIZE, FrameType, TSS_CONTENT_TYPE_FRAMED, TSS_CONTENT_TYPE_FRAMED_VERSIONED, TSS_FRAMED_PROTOCOL_VERSION, } from '@tanstack/start-client-core';
3
+ /**
4
+ * Encodes a single frame with header and payload.
5
+ */
6
+ export declare function encodeFrame(type: FrameType, streamId: number, payload: Uint8Array): Uint8Array;
7
+ /**
8
+ * Encodes a JSON frame (type 0, streamId 0).
9
+ */
10
+ export declare function encodeJSONFrame(json: string): Uint8Array;
11
+ /**
12
+ * Encodes a raw stream chunk frame.
13
+ */
14
+ export declare function encodeChunkFrame(streamId: number, chunk: Uint8Array): Uint8Array;
15
+ /**
16
+ * Encodes a raw stream end frame.
17
+ */
18
+ export declare function encodeEndFrame(streamId: number): Uint8Array;
19
+ /**
20
+ * Encodes a raw stream error frame.
21
+ */
22
+ export declare function encodeErrorFrame(streamId: number, error: unknown): Uint8Array;
23
+ /**
24
+ * Creates a multiplexed ReadableStream from JSON stream and raw streams.
25
+ *
26
+ * The JSON stream emits NDJSON lines (from seroval's toCrossJSONStream).
27
+ * Raw streams are pumped concurrently, interleaved with JSON frames.
28
+ *
29
+ * @param jsonStream Stream of JSON strings (each string is one NDJSON line)
30
+ * @param rawStreams Map of stream IDs to raw binary streams
31
+ */
32
+ export declare function createMultiplexedStream(jsonStream: ReadableStream<string>, rawStreams: Map<number, ReadableStream<Uint8Array>>): ReadableStream<Uint8Array>;
@@ -0,0 +1,139 @@
1
+ import { FrameType, FRAME_HEADER_SIZE } from "@tanstack/start-client-core";
2
+ import { FRAME_HEADER_SIZE as FRAME_HEADER_SIZE2, FrameType as FrameType2, TSS_CONTENT_TYPE_FRAMED, TSS_CONTENT_TYPE_FRAMED_VERSIONED, TSS_FRAMED_PROTOCOL_VERSION } from "@tanstack/start-client-core";
3
+ const textEncoder = new TextEncoder();
4
+ const EMPTY_PAYLOAD = new Uint8Array(0);
5
+ function encodeFrame(type, streamId, payload) {
6
+ const frame = new Uint8Array(FRAME_HEADER_SIZE + payload.length);
7
+ frame[0] = type;
8
+ frame[1] = streamId >>> 24 & 255;
9
+ frame[2] = streamId >>> 16 & 255;
10
+ frame[3] = streamId >>> 8 & 255;
11
+ frame[4] = streamId & 255;
12
+ frame[5] = payload.length >>> 24 & 255;
13
+ frame[6] = payload.length >>> 16 & 255;
14
+ frame[7] = payload.length >>> 8 & 255;
15
+ frame[8] = payload.length & 255;
16
+ frame.set(payload, FRAME_HEADER_SIZE);
17
+ return frame;
18
+ }
19
+ function encodeJSONFrame(json) {
20
+ return encodeFrame(FrameType.JSON, 0, textEncoder.encode(json));
21
+ }
22
+ function encodeChunkFrame(streamId, chunk) {
23
+ return encodeFrame(FrameType.CHUNK, streamId, chunk);
24
+ }
25
+ function encodeEndFrame(streamId) {
26
+ return encodeFrame(FrameType.END, streamId, EMPTY_PAYLOAD);
27
+ }
28
+ function encodeErrorFrame(streamId, error) {
29
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
30
+ return encodeFrame(FrameType.ERROR, streamId, textEncoder.encode(message));
31
+ }
32
+ function createMultiplexedStream(jsonStream, rawStreams) {
33
+ let activePumps = 1 + rawStreams.size;
34
+ let controllerRef = null;
35
+ let cancelled = false;
36
+ const cancelReaders = [];
37
+ const safeEnqueue = (chunk) => {
38
+ if (cancelled || !controllerRef) return;
39
+ try {
40
+ controllerRef.enqueue(chunk);
41
+ } catch {
42
+ }
43
+ };
44
+ const safeError = (err) => {
45
+ if (cancelled || !controllerRef) return;
46
+ try {
47
+ controllerRef.error(err);
48
+ } catch {
49
+ }
50
+ };
51
+ const safeClose = () => {
52
+ if (cancelled || !controllerRef) return;
53
+ try {
54
+ controllerRef.close();
55
+ } catch {
56
+ }
57
+ };
58
+ const checkComplete = () => {
59
+ activePumps--;
60
+ if (activePumps === 0) {
61
+ safeClose();
62
+ }
63
+ };
64
+ return new ReadableStream({
65
+ start(controller) {
66
+ controllerRef = controller;
67
+ cancelReaders.length = 0;
68
+ const pumpJSON = async () => {
69
+ const reader = jsonStream.getReader();
70
+ cancelReaders.push(() => {
71
+ reader.cancel().catch(() => {
72
+ });
73
+ });
74
+ try {
75
+ while (true) {
76
+ const { done, value } = await reader.read();
77
+ if (cancelled) break;
78
+ if (done) break;
79
+ safeEnqueue(encodeJSONFrame(value));
80
+ }
81
+ } catch (error) {
82
+ safeError(error);
83
+ } finally {
84
+ reader.releaseLock();
85
+ checkComplete();
86
+ }
87
+ };
88
+ const pumpRawStream = async (streamId, stream) => {
89
+ const reader = stream.getReader();
90
+ cancelReaders.push(() => {
91
+ reader.cancel().catch(() => {
92
+ });
93
+ });
94
+ try {
95
+ while (true) {
96
+ const { done, value } = await reader.read();
97
+ if (cancelled) break;
98
+ if (done) {
99
+ safeEnqueue(encodeEndFrame(streamId));
100
+ break;
101
+ }
102
+ safeEnqueue(encodeChunkFrame(streamId, value));
103
+ }
104
+ } catch (error) {
105
+ safeEnqueue(encodeErrorFrame(streamId, error));
106
+ } finally {
107
+ reader.releaseLock();
108
+ checkComplete();
109
+ }
110
+ };
111
+ pumpJSON();
112
+ for (const [streamId, stream] of rawStreams) {
113
+ pumpRawStream(streamId, stream);
114
+ }
115
+ },
116
+ cancel() {
117
+ cancelled = true;
118
+ controllerRef = null;
119
+ for (const cancelReader of cancelReaders) {
120
+ cancelReader();
121
+ }
122
+ cancelReaders.length = 0;
123
+ }
124
+ });
125
+ }
126
+ export {
127
+ FRAME_HEADER_SIZE2 as FRAME_HEADER_SIZE,
128
+ FrameType2 as FrameType,
129
+ TSS_CONTENT_TYPE_FRAMED,
130
+ TSS_CONTENT_TYPE_FRAMED_VERSIONED,
131
+ TSS_FRAMED_PROTOCOL_VERSION,
132
+ createMultiplexedStream,
133
+ encodeChunkFrame,
134
+ encodeEndFrame,
135
+ encodeErrorFrame,
136
+ encodeFrame,
137
+ encodeJSONFrame
138
+ };
139
+ //# sourceMappingURL=frame-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-protocol.js","sources":["../../src/frame-protocol.ts"],"sourcesContent":["/**\n * Binary frame protocol for multiplexing JSON and raw streams over HTTP.\n *\n * Frame format: [type:1][streamId:4][length:4][payload:length]\n * - type: 1 byte - frame type (JSON, CHUNK, END, ERROR)\n * - streamId: 4 bytes big-endian uint32 - stream identifier\n * - length: 4 bytes big-endian uint32 - payload length\n * - payload: variable length bytes\n */\n\n// Re-export constants from shared location\nimport { FRAME_HEADER_SIZE, FrameType } from '@tanstack/start-client-core'\n\nexport {\n FRAME_HEADER_SIZE,\n FrameType,\n TSS_CONTENT_TYPE_FRAMED,\n TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n TSS_FRAMED_PROTOCOL_VERSION,\n} from '@tanstack/start-client-core'\n\n/** Cached TextEncoder for frame encoding */\nconst textEncoder = new TextEncoder()\n\n/** Shared empty payload for END frames - avoids allocation per call */\nconst EMPTY_PAYLOAD = new Uint8Array(0)\n\n/**\n * Encodes a single frame with header and payload.\n */\nexport function encodeFrame(\n type: FrameType,\n streamId: number,\n payload: Uint8Array,\n): Uint8Array {\n const frame = new Uint8Array(FRAME_HEADER_SIZE + payload.length)\n // Write header bytes directly to avoid DataView allocation per frame\n // Frame format: [type:1][streamId:4 BE][length:4 BE]\n frame[0] = type\n frame[1] = (streamId >>> 24) & 0xff\n frame[2] = (streamId >>> 16) & 0xff\n frame[3] = (streamId >>> 8) & 0xff\n frame[4] = streamId & 0xff\n frame[5] = (payload.length >>> 24) & 0xff\n frame[6] = (payload.length >>> 16) & 0xff\n frame[7] = (payload.length >>> 8) & 0xff\n frame[8] = payload.length & 0xff\n frame.set(payload, FRAME_HEADER_SIZE)\n return frame\n}\n\n/**\n * Encodes a JSON frame (type 0, streamId 0).\n */\nexport function encodeJSONFrame(json: string): Uint8Array {\n return encodeFrame(FrameType.JSON, 0, textEncoder.encode(json))\n}\n\n/**\n * Encodes a raw stream chunk frame.\n */\nexport function encodeChunkFrame(\n streamId: number,\n chunk: Uint8Array,\n): Uint8Array {\n return encodeFrame(FrameType.CHUNK, streamId, chunk)\n}\n\n/**\n * Encodes a raw stream end frame.\n */\nexport function encodeEndFrame(streamId: number): Uint8Array {\n return encodeFrame(FrameType.END, streamId, EMPTY_PAYLOAD)\n}\n\n/**\n * Encodes a raw stream error frame.\n */\nexport function encodeErrorFrame(streamId: number, error: unknown): Uint8Array {\n const message =\n error instanceof Error ? error.message : String(error ?? 'Unknown error')\n return encodeFrame(FrameType.ERROR, streamId, textEncoder.encode(message))\n}\n\n/**\n * Creates a multiplexed ReadableStream from JSON stream and raw streams.\n *\n * The JSON stream emits NDJSON lines (from seroval's toCrossJSONStream).\n * Raw streams are pumped concurrently, interleaved with JSON frames.\n *\n * @param jsonStream Stream of JSON strings (each string is one NDJSON line)\n * @param rawStreams Map of stream IDs to raw binary streams\n */\nexport function createMultiplexedStream(\n jsonStream: ReadableStream<string>,\n rawStreams: Map<number, ReadableStream<Uint8Array>>,\n): ReadableStream<Uint8Array> {\n // Track active pumps for completion\n let activePumps = 1 + rawStreams.size // 1 for JSON + raw streams\n let controllerRef: ReadableStreamDefaultController<Uint8Array> | null = null\n let cancelled = false as boolean\n const cancelReaders: Array<() => void> = []\n\n const safeEnqueue = (chunk: Uint8Array) => {\n if (cancelled || !controllerRef) return\n try {\n controllerRef.enqueue(chunk)\n } catch {\n // Ignore enqueue after close/cancel\n }\n }\n\n const safeError = (err: unknown) => {\n if (cancelled || !controllerRef) return\n try {\n controllerRef.error(err)\n } catch {\n // Ignore\n }\n }\n\n const safeClose = () => {\n if (cancelled || !controllerRef) return\n try {\n controllerRef.close()\n } catch {\n // Ignore\n }\n }\n\n const checkComplete = () => {\n activePumps--\n if (activePumps === 0) {\n safeClose()\n }\n }\n\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controllerRef = controller\n cancelReaders.length = 0\n\n // Pump JSON stream (streamId 0)\n const pumpJSON = async () => {\n const reader = jsonStream.getReader()\n cancelReaders.push(() => {\n // Catch async rejection - reader may already be released\n reader.cancel().catch(() => {})\n })\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { done, value } = await reader.read()\n // Check cancelled after await - flag may have changed while waiting\n if (cancelled) break\n if (done) break\n safeEnqueue(encodeJSONFrame(value))\n }\n } catch (error) {\n // JSON stream error - fatal, error the whole response\n safeError(error)\n } finally {\n reader.releaseLock()\n checkComplete()\n }\n }\n\n // Pump a single raw stream with its streamId\n const pumpRawStream = async (\n streamId: number,\n stream: ReadableStream<Uint8Array>,\n ) => {\n const reader = stream.getReader()\n cancelReaders.push(() => {\n // Catch async rejection - reader may already be released\n reader.cancel().catch(() => {})\n })\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { done, value } = await reader.read()\n // Check cancelled after await - flag may have changed while waiting\n if (cancelled) break\n if (done) {\n safeEnqueue(encodeEndFrame(streamId))\n break\n }\n safeEnqueue(encodeChunkFrame(streamId, value))\n }\n } catch (error) {\n // Stream error - send ERROR frame (non-fatal, other streams continue)\n safeEnqueue(encodeErrorFrame(streamId, error))\n } finally {\n reader.releaseLock()\n checkComplete()\n }\n }\n\n // Start all pumps concurrently\n pumpJSON()\n for (const [streamId, stream] of rawStreams) {\n pumpRawStream(streamId, stream)\n }\n },\n\n cancel() {\n cancelled = true\n controllerRef = null\n // Proactively cancel all underlying readers to stop work quickly.\n for (const cancelReader of cancelReaders) {\n cancelReader()\n }\n cancelReaders.length = 0\n },\n })\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- export declare const handleServerAction: ({ request, context, }: {
1
+ export declare const handleServerAction: ({ request, context, serverFnId, }: {
2
2
  request: Request;
3
3
  context: any;
4
+ serverFnId: string;
4
5
  }) => Promise<any>;
@@ -1,35 +1,31 @@
1
- import { isNotFound } from "@tanstack/router-core";
1
+ import { createRawStreamRPCPlugin, isNotFound, isRedirect } from "@tanstack/router-core";
2
2
  import invariant from "tiny-invariant";
3
- import { getDefaultSerovalPlugins, TSS_FORMDATA_CONTEXT, X_TSS_RAW_RESPONSE, X_TSS_SERIALIZED } from "@tanstack/start-client-core";
4
- import { fromJSON, toCrossJSONStream, toCrossJSONAsync } from "seroval";
3
+ import { getDefaultSerovalPlugins, X_TSS_SERIALIZED, TSS_CONTENT_TYPE_FRAMED_VERSIONED, TSS_FORMDATA_CONTEXT, safeObjectMerge, X_TSS_RAW_RESPONSE } from "@tanstack/start-client-core";
4
+ import { toCrossJSONStream, fromJSON, toCrossJSONAsync } from "seroval";
5
5
  import { getResponse } from "./request-response.js";
6
+ import { createMultiplexedStream } from "./frame-protocol.js";
6
7
  import { getServerFnById } from "#tanstack-start-server-fn-resolver";
7
- let regex = void 0;
8
8
  let serovalPlugins = void 0;
9
+ const textEncoder = new TextEncoder();
9
10
  const FORM_DATA_CONTENT_TYPES = [
10
11
  "multipart/form-data",
11
12
  "application/x-www-form-urlencoded"
12
13
  ];
14
+ const MAX_PAYLOAD_SIZE = 1e6;
13
15
  const handleServerAction = async ({
14
16
  request,
15
- context
17
+ context,
18
+ serverFnId
16
19
  }) => {
17
20
  const controller = new AbortController();
18
21
  const signal = controller.signal;
19
22
  const abort = () => controller.abort();
20
23
  request.signal.addEventListener("abort", abort);
21
- if (regex === void 0) {
22
- regex = new RegExp(`${process.env.TSS_SERVER_FN_BASE}([^/?#]+)`);
23
- }
24
24
  const method = request.method;
25
25
  const methodLower = method.toLowerCase();
26
- const url = new URL(request.url, "http://localhost:3000");
27
- const match = url.pathname.match(regex);
28
- const serverFnId = match ? match[1] : null;
29
- if (typeof serverFnId !== "string") {
30
- throw new Error("Invalid server action param for serverFnId: " + serverFnId);
31
- }
26
+ const url = new URL(request.url);
32
27
  const action = await getServerFnById(serverFnId, { fromClient: true });
28
+ const isServerFn = request.headers.get("x-tsr-serverFn") === "true";
33
29
  if (!serovalPlugins) {
34
30
  serovalPlugins = getDefaultSerovalPlugins();
35
31
  }
@@ -40,7 +36,119 @@ const handleServerAction = async ({
40
36
  }
41
37
  const response = await (async () => {
42
38
  try {
43
- const result = await (async () => {
39
+ let serializeResult = function(res2) {
40
+ let nonStreamingBody = void 0;
41
+ const alsResponse = getResponse();
42
+ if (res2 !== void 0) {
43
+ const rawStreams = /* @__PURE__ */ new Map();
44
+ const rawStreamPlugin = createRawStreamRPCPlugin(
45
+ (id, stream2) => {
46
+ rawStreams.set(id, stream2);
47
+ }
48
+ );
49
+ const plugins = [rawStreamPlugin, ...serovalPlugins || []];
50
+ let done = false;
51
+ const callbacks = {
52
+ onParse: (value) => {
53
+ nonStreamingBody = value;
54
+ },
55
+ onDone: () => {
56
+ done = true;
57
+ },
58
+ onError: (error) => {
59
+ throw error;
60
+ }
61
+ };
62
+ toCrossJSONStream(res2, {
63
+ refs: /* @__PURE__ */ new Map(),
64
+ plugins,
65
+ onParse(value) {
66
+ callbacks.onParse(value);
67
+ },
68
+ onDone() {
69
+ callbacks.onDone();
70
+ },
71
+ onError: (error) => {
72
+ callbacks.onError(error);
73
+ }
74
+ });
75
+ if (done && rawStreams.size === 0) {
76
+ return new Response(
77
+ nonStreamingBody ? JSON.stringify(nonStreamingBody) : void 0,
78
+ {
79
+ status: alsResponse.status,
80
+ statusText: alsResponse.statusText,
81
+ headers: {
82
+ "Content-Type": "application/json",
83
+ [X_TSS_SERIALIZED]: "true"
84
+ }
85
+ }
86
+ );
87
+ }
88
+ if (rawStreams.size > 0) {
89
+ const jsonStream = new ReadableStream({
90
+ start(controller2) {
91
+ callbacks.onParse = (value) => {
92
+ controller2.enqueue(JSON.stringify(value) + "\n");
93
+ };
94
+ callbacks.onDone = () => {
95
+ try {
96
+ controller2.close();
97
+ } catch {
98
+ }
99
+ };
100
+ callbacks.onError = (error) => controller2.error(error);
101
+ if (nonStreamingBody !== void 0) {
102
+ callbacks.onParse(nonStreamingBody);
103
+ }
104
+ }
105
+ });
106
+ const multiplexedStream = createMultiplexedStream(
107
+ jsonStream,
108
+ rawStreams
109
+ );
110
+ return new Response(multiplexedStream, {
111
+ status: alsResponse.status,
112
+ statusText: alsResponse.statusText,
113
+ headers: {
114
+ "Content-Type": TSS_CONTENT_TYPE_FRAMED_VERSIONED,
115
+ [X_TSS_SERIALIZED]: "true"
116
+ }
117
+ });
118
+ }
119
+ const stream = new ReadableStream({
120
+ start(controller2) {
121
+ callbacks.onParse = (value) => controller2.enqueue(
122
+ textEncoder.encode(JSON.stringify(value) + "\n")
123
+ );
124
+ callbacks.onDone = () => {
125
+ try {
126
+ controller2.close();
127
+ } catch (error) {
128
+ controller2.error(error);
129
+ }
130
+ };
131
+ callbacks.onError = (error) => controller2.error(error);
132
+ if (nonStreamingBody !== void 0) {
133
+ callbacks.onParse(nonStreamingBody);
134
+ }
135
+ }
136
+ });
137
+ return new Response(stream, {
138
+ status: alsResponse.status,
139
+ statusText: alsResponse.statusText,
140
+ headers: {
141
+ "Content-Type": "application/x-ndjson",
142
+ [X_TSS_SERIALIZED]: "true"
143
+ }
144
+ });
145
+ }
146
+ return new Response(void 0, {
147
+ status: alsResponse.status,
148
+ statusText: alsResponse.statusText
149
+ });
150
+ };
151
+ let res = await (async () => {
44
152
  if (FORM_DATA_CONTENT_TYPES.some(
45
153
  (type) => contentType && contentType.includes(type)
46
154
  )) {
@@ -62,17 +170,26 @@ const handleServerAction = async ({
62
170
  plugins: serovalPlugins
63
171
  });
64
172
  if (typeof deserializedContext === "object" && deserializedContext) {
65
- params.context = { ...context, ...deserializedContext };
173
+ params.context = safeObjectMerge(
174
+ context,
175
+ deserializedContext
176
+ );
177
+ }
178
+ } catch (e) {
179
+ if (process.env.NODE_ENV === "development") {
180
+ console.warn("Failed to parse FormData context:", e);
66
181
  }
67
- } catch {
68
182
  }
69
183
  }
70
184
  return await action(params, signal);
71
185
  }
72
186
  if (methodLower === "get") {
73
187
  const payloadParam = url.searchParams.get("payload");
188
+ if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) {
189
+ throw new Error("Payload too large");
190
+ }
74
191
  const payload2 = payloadParam ? parsePayload(JSON.parse(payloadParam)) : {};
75
- payload2.context = { ...context, ...payload2.context };
192
+ payload2.context = safeObjectMerge(context, payload2.context);
76
193
  return await action(payload2, signal);
77
194
  }
78
195
  if (methodLower !== "post") {
@@ -83,87 +200,24 @@ const handleServerAction = async ({
83
200
  jsonPayload = await request.json();
84
201
  }
85
202
  const payload = jsonPayload ? parsePayload(jsonPayload) : {};
86
- payload.context = { ...payload.context, ...context };
203
+ payload.context = safeObjectMerge(payload.context, context);
87
204
  return await action(payload, signal);
88
205
  })();
89
- if (result.result instanceof Response) {
90
- result.result.headers.set(X_TSS_RAW_RESPONSE, "true");
91
- return result.result;
206
+ const unwrapped = res.result || res.error;
207
+ if (isNotFound(res)) {
208
+ res = isNotFoundResponse(res);
92
209
  }
93
- if (isNotFound(result)) {
94
- return isNotFoundResponse(result);
210
+ if (!isServerFn) {
211
+ return unwrapped;
95
212
  }
96
- const response2 = getResponse();
97
- let nonStreamingBody = void 0;
98
- if (result !== void 0) {
99
- let done = false;
100
- const callbacks = {
101
- onParse: (value) => {
102
- nonStreamingBody = value;
103
- },
104
- onDone: () => {
105
- done = true;
106
- },
107
- onError: (error) => {
108
- throw error;
109
- }
110
- };
111
- toCrossJSONStream(result, {
112
- refs: /* @__PURE__ */ new Map(),
113
- plugins: serovalPlugins,
114
- onParse(value) {
115
- callbacks.onParse(value);
116
- },
117
- onDone() {
118
- callbacks.onDone();
119
- },
120
- onError: (error) => {
121
- callbacks.onError(error);
122
- }
123
- });
124
- if (done) {
125
- return new Response(
126
- nonStreamingBody ? JSON.stringify(nonStreamingBody) : void 0,
127
- {
128
- status: response2.status,
129
- statusText: response2.statusText,
130
- headers: {
131
- "Content-Type": "application/json",
132
- [X_TSS_SERIALIZED]: "true"
133
- }
134
- }
135
- );
213
+ if (unwrapped instanceof Response) {
214
+ if (isRedirect(unwrapped)) {
215
+ return unwrapped;
136
216
  }
137
- const encoder = new TextEncoder();
138
- const stream = new ReadableStream({
139
- start(controller2) {
140
- callbacks.onParse = (value) => controller2.enqueue(encoder.encode(JSON.stringify(value) + "\n"));
141
- callbacks.onDone = () => {
142
- try {
143
- controller2.close();
144
- } catch (error) {
145
- controller2.error(error);
146
- }
147
- };
148
- callbacks.onError = (error) => controller2.error(error);
149
- if (nonStreamingBody !== void 0) {
150
- callbacks.onParse(nonStreamingBody);
151
- }
152
- }
153
- });
154
- return new Response(stream, {
155
- status: response2.status,
156
- statusText: response2.statusText,
157
- headers: {
158
- "Content-Type": "application/x-ndjson",
159
- [X_TSS_SERIALIZED]: "true"
160
- }
161
- });
217
+ unwrapped.headers.set(X_TSS_RAW_RESPONSE, "true");
218
+ return unwrapped;
162
219
  }
163
- return new Response(void 0, {
164
- status: response2.status,
165
- statusText: response2.statusText
166
- });
220
+ return serializeResult(res);
167
221
  } catch (error) {
168
222
  if (error instanceof Response) {
169
223
  return error;