UNPKG

@tanstack/start-server-core

Version:

Modern and scalable routing for React applications

198 lines (197 loc) 7.27 kB
import { getResponse } from "./request-response.js"; import { getServerFnById } from "./getServerFnById.js"; import { TSS_CONTENT_TYPE_FRAMED_VERSIONED, createMultiplexedStream } from "./frame-protocol.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 { fromJSON, toCrossJSONAsync, toCrossJSONStream } from "seroval"; //#region src/server-functions-handler.ts var serovalPlugins = void 0; var textEncoder = new TextEncoder(); var FORM_DATA_CONTENT_TYPES = ["multipart/form-data", "application/x-www-form-urlencoded"]; var MAX_PAYLOAD_SIZE = 1e6; var handleServerAction = async ({ request, context, serverFnId }) => { const methodUpper = request.method.toUpperCase(); const url = new URL(request.url); const action = await getServerFnById(serverFnId, { fromClient: true }); if (action.method && methodUpper !== action.method) return new Response(`expected ${action.method} method. Got ${methodUpper}`, { status: 405, headers: { Allow: action.method } }); const isServerFn = request.headers.get("x-tsr-serverFn") === "true"; if (!serovalPlugins) serovalPlugins = getDefaultSerovalPlugins(); const contentType = request.headers.get("Content-Type"); function parsePayload(payload) { return fromJSON(payload, { plugins: serovalPlugins }); } return await (async () => { try { let res = await (async () => { if (FORM_DATA_CONTENT_TYPES.some((type) => contentType && contentType.includes(type))) { invariant(methodUpper !== "GET", "GET requests with FormData payloads are not supported"); const formData = await request.formData(); const serializedContext = formData.get(TSS_FORMDATA_CONTEXT); formData.delete(TSS_FORMDATA_CONTEXT); const params = { context, data: formData, method: methodUpper }; if (typeof serializedContext === "string") try { const deserializedContext = fromJSON(JSON.parse(serializedContext), { plugins: serovalPlugins }); if (typeof deserializedContext === "object" && deserializedContext) params.context = safeObjectMerge(context, deserializedContext); } catch (e) { if (process.env.NODE_ENV === "development") console.warn("Failed to parse FormData context:", e); } return await action(params); } if (methodUpper === "GET") { const payloadParam = url.searchParams.get("payload"); if (payloadParam && payloadParam.length > MAX_PAYLOAD_SIZE) throw new Error("Payload too large"); const payload = payloadParam ? parsePayload(JSON.parse(payloadParam)) : {}; payload.context = safeObjectMerge(context, payload.context); payload.method = methodUpper; return await action(payload); } let jsonPayload; if (contentType?.includes("application/json")) jsonPayload = await request.json(); const payload = jsonPayload ? parsePayload(jsonPayload) : {}; payload.context = safeObjectMerge(payload.context, context); payload.method = methodUpper; return await action(payload); })(); const unwrapped = res.result || res.error; if (isNotFound(res)) res = isNotFoundResponse(res); if (!isServerFn) return unwrapped; if (unwrapped instanceof Response) { if (isRedirect(unwrapped)) return unwrapped; unwrapped.headers.set(X_TSS_RAW_RESPONSE, "true"); return unwrapped; } return serializeResult(res); function serializeResult(res) { let nonStreamingBody = void 0; const alsResponse = getResponse(); if (res !== void 0) { const rawStreams = /* @__PURE__ */ new Map(); const plugins = [createRawStreamRPCPlugin((id, stream) => { rawStreams.set(id, stream); }), ...serovalPlugins || []]; let done = false; const callbacks = { onParse: (value) => { nonStreamingBody = value; }, onDone: () => { done = true; }, onError: (error) => { throw error; } }; toCrossJSONStream(res, { refs: /* @__PURE__ */ new Map(), plugins, onParse(value) { callbacks.onParse(value); }, onDone() { callbacks.onDone(); }, onError: (error) => { callbacks.onError(error); } }); if (done && rawStreams.size === 0) return new Response(nonStreamingBody ? JSON.stringify(nonStreamingBody) : void 0, { status: alsResponse.status, statusText: alsResponse.statusText, headers: { "Content-Type": "application/json", [X_TSS_SERIALIZED]: "true" } }); if (rawStreams.size > 0) { const multiplexedStream = createMultiplexedStream(new ReadableStream({ start(controller) { callbacks.onParse = (value) => { controller.enqueue(JSON.stringify(value) + "\n"); }; callbacks.onDone = () => { try { controller.close(); } catch {} }; callbacks.onError = (error) => controller.error(error); if (nonStreamingBody !== void 0) callbacks.onParse(nonStreamingBody); } }), rawStreams); return new Response(multiplexedStream, { status: alsResponse.status, statusText: alsResponse.statusText, headers: { "Content-Type": TSS_CONTENT_TYPE_FRAMED_VERSIONED, [X_TSS_SERIALIZED]: "true" } }); } const stream = new ReadableStream({ start(controller) { callbacks.onParse = (value) => controller.enqueue(textEncoder.encode(JSON.stringify(value) + "\n")); callbacks.onDone = () => { try { controller.close(); } catch (error) { controller.error(error); } }; callbacks.onError = (error) => controller.error(error); if (nonStreamingBody !== void 0) callbacks.onParse(nonStreamingBody); } }); return new Response(stream, { status: alsResponse.status, statusText: alsResponse.statusText, headers: { "Content-Type": "application/x-ndjson", [X_TSS_SERIALIZED]: "true" } }); } return new Response(void 0, { status: alsResponse.status, statusText: alsResponse.statusText }); } } catch (error) { if (error instanceof Response) return error; if (isNotFound(error)) return isNotFoundResponse(error); console.info(); console.info("Server Fn Error!"); console.info(); console.error(error); console.info(); const serializedError = JSON.stringify(await Promise.resolve(toCrossJSONAsync(error, { refs: /* @__PURE__ */ new Map(), plugins: serovalPlugins }))); const response = getResponse(); return new Response(serializedError, { status: response.status ?? 500, statusText: response.statusText, headers: { "Content-Type": "application/json", [X_TSS_SERIALIZED]: "true" } }); } })(); }; function isNotFoundResponse(error) { const { headers, ...rest } = error; return new Response(JSON.stringify(rest), { status: 404, headers: { "Content-Type": "application/json", ...headers || {} } }); } //#endregion export { handleServerAction }; //# sourceMappingURL=server-functions-handler.js.map