mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
adapter(cloudflare): removed durable mode, added withPlatformProxy (#233)
* removed `durable` mode as it requires an import from "cloudflare:" that often fails in non-cf environments * remove worker configuration types * add `withPlatformProxy` * withPlatformProxy: make configuration optional
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { inspect } from "node:util";
|
||||
|
||||
export type BindingTypeMap = {
|
||||
D1Database: D1Database;
|
||||
KVNamespace: KVNamespace;
|
||||
@@ -13,8 +15,9 @@ export function getBindings<T extends GetBindingType>(env: any, type: T): Bindin
|
||||
for (const key in env) {
|
||||
try {
|
||||
if (
|
||||
env[key] &&
|
||||
((env[key] as any).constructor.name === type || String(env[key]) === `[object ${type}]`)
|
||||
(env[key] as any).constructor.name === type ||
|
||||
String(env[key]) === `[object ${type}]` ||
|
||||
inspect(env[key]).includes(type)
|
||||
) {
|
||||
bindings.push({
|
||||
key,
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("cf adapter", () => {
|
||||
});
|
||||
|
||||
it("makes config", async () => {
|
||||
const staticConfig = makeConfig(
|
||||
const staticConfig = await makeConfig(
|
||||
{
|
||||
connection: { url: DB_URL },
|
||||
initialConfig: { data: { basepath: DB_URL } },
|
||||
@@ -28,7 +28,7 @@ describe("cf adapter", () => {
|
||||
expect(staticConfig.initialConfig).toEqual({ data: { basepath: DB_URL } });
|
||||
expect(staticConfig.connection).toBeDefined();
|
||||
|
||||
const dynamicConfig = makeConfig(
|
||||
const dynamicConfig = await makeConfig(
|
||||
{
|
||||
app: (env) => ({
|
||||
initialConfig: { data: { basepath: env.DB_URL } },
|
||||
|
||||
@@ -5,8 +5,7 @@ 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 type { App } from "bknd";
|
||||
import type { App, MaybePromise } from "bknd";
|
||||
import { $console } from "core/utils";
|
||||
|
||||
declare global {
|
||||
@@ -17,12 +16,11 @@ declare global {
|
||||
|
||||
export type CloudflareEnv = Cloudflare.Env;
|
||||
export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> & {
|
||||
mode?: "warm" | "fresh" | "cache" | "durable";
|
||||
bindings?: (args: Env) => {
|
||||
mode?: "warm" | "fresh" | "cache";
|
||||
bindings?: (args: Env) => MaybePromise<{
|
||||
kv?: KVNamespace;
|
||||
dobj?: DurableObjectNamespace;
|
||||
db?: D1Database;
|
||||
};
|
||||
}>;
|
||||
d1?: {
|
||||
session?: boolean;
|
||||
transport?: "header" | "cookie";
|
||||
@@ -93,8 +91,6 @@ export function serve<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
case "cache":
|
||||
app = await getCached(config, context);
|
||||
break;
|
||||
case "durable":
|
||||
return await getDurable(config, context);
|
||||
default:
|
||||
throw new Error(`Unknown mode ${mode}`);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export function d1SessionHelper(config: CloudflareBkndConfig<any>) {
|
||||
}
|
||||
|
||||
let media_registered: boolean = false;
|
||||
export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
export async function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
config: CloudflareBkndConfig<Env>,
|
||||
args?: CfMakeConfigArgs<Env>,
|
||||
) {
|
||||
@@ -102,7 +102,7 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
media_registered = true;
|
||||
}
|
||||
|
||||
const appConfig = makeAdapterConfig(config, args?.env);
|
||||
const appConfig = await makeAdapterConfig(config, args?.env);
|
||||
|
||||
// if connection instance is given, don't do anything
|
||||
// other than checking if D1 session is defined
|
||||
@@ -115,12 +115,12 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
}
|
||||
// if connection is given, try to open with unified sqlite adapter
|
||||
} else if (appConfig.connection) {
|
||||
appConfig.connection = sqlite(appConfig.connection);
|
||||
appConfig.connection = sqlite(appConfig.connection) as any;
|
||||
|
||||
// if connection is not given, but env is set
|
||||
// try to make D1 from bindings
|
||||
} else if (args?.env) {
|
||||
const bindings = config.bindings?.(args?.env);
|
||||
const bindings = await config.bindings?.(args?.env);
|
||||
const sessionHelper = d1SessionHelper(config);
|
||||
const sessionId = sessionHelper.get(args.request);
|
||||
let session: D1DatabaseSession | undefined;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { d1Sqlite, type D1ConnectionConfig } from "./connection/D1Connection";
|
||||
export * from "./cloudflare-workers.adapter";
|
||||
export { makeApp, getFresh } from "./modes/fresh";
|
||||
export { getCached } from "./modes/cached";
|
||||
export { DurableBkndApp, getDurable } from "./modes/durable";
|
||||
export { d1Sqlite, type D1ConnectionConfig };
|
||||
export {
|
||||
getBinding,
|
||||
@@ -15,6 +14,7 @@ export {
|
||||
export { constants } from "./config";
|
||||
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
||||
export { registries } from "bknd";
|
||||
export { withPlatformProxy } from "./proxy";
|
||||
|
||||
// for compatibility with old code
|
||||
export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function getCached<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
args: Context<Env>,
|
||||
) {
|
||||
const { env, ctx } = args;
|
||||
const { kv } = config.bindings?.(env)!;
|
||||
const { kv } = await config.bindings?.(env)!;
|
||||
if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings");
|
||||
const key = config.key ?? "app";
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import { DurableObject } from "cloudflare:workers";
|
||||
import type { App, CreateAppConfig } from "bknd";
|
||||
import { createRuntimeApp, makeConfig } from "bknd/adapter";
|
||||
import type { CloudflareBkndConfig, Context, CloudflareEnv } from "../index";
|
||||
import { constants, registerAsyncsExecutionContext } from "../config";
|
||||
import { $console } from "core/utils";
|
||||
|
||||
export async function getDurable<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
config: CloudflareBkndConfig<Env>,
|
||||
ctx: Context<Env>,
|
||||
) {
|
||||
const { dobj } = config.bindings?.(ctx.env)!;
|
||||
if (!dobj) throw new Error("durable object is not defined in cloudflare.bindings");
|
||||
const key = config.key ?? "app";
|
||||
|
||||
if ([config.onBuilt, config.beforeBuild].some((x) => x)) {
|
||||
$console.warn("onBuilt and beforeBuild are not supported with DurableObject mode");
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
const id = dobj.idFromName(key);
|
||||
const stub = dobj.get(id) as unknown as DurableBkndApp;
|
||||
|
||||
const create_config = makeConfig(config, ctx.env);
|
||||
|
||||
const res = await stub.fire(ctx.request, {
|
||||
config: create_config,
|
||||
keepAliveSeconds: config.keepAliveSeconds,
|
||||
});
|
||||
|
||||
const headers = new Headers(res.headers);
|
||||
headers.set("X-TTDO", String(performance.now() - start));
|
||||
|
||||
return new Response(res.body, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
export class DurableBkndApp extends DurableObject {
|
||||
protected id = Math.random().toString(36).slice(2);
|
||||
protected app?: App;
|
||||
protected interval?: any;
|
||||
|
||||
async fire(
|
||||
request: Request,
|
||||
options: {
|
||||
config: CreateAppConfig;
|
||||
html?: string;
|
||||
keepAliveSeconds?: number;
|
||||
setAdminHtml?: boolean;
|
||||
},
|
||||
) {
|
||||
let buildtime = 0;
|
||||
if (!this.app) {
|
||||
const start = performance.now();
|
||||
const config = options.config;
|
||||
|
||||
// change protocol to websocket if libsql
|
||||
if (
|
||||
config?.connection &&
|
||||
"type" in config.connection &&
|
||||
config.connection.type === "libsql"
|
||||
) {
|
||||
//config.connection.config.protocol = "wss";
|
||||
}
|
||||
|
||||
this.app = await createRuntimeApp({
|
||||
...config,
|
||||
onBuilt: async (app) => {
|
||||
registerAsyncsExecutionContext(app, this.ctx);
|
||||
app.modules.server.get(constants.do_endpoint, async (c) => {
|
||||
// @ts-ignore
|
||||
const context: any = c.req.raw.cf ? c.req.raw.cf : c.env.cf;
|
||||
return c.json({
|
||||
id: this.id,
|
||||
keepAliveSeconds: options?.keepAliveSeconds ?? 0,
|
||||
colo: context.colo,
|
||||
});
|
||||
});
|
||||
|
||||
await this.onBuilt(app);
|
||||
},
|
||||
adminOptions: { html: options.html },
|
||||
beforeBuild: async (app) => {
|
||||
await this.beforeBuild(app);
|
||||
},
|
||||
});
|
||||
|
||||
buildtime = performance.now() - start;
|
||||
}
|
||||
|
||||
if (options?.keepAliveSeconds) {
|
||||
this.keepAlive(options.keepAliveSeconds);
|
||||
}
|
||||
|
||||
const res = await this.app!.fetch(request);
|
||||
const headers = new Headers(res.headers);
|
||||
headers.set("X-BuildTime", buildtime.toString());
|
||||
headers.set("X-DO-ID", this.id);
|
||||
|
||||
return new Response(res.body, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
async onBuilt(app: App) {}
|
||||
|
||||
async beforeBuild(app: App) {}
|
||||
|
||||
protected keepAlive(seconds: number) {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
this.interval = setInterval(() => {
|
||||
i += 1;
|
||||
if (i === seconds) {
|
||||
console.log("cleared");
|
||||
clearInterval(this.interval);
|
||||
|
||||
// ping every 30 seconds
|
||||
} else if (i % 30 === 0) {
|
||||
console.log("ping");
|
||||
this.app?.modules.ctx().connection.ping();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export async function makeApp<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
args?: CfMakeConfigArgs<Env>,
|
||||
opts?: RuntimeOptions,
|
||||
) {
|
||||
return await createRuntimeApp<Env>(makeConfig(config, args), args?.env, opts);
|
||||
return await createRuntimeApp<Env>(await makeConfig(config, args), args?.env, opts);
|
||||
}
|
||||
|
||||
export async function getFresh<Env extends CloudflareEnv = CloudflareEnv>(
|
||||
|
||||
66
app/src/adapter/cloudflare/proxy.ts
Normal file
66
app/src/adapter/cloudflare/proxy.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
d1Sqlite,
|
||||
getBinding,
|
||||
registerMedia,
|
||||
type CloudflareBkndConfig,
|
||||
type CloudflareEnv,
|
||||
} from ".";
|
||||
import type { PlatformProxy } from "wrangler";
|
||||
import process from "node:process";
|
||||
|
||||
export type WithPlatformProxyOptions = {
|
||||
/**
|
||||
* By default, proxy is used if the PROXY environment variable is set to 1.
|
||||
* You can override/force this by setting this option.
|
||||
*/
|
||||
useProxy?: boolean;
|
||||
};
|
||||
|
||||
export function withPlatformProxy<Env extends CloudflareEnv>(
|
||||
config?: CloudflareBkndConfig<Env>,
|
||||
opts?: WithPlatformProxyOptions,
|
||||
) {
|
||||
const use_proxy =
|
||||
typeof opts?.useProxy === "boolean" ? opts.useProxy : process.env.PROXY === "1";
|
||||
let proxy: PlatformProxy | undefined;
|
||||
|
||||
async function getEnv(env?: Env): Promise<Env> {
|
||||
if (use_proxy) {
|
||||
if (!proxy) {
|
||||
const getPlatformProxy = await import("wrangler").then((mod) => mod.getPlatformProxy);
|
||||
proxy = await getPlatformProxy();
|
||||
setTimeout(proxy?.dispose, 1000);
|
||||
}
|
||||
return proxy.env as unknown as Env;
|
||||
}
|
||||
return env || ({} as Env);
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
beforeBuild: async (app, registries) => {
|
||||
if (!use_proxy) return;
|
||||
const env = await getEnv();
|
||||
registerMedia(env, registries);
|
||||
await config?.beforeBuild?.(app, registries);
|
||||
},
|
||||
bindings: async (env) => {
|
||||
return (await config?.bindings?.(await getEnv(env))) || {};
|
||||
},
|
||||
app: async (_env) => {
|
||||
const env = await getEnv(_env);
|
||||
|
||||
if (config?.app === undefined && use_proxy) {
|
||||
const binding = getBinding(env, "D1Database");
|
||||
return {
|
||||
connection: d1Sqlite({
|
||||
binding: binding.value,
|
||||
}),
|
||||
};
|
||||
} else if (typeof config?.app === "function") {
|
||||
return config?.app(env);
|
||||
}
|
||||
return config?.app || {};
|
||||
},
|
||||
} satisfies CloudflareBkndConfig<Env>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { registries, isDebug, guessMimeType } from "bknd";
|
||||
import { registries as $registries, isDebug, guessMimeType } from "bknd";
|
||||
import { getBindings } from "../bindings";
|
||||
import { s } from "bknd/utils";
|
||||
import { StorageAdapter, type FileBody } from "bknd";
|
||||
@@ -12,7 +12,10 @@ export function makeSchema(bindings: string[] = []) {
|
||||
);
|
||||
}
|
||||
|
||||
export function registerMedia(env: Record<string, any>) {
|
||||
export function registerMedia(
|
||||
env: Record<string, any>,
|
||||
registries: typeof $registries = $registries,
|
||||
) {
|
||||
const r2_bindings = getBindings(env, "R2Bucket");
|
||||
|
||||
registries.media.register(
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { config as $config, App, type CreateAppConfig, Connection, guessMimeType } from "bknd";
|
||||
import {
|
||||
config as $config,
|
||||
App,
|
||||
type CreateAppConfig,
|
||||
Connection,
|
||||
guessMimeType,
|
||||
type MaybePromise,
|
||||
registries as $registries,
|
||||
} from "bknd";
|
||||
import { $console } from "bknd/utils";
|
||||
import type { Context, MiddlewareHandler, Next } from "hono";
|
||||
import type { AdminControllerOptions } from "modules/server/AdminController";
|
||||
import type { Manifest } from "vite";
|
||||
|
||||
export type BkndConfig<Args = any> = CreateAppConfig & {
|
||||
app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
|
||||
app?: CreateAppConfig | ((args: Args) => MaybePromise<CreateAppConfig>);
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
beforeBuild?: (app: App) => Promise<void>;
|
||||
beforeBuild?: (app: App, registries?: typeof $registries) => Promise<void>;
|
||||
buildConfig?: Parameters<App["build"]>[0];
|
||||
};
|
||||
|
||||
@@ -30,10 +38,10 @@ export type DefaultArgs = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export function makeConfig<Args = DefaultArgs>(
|
||||
export async function makeConfig<Args = DefaultArgs>(
|
||||
config: BkndConfig<Args>,
|
||||
args?: Args,
|
||||
): CreateAppConfig {
|
||||
): Promise<CreateAppConfig> {
|
||||
let additionalConfig: CreateAppConfig = {};
|
||||
const { app, ...rest } = config;
|
||||
if (app) {
|
||||
@@ -41,7 +49,7 @@ export function makeConfig<Args = DefaultArgs>(
|
||||
if (!args) {
|
||||
throw new Error("args is required when config.app is a function");
|
||||
}
|
||||
additionalConfig = app(args);
|
||||
additionalConfig = await app(args);
|
||||
} else {
|
||||
additionalConfig = app;
|
||||
}
|
||||
@@ -60,7 +68,7 @@ export async function createAdapterApp<Config extends BkndConfig = BkndConfig, A
|
||||
const id = opts?.id ?? "app";
|
||||
let app = apps.get(id);
|
||||
if (!app || opts?.force) {
|
||||
const appConfig = makeConfig(config, args);
|
||||
const appConfig = await makeConfig(config, args);
|
||||
if (!appConfig.connection || !Connection.isConnection(appConfig.connection)) {
|
||||
let connection: Connection | undefined;
|
||||
if (Connection.isConnection(config.connection)) {
|
||||
@@ -68,7 +76,7 @@ export async function createAdapterApp<Config extends BkndConfig = BkndConfig, A
|
||||
} else {
|
||||
const sqlite = (await import("bknd/adapter/sqlite")).sqlite;
|
||||
const conf = appConfig.connection ?? { url: ":memory:" };
|
||||
connection = sqlite(conf);
|
||||
connection = sqlite(conf) as any;
|
||||
$console.info(`Using ${connection!.name} connection`, conf.url);
|
||||
}
|
||||
appConfig.connection = connection;
|
||||
@@ -98,7 +106,7 @@ export async function createFrameworkApp<Args = DefaultArgs>(
|
||||
);
|
||||
}
|
||||
|
||||
await config.beforeBuild?.(app);
|
||||
await config.beforeBuild?.(app, $registries);
|
||||
await app.build(config.buildConfig);
|
||||
}
|
||||
|
||||
@@ -131,7 +139,7 @@ export async function createRuntimeApp<Args = DefaultArgs>(
|
||||
"sync",
|
||||
);
|
||||
|
||||
await config.beforeBuild?.(app);
|
||||
await config.beforeBuild?.(app, $registries);
|
||||
await app.build(config.buildConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ async function makeApp(config: MakeAppConfig) {
|
||||
}
|
||||
|
||||
export async function makeConfigApp(_config: CliBkndConfig, platform?: Platform) {
|
||||
const config = makeConfig(_config, process.env);
|
||||
const config = await makeConfig(_config, process.env);
|
||||
return makeApp({
|
||||
...config,
|
||||
server: { platform },
|
||||
|
||||
@@ -39,6 +39,7 @@ export { registries } from "modules/registries";
|
||||
/**
|
||||
* Core
|
||||
*/
|
||||
export type { MaybePromise } from "core/types";
|
||||
export { Exception, BkndError } from "core/errors";
|
||||
export { isDebug, env } from "core/env";
|
||||
export { type PrimaryFieldType, config, type DB, type AppEntity } from "core/config";
|
||||
|
||||
Reference in New Issue
Block a user