fix: updated cloudflare adapter to use runtime config, aligned vite

This commit is contained in:
dswbx
2025-04-05 18:03:58 +02:00
parent 2c29e06fb8
commit 7e1757b7f4
10 changed files with 77 additions and 90 deletions

View File

@@ -3,7 +3,7 @@
"type": "module",
"sideEffects": false,
"bin": "./dist/cli/index.js",
"version": "0.11.0-rc.2",
"version": "0.11.0-rc.5",
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
"homepage": "https://bknd.io",
"repository": {

View File

@@ -180,7 +180,10 @@ export class App {
registerAdminController(config?: AdminControllerOptions) {
// register admin
this.adminController = new AdminController(this, config);
this.modules.server.route(config?.basepath ?? "/", this.adminController.getController());
this.modules.server.route(
this.adminController.basepath,
this.adminController.getController(),
);
return this;
}

View File

@@ -43,7 +43,7 @@ export async function createApp<Env extends AwsLambdaEnv = AwsLambdaEnv>(
case "url":
additional.adminOptions = {
...(typeof adminOptions === "object" ? adminOptions : {}),
assets_path: assets.url,
assetsPath: assets.url,
};
break;
default:

View File

@@ -1,14 +1,15 @@
/// <reference types="@cloudflare/workers-types" />
import type { FrameworkBkndConfig } from "bknd/adapter";
import type { RuntimeBkndConfig } from "bknd/adapter";
import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers";
import { getFresh } from "./modes/fresh";
import { getCached } from "./modes/cached";
import { getDurable } from "./modes/durable";
import { getFresh, getWarm } from "./modes/fresh";
import type { App } from "bknd";
export type CloudflareEnv = object;
export type CloudflareBkndConfig<Env = CloudflareEnv> = FrameworkBkndConfig<Env> & {
export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> & {
mode?: "warm" | "fresh" | "cache" | "durable";
bindings?: (args: Env) => {
kv?: KVNamespace;
@@ -20,8 +21,6 @@ export type CloudflareBkndConfig<Env = CloudflareEnv> = FrameworkBkndConfig<Env>
keepAliveSeconds?: number;
forceHttps?: boolean;
manifest?: string;
setAdminHtml?: boolean;
html?: string;
};
export type Context<Env = CloudflareEnv> = {
@@ -43,7 +42,7 @@ export function serve<Env extends CloudflareEnv = CloudflareEnv>(
throw new Error("manifest is required with static 'kv'");
}
if (config.manifest && config.static !== "assets") {
if (config.manifest && config.static === "kv") {
const pathname = url.pathname.slice(1);
const assetManifest = JSON.parse(config.manifest);
if (pathname && pathname in assetManifest) {
@@ -70,18 +69,24 @@ export function serve<Env extends CloudflareEnv = CloudflareEnv>(
const context = { request, env, ctx } as Context<Env>;
const mode = config.mode ?? "warm";
let app: App;
switch (mode) {
case "fresh":
return await getFresh(config, context);
app = await getFresh(config, context, { force: true });
break;
case "warm":
return await getWarm(config, context);
app = await getFresh(config, context);
break;
case "cache":
return await getCached(config, context);
app = await getCached(config, context);
break;
case "durable":
return await getDurable(config, context);
default:
throw new Error(`Unknown mode ${mode}`);
}
return app.fetch(request, env, ctx);
},
};
}

View File

@@ -1,7 +1,7 @@
import { D1Connection, type D1ConnectionConfig } from "./D1Connection";
export * from "./cloudflare-workers.adapter";
export { makeApp, getFresh, getWarm } from "./modes/fresh";
export { makeApp, getFresh } from "./modes/fresh";
export { getCached } from "./modes/cached";
export { DurableBkndApp, getDurable } from "./modes/durable";
export { D1Connection, type D1ConnectionConfig };

View File

@@ -40,7 +40,6 @@ export async function getCached<Env extends CloudflareEnv = CloudflareEnv>(
);
await config.beforeBuild?.(app);
},
adminOptions: { html: config.html },
},
{ env, ctx, ...args },
);

View File

@@ -25,9 +25,7 @@ export async function getDurable<Env extends CloudflareEnv = CloudflareEnv>(
const res = await stub.fire(ctx.request, {
config: create_config,
html: config.html,
keepAliveSeconds: config.keepAliveSeconds,
setAdminHtml: config.setAdminHtml,
});
const headers = new Headers(res.headers);
@@ -110,6 +108,7 @@ export class DurableBkndApp extends DurableObject {
}
async onBuilt(app: App) {}
async beforeBuild(app: App) {}
protected keepAlive(seconds: number) {

View File

@@ -7,22 +7,15 @@ export async function makeApp<Env extends CloudflareEnv = CloudflareEnv>(
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
return await createRuntimeApp<Env>(
{
...makeConfig(config, args),
adminOptions: config.html ? { html: config.html } : undefined,
},
args,
opts,
);
return await createRuntimeApp<Env>(makeConfig(config, args), args, opts);
}
export async function getWarm<Env extends CloudflareEnv = CloudflareEnv>(
export async function getFresh<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env>,
ctx: Context<Env>,
opts: RuntimeOptions = {},
) {
const app = await makeApp(
return await makeApp(
{
...config,
onBuilt: async (app) => {
@@ -33,16 +26,4 @@ export async function getWarm<Env extends CloudflareEnv = CloudflareEnv>(
ctx.env,
opts,
);
return app.fetch(ctx.request);
}
export async function getFresh<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env>,
ctx: Context<Env>,
opts: RuntimeOptions = {},
) {
return await getWarm(config, ctx, {
...opts,
force: true,
});
}

View File

@@ -1,18 +1,24 @@
import { serveStatic } from "@hono/node-server/serve-static";
import { type DevServerOptions, default as honoViteDevServer } from "@hono/vite-dev-server";
import {
type DevServerOptions,
default as honoViteDevServer,
} from "@hono/vite-dev-server";
import type { App } from "bknd";
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
import {
type RuntimeBkndConfig,
createRuntimeApp,
type FrameworkOptions,
} from "bknd/adapter";
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { devServerConfig } from "./dev-server-config";
export type ViteBkndConfig<Env = any> = RuntimeBkndConfig<Env> & {
mode?: "cached" | "fresh";
setAdminHtml?: boolean;
forceDev?: boolean | { mainPath: string };
html?: string;
};
export type ViteEnv = NodeJS.ProcessEnv;
export type ViteBkndConfig<Env = ViteEnv> = RuntimeBkndConfig<Env> & {};
export function addViteScript(html: string, addBkndContext: boolean = true) {
export function addViteScript(
html: string,
addBkndContext: boolean = true,
) {
return html.replace(
"</head>",
`<script type="module">
@@ -28,52 +34,40 @@ ${addBkndContext ? "<!-- BKND_CONTEXT -->" : ""}
);
}
async function createApp(config: ViteBkndConfig = {}, env?: any) {
async function createApp<ViteEnv>(
config: ViteBkndConfig<ViteEnv> = {},
env: ViteEnv = {} as ViteEnv,
opts: FrameworkOptions = {},
): Promise<App> {
registerLocalMediaAdapter();
return await createRuntimeApp(
{
...config,
adminOptions:
config.setAdminHtml === false
? undefined
: {
html: config.html,
forceDev: config.forceDev ?? {
mainPath: "/src/main.tsx",
},
},
adminOptions: config.adminOptions ?? {
forceDev: {
mainPath: "/src/main.tsx",
},
},
serveStatic: ["/assets/*", serveStatic({ root: config.distPath ?? "./" })],
},
env,
opts,
);
}
export function serveFresh(config: Omit<ViteBkndConfig, "mode"> = {}) {
export function serve<ViteEnv>(
config: ViteBkndConfig<ViteEnv> = {},
args?: ViteEnv,
opts?: FrameworkOptions,
) {
return {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
const app = await createApp(config, env);
const app = await createApp(config, env, opts);
return app.fetch(request, env, ctx);
},
};
}
let app: App;
export function serveCached(config: Omit<ViteBkndConfig, "mode"> = {}) {
return {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
if (!app) {
app = await createApp(config, env);
}
return app.fetch(request, env, ctx);
},
};
}
export function serve({ mode, ...config }: ViteBkndConfig = {}) {
return mode === "fresh" ? serveFresh(config) : serveCached(config);
}
export function devServer(options: DevServerOptions) {
return honoViteDevServer({
...devServerConfig,

View File

@@ -14,10 +14,11 @@ const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
// @todo: add migration to remove admin path from config
export type AdminControllerOptions = {
basepath?: string;
assets_path?: string;
adminBasepath?: string;
assetsPath?: string;
html?: string;
forceDev?: boolean | { mainPath: string };
debug_rerenders?: boolean;
debugRerenders?: boolean;
};
export class AdminController extends Controller {
@@ -36,7 +37,8 @@ export class AdminController extends Controller {
return {
...this._options,
basepath: this._options.basepath ?? "/",
assets_path: this._options.assets_path ?? config.server.assets_path,
adminBasepath: this._options.adminBasepath ?? "",
assetsPath: this._options.assetsPath ?? config.server.assets_path,
};
}
@@ -48,6 +50,10 @@ export class AdminController extends Controller {
return (this.basepath + route).replace(/(?<!:)\/+/g, "/");
}
private withAdminBasePath(route: string = "") {
return this.withBasePath(this.options.adminBasepath + route);
}
override getController() {
const { auth: authMiddleware, permission } = this.middlewares;
const hono = this.create().use(
@@ -63,16 +69,16 @@ export class AdminController extends Controller {
const authRoutes = {
root: "/",
success: configs.auth.cookie.pathSuccess ?? "/",
loggedOut: configs.auth.cookie.pathLoggedOut ?? "/",
login: "/auth/login",
logout: "/auth/logout",
success: configs.auth.cookie.pathSuccess ?? this.withAdminBasePath("/"),
loggedOut: configs.auth.cookie.pathLoggedOut ?? this.withAdminBasePath("/"),
login: this.withAdminBasePath("/auth/login"),
logout: this.withAdminBasePath("/auth/logout"),
};
hono.use("*", async (c, next) => {
const obj = {
user: c.get("auth")?.user,
logout_route: this.withBasePath(authRoutes.logout),
logout_route: this.withAdminBasePath(authRoutes.logout),
};
const html = await this.getHtml(obj);
if (!html) {
@@ -164,8 +170,8 @@ export class AdminController extends Controller {
if (isProd) {
let manifest: any;
if (this.options.assets_path.startsWith("http")) {
manifest = await fetch(this.options.assets_path + "manifest.json", {
if (this.options.assetsPath.startsWith("http")) {
manifest = await fetch(this.options.assetsPath + "manifest.json", {
headers: {
Accept: "application/json",
},
@@ -182,7 +188,7 @@ export class AdminController extends Controller {
assets.css = manifest["src/ui/main.tsx"].css[0] as any;
}
const favicon = isProd ? this.options.assets_path + "favicon.ico" : "/favicon.ico";
const favicon = isProd ? this.options.assetsPath + "favicon.ico" : "/favicon.ico";
return (
<Fragment>
@@ -197,7 +203,7 @@ export class AdminController extends Controller {
/>
<link rel="icon" href={favicon} type="image/x-icon" />
<title>BKND</title>
{this.options.debug_rerenders && (
{this.options.debugRerenders && (
<script
crossOrigin="anonymous"
src="//unpkg.com/react-scan/dist/auto.global.js"
@@ -205,8 +211,8 @@ export class AdminController extends Controller {
)}
{isProd ? (
<Fragment>
<script type="module" src={this.options.assets_path + assets?.js} />
<link rel="stylesheet" href={this.options.assets_path + assets?.css} />
<script type="module" src={this.options.assetsPath + assets?.js} />
<link rel="stylesheet" href={this.options.assetsPath + assets?.css} />
</Fragment>
) : (
<Fragment>