Files
bknd/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts
dswbx c3ae4a3999 fix cloudflare adapter warm mode and type improvements
Adds warm mode support to cloudflare adapter to enable app instance caching. Improves BkndConfig type handling and makes hybrid mode reader optional. Fixes entity hook types to properly handle Generated ID types from kysely.
2025-11-21 17:35:45 +01:00

109 lines
3.3 KiB
TypeScript

/// <reference types="@cloudflare/workers-types" />
import type { RuntimeBkndConfig } from "bknd/adapter";
import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers";
import type { App, MaybePromise } from "bknd";
import { $console } from "bknd/utils";
import { createRuntimeApp } from "bknd/adapter";
import { registerAsyncsExecutionContext, makeConfig, type CloudflareContext } from "./config";
declare global {
namespace Cloudflare {
interface Env {}
}
}
export type CloudflareEnv = Cloudflare.Env;
export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> & {
bindings?: (args: Env) => MaybePromise<{
kv?: KVNamespace;
db?: D1Database;
}>;
d1?: {
session?: boolean;
transport?: "header" | "cookie";
first?: D1SessionConstraint;
};
static?: "kv" | "assets";
key?: string;
keepAliveSeconds?: number;
forceHttps?: boolean;
manifest?: string;
registerMedia?: boolean | ((env: Env) => void);
};
export async function createApp<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env> = {},
ctx: Partial<CloudflareContext<Env>> = {},
) {
const appConfig = await makeConfig(config, ctx);
return await createRuntimeApp<Env>(
{
...appConfig,
onBuilt: async (app) => {
if (ctx.ctx) {
registerAsyncsExecutionContext(app, ctx?.ctx);
}
await appConfig.onBuilt?.(app);
},
},
ctx?.env,
);
}
// compatiblity
export const getFresh = createApp;
let app: App | undefined;
export function serve<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env> = {},
serveOptions?: (args: Env) => {
warm?: boolean;
},
) {
return {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
if (config.manifest && config.static === "assets") {
$console.warn("manifest is not useful with static 'assets'");
} else if (!config.manifest && config.static === "kv") {
throw new Error("manifest is required with static 'kv'");
}
if (config.manifest && config.static === "kv") {
const pathname = url.pathname.slice(1);
const assetManifest = JSON.parse(config.manifest);
if (pathname && pathname in assetManifest) {
const hono = new Hono();
hono.all("*", async (c, next) => {
const res = await serveStatic({
path: `./${pathname}`,
manifest: config.manifest!,
})(c as any, next);
if (res instanceof Response) {
const ttl = 60 * 60 * 24 * 365;
res.headers.set("Cache-Control", `public, max-age=${ttl}`);
return res;
}
return c.notFound();
});
return hono.fetch(request, env);
}
}
const { warm } = serveOptions?.(env) ?? {};
if (!app || warm !== true) {
const context = { request, env, ctx } as CloudflareContext<Env>;
app = await createApp(config, context);
}
return app.fetch(request, env, ctx);
},
};
}