History

simplify server

Rhys Sullivan committed 7 months ago

Commit 459f5b01 parent 91383495 files · +83 −44Browse files

Unified · Split

5 files
  1. apps/cli/src/main.ts +3 −7
  2. apps/local/src/server/api-handler.ts +0 −15
  3. apps/local/src/start.ts +2 −18
  4. apps/local/vite.config.ts +76 −1
  5. packages/core/api/src/api.ts +2 −3
apps/cli/src/main.ts+3−7
@@ -75,7 +75,7 @@ const renderSessionSummary = (kind: "web" | "mcp", baseUrl: string): string => { · 74 unmodified lines
75 `Executor ${displayKind} session is ready.`,75 `Executor ${displayKind} session is ready.`,
76 `${primaryLabel}: ${primaryUrl}`,76 `${primaryLabel}: ${primaryUrl}`,
77 `${secondaryLabel}: ${secondaryUrl}`,77 `${secondaryLabel}: ${secondaryUrl}`,
78 `OpenAPI: ${appendUrlPath(baseUrl, "docs")}`,78 `OpenAPI: ${appendUrlPath(baseUrl, "api/docs")}`,
79 "",79 "",
80 guidance,80 guidance,
81 "Press Ctrl+C to stop.",81 "Press Ctrl+C to stop.",
@@ -88,7 +88,7 @@ const renderSessionSummary = (kind: "web" | "mcp", baseUrl: string): string => { · 6 unmodified lines
88 88
89const isServerReachable = async (baseUrl: string): Promise<boolean> => {89const isServerReachable = async (baseUrl: string): Promise<boolean> => {
90 try {90 try {
91 const res = await fetch(`${baseUrl}/docs`, { signal: AbortSignal.timeout(2000) });91 const res = await fetch(`${baseUrl}/api/docs`, { signal: AbortSignal.timeout(2000) });
92 return res.ok;92 return res.ok;
93 } catch {93 } catch {
94 return false;94 return false;
@@ -217,11 +217,7 @@ const runForegroundSession = (input: { kind: "web" | "mcp"; port: number }) => · 122 unmodified lines
217 return handlers.mcp.handleRequest(request);217 return handlers.mcp.handleRequest(request);
218 }218 }
219 219
220 if (220 if (url.pathname.startsWith("/api/") || url.pathname === "/api") {
221 url.pathname.startsWith("/v1/") ||
222 url.pathname.startsWith("/docs") ||
223 url.pathname === "/openapi.json"
224 ) {
225 return handlers.api.handler(request);221 return handlers.api.handler(request);
226 }222 }
227 223
apps/local/src/server/api-handler.ts+0−15
@@ -1,15 +0,0 @@
1import { getServerHandlers } from "./main";
2
3export const handleApiRequest = async (request: Request): Promise<Response> => {
4 const handlers = await getServerHandlers();
5 // Strip /api prefix — Start request middleware forwards /api/* here,
6 // but the Effect handler endpoints are defined without the prefix.
7 const url = new URL(request.url);
8 url.pathname = url.pathname.replace(/^\/api/, "");
9 return handlers.api.handler(new Request(url, request));
10};
11
12export const handleMcpRequest = async (request: Request): Promise<Response> => {
13 const handlers = await getServerHandlers();
14 return handlers.mcp.handleRequest(request);
15};
apps/local/src/start.ts+2−18
@@ -1,19 +1,3 @@
1import { createMiddleware, createStart } from "@tanstack/react-start";1import { createStart } from "@tanstack/react-start";
2 2
3const serverRequestMiddleware = createMiddleware({ type: "request" }).server(3export const startInstance = createStart(() => ({}));
4 async ({ pathname, request, next }) => {
5 if (pathname === "/api" || pathname.startsWith("/api/")) {
6 const { handleApiRequest } = await import("./server/api-handler");
7 return handleApiRequest(request);
8 }
9 if (pathname === "/mcp" || pathname.startsWith("/mcp/")) {
10 const { handleMcpRequest } = await import("./server/api-handler");
11 return handleMcpRequest(request);
12 }
13 return next();
14 },
15);
16
17export const startInstance = createStart(() => ({
18 requestMiddleware: [serverRequestMiddleware],
19}));
apps/local/vite.config.ts+76−1
@@ -1,5 +1,5 @@
1import { readFileSync } from "node:fs";1import { readFileSync } from "node:fs";
2import { defineConfig } from "vite";2import { defineConfig, type Plugin } from "vite";
3import { tanstackStart } from "@tanstack/react-start/plugin/vite";3import { tanstackStart } from "@tanstack/react-start/plugin/vite";
4import react from "@vitejs/plugin-react";4import react from "@vitejs/plugin-react";
5import tailwindcss from "@tailwindcss/vite";5import tailwindcss from "@tailwindcss/vite";
@@ -22,6 +22,80 @@ const EXECUTOR_GITHUB_URL = (rootPackage.homepage ?? repositoryUrl ?? "https://g · 16 unmodified lines
22 .replace(/^git\+/, "")22 .replace(/^git\+/, "")
23 .replace(/\.git$/, "");23 .replace(/\.git$/, "");
24 24
25/**
26 * Vite plugin that forwards API and MCP requests to the Effect handlers
27 * during development — replaces the TanStack Start request middleware.
28 */
29function executorApiPlugin(): Plugin {
30 let handlers: import("./src/server/main").ServerHandlers | null = null;
31
32 return {
33 name: "executor-api",
34 configureServer(server) {
35 server.middlewares.use(async (req, res, next) => {
36 const rawUrl = req.url ?? "/";
37 const isApi = rawUrl.startsWith("/api/") || rawUrl === "/api";
38 const isMcp = rawUrl.startsWith("/mcp");
39
40 if (!isApi && !isMcp) return next();
41
42 // Strip /api prefix — Effect handlers define routes without it
43 const url = isApi ? (rawUrl.slice("/api".length) || "/") : rawUrl;
44
45 try {
46 if (!handlers) {
47 const { getServerHandlers } = await import("./src/server/main");
48 handlers = await getServerHandlers();
49 }
50
51 // Convert Node request → Web Request
52 const origin = `http://${req.headers.host ?? "localhost"}`;
53 const headers = new Headers();
54 for (const [key, value] of Object.entries(req.headers)) {
55 if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
56 }
57
58 const hasBody = req.method !== "GET" && req.method !== "HEAD";
59 const webRequest = new Request(new URL(url, origin), {
60 method: req.method,
61 headers,
62 body: hasBody ? (req as unknown as BodyInit) : undefined,
63 duplex: hasBody ? "half" : undefined,
64 } as RequestInit);
65
66 const response = isMcp
67 ? await handlers.mcp.handleRequest(webRequest)
68 : await handlers.api.handler(webRequest);
69
70 res.statusCode = response.status;
71 response.headers.forEach((v, k) => res.setHeader(k, v));
72
73 if (response.body) {
74 const reader = response.body.getReader();
75 const pump = async () => {
76 while (true) {
77 const { done, value } = await reader.read();
78 if (done) break;
79 res.write(value);
80 }
81 res.end();
82 };
83 await pump();
84 } else {
85 res.end();
86 }
87 } catch (err) {
88 console.error("[executor-api]", err);
89 if (!res.headersSent) {
90 res.statusCode = 500;
91 res.end("Internal Server Error");
92 }
93 }
94 });
95 },
96 };
97}
98
25export default defineConfig({99export default defineConfig({
26 define: {100 define: {
27 "import.meta.env.VITE_APP_VERSION": JSON.stringify(EXECUTOR_VERSION),101 "import.meta.env.VITE_APP_VERSION": JSON.stringify(EXECUTOR_VERSION),
@@ -49,5 +123,6 @@ export default defineConfig({ · 21 unmodified lines
49 spa: { enabled: true },123 spa: { enabled: true },
50 }),124 }),
51 ...react(),125 ...react(),
126 executorApiPlugin(),
52 ],127 ],
53});128});
packages/core/api/src/api.ts+2−3
@@ -23,9 +23,8 @@ export const CoreExecutorApi = HttpApi.make("executor") · 22 unmodified lines
23/**23/**
24 * Compose the core API with a plugin group.24 * Compose the core API with a plugin group.
25 */25 */
26export const addGroup = <G extends HttpApiGroup.HttpApiGroup.Any>(26export const addGroup = <G extends HttpApiGroup.HttpApiGroup.Any>(group: G) =>
27 group: G,27 CoreExecutorApi.add(group);
28) => CoreExecutorApi.add(group);
29 28
30/** Default API with no plugin groups */29/** Default API with no plugin groups */
31export const ExecutorApi = CoreExecutorApi;30export const ExecutorApi = CoreExecutorApi;