mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
feat: add fate vite integration
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
/.fate
|
||||
/.react-router
|
||||
/.cache
|
||||
/.data
|
||||
|
||||
@@ -552,6 +552,8 @@ Do not add project-level wrappers such as `HeadplaneLivePublisher`, `HeadplaneDa
|
||||
- Added a plain Vite SPA `index.html` and `app/spa` entry with TanStack Router and a raw Fate client shell using `createClient` + `createHTTPTransport`.
|
||||
- Moved the old React Router Vite config to `vite-old.config.ts` and replaced `vite.config.ts` with a clean SPA config.
|
||||
- Added Hono and `@hono/node-server`, plus a minimal Hono server shell in `app/server/hono-app.ts`, `app/server/hono-dev.ts`, and `app/server/hono-main.ts`.
|
||||
- Added `app/server/fate.ts`, which exports a raw Fate server and live bus. Hono mounts Fate's `createHonoFateHandler(fate)` at `/admin/fate` and `/admin/fate/*`, passing the existing app context through Hono variables.
|
||||
- Wired the official `react-fate/vite` plugin to `app/server/fate.ts`, ignored generated `.fate/` output, and made `pnpm run typecheck` run `fate generate` before `tsgo` so generated `react-fate/client` typings exist from a clean checkout.
|
||||
- `pnpm dev` now runs the Hono/Vite middleware shell with local `.data` storage for the example config; `pnpm build` now runs `vite build` for the SPA.
|
||||
- Fate's Drizzle peer currently warns against the repo's Drizzle `1.0.0-beta.21`; avoid Fate's Drizzle adapter until that compatibility is resolved, and start with a direct Headscale source/resolver instead.
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
createFateServer,
|
||||
createLiveEventBus,
|
||||
type SourceDefinition,
|
||||
type SourceRegistry,
|
||||
} from "@nkzw/fate/server";
|
||||
import type { Context } from "hono";
|
||||
|
||||
import type { AppContext } from "./context";
|
||||
import type { RuntimeApiClient } from "./headscale/api/endpoints";
|
||||
import type { Principal } from "./web/auth";
|
||||
|
||||
export interface HonoFateEnv {
|
||||
Variables: {
|
||||
appContext: AppContext;
|
||||
};
|
||||
}
|
||||
|
||||
interface FateContext {
|
||||
api: RuntimeApiClient;
|
||||
app: AppContext;
|
||||
principal: Principal;
|
||||
request: Request;
|
||||
}
|
||||
|
||||
type FateAdapterContext = Context<HonoFateEnv>;
|
||||
|
||||
export const live = createLiveEventBus();
|
||||
|
||||
export const roots = {};
|
||||
const queries = {};
|
||||
const lists = {};
|
||||
const mutations = {};
|
||||
|
||||
const registry = new Map() as SourceRegistry<FateContext>;
|
||||
|
||||
export const fate = createFateServer<
|
||||
FateContext,
|
||||
typeof roots,
|
||||
typeof queries,
|
||||
typeof lists,
|
||||
typeof mutations,
|
||||
FateAdapterContext
|
||||
>({
|
||||
context: async ({ adapterContext, request }) => {
|
||||
if (!adapterContext) {
|
||||
throw new Error("Fate request is missing Hono context");
|
||||
}
|
||||
|
||||
const app = adapterContext.get("appContext");
|
||||
const principal = await app.auth.require(request);
|
||||
const api = app.hsApi.getRuntimeClient(app.auth.getHeadscaleApiKey(principal));
|
||||
|
||||
return {
|
||||
api,
|
||||
app,
|
||||
principal,
|
||||
request,
|
||||
};
|
||||
},
|
||||
live,
|
||||
roots,
|
||||
sources: {
|
||||
registry,
|
||||
getSource(target) {
|
||||
return target as SourceDefinition;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export type { FateAdapterContext, FateContext };
|
||||
+11
-3
@@ -1,9 +1,11 @@
|
||||
import { versions } from "node:process";
|
||||
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { createHonoFateHandler } from "@nkzw/fate/server";
|
||||
import { Hono, type Context } from "hono";
|
||||
|
||||
import type { AppContext } from "./context";
|
||||
import { fate, type HonoFateEnv } from "./fate";
|
||||
|
||||
interface HonoAppOptions {
|
||||
context: AppContext;
|
||||
@@ -12,7 +14,13 @@ interface HonoAppOptions {
|
||||
}
|
||||
|
||||
export function createHeadplaneHonoApp({ context, prefix, staticRoot }: HonoAppOptions) {
|
||||
const app = new Hono();
|
||||
const app = new Hono<HonoFateEnv>();
|
||||
const fateHandler = createHonoFateHandler(fate);
|
||||
|
||||
app.use("*", async (c, next) => {
|
||||
c.set("appContext", context);
|
||||
await next();
|
||||
});
|
||||
|
||||
const health = async (c: Context) => {
|
||||
const api = context.hsApi.getRuntimeClient("fake-api-key");
|
||||
@@ -86,8 +94,8 @@ export function createHeadplaneHonoApp({ context, prefix, staticRoot }: HonoAppO
|
||||
}
|
||||
});
|
||||
|
||||
app.all(`${prefix}/fate`, (c) => c.json({ error: "Fate server is not mounted yet" }, 501));
|
||||
app.all(`${prefix}/fate/*`, (c) => c.json({ error: "Fate server is not mounted yet" }, 501));
|
||||
app.all(`${prefix}/fate`, fateHandler);
|
||||
app.all(`${prefix}/fate/*`, fateHandler);
|
||||
|
||||
if (staticRoot) {
|
||||
const stripPrefix = (path: string) => path.slice(prefix.length) || "/";
|
||||
|
||||
+5
-8
@@ -1,15 +1,12 @@
|
||||
import { RouterProvider } from "@tanstack/react-router";
|
||||
import { FateClient, createClient, createHTTPTransport } from "react-fate";
|
||||
import { FateClient } from "react-fate";
|
||||
import { createFateClient } from "react-fate/client";
|
||||
|
||||
import { router } from "./router";
|
||||
|
||||
const fate = createClient({
|
||||
roots: {},
|
||||
transport: createHTTPTransport({
|
||||
fetch: (input, init) => fetch(input, { ...init, credentials: "include" }),
|
||||
url: `${__PREFIX__}/fate`,
|
||||
}),
|
||||
types: [],
|
||||
const fate = createFateClient({
|
||||
fetch: (input, init) => fetch(input, { ...init, credentials: "include" }),
|
||||
url: `${__PREFIX__}/fate`,
|
||||
});
|
||||
|
||||
export function App() {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"build": "vite build",
|
||||
"dev": "HEADPLANE_CONFIG_PATH=./config.example.yaml HEADPLANE_SERVER__DATA_PATH=./.data tsx app/server/hono-dev.ts",
|
||||
"start": "tsx app/server/hono-main.ts",
|
||||
"typecheck": "react-router typegen && tsgo",
|
||||
"typecheck": "fate generate && react-router typegen && tsgo",
|
||||
"test:unit": "vitest run --project unit",
|
||||
"test:integration": "vitest run --project integration:*",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
|
||||
+7
-1
@@ -1,5 +1,11 @@
|
||||
{
|
||||
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
|
||||
"include": [
|
||||
"**/*",
|
||||
"**/.server/**/*",
|
||||
"**/.client/**/*",
|
||||
".fate/**/*",
|
||||
".react-router/types/**/*"
|
||||
],
|
||||
"exclude": ["docs"],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2023"],
|
||||
|
||||
+9
-1
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { fate } from "react-fate/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import { parse } from "yaml";
|
||||
|
||||
@@ -41,7 +42,14 @@ const { server } = parse(config);
|
||||
export default defineConfig({
|
||||
appType: "spa",
|
||||
base: `${PREFIX}/`,
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [
|
||||
fate({
|
||||
module: "./app/server/fate.ts",
|
||||
transport: "native",
|
||||
}),
|
||||
react(),
|
||||
tailwindcss(),
|
||||
],
|
||||
server: {
|
||||
host: server.host,
|
||||
port: server.port,
|
||||
|
||||
Reference in New Issue
Block a user