///
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 = RuntimeBkndConfig & {
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(
config: CloudflareBkndConfig = {},
ctx: Partial> = {},
) {
const appConfig = await makeConfig(config, ctx);
return await createRuntimeApp(
{
...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(
config: CloudflareBkndConfig = {},
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;
app = await createApp(config, context);
}
return app.fetch(request, env, ctx);
},
};
}