UNPKG

vite-plugin-react-server

Version:
147 lines (131 loc) 4.28 kB
import type { Logger } from "vite"; import type { IncomingMessage, ServerResponse } from "node:http"; import type { Worker } from "node:worker_threads"; import type { RscWorkerInputMessage } from "../worker/rsc/types.js"; import type { MessageHandler } from "../types.js"; import { cleanupServerAction } from "../dev-server/cleanupServerAction.client.js"; import { logError, toError } from "../error/index.js"; import { PassThrough } from "node:stream"; import type { ServerActionHandlerOptions, } from "./handleServerActionHelper.js"; import { parseServerActionRequestBody, createServerActionResponse, setupServerActionHeaders } from "./handleServerActionHelper.js"; // Use shared helper instead of duplicating logic // Use shared helper instead of duplicating logic /** * Creates a pass-through stream for server action responses. */ export function createServerActionStream(res: ServerResponse): PassThrough { const passThrough = new PassThrough(); passThrough.pipe(res, { end: true }); passThrough.on('end', () => { res.end(); }); return passThrough; } /** * Handles errors in server action processing. */ export function handleServerActionError(error: unknown, res: ServerResponse, logger?: Logger) { const err = toError(error); logError(err, logger); res.statusCode = 500; res.end(JSON.stringify(createServerActionResponse(undefined, err.message))); } /** * Client-side server action handler that delegates to worker */ export async function handleServerAction( req: IncomingMessage, res: ServerResponse, options: ServerActionHandlerOptions & { worker?: Worker } ): Promise<void> { if (!options.worker) { throw new Error("Worker is required for client-side server actions"); } let messageHandler: MessageHandler<any> | null = null; const passThrough = createServerActionStream(res); try { // Read request body const chunks: Buffer[] = []; for await (const chunk of req) { chunks.push(chunk); } const body = Buffer.concat(chunks).toString(); // Parse the server action request const { id, args } = parseServerActionRequestBody(body, req.url); // Set up response headers setupServerActionHeaders(res); // Send server action request to worker options.worker.postMessage({ type: "SERVER_ACTION", id, args, } satisfies RscWorkerInputMessage); // Handle worker messages messageHandler = (message: any) => { if (message.type === "RSC_CHUNK") { passThrough.write(message.chunk); } else if (message.type === "RSC_END") { if (messageHandler) { cleanupServerAction(passThrough, options.worker!, messageHandler, res); } } else if (message.type === "ERROR") { if (messageHandler) { cleanupServerAction( passThrough, options.worker!, messageHandler, res, message.error, options.logger ); } } }; options.worker.on("message", messageHandler); // Handle errors passThrough.on("error", (error: unknown) => { if (messageHandler) { cleanupServerAction( passThrough, options.worker!, messageHandler, res, error, options.logger ); } }); } catch (error) { handleServerActionError(error, res, options.logger); } } /** * Client-side ViteDevServer-specific handler that delegates to worker */ export async function handleServerActionWithViteServer( req: IncomingMessage, res: ServerResponse, server: any, handlerOptions: { verbose?: boolean; projectRoot: string; worker?: Worker; } ): Promise<void> { return handleServerAction(req, res, { projectRoot: handlerOptions.projectRoot, verbose: handlerOptions.verbose, logger: server.config.customLogger || server.config.logger, worker: handlerOptions.worker, }); } // Re-export helper functions for the entry point export { parseServerActionRequest, parseServerActionRequestBody, createServerActionResponse, setupServerActionHeaders } from "./handleServerActionHelper.js"; export type { ServerActionRequest, ServerActionHandlerOptions } from "./handleServerActionHelper.js";