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
7575 `Executor ${displayKind} session is ready.`,
7676 `${primaryLabel}: ${primaryUrl}`,
7777 `${secondaryLabel}: ${secondaryUrl}`,
78− `OpenAPI: ${appendUrlPath(baseUrl, "docs")}`,
78+ `OpenAPI: ${appendUrlPath(baseUrl, "api/docs")}`,
7979 "",
8080 guidance,
8181 "Press Ctrl+C to stop.",
@@ -88,7 +88,7 @@ const renderSessionSummary = (kind: "web" | "mcp", baseUrl: string): string => { · 6 unmodified lines
8888
8989 const isServerReachable = async (baseUrl: string): Promise<boolean> => {
9090 try {
91− const res = await fetch(`${baseUrl}/docs`, { signal: AbortSignal.timeout(2000) });
91+ const res = await fetch(`${baseUrl}/api/docs`, { signal: AbortSignal.timeout(2000) });
9292 return res.ok;
9393 } catch {
9494 return false;
@@ -217,11 +217,7 @@ const runForegroundSession = (input: { kind: "web" | "mcp"; port: number }) => · 122 unmodified lines
217217 return handlers.mcp.handleRequest(request);
218218 }
219219
220− if (
221− url.pathname.startsWith("/v1/") ||
222− url.pathname.startsWith("/docs") ||
223− url.pathname === "/openapi.json"
224− ) {
220+ if (url.pathname.startsWith("/api/") || url.pathname === "/api") {
225221 return handlers.api.handler(request);
226222 }
227223
apps/local/src/server/api-handler.ts+0−15
@@ -1,15 +0,0 @@
1−import { getServerHandlers } from "./main";
2−
3−export 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−
12−export 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 @@
1−import { createMiddleware, createStart } from "@tanstack/react-start";
1+import { createStart } from "@tanstack/react-start";
22
3−const serverRequestMiddleware = createMiddleware({ type: "request" }).server(
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−
17−export const startInstance = createStart(() => ({
18− requestMiddleware: [serverRequestMiddleware],
19−}));
3+export const startInstance = createStart(() => ({}));
apps/local/vite.config.ts+76−1
@@ -1,5 +1,5 @@
11 import { readFileSync } from "node:fs";
2−import { defineConfig } from "vite";
2+import { defineConfig, type Plugin } from "vite";
33 import { tanstackStart } from "@tanstack/react-start/plugin/vite";
44 import react from "@vitejs/plugin-react";
55 import tailwindcss from "@tailwindcss/vite";
@@ -22,6 +22,80 @@ const EXECUTOR_GITHUB_URL = (rootPackage.homepage ?? repositoryUrl ?? "https://g · 16 unmodified lines
2222 .replace(/^git\+/, "")
2323 .replace(/\.git$/, "");
2424
25+/**
26+ * Vite plugin that forwards API and MCP requests to the Effect handlers
27+ * during development — replaces the TanStack Start request middleware.
28+ */
29+function 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+
2599 export default defineConfig({
26100 define: {
27101 "import.meta.env.VITE_APP_VERSION": JSON.stringify(EXECUTOR_VERSION),
@@ -49,5 +123,6 @@ export default defineConfig({ · 21 unmodified lines
49123 spa: { enabled: true },
50124 }),
51125 ...react(),
126+ executorApiPlugin(),
52127 ],
53128 });
packages/core/api/src/api.ts+2−3
@@ -23,9 +23,8 @@ export const CoreExecutorApi = HttpApi.make("executor") · 22 unmodified lines
2323 /**
2424 * Compose the core API with a plugin group.
2525 */
26−export const addGroup = <G extends HttpApiGroup.HttpApiGroup.Any>(
27− group: G,
28−) => CoreExecutorApi.add(group);
26+export const addGroup = <G extends HttpApiGroup.HttpApiGroup.Any>(group: G) =>
27+ CoreExecutorApi.add(group);
2928
3029 /** Default API with no plugin groups */
3130 export const ExecutorApi = CoreExecutorApi;