From c3ae4a3999743432cac972199bc91fa1f20be8fa Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 21 Nov 2025 17:35:45 +0100 Subject: [PATCH] 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. --- .../cloudflare/cloudflare-workers.adapter.ts | 13 ++++++++++--- app/src/adapter/index.ts | 17 +++++++++-------- app/src/modes/hybrid.ts | 4 ++-- app/src/ui/client/api/use-entity.ts | 9 ++++++--- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts index e263756..98df2b0 100644 --- a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts +++ b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts @@ -3,7 +3,7 @@ import type { RuntimeBkndConfig } from "bknd/adapter"; import { Hono } from "hono"; import { serveStatic } from "hono/cloudflare-workers"; -import type { MaybePromise } from "bknd"; +import type { App, MaybePromise } from "bknd"; import { $console } from "bknd/utils"; import { createRuntimeApp } from "bknd/adapter"; import { registerAsyncsExecutionContext, makeConfig, type CloudflareContext } from "./config"; @@ -55,8 +55,12 @@ export async function createApp( // 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) { @@ -92,8 +96,11 @@ export function serve( } } - const context = { request, env, ctx } as CloudflareContext; - const app = await createApp(config, context); + 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); }, diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 79f4c97..6568d29 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -14,14 +14,15 @@ import type { AdminControllerOptions } from "modules/server/AdminController"; import type { Manifest } from "vite"; export type BkndConfig = Merge< - CreateAppConfig & { - app?: - | Merge & Additional> - | ((args: Args) => MaybePromise, "app"> & Additional>>); - onBuilt?: (app: App) => MaybePromise; - beforeBuild?: (app?: App, registries?: typeof $registries) => MaybePromise; - buildConfig?: Parameters[0]; - } & Additional + CreateAppConfig & + Omit & { + app?: + | Omit, "app"> + | ((args: Args) => MaybePromise, "app">>); + onBuilt?: (app: App) => MaybePromise; + beforeBuild?: (app?: App, registries?: typeof $registries) => MaybePromise; + buildConfig?: Parameters[0]; + } >; export type FrameworkBkndConfig = BkndConfig; diff --git a/app/src/modes/hybrid.ts b/app/src/modes/hybrid.ts index ce72630..40fca8c 100644 --- a/app/src/modes/hybrid.ts +++ b/app/src/modes/hybrid.ts @@ -9,7 +9,7 @@ export type BkndHybridModeOptions = { * Reader function to read the configuration from the file system. * This is required for hybrid mode to work. */ - reader: (path: string) => MaybePromise; + reader?: (path: string) => MaybePromise; /** * Provided secrets to be merged into the configuration */ @@ -47,7 +47,7 @@ export function hybrid< "You must set a `reader` option when using hybrid mode", ); - const fileContent = await appConfig.reader(configFilePath); + const fileContent = await appConfig.reader?.(configFilePath); let fileConfig = typeof fileContent === "string" ? JSON.parse(fileContent) : fileContent; if (!fileConfig) { $console.warn("No config found, using default config"); diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index f53798c..1042344 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -8,7 +8,7 @@ import type { ModuleApi, } from "bknd"; import { objectTransform, encodeSearch } from "bknd/utils"; -import type { Insertable, Selectable, Updateable } from "kysely"; +import type { Insertable, Selectable, Updateable, Generated } from "kysely"; import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr"; import { type Api, useApi } from "ui/client"; @@ -33,6 +33,7 @@ interface UseEntityReturn< Entity extends keyof DB | string, Id extends PrimaryFieldType | undefined, Data = Entity extends keyof DB ? DB[Entity] : EntityData, + ActualId = Data extends { id: infer I } ? (I extends Generated ? T : I) : never, Response = ResponseObject>>, > { create: (input: Insertable) => Promise; @@ -42,9 +43,11 @@ interface UseEntityReturn< ResponseObject[] : Selectable>> >; update: Id extends undefined - ? (input: Updateable, id: Id) => Promise + ? (input: Updateable, id: ActualId) => Promise : (input: Updateable) => Promise; - _delete: Id extends undefined ? (id: Id) => Promise : () => Promise; + _delete: Id extends undefined + ? (id: PrimaryFieldType) => Promise + : () => Promise; } export const useEntity = <