mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #245 from bknd-io/fix/cf-instance-flash
cloudflare: fixing multiple instances competing with configuration state by always serving fresh
This commit is contained in:
@@ -53,7 +53,7 @@ export function adapterTestSuite<
|
|||||||
url: overrides.dbUrl ?? ":memory:",
|
url: overrides.dbUrl ?? ":memory:",
|
||||||
origin: "localhost",
|
origin: "localhost",
|
||||||
} as any,
|
} as any,
|
||||||
{ id },
|
{ force: false, id },
|
||||||
);
|
);
|
||||||
expect(app).toBeDefined();
|
expect(app).toBeDefined();
|
||||||
expect(app.toJSON().server.cors.origin).toEqual("localhost");
|
expect(app.toJSON().server.cors.origin).toEqual("localhost");
|
||||||
@@ -69,7 +69,7 @@ export function adapterTestSuite<
|
|||||||
};
|
};
|
||||||
|
|
||||||
test("responds with the same app id", async () => {
|
test("responds with the same app id", async () => {
|
||||||
const fetcher = makeHandler(undefined, undefined, { id });
|
const fetcher = makeHandler(undefined, undefined, { force: false, id });
|
||||||
|
|
||||||
const { res, data } = await getConfig(fetcher);
|
const { res, data } = await getConfig(fetcher);
|
||||||
expect(res.ok).toBe(true);
|
expect(res.ok).toBe(true);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
||||||
import { makeApp } from "./modes/fresh";
|
import { makeConfig, type CloudflareContext } from "./config";
|
||||||
import { makeConfig, type CfMakeConfigArgs } from "./config";
|
|
||||||
import { disableConsoleLog, enableConsoleLog } from "core/utils";
|
import { disableConsoleLog, enableConsoleLog } from "core/utils";
|
||||||
import { adapterTestSuite } from "adapter/adapter-test-suite";
|
import { adapterTestSuite } from "adapter/adapter-test-suite";
|
||||||
import { bunTestRunner } from "adapter/bun/test";
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import type { CloudflareBkndConfig } from "./cloudflare-workers.adapter";
|
import { type CloudflareBkndConfig, createApp } from "./cloudflare-workers.adapter";
|
||||||
|
|
||||||
beforeAll(disableConsoleLog);
|
beforeAll(disableConsoleLog);
|
||||||
afterAll(enableConsoleLog);
|
afterAll(enableConsoleLog);
|
||||||
@@ -41,18 +40,19 @@ describe("cf adapter", () => {
|
|||||||
expect(dynamicConfig.connection).toBeDefined();
|
expect(dynamicConfig.connection).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
adapterTestSuite<CloudflareBkndConfig, CfMakeConfigArgs<any>>(bunTestRunner, {
|
adapterTestSuite<CloudflareBkndConfig, CloudflareContext<any>>(bunTestRunner, {
|
||||||
makeApp: async (c, a, o) => {
|
makeApp: async (c, a, o) => {
|
||||||
return await makeApp(c, { env: a } as any, o);
|
return await createApp(c, { env: a } as any, o);
|
||||||
},
|
},
|
||||||
makeHandler: (c, a, o) => {
|
makeHandler: (c, a, o) => {
|
||||||
|
console.log("args", a);
|
||||||
return async (request: any) => {
|
return async (request: any) => {
|
||||||
const app = await makeApp(
|
const app = await createApp(
|
||||||
// needs a fallback, otherwise tries to launch D1
|
// needs a fallback, otherwise tries to launch D1
|
||||||
c ?? {
|
c ?? {
|
||||||
connection: { url: DB_URL },
|
connection: { url: DB_URL },
|
||||||
},
|
},
|
||||||
a!,
|
a as any,
|
||||||
o,
|
o,
|
||||||
);
|
);
|
||||||
return app.fetch(request);
|
return app.fetch(request);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
import type { RuntimeBkndConfig } from "bknd/adapter";
|
import type { RuntimeBkndConfig } from "bknd/adapter";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { serveStatic } from "hono/cloudflare-workers";
|
import { serveStatic } from "hono/cloudflare-workers";
|
||||||
import { getFresh } from "./modes/fresh";
|
import type { MaybePromise } from "bknd";
|
||||||
import { getCached } from "./modes/cached";
|
|
||||||
import type { App, MaybePromise } from "bknd";
|
|
||||||
import { $console } from "bknd/utils";
|
import { $console } from "bknd/utils";
|
||||||
|
import { createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
|
||||||
|
import { registerAsyncsExecutionContext, makeConfig, type CloudflareContext } from "./config";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Cloudflare {
|
namespace Cloudflare {
|
||||||
@@ -16,7 +16,6 @@ declare global {
|
|||||||
|
|
||||||
export type CloudflareEnv = Cloudflare.Env;
|
export type CloudflareEnv = Cloudflare.Env;
|
||||||
export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> & {
|
export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> & {
|
||||||
mode?: "warm" | "fresh" | "cache";
|
|
||||||
bindings?: (args: Env) => MaybePromise<{
|
bindings?: (args: Env) => MaybePromise<{
|
||||||
kv?: KVNamespace;
|
kv?: KVNamespace;
|
||||||
db?: D1Database;
|
db?: D1Database;
|
||||||
@@ -34,11 +33,31 @@ export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> &
|
|||||||
registerMedia?: boolean | ((env: Env) => void);
|
registerMedia?: boolean | ((env: Env) => void);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Context<Env = CloudflareEnv> = {
|
export async function createApp<Env extends CloudflareEnv = CloudflareEnv>(
|
||||||
request: Request;
|
config: CloudflareBkndConfig<Env>,
|
||||||
env: Env;
|
ctx: Partial<CloudflareContext<Env>> = {},
|
||||||
ctx: ExecutionContext;
|
opts: RuntimeOptions = {
|
||||||
};
|
// by default, require the app to be rebuilt every time
|
||||||
|
force: true,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const appConfig = await makeConfig(
|
||||||
|
{
|
||||||
|
...config,
|
||||||
|
onBuilt: async (app) => {
|
||||||
|
if (ctx.ctx) {
|
||||||
|
registerAsyncsExecutionContext(app, ctx?.ctx);
|
||||||
|
}
|
||||||
|
await config.onBuilt?.(app);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
return await createRuntimeApp<Env>(appConfig, ctx?.env, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compatiblity
|
||||||
|
export const getFresh = createApp;
|
||||||
|
|
||||||
export function serve<Env extends CloudflareEnv = CloudflareEnv>(
|
export function serve<Env extends CloudflareEnv = CloudflareEnv>(
|
||||||
config: CloudflareBkndConfig<Env> = {},
|
config: CloudflareBkndConfig<Env> = {},
|
||||||
@@ -77,23 +96,8 @@ export function serve<Env extends CloudflareEnv = CloudflareEnv>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = { request, env, ctx } as Context<Env>;
|
const context = { request, env, ctx } as CloudflareContext<Env>;
|
||||||
const mode = config.mode ?? "warm";
|
const app = await createApp(config, context);
|
||||||
|
|
||||||
let app: App;
|
|
||||||
switch (mode) {
|
|
||||||
case "fresh":
|
|
||||||
app = await getFresh(config, context, { force: true });
|
|
||||||
break;
|
|
||||||
case "warm":
|
|
||||||
app = await getFresh(config, context);
|
|
||||||
break;
|
|
||||||
case "cache":
|
|
||||||
app = await getCached(config, context);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown mode ${mode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.fetch(request, env, ctx);
|
return app.fetch(request, env, ctx);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { getBinding } from "./bindings";
|
|||||||
import { d1Sqlite } from "./connection/D1Connection";
|
import { d1Sqlite } from "./connection/D1Connection";
|
||||||
import type { CloudflareBkndConfig, CloudflareEnv } from ".";
|
import type { CloudflareBkndConfig, CloudflareEnv } from ".";
|
||||||
import { App } from "bknd";
|
import { App } from "bknd";
|
||||||
import type { Context, ExecutionContext } from "hono";
|
import type { Context as HonoContext, ExecutionContext } from "hono";
|
||||||
import { $console } from "bknd/utils";
|
import { $console } from "bknd/utils";
|
||||||
import { setCookie } from "hono/cookie";
|
import { setCookie } from "hono/cookie";
|
||||||
|
|
||||||
@@ -22,10 +22,10 @@ export const constants = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CfMakeConfigArgs<Env extends CloudflareEnv = CloudflareEnv> = {
|
export type CloudflareContext<Env extends CloudflareEnv = CloudflareEnv> = {
|
||||||
env: Env;
|
env: Env;
|
||||||
ctx?: ExecutionContext;
|
ctx: ExecutionContext;
|
||||||
request?: Request;
|
request: Request;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCookieValue(cookies: string | null, name: string) {
|
function getCookieValue(cookies: string | null, name: string) {
|
||||||
@@ -67,7 +67,7 @@ export function d1SessionHelper(config: CloudflareBkndConfig<any>) {
|
|||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
set: (c: Context, d1?: D1DatabaseSession) => {
|
set: (c: HonoContext, d1?: D1DatabaseSession) => {
|
||||||
if (!d1 || !config.d1?.session) return;
|
if (!d1 || !config.d1?.session) return;
|
||||||
|
|
||||||
const session = d1.getBookmark();
|
const session = d1.getBookmark();
|
||||||
@@ -91,7 +91,7 @@ export function d1SessionHelper(config: CloudflareBkndConfig<any>) {
|
|||||||
let media_registered: boolean = false;
|
let media_registered: boolean = false;
|
||||||
export async function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
|
export async function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
|
||||||
config: CloudflareBkndConfig<Env>,
|
config: CloudflareBkndConfig<Env>,
|
||||||
args?: CfMakeConfigArgs<Env>,
|
args?: Partial<CloudflareContext<Env>>,
|
||||||
) {
|
) {
|
||||||
if (!media_registered && config.registerMedia !== false) {
|
if (!media_registered && config.registerMedia !== false) {
|
||||||
if (typeof config.registerMedia === "function") {
|
if (typeof config.registerMedia === "function") {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { d1Sqlite, type D1ConnectionConfig } from "./connection/D1Connection";
|
import { d1Sqlite, type D1ConnectionConfig } from "./connection/D1Connection";
|
||||||
|
|
||||||
export * from "./cloudflare-workers.adapter";
|
export {
|
||||||
export { makeApp, getFresh } from "./modes/fresh";
|
getFresh,
|
||||||
export { getCached } from "./modes/cached";
|
createApp,
|
||||||
|
type CloudflareEnv,
|
||||||
|
type CloudflareBkndConfig,
|
||||||
|
} from "./cloudflare-workers.adapter";
|
||||||
export { d1Sqlite, type D1ConnectionConfig };
|
export { d1Sqlite, type D1ConnectionConfig };
|
||||||
export { doSqlite, type DoConnectionConfig } from "./connection/DoConnection";
|
export { doSqlite, type DoConnectionConfig } from "./connection/DoConnection";
|
||||||
export {
|
export {
|
||||||
@@ -12,7 +15,7 @@ export {
|
|||||||
type GetBindingType,
|
type GetBindingType,
|
||||||
type BindingMap,
|
type BindingMap,
|
||||||
} from "./bindings";
|
} from "./bindings";
|
||||||
export { constants } from "./config";
|
export { constants, type CloudflareContext } from "./config";
|
||||||
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
||||||
export { registries } from "bknd";
|
export { registries } from "bknd";
|
||||||
export { devFsVitePlugin, devFsWrite } from "./vite";
|
export { devFsVitePlugin, devFsWrite } from "./vite";
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import { App } from "bknd";
|
|
||||||
import { createRuntimeApp } from "bknd/adapter";
|
|
||||||
import type { CloudflareBkndConfig, Context, CloudflareEnv } from "../index";
|
|
||||||
import { makeConfig, registerAsyncsExecutionContext, constants } from "../config";
|
|
||||||
|
|
||||||
export async function getCached<Env extends CloudflareEnv = CloudflareEnv>(
|
|
||||||
config: CloudflareBkndConfig<Env>,
|
|
||||||
args: Context<Env>,
|
|
||||||
) {
|
|
||||||
const { env, ctx } = args;
|
|
||||||
const { kv } = await config.bindings?.(env)!;
|
|
||||||
if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings");
|
|
||||||
const key = config.key ?? "app";
|
|
||||||
|
|
||||||
const cachedConfig = await kv.get(key);
|
|
||||||
const initialConfig = cachedConfig ? JSON.parse(cachedConfig) : undefined;
|
|
||||||
|
|
||||||
async function saveConfig(__config: any) {
|
|
||||||
ctx.waitUntil(kv!.put(key, JSON.stringify(__config)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = await createRuntimeApp(
|
|
||||||
{
|
|
||||||
...makeConfig(config, args),
|
|
||||||
initialConfig,
|
|
||||||
onBuilt: async (app) => {
|
|
||||||
registerAsyncsExecutionContext(app, ctx);
|
|
||||||
app.module.server.client.get(constants.cache_endpoint, async (c) => {
|
|
||||||
await kv.delete(key);
|
|
||||||
return c.json({ message: "Cache cleared" });
|
|
||||||
});
|
|
||||||
await config.onBuilt?.(app);
|
|
||||||
},
|
|
||||||
beforeBuild: async (app) => {
|
|
||||||
app.emgr.onEvent(
|
|
||||||
App.Events.AppConfigUpdatedEvent,
|
|
||||||
async ({ params: { app } }) => {
|
|
||||||
saveConfig(app.toJSON(true));
|
|
||||||
},
|
|
||||||
"sync",
|
|
||||||
);
|
|
||||||
await config.beforeBuild?.(app);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cachedConfig) {
|
|
||||||
saveConfig(app.toJSON(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
|
|
||||||
import type { CloudflareBkndConfig, Context, CloudflareEnv } from "../index";
|
|
||||||
import { makeConfig, registerAsyncsExecutionContext, type CfMakeConfigArgs } from "../config";
|
|
||||||
|
|
||||||
export async function makeApp<Env extends CloudflareEnv = CloudflareEnv>(
|
|
||||||
config: CloudflareBkndConfig<Env>,
|
|
||||||
args?: CfMakeConfigArgs<Env>,
|
|
||||||
opts?: RuntimeOptions,
|
|
||||||
) {
|
|
||||||
return await createRuntimeApp<Env>(await makeConfig(config, args), args?.env, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFresh<Env extends CloudflareEnv = CloudflareEnv>(
|
|
||||||
config: CloudflareBkndConfig<Env>,
|
|
||||||
ctx: Context<Env>,
|
|
||||||
opts: RuntimeOptions = {},
|
|
||||||
) {
|
|
||||||
return await makeApp(
|
|
||||||
{
|
|
||||||
...config,
|
|
||||||
onBuilt: async (app) => {
|
|
||||||
registerAsyncsExecutionContext(app, ctx.ctx);
|
|
||||||
await config.onBuilt?.(app);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ export function devFsVitePlugin({
|
|||||||
}: {
|
}: {
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
configFile?: string;
|
configFile?: string;
|
||||||
}): Plugin {
|
} = {}): any {
|
||||||
let isDev = false;
|
let isDev = false;
|
||||||
let projectRoot = "";
|
let projectRoot = "";
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ if (typeof globalThis !== 'undefined') {
|
|||||||
return polyfill + code;
|
return polyfill + code;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
} satisfies Plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write function that uses the dev-fs polyfill injected by our Vite plugin
|
// Write function that uses the dev-fs polyfill injected by our Vite plugin
|
||||||
|
|||||||
@@ -83,8 +83,12 @@ export async function createAdapterApp<Config extends BkndConfig = BkndConfig, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
app = App.create(appConfig);
|
app = App.create(appConfig);
|
||||||
apps.set(id, app);
|
|
||||||
|
if (!opts?.force) {
|
||||||
|
apps.set(id, app);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { Handler } from "hono/types";
|
|
||||||
import type { ModuleBuildContext } from "modules";
|
import type { ModuleBuildContext } from "modules";
|
||||||
import { Controller } from "modules/Controller";
|
import { Controller } from "modules/Controller";
|
||||||
import { jsc, s, describeRoute, schemaToSpec, omitKeys, pickKeys, mcpTool } from "bknd/utils";
|
import { jsc, s, describeRoute, schemaToSpec, omitKeys, pickKeys, mcpTool } from "bknd/utils";
|
||||||
@@ -37,14 +36,6 @@ export class DataController extends Controller {
|
|||||||
const hono = this.create().use(auth(), permission(SystemPermissions.accessApi));
|
const hono = this.create().use(auth(), permission(SystemPermissions.accessApi));
|
||||||
const entitiesEnum = this.getEntitiesEnum(this.em);
|
const entitiesEnum = this.getEntitiesEnum(this.em);
|
||||||
|
|
||||||
// @todo: sample implementation how to augment handler with additional info
|
|
||||||
function handler<HH extends Handler>(name: string, h: HH): any {
|
|
||||||
const func = h;
|
|
||||||
// @ts-ignore
|
|
||||||
func.description = name;
|
|
||||||
return func;
|
|
||||||
}
|
|
||||||
|
|
||||||
// info
|
// info
|
||||||
hono.get(
|
hono.get(
|
||||||
"/",
|
"/",
|
||||||
@@ -52,10 +43,7 @@ export class DataController extends Controller {
|
|||||||
summary: "Retrieve data configuration",
|
summary: "Retrieve data configuration",
|
||||||
tags: ["data"],
|
tags: ["data"],
|
||||||
}),
|
}),
|
||||||
handler("data info", (c) => {
|
(c) => c.json(this.em.toJSON()),
|
||||||
// sample implementation
|
|
||||||
return c.json(this.em.toJSON());
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// sync endpoint
|
// sync endpoint
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class Controller {
|
|||||||
|
|
||||||
protected getEntitiesEnum(em: EntityManager<any>): s.StringSchema {
|
protected getEntitiesEnum(em: EntityManager<any>): s.StringSchema {
|
||||||
const entities = em.entities.map((e) => e.name);
|
const entities = em.entities.map((e) => e.name);
|
||||||
return entities.length > 0 ? s.string({ enum: entities }) : s.string();
|
return entities.length > 0 ? s.anyOf([s.string({ enum: entities }), s.string()]) : s.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMcp(): void {}
|
registerMcp(): void {}
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ export class SystemController extends Controller {
|
|||||||
}),
|
}),
|
||||||
(c) =>
|
(c) =>
|
||||||
c.json({
|
c.json({
|
||||||
|
id: this.app._id,
|
||||||
version: {
|
version: {
|
||||||
config: c.get("app")?.version(),
|
config: c.get("app")?.version(),
|
||||||
bknd: getVersion(),
|
bknd: getVersion(),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const schema = s.partialObject({
|
|||||||
metadata: s.string({ enum: ["copyright", "keep", "none"] }),
|
metadata: s.string({ enum: ["copyright", "keep", "none"] }),
|
||||||
quality: s.number({ minimum: 1, maximum: 100 }),
|
quality: s.number({ minimum: 1, maximum: 100 }),
|
||||||
});
|
});
|
||||||
type ImageOptimizationSchema = s.Static<typeof schema>;
|
export type CloudflareImageOptimizationSchema = s.Static<typeof schema>;
|
||||||
|
|
||||||
export type CloudflareImageOptimizationOptions = {
|
export type CloudflareImageOptimizationOptions = {
|
||||||
/**
|
/**
|
||||||
@@ -38,12 +38,12 @@ export type CloudflareImageOptimizationOptions = {
|
|||||||
* The default options to use
|
* The default options to use
|
||||||
* @default {}
|
* @default {}
|
||||||
*/
|
*/
|
||||||
defaultOptions?: ImageOptimizationSchema;
|
defaultOptions?: CloudflareImageOptimizationSchema;
|
||||||
/**
|
/**
|
||||||
* The fixed options to use
|
* The fixed options to use
|
||||||
* @default {}
|
* @default {}
|
||||||
*/
|
*/
|
||||||
fixedOptions?: ImageOptimizationSchema;
|
fixedOptions?: CloudflareImageOptimizationSchema;
|
||||||
/**
|
/**
|
||||||
* The cache control to use
|
* The cache control to use
|
||||||
* @default public, max-age=31536000, immutable
|
* @default public, max-age=31536000, immutable
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
cloudflareImageOptimization,
|
cloudflareImageOptimization,
|
||||||
|
type CloudflareImageOptimizationSchema,
|
||||||
type CloudflareImageOptimizationOptions,
|
type CloudflareImageOptimizationOptions,
|
||||||
} from "./cloudflare/image-optimization.plugin";
|
} from "./cloudflare/image-optimization.plugin";
|
||||||
export { showRoutes, type ShowRoutesOptions } from "./dev/show-routes.plugin";
|
export { showRoutes, type ShowRoutesOptions } from "./dev/show-routes.plugin";
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ bun add bknd
|
|||||||
|
|
||||||
## Serve the API
|
## Serve the API
|
||||||
|
|
||||||
If you don't choose anything specific, the following code will use the `warm` mode and uses the first D1 binding it finds. See the
|
If you don't choose anything specific, it uses the first D1 binding it finds.
|
||||||
chapter [Using a different mode](#using-a-different-mode) for available modes.
|
|
||||||
|
|
||||||
```ts title="src/index.ts"
|
```ts title="src/index.ts"
|
||||||
import { serve, d1 } from "bknd/adapter/cloudflare";
|
import { serve, d1 } from "bknd/adapter/cloudflare";
|
||||||
@@ -130,46 +129,6 @@ export default serve<Env>({
|
|||||||
|
|
||||||
The property `app.server` is a [Hono](https://hono.dev/) instance, you can literally anything you can do with Hono.
|
The property `app.server` is a [Hono](https://hono.dev/) instance, you can literally anything you can do with Hono.
|
||||||
|
|
||||||
## Using a different mode
|
|
||||||
|
|
||||||
With the Cloudflare Workers adapter, you're being offered to 4 modes to choose from (default:
|
|
||||||
`warm`):
|
|
||||||
|
|
||||||
| Mode | Description | Use Case |
|
|
||||||
| :-------- | :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `fresh` | On every request, the configuration gets refetched, app built and then served. | Ideal if you don't want to deal with eviction, KV or Durable Objects. |
|
|
||||||
| `warm` | It tries to keep the built app in memory for as long as possible, and rebuilds if evicted. | Better response times, should be the default choice. |
|
|
||||||
| `cache` | The configuration is fetched from KV to reduce the initial roundtrip to the database. | Generally faster response times with irregular access patterns. |
|
|
||||||
|
|
||||||
### Modes: `fresh` and `warm`
|
|
||||||
|
|
||||||
To use either `fresh` or `warm`, all you have to do is adding the desired mode to `cloudflare.
|
|
||||||
mode`, like so:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { serve } from "bknd/adapter/cloudflare";
|
|
||||||
|
|
||||||
export default serve({
|
|
||||||
// ...
|
|
||||||
mode: "fresh", // mode: "fresh" | "warm" | "cache" | "durable"
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode: `cache`
|
|
||||||
|
|
||||||
For the cache mode to work, you also need to specify the KV to be used. For this, use the
|
|
||||||
`bindings` property:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { serve } from "bknd/adapter/cloudflare";
|
|
||||||
|
|
||||||
export default serve<Env>({
|
|
||||||
// ...
|
|
||||||
mode: "cache",
|
|
||||||
bindings: ({ env }) => ({ kv: env.KV }),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## D1 Sessions (experimental)
|
## D1 Sessions (experimental)
|
||||||
|
|
||||||
D1 now supports to enable [global read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/). This allows to reduce latency by reading from the closest region. In order for this to work, D1 has to be started from a bookmark. You can enable this behavior on bknd by setting the `d1.session` property:
|
D1 now supports to enable [global read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/). This allows to reduce latency by reading from the closest region. In order for this to work, D1 has to be started from a bookmark. You can enable this behavior on bknd by setting the `d1.session` property:
|
||||||
@@ -178,9 +137,6 @@ D1 now supports to enable [global read replication](https://developers.cloudflar
|
|||||||
import { serve } from "bknd/adapter/cloudflare";
|
import { serve } from "bknd/adapter/cloudflare";
|
||||||
|
|
||||||
export default serve({
|
export default serve({
|
||||||
// currently recommended to use "fresh" mode
|
|
||||||
// otherwise consecutive requests will use the same bookmark
|
|
||||||
mode: "fresh",
|
|
||||||
// ...
|
// ...
|
||||||
d1: {
|
d1: {
|
||||||
// enables D1 sessions
|
// enables D1 sessions
|
||||||
@@ -207,7 +163,7 @@ The [Cloudflare Vite Plugin](https://developers.cloudflare.com/workers/vite-plug
|
|||||||
To fix this, bknd exports a Vite plugin that provides filesystem access during development. You can use it by adding the following to your `vite.config.ts` file:
|
To fix this, bknd exports a Vite plugin that provides filesystem access during development. You can use it by adding the following to your `vite.config.ts` file:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { devFsVitePlugin } from "bknd/adapter/cloudflare/vite";
|
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [devFsVitePlugin()], // [!code highlight]
|
plugins: [devFsVitePlugin()], // [!code highlight]
|
||||||
@@ -217,7 +173,7 @@ export default defineConfig({
|
|||||||
Now to use this polyfill, you can use the `devFsWrite` function to write files to the filesystem.
|
Now to use this polyfill, you can use the `devFsWrite` function to write files to the filesystem.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { devFsWrite } from "bknd/adapter/cloudflare/vite"; // [!code highlight]
|
import { devFsWrite } from "bknd/adapter/cloudflare"; // [!code highlight]
|
||||||
import { syncTypes } from "bknd/plugins";
|
import { syncTypes } from "bknd/plugins";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
||||||
import { syncTypes } from "bknd/plugins";
|
|
||||||
import { writeFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
const isDev = import.meta.env && !import.meta.env.PROD;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
d1: {
|
d1: {
|
||||||
session: true,
|
session: true,
|
||||||
},
|
},
|
||||||
options: {
|
|
||||||
plugins: [
|
|
||||||
syncTypes({
|
|
||||||
enabled: isDev,
|
|
||||||
write: async (et) => {
|
|
||||||
await writeFile("bknd-types.d.ts", et.toString());
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
} satisfies CloudflareBkndConfig;
|
} satisfies CloudflareBkndConfig;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"$schema": "node_modules/wrangler/config-schema.json",
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
"name": "bknd-cf-worker-example",
|
"name": "bknd-cf-worker-example",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"compatibility_date": "2025-02-04",
|
"compatibility_date": "2025-08-03",
|
||||||
"compatibility_flags": ["nodejs_compat"],
|
"compatibility_flags": ["nodejs_compat"],
|
||||||
"workers_dev": true,
|
"workers_dev": true,
|
||||||
"minify": true,
|
"minify": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user