@tanstack/start-server-core
Advanced tools
@@ -5,4 +5,3 @@ import { getResponse } from "./request-response.js"; | ||
| import { TSS_FORMDATA_CONTEXT, X_TSS_RAW_RESPONSE, X_TSS_SERIALIZED, getDefaultSerovalPlugins, safeObjectMerge } from "@tanstack/start-client-core"; | ||
| import { createRawStreamRPCPlugin, isNotFound, isRedirect } from "@tanstack/router-core"; | ||
| import invariant from "tiny-invariant"; | ||
| import { createRawStreamRPCPlugin, invariant, isNotFound, isRedirect } from "@tanstack/router-core"; | ||
| import { fromJSON, toCrossJSONAsync, toCrossJSONStream } from "seroval"; | ||
@@ -32,3 +31,6 @@ //#region src/server-functions-handler.ts | ||
| if (FORM_DATA_CONTENT_TYPES.some((type) => contentType && contentType.includes(type))) { | ||
| invariant(methodUpper !== "GET", "GET requests with FormData payloads are not supported"); | ||
| if (methodUpper === "GET") { | ||
| if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: GET requests with FormData payloads are not supported"); | ||
| invariant(); | ||
| } | ||
| const formData = await request.formData(); | ||
@@ -35,0 +37,0 @@ const serializedContext = formData.get(TSS_FORMDATA_CONTEXT); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server-functions-handler.js","names":[],"sources":["../../src/server-functions-handler.ts"],"sourcesContent":["import {\n createRawStreamRPCPlugin,\n isNotFound,\n isRedirect,\n} from '@tanstack/router-core'\nimport invariant from 'tiny-invariant'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n getDefaultSerovalPlugins,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport { fromJSON, toCrossJSONAsync, toCrossJSONStream } from 'seroval'\nimport { getResponse } from './request-response'\nimport { getServerFnById } from './getServerFnById'\nimport {\n TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n createMultiplexedStream,\n} from './frame-protocol'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\n// Cache serovalPlugins at module level to avoid repeated calls\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | undefined = undefined\n\n// Cache TextEncoder for NDJSON serialization\nconst textEncoder = new TextEncoder()\n\n// Known FormData 'Content-Type' header values - module-level constant\nconst FORM_DATA_CONTENT_TYPES = [\n 'multipart/form-data',\n 'application/x-www-form-urlencoded',\n]\n\n// Maximum payload size for GET requests (1MB)\nconst MAX_PAYLOAD_SIZE = 1_000_000\n\nexport const handleServerAction = async ({\n request,\n context,\n serverFnId,\n}: {\n request: Request\n context: any\n serverFnId: string\n}) => {\n const method = request.method\n const methodUpper = method.toUpperCase()\n const url = new URL(request.url)\n\n const action = await getServerFnById(serverFnId, { fromClient: true })\n\n // Early method check: reject mismatched HTTP methods before parsing\n // the request payload (FormData, JSON, query string, etc.)\n if (action.method && methodUpper !== action.method) {\n return new Response(\n `expected ${action.method} method. Got ${methodUpper}`,\n {\n status: 405,\n headers: {\n Allow: action.method,\n },\n },\n )\n }\n\n const isServerFn = request.headers.get('x-tsr-serverFn') === 'true'\n\n // Initialize serovalPlugins lazily (cached at module level)\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n\n const contentType = request.headers.get('Content-Type')\n\n function parsePayload(payload: any) {\n const parsedPayload = fromJSON(payload, { plugins: serovalPlugins })\n return parsedPayload as any\n }\n\n const response = await (async () => {\n try {\n let res = await (async () => {\n // FormData\n if (\n FORM_DATA_CONTENT_TYPES.some(\n (type) => contentType && contentType.includes(type),\n )\n ) {\n // We don't support GET requests with FormData payloads... that seems impossible\n invariant(\n methodUpper !== 'GET',\n 'GET requests with FormData payloads are not supported',\n )\n const formData = await request.formData()\n const serializedContext = formData.get(TSS_FORMDATA_CONTEXT)\n formData.delete(TSS_FORMDATA_CONTEXT)\n\n const params = {\n context,\n data: formData,\n method: methodUpper,\n }\n if (typeof serializedContext === 'string') {\n try {\n const parsedContext = JSON.parse(serializedContext)\n const deserializedContext = fromJSON(parsedContext, {\n plugins: serovalPlugins,\n })\n if (\n typeof deserializedContext === 'object' &&\n deserializedContext\n ) {\n params.context = safeObjectMerge(\n context,\n deserializedContext as Record<string, unknown>,\n )\n }\n } catch (e) {\n // Log warning for debugging but don't expose to client\n if (process.env.NODE_ENV === 'development') {\n console.warn('Failed to parse FormData context:', e)\n }\n }\n }\n\n return await action(params)\n }\n\n // Get requests use the query string\n if (methodUpper === 'GET') {\n // Get payload directly from searchParams\n const payloadParam = url.searchParams.get('payload')\n // Reject oversized payloads to prevent DoS\n if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) {\n throw new Error('Payload too large')\n }\n // If there's a payload, we should try to parse it\n const payload: any = payloadParam\n ? parsePayload(JSON.parse(payloadParam))\n : {}\n payload.context = safeObjectMerge(context, payload.context)\n payload.method = methodUpper\n // Send it through!\n return await action(payload)\n }\n\n let jsonPayload\n if (contentType?.includes('application/json')) {\n jsonPayload = await request.json()\n }\n\n const payload = jsonPayload ? parsePayload(jsonPayload) : {}\n payload.context = safeObjectMerge(payload.context, context)\n payload.method = methodUpper\n return await action(payload)\n })()\n\n const unwrapped = res.result || res.error\n\n if (isNotFound(res)) {\n res = isNotFoundResponse(res)\n }\n\n if (!isServerFn) {\n return unwrapped\n }\n\n if (unwrapped instanceof Response) {\n if (isRedirect(unwrapped)) {\n return unwrapped\n }\n unwrapped.headers.set(X_TSS_RAW_RESPONSE, 'true')\n return unwrapped\n }\n\n return serializeResult(res)\n\n function serializeResult(res: unknown): Response {\n let nonStreamingBody: any = undefined\n\n const alsResponse = getResponse()\n if (res !== undefined) {\n // Collect raw streams encountered during serialization\n const rawStreams = new Map<number, ReadableStream<Uint8Array>>()\n const rawStreamPlugin = createRawStreamRPCPlugin(\n (id: number, stream: ReadableStream<Uint8Array>) => {\n rawStreams.set(id, stream)\n },\n )\n\n // Build plugins with RawStreamRPCPlugin first (before default SSR plugin)\n const plugins = [rawStreamPlugin, ...(serovalPlugins || [])]\n\n // first run without the stream in case `result` does not need streaming\n let done = false as boolean\n const callbacks: {\n onParse: (value: any) => void\n onDone: () => void\n onError: (error: any) => void\n } = {\n onParse: (value) => {\n nonStreamingBody = value\n },\n onDone: () => {\n done = true\n },\n onError: (error) => {\n throw error\n },\n }\n toCrossJSONStream(res, {\n refs: new Map(),\n plugins,\n onParse(value) {\n callbacks.onParse(value)\n },\n onDone() {\n callbacks.onDone()\n },\n onError: (error) => {\n callbacks.onError(error)\n },\n })\n\n // If no raw streams and done synchronously, return simple JSON\n if (done && rawStreams.size === 0) {\n return new Response(\n nonStreamingBody ? JSON.stringify(nonStreamingBody) : undefined,\n {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n },\n )\n }\n\n // If we have raw streams, use framed protocol\n if (rawStreams.size > 0) {\n // Create a stream of JSON chunks (NDJSON style)\n const jsonStream = new ReadableStream<string>({\n start(controller) {\n callbacks.onParse = (value) => {\n controller.enqueue(JSON.stringify(value) + '\\n')\n }\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch {\n // Already closed\n }\n }\n callbacks.onError = (error) => controller.error(error)\n // Emit initial body if we have one\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n },\n })\n\n // Create multiplexed stream with JSON and raw streams\n const multiplexedStream = createMultiplexedStream(\n jsonStream,\n rawStreams,\n )\n\n return new Response(multiplexedStream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n // No raw streams but not done yet - use standard NDJSON streaming\n const stream = new ReadableStream({\n start(controller) {\n callbacks.onParse = (value) =>\n controller.enqueue(\n textEncoder.encode(JSON.stringify(value) + '\\n'),\n )\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch (error) {\n controller.error(error)\n }\n }\n callbacks.onError = (error) => controller.error(error)\n // stream initial body\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n },\n })\n return new Response(stream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/x-ndjson',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n return new Response(undefined, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n })\n }\n } catch (error: any) {\n if (error instanceof Response) {\n return error\n }\n // else if (\n // isPlainObject(error) &&\n // 'result' in error &&\n // error.result instanceof Response\n // ) {\n // return error.result\n // }\n\n // Currently this server-side context has no idea how to\n // build final URLs, so we need to defer that to the client.\n // The client will check for __redirect and __notFound keys,\n // and if they exist, it will handle them appropriately.\n\n if (isNotFound(error)) {\n return isNotFoundResponse(error)\n }\n\n console.info()\n console.info('Server Fn Error!')\n console.info()\n console.error(error)\n console.info()\n\n const serializedError = JSON.stringify(\n await Promise.resolve(\n toCrossJSONAsync(error, {\n refs: new Map(),\n plugins: serovalPlugins,\n }),\n ),\n )\n const response = getResponse()\n return new Response(serializedError, {\n status: response.status ?? 500,\n statusText: response.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n })()\n\n return response\n}\n\nfunction isNotFoundResponse(error: any) {\n const { headers, ...rest } = error\n\n return new Response(JSON.stringify(rest), {\n status: 404,\n headers: {\n 'Content-Type': 'application/json',\n ...(headers || {}),\n },\n })\n}\n"],"mappings":";;;;;;;;AAuBA,IAAI,iBAA6D,KAAA;AAGjE,IAAM,cAAc,IAAI,aAAa;AAGrC,IAAM,0BAA0B,CAC9B,uBACA,oCACD;AAGD,IAAM,mBAAmB;AAEzB,IAAa,qBAAqB,OAAO,EACvC,SACA,SACA,iBAKI;CAEJ,MAAM,cADS,QAAQ,OACI,aAAa;CACxC,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;CAEhC,MAAM,SAAS,MAAM,gBAAgB,YAAY,EAAE,YAAY,MAAM,CAAC;AAItE,KAAI,OAAO,UAAU,gBAAgB,OAAO,OAC1C,QAAO,IAAI,SACT,YAAY,OAAO,OAAO,eAAe,eACzC;EACE,QAAQ;EACR,SAAS,EACP,OAAO,OAAO,QACf;EACF,CACF;CAGH,MAAM,aAAa,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAG7D,KAAI,CAAC,eACH,kBAAiB,0BAA0B;CAG7C,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe;CAEvD,SAAS,aAAa,SAAc;AAElC,SADsB,SAAS,SAAS,EAAE,SAAS,gBAAgB,CAAC;;AA8RtE,QA1RiB,OAAO,YAAY;AAClC,MAAI;GACF,IAAI,MAAM,OAAO,YAAY;AAE3B,QACE,wBAAwB,MACrB,SAAS,eAAe,YAAY,SAAS,KAAK,CACpD,EACD;AAEA,eACE,gBAAgB,OAChB,wDACD;KACD,MAAM,WAAW,MAAM,QAAQ,UAAU;KACzC,MAAM,oBAAoB,SAAS,IAAI,qBAAqB;AAC5D,cAAS,OAAO,qBAAqB;KAErC,MAAM,SAAS;MACb;MACA,MAAM;MACN,QAAQ;MACT;AACD,SAAI,OAAO,sBAAsB,SAC/B,KAAI;MAEF,MAAM,sBAAsB,SADN,KAAK,MAAM,kBAAkB,EACC,EAClD,SAAS,gBACV,CAAC;AACF,UACE,OAAO,wBAAwB,YAC/B,oBAEA,QAAO,UAAU,gBACf,SACA,oBACD;cAEI,GAAG;AAEV,UAAA,QAAA,IAAA,aAA6B,cAC3B,SAAQ,KAAK,qCAAqC,EAAE;;AAK1D,YAAO,MAAM,OAAO,OAAO;;AAI7B,QAAI,gBAAgB,OAAO;KAEzB,MAAM,eAAe,IAAI,aAAa,IAAI,UAAU;AAEpD,SAAI,gBAAgB,aAAa,SAAS,iBACxC,OAAM,IAAI,MAAM,oBAAoB;KAGtC,MAAM,UAAe,eACjB,aAAa,KAAK,MAAM,aAAa,CAAC,GACtC,EAAE;AACN,aAAQ,UAAU,gBAAgB,SAAS,QAAQ,QAAQ;AAC3D,aAAQ,SAAS;AAEjB,YAAO,MAAM,OAAO,QAAQ;;IAG9B,IAAI;AACJ,QAAI,aAAa,SAAS,mBAAmB,CAC3C,eAAc,MAAM,QAAQ,MAAM;IAGpC,MAAM,UAAU,cAAc,aAAa,YAAY,GAAG,EAAE;AAC5D,YAAQ,UAAU,gBAAgB,QAAQ,SAAS,QAAQ;AAC3D,YAAQ,SAAS;AACjB,WAAO,MAAM,OAAO,QAAQ;OAC1B;GAEJ,MAAM,YAAY,IAAI,UAAU,IAAI;AAEpC,OAAI,WAAW,IAAI,CACjB,OAAM,mBAAmB,IAAI;AAG/B,OAAI,CAAC,WACH,QAAO;AAGT,OAAI,qBAAqB,UAAU;AACjC,QAAI,WAAW,UAAU,CACvB,QAAO;AAET,cAAU,QAAQ,IAAI,oBAAoB,OAAO;AACjD,WAAO;;AAGT,UAAO,gBAAgB,IAAI;GAE3B,SAAS,gBAAgB,KAAwB;IAC/C,IAAI,mBAAwB,KAAA;IAE5B,MAAM,cAAc,aAAa;AACjC,QAAI,QAAQ,KAAA,GAAW;KAErB,MAAM,6BAAa,IAAI,KAAyC;KAQhE,MAAM,UAAU,CAPQ,0BACrB,IAAY,WAAuC;AAClD,iBAAW,IAAI,IAAI,OAAO;OAE7B,EAGiC,GAAI,kBAAkB,EAAE,CAAE;KAG5D,IAAI,OAAO;KACX,MAAM,YAIF;MACF,UAAU,UAAU;AAClB,0BAAmB;;MAErB,cAAc;AACZ,cAAO;;MAET,UAAU,UAAU;AAClB,aAAM;;MAET;AACD,uBAAkB,KAAK;MACrB,sBAAM,IAAI,KAAK;MACf;MACA,QAAQ,OAAO;AACb,iBAAU,QAAQ,MAAM;;MAE1B,SAAS;AACP,iBAAU,QAAQ;;MAEpB,UAAU,UAAU;AAClB,iBAAU,QAAQ,MAAM;;MAE3B,CAAC;AAGF,SAAI,QAAQ,WAAW,SAAS,EAC9B,QAAO,IAAI,SACT,mBAAmB,KAAK,UAAU,iBAAiB,GAAG,KAAA,GACtD;MACE,QAAQ,YAAY;MACpB,YAAY,YAAY;MACxB,SAAS;OACP,gBAAgB;QACf,mBAAmB;OACrB;MACF,CACF;AAIH,SAAI,WAAW,OAAO,GAAG;MAuBvB,MAAM,oBAAoB,wBArBP,IAAI,eAAuB,EAC5C,MAAM,YAAY;AAChB,iBAAU,WAAW,UAAU;AAC7B,mBAAW,QAAQ,KAAK,UAAU,MAAM,GAAG,KAAK;;AAElD,iBAAU,eAAe;AACvB,YAAI;AACF,oBAAW,OAAO;gBACZ;;AAIV,iBAAU,WAAW,UAAU,WAAW,MAAM,MAAM;AAEtD,WAAI,qBAAqB,KAAA,EACvB,WAAU,QAAQ,iBAAiB;SAGxC,CAAC,EAKA,WACD;AAED,aAAO,IAAI,SAAS,mBAAmB;OACrC,QAAQ,YAAY;OACpB,YAAY,YAAY;OACxB,SAAS;QACP,gBAAgB;SACf,mBAAmB;QACrB;OACF,CAAC;;KAIJ,MAAM,SAAS,IAAI,eAAe,EAChC,MAAM,YAAY;AAChB,gBAAU,WAAW,UACnB,WAAW,QACT,YAAY,OAAO,KAAK,UAAU,MAAM,GAAG,KAAK,CACjD;AACH,gBAAU,eAAe;AACvB,WAAI;AACF,mBAAW,OAAO;gBACX,OAAO;AACd,mBAAW,MAAM,MAAM;;;AAG3B,gBAAU,WAAW,UAAU,WAAW,MAAM,MAAM;AAEtD,UAAI,qBAAqB,KAAA,EACvB,WAAU,QAAQ,iBAAiB;QAGxC,CAAC;AACF,YAAO,IAAI,SAAS,QAAQ;MAC1B,QAAQ,YAAY;MACpB,YAAY,YAAY;MACxB,SAAS;OACP,gBAAgB;QACf,mBAAmB;OACrB;MACF,CAAC;;AAGJ,WAAO,IAAI,SAAS,KAAA,GAAW;KAC7B,QAAQ,YAAY;KACpB,YAAY,YAAY;KACzB,CAAC;;WAEG,OAAY;AACnB,OAAI,iBAAiB,SACnB,QAAO;AAeT,OAAI,WAAW,MAAM,CACnB,QAAO,mBAAmB,MAAM;AAGlC,WAAQ,MAAM;AACd,WAAQ,KAAK,mBAAmB;AAChC,WAAQ,MAAM;AACd,WAAQ,MAAM,MAAM;AACpB,WAAQ,MAAM;GAEd,MAAM,kBAAkB,KAAK,UAC3B,MAAM,QAAQ,QACZ,iBAAiB,OAAO;IACtB,sBAAM,IAAI,KAAK;IACf,SAAS;IACV,CAAC,CACH,CACF;GACD,MAAM,WAAW,aAAa;AAC9B,UAAO,IAAI,SAAS,iBAAiB;IACnC,QAAQ,SAAS,UAAU;IAC3B,YAAY,SAAS;IACrB,SAAS;KACP,gBAAgB;MACf,mBAAmB;KACrB;IACF,CAAC;;KAEF;;AAKN,SAAS,mBAAmB,OAAY;CACtC,MAAM,EAAE,SAAS,GAAG,SAAS;AAE7B,QAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE;EACxC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,GAAI,WAAW,EAAE;GAClB;EACF,CAAC"} | ||
| {"version":3,"file":"server-functions-handler.js","names":[],"sources":["../../src/server-functions-handler.ts"],"sourcesContent":["import {\n createRawStreamRPCPlugin,\n invariant,\n isNotFound,\n isRedirect,\n} from '@tanstack/router-core'\nimport {\n TSS_FORMDATA_CONTEXT,\n X_TSS_RAW_RESPONSE,\n X_TSS_SERIALIZED,\n getDefaultSerovalPlugins,\n safeObjectMerge,\n} from '@tanstack/start-client-core'\nimport { fromJSON, toCrossJSONAsync, toCrossJSONStream } from 'seroval'\nimport { getResponse } from './request-response'\nimport { getServerFnById } from './getServerFnById'\nimport {\n TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n createMultiplexedStream,\n} from './frame-protocol'\nimport type { Plugin as SerovalPlugin } from 'seroval'\n\n// Cache serovalPlugins at module level to avoid repeated calls\nlet serovalPlugins: Array<SerovalPlugin<any, any>> | undefined = undefined\n\n// Cache TextEncoder for NDJSON serialization\nconst textEncoder = new TextEncoder()\n\n// Known FormData 'Content-Type' header values - module-level constant\nconst FORM_DATA_CONTENT_TYPES = [\n 'multipart/form-data',\n 'application/x-www-form-urlencoded',\n]\n\n// Maximum payload size for GET requests (1MB)\nconst MAX_PAYLOAD_SIZE = 1_000_000\n\nexport const handleServerAction = async ({\n request,\n context,\n serverFnId,\n}: {\n request: Request\n context: any\n serverFnId: string\n}) => {\n const method = request.method\n const methodUpper = method.toUpperCase()\n const url = new URL(request.url)\n\n const action = await getServerFnById(serverFnId, { fromClient: true })\n\n // Early method check: reject mismatched HTTP methods before parsing\n // the request payload (FormData, JSON, query string, etc.)\n if (action.method && methodUpper !== action.method) {\n return new Response(\n `expected ${action.method} method. Got ${methodUpper}`,\n {\n status: 405,\n headers: {\n Allow: action.method,\n },\n },\n )\n }\n\n const isServerFn = request.headers.get('x-tsr-serverFn') === 'true'\n\n // Initialize serovalPlugins lazily (cached at module level)\n if (!serovalPlugins) {\n serovalPlugins = getDefaultSerovalPlugins()\n }\n\n const contentType = request.headers.get('Content-Type')\n\n function parsePayload(payload: any) {\n const parsedPayload = fromJSON(payload, { plugins: serovalPlugins })\n return parsedPayload as any\n }\n\n const response = await (async () => {\n try {\n let res = await (async () => {\n // FormData\n if (\n FORM_DATA_CONTENT_TYPES.some(\n (type) => contentType && contentType.includes(type),\n )\n ) {\n // We don't support GET requests with FormData payloads... that seems impossible\n if (methodUpper === 'GET') {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n 'Invariant failed: GET requests with FormData payloads are not supported',\n )\n }\n\n invariant()\n }\n const formData = await request.formData()\n const serializedContext = formData.get(TSS_FORMDATA_CONTEXT)\n formData.delete(TSS_FORMDATA_CONTEXT)\n\n const params = {\n context,\n data: formData,\n method: methodUpper,\n }\n if (typeof serializedContext === 'string') {\n try {\n const parsedContext = JSON.parse(serializedContext)\n const deserializedContext = fromJSON(parsedContext, {\n plugins: serovalPlugins,\n })\n if (\n typeof deserializedContext === 'object' &&\n deserializedContext\n ) {\n params.context = safeObjectMerge(\n context,\n deserializedContext as Record<string, unknown>,\n )\n }\n } catch (e) {\n // Log warning for debugging but don't expose to client\n if (process.env.NODE_ENV === 'development') {\n console.warn('Failed to parse FormData context:', e)\n }\n }\n }\n\n return await action(params)\n }\n\n // Get requests use the query string\n if (methodUpper === 'GET') {\n // Get payload directly from searchParams\n const payloadParam = url.searchParams.get('payload')\n // Reject oversized payloads to prevent DoS\n if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) {\n throw new Error('Payload too large')\n }\n // If there's a payload, we should try to parse it\n const payload: any = payloadParam\n ? parsePayload(JSON.parse(payloadParam))\n : {}\n payload.context = safeObjectMerge(context, payload.context)\n payload.method = methodUpper\n // Send it through!\n return await action(payload)\n }\n\n let jsonPayload\n if (contentType?.includes('application/json')) {\n jsonPayload = await request.json()\n }\n\n const payload = jsonPayload ? parsePayload(jsonPayload) : {}\n payload.context = safeObjectMerge(payload.context, context)\n payload.method = methodUpper\n return await action(payload)\n })()\n\n const unwrapped = res.result || res.error\n\n if (isNotFound(res)) {\n res = isNotFoundResponse(res)\n }\n\n if (!isServerFn) {\n return unwrapped\n }\n\n if (unwrapped instanceof Response) {\n if (isRedirect(unwrapped)) {\n return unwrapped\n }\n unwrapped.headers.set(X_TSS_RAW_RESPONSE, 'true')\n return unwrapped\n }\n\n return serializeResult(res)\n\n function serializeResult(res: unknown): Response {\n let nonStreamingBody: any = undefined\n\n const alsResponse = getResponse()\n if (res !== undefined) {\n // Collect raw streams encountered during serialization\n const rawStreams = new Map<number, ReadableStream<Uint8Array>>()\n const rawStreamPlugin = createRawStreamRPCPlugin(\n (id: number, stream: ReadableStream<Uint8Array>) => {\n rawStreams.set(id, stream)\n },\n )\n\n // Build plugins with RawStreamRPCPlugin first (before default SSR plugin)\n const plugins = [rawStreamPlugin, ...(serovalPlugins || [])]\n\n // first run without the stream in case `result` does not need streaming\n let done = false as boolean\n const callbacks: {\n onParse: (value: any) => void\n onDone: () => void\n onError: (error: any) => void\n } = {\n onParse: (value) => {\n nonStreamingBody = value\n },\n onDone: () => {\n done = true\n },\n onError: (error) => {\n throw error\n },\n }\n toCrossJSONStream(res, {\n refs: new Map(),\n plugins,\n onParse(value) {\n callbacks.onParse(value)\n },\n onDone() {\n callbacks.onDone()\n },\n onError: (error) => {\n callbacks.onError(error)\n },\n })\n\n // If no raw streams and done synchronously, return simple JSON\n if (done && rawStreams.size === 0) {\n return new Response(\n nonStreamingBody ? JSON.stringify(nonStreamingBody) : undefined,\n {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n },\n )\n }\n\n // If we have raw streams, use framed protocol\n if (rawStreams.size > 0) {\n // Create a stream of JSON chunks (NDJSON style)\n const jsonStream = new ReadableStream<string>({\n start(controller) {\n callbacks.onParse = (value) => {\n controller.enqueue(JSON.stringify(value) + '\\n')\n }\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch {\n // Already closed\n }\n }\n callbacks.onError = (error) => controller.error(error)\n // Emit initial body if we have one\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n },\n })\n\n // Create multiplexed stream with JSON and raw streams\n const multiplexedStream = createMultiplexedStream(\n jsonStream,\n rawStreams,\n )\n\n return new Response(multiplexedStream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': TSS_CONTENT_TYPE_FRAMED_VERSIONED,\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n // No raw streams but not done yet - use standard NDJSON streaming\n const stream = new ReadableStream({\n start(controller) {\n callbacks.onParse = (value) =>\n controller.enqueue(\n textEncoder.encode(JSON.stringify(value) + '\\n'),\n )\n callbacks.onDone = () => {\n try {\n controller.close()\n } catch (error) {\n controller.error(error)\n }\n }\n callbacks.onError = (error) => controller.error(error)\n // stream initial body\n if (nonStreamingBody !== undefined) {\n callbacks.onParse(nonStreamingBody)\n }\n },\n })\n return new Response(stream, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n headers: {\n 'Content-Type': 'application/x-ndjson',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n\n return new Response(undefined, {\n status: alsResponse.status,\n statusText: alsResponse.statusText,\n })\n }\n } catch (error: any) {\n if (error instanceof Response) {\n return error\n }\n // else if (\n // isPlainObject(error) &&\n // 'result' in error &&\n // error.result instanceof Response\n // ) {\n // return error.result\n // }\n\n // Currently this server-side context has no idea how to\n // build final URLs, so we need to defer that to the client.\n // The client will check for __redirect and __notFound keys,\n // and if they exist, it will handle them appropriately.\n\n if (isNotFound(error)) {\n return isNotFoundResponse(error)\n }\n\n console.info()\n console.info('Server Fn Error!')\n console.info()\n console.error(error)\n console.info()\n\n const serializedError = JSON.stringify(\n await Promise.resolve(\n toCrossJSONAsync(error, {\n refs: new Map(),\n plugins: serovalPlugins,\n }),\n ),\n )\n const response = getResponse()\n return new Response(serializedError, {\n status: response.status ?? 500,\n statusText: response.statusText,\n headers: {\n 'Content-Type': 'application/json',\n [X_TSS_SERIALIZED]: 'true',\n },\n })\n }\n })()\n\n return response\n}\n\nfunction isNotFoundResponse(error: any) {\n const { headers, ...rest } = error\n\n return new Response(JSON.stringify(rest), {\n status: 404,\n headers: {\n 'Content-Type': 'application/json',\n ...(headers || {}),\n },\n })\n}\n"],"mappings":"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"} |
+4
-5
| { | ||
| "name": "@tanstack/start-server-core", | ||
| "version": "1.167.1", | ||
| "version": "1.167.2", | ||
| "description": "Modern and scalable routing for React applications", | ||
@@ -68,7 +68,6 @@ "author": "Tanner Linsley", | ||
| "seroval": "^1.4.2", | ||
| "tiny-invariant": "^1.3.3", | ||
| "@tanstack/history": "1.161.6", | ||
| "@tanstack/router-core": "1.168.1", | ||
| "@tanstack/start-client-core": "1.167.1", | ||
| "@tanstack/start-storage-context": "1.166.15" | ||
| "@tanstack/router-core": "1.168.2", | ||
| "@tanstack/start-client-core": "1.167.2", | ||
| "@tanstack/start-storage-context": "1.166.16" | ||
| }, | ||
@@ -75,0 +74,0 @@ "devDependencies": { |
| import { | ||
| createRawStreamRPCPlugin, | ||
| invariant, | ||
| isNotFound, | ||
| isRedirect, | ||
| } from '@tanstack/router-core' | ||
| import invariant from 'tiny-invariant' | ||
| import { | ||
@@ -91,6 +91,11 @@ TSS_FORMDATA_CONTEXT, | ||
| // We don't support GET requests with FormData payloads... that seems impossible | ||
| invariant( | ||
| methodUpper !== 'GET', | ||
| 'GET requests with FormData payloads are not supported', | ||
| ) | ||
| if (methodUpper === 'GET') { | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| throw new Error( | ||
| 'Invariant failed: GET requests with FormData payloads are not supported', | ||
| ) | ||
| } | ||
| invariant() | ||
| } | ||
| const formData = await request.formData() | ||
@@ -97,0 +102,0 @@ const serializedContext = formData.get(TSS_FORMDATA_CONTEXT) |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 8 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 8 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
244460
0.14%6
-14.29%3877
0.15%35
6.06%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed