@tanstack/start-server-core
Version:
Modern and scalable routing for React applications
198 lines (197 loc) • 7.27 kB
JavaScript
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