adapters: remove runtime options, serve app always fresh to prevent race conditions

This commit is contained in:
dswbx
2025-09-04 19:36:21 +02:00
parent e8f2c70279
commit fdec5f0693
14 changed files with 37 additions and 90 deletions

View File

@@ -1,5 +1,5 @@
import type { TestRunner } from "core/test";
import type { BkndConfig, DefaultArgs, FrameworkOptions, RuntimeOptions } from "./index";
import type { BkndConfig, DefaultArgs } from "./index";
import type { App } from "App";
export function adapterTestSuite<
@@ -13,16 +13,8 @@ export function adapterTestSuite<
label = "app",
overrides = {},
}: {
makeApp: (
config: Config,
args?: Args,
opts?: RuntimeOptions | FrameworkOptions,
) => Promise<App>;
makeHandler?: (
config?: Config,
args?: Args,
opts?: RuntimeOptions | FrameworkOptions,
) => (request: Request) => Promise<Response>;
makeApp: (config: Config, args?: Args) => Promise<App>;
makeHandler?: (config?: Config, args?: Args) => (request: Request) => Promise<Response>;
label?: string;
overrides?: {
dbUrl?: string;
@@ -30,7 +22,6 @@ export function adapterTestSuite<
},
) {
const { test, expect, mock } = testRunner;
const id = crypto.randomUUID();
test(`creates ${label}`, async () => {
const beforeBuild = mock(async () => null) as any;
@@ -53,7 +44,6 @@ export function adapterTestSuite<
url: overrides.dbUrl ?? ":memory:",
origin: "localhost",
} as any,
{ force: false, id },
);
expect(app).toBeDefined();
expect(app.toJSON().server.cors.origin).toEqual("localhost");
@@ -68,8 +58,8 @@ export function adapterTestSuite<
return { res, data };
};
test.skip("responds with the same app id", async () => {
const fetcher = makeHandler(undefined, undefined, { force: false, id });
/* test.skip("responds with the same app id", async () => {
const fetcher = makeHandler(undefined, undefined, { id });
const { res, data } = await getConfig(fetcher);
expect(res.ok).toBe(true);
@@ -79,12 +69,12 @@ export function adapterTestSuite<
test.skip("creates fresh & responds to api config", async () => {
// set the same id, but force recreate
const fetcher = makeHandler(undefined, undefined, { id, force: true });
const fetcher = makeHandler(undefined, undefined, { id });
const { res, data } = await getConfig(fetcher);
expect(res.ok).toBe(true);
expect(res.status).toBe(200);
expect(data.server.cors.origin).toEqual("*");
});
}); */
}
}

View File

@@ -10,6 +10,6 @@ afterAll(enableConsoleLog);
describe("astro adapter", () => {
adapterTestSuite(bunTestRunner, {
makeApp: astro.getApp,
makeHandler: (c, a, o) => (request: Request) => astro.serve(c, a, o)({ request }),
makeHandler: (c, a) => (request: Request) => astro.serve(c, a)({ request }),
});
});

View File

@@ -1,4 +1,4 @@
import { type FrameworkBkndConfig, createFrameworkApp, type FrameworkOptions } from "bknd/adapter";
import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter";
type AstroEnv = NodeJS.ProcessEnv;
type TAstro = {
@@ -9,17 +9,12 @@ export type AstroBkndConfig<Env = AstroEnv> = FrameworkBkndConfig<Env>;
export async function getApp<Env = AstroEnv>(
config: AstroBkndConfig<Env> = {},
args: Env = {} as Env,
opts: FrameworkOptions = {},
) {
return await createFrameworkApp(config, args ?? import.meta.env, opts);
return await createFrameworkApp(config, args ?? import.meta.env);
}
export function serve<Env = AstroEnv>(
config: AstroBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: FrameworkOptions,
) {
export function serve<Env = AstroEnv>(config: AstroBkndConfig<Env> = {}, args: Env = {} as Env) {
return async (fnArgs: TAstro) => {
return (await getApp(config, args, opts)).fetch(fnArgs.request);
return (await getApp(config, args)).fetch(fnArgs.request);
};
}

View File

@@ -1,7 +1,7 @@
import type { App } from "bknd";
import { handle } from "hono/aws-lambda";
import { serveStatic } from "@hono/node-server/serve-static";
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
type AwsLambdaEnv = object;
export type AwsLambdaBkndConfig<Env extends AwsLambdaEnv = AwsLambdaEnv> =
@@ -20,7 +20,6 @@ export type AwsLambdaBkndConfig<Env extends AwsLambdaEnv = AwsLambdaEnv> =
export async function createApp<Env extends AwsLambdaEnv = AwsLambdaEnv>(
{ adminOptions = false, assets, ...config }: AwsLambdaBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
): Promise<App> {
let additional: Partial<RuntimeBkndConfig> = {
adminOptions,
@@ -57,17 +56,15 @@ export async function createApp<Env extends AwsLambdaEnv = AwsLambdaEnv>(
...additional,
},
args ?? process.env,
opts,
);
}
export function serve<Env extends AwsLambdaEnv = AwsLambdaEnv>(
config: AwsLambdaBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
return async (event) => {
const app = await createApp(config, args, opts);
const app = await createApp(config, args);
return await handle(app.server)(event);
};
}

View File

@@ -11,8 +11,8 @@ describe("aws adapter", () => {
adapterTestSuite(bunTestRunner, {
makeApp: awsLambda.createApp,
// @todo: add a request to lambda event translator?
makeHandler: (c, a, o) => async (request: Request) => {
const app = await awsLambda.createApp(c, a, o);
makeHandler: (c, a) => async (request: Request) => {
const app = await awsLambda.createApp(c, a);
return app.fetch(request);
},
});

View File

@@ -1,7 +1,7 @@
/// <reference types="bun-types" />
import path from "node:path";
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
import { registerLocalMediaAdapter } from ".";
import { config, type App } from "bknd";
import type { ServeOptions } from "bun";
@@ -13,7 +13,6 @@ export type BunBkndConfig<Env = BunEnv> = RuntimeBkndConfig<Env> & Omit<ServeOpt
export async function createApp<Env = BunEnv>(
{ distPath, serveStatic: _serveStatic, ...config }: BunBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static");
registerLocalMediaAdapter();
@@ -28,19 +27,17 @@ export async function createApp<Env = BunEnv>(
...config,
},
args ?? (process.env as Env),
opts,
);
}
export function createHandler<Env = BunEnv>(
config: BunBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
let app: App | undefined;
return async (req: Request) => {
if (!app) {
app = await createApp(config, args ?? (process.env as Env), opts);
app = await createApp(config, args ?? (process.env as Env));
}
return app.fetch(req);
};
@@ -60,7 +57,6 @@ export function serve<Env = BunEnv>(
...serveOptions
}: BunBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
Bun.serve({
...serveOptions,
@@ -77,7 +73,6 @@ export function serve<Env = BunEnv>(
serveStatic,
},
args,
opts,
),
});

View File

@@ -41,10 +41,10 @@ describe("cf adapter", () => {
});
adapterTestSuite<CloudflareBkndConfig, CloudflareContext<any>>(bunTestRunner, {
makeApp: async (c, a, o) => {
return await createApp(c, { env: a } as any, o);
makeApp: async (c, a) => {
return await createApp(c, { env: a } as any);
},
makeHandler: (c, a, o) => {
makeHandler: (c, a) => {
console.log("args", a);
return async (request: any) => {
const app = await createApp(
@@ -53,7 +53,6 @@ describe("cf adapter", () => {
connection: { url: DB_URL },
},
a as any,
o,
);
return app.fetch(request);
};

View File

@@ -5,7 +5,7 @@ import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers";
import type { MaybePromise } from "bknd";
import { $console } from "bknd/utils";
import { createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
import { createRuntimeApp } from "bknd/adapter";
import { registerAsyncsExecutionContext, makeConfig, type CloudflareContext } from "./config";
declare global {
@@ -36,10 +36,6 @@ export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> &
export async function createApp<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env>,
ctx: Partial<CloudflareContext<Env>> = {},
opts: RuntimeOptions = {
// by default, require the app to be rebuilt every time
force: true,
},
) {
const appConfig = await makeConfig(
{
@@ -53,7 +49,7 @@ export async function createApp<Env extends CloudflareEnv = CloudflareEnv>(
},
ctx,
);
return await createRuntimeApp<Env>(appConfig, ctx?.env, opts);
return await createRuntimeApp<Env>(appConfig, ctx?.env);
}
// compatiblity

View File

@@ -21,13 +21,6 @@ export type BkndConfig<Args = any> = CreateAppConfig & {
export type FrameworkBkndConfig<Args = any> = BkndConfig<Args>;
export type CreateAdapterAppOptions = {
force?: boolean;
id?: string;
};
export type FrameworkOptions = CreateAdapterAppOptions;
export type RuntimeOptions = CreateAdapterAppOptions;
export type RuntimeBkndConfig<Args = any> = BkndConfig<Args> & {
distPath?: string;
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
@@ -63,7 +56,6 @@ const apps = new Map<string, App>();
export async function createAdapterApp<Config extends BkndConfig = BkndConfig, Args = DefaultArgs>(
config: Config = {} as Config,
args?: Args,
opts?: CreateAdapterAppOptions,
): Promise<App> {
const appConfig = await makeConfig(config, args);
if (!appConfig.connection || !Connection.isConnection(appConfig.connection)) {
@@ -85,9 +77,8 @@ export async function createAdapterApp<Config extends BkndConfig = BkndConfig, A
export async function createFrameworkApp<Args = DefaultArgs>(
config: FrameworkBkndConfig = {},
args?: Args,
opts?: FrameworkOptions,
): Promise<App> {
const app = await createAdapterApp(config, args, opts);
const app = await createAdapterApp(config, args);
if (!app.isBuilt()) {
if (config.onBuilt) {
@@ -110,9 +101,8 @@ export async function createFrameworkApp<Args = DefaultArgs>(
export async function createRuntimeApp<Args = DefaultArgs>(
{ serveStatic, adminOptions, ...config }: RuntimeBkndConfig<Args> = {},
args?: Args,
opts?: RuntimeOptions,
): Promise<App> {
const app = await createAdapterApp(config, args, opts);
const app = await createAdapterApp(config, args);
if (!app.isBuilt()) {
app.emgr.onEvent(

View File

@@ -1,4 +1,4 @@
import { createFrameworkApp, type FrameworkBkndConfig, type FrameworkOptions } from "bknd/adapter";
import { createFrameworkApp, type FrameworkBkndConfig } from "bknd/adapter";
import { isNode } from "bknd/utils";
import type { NextApiRequest } from "next";
@@ -10,9 +10,8 @@ export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
export async function getApp<Env = NextjsEnv>(
config: NextjsBkndConfig<Env>,
args: Env = {} as Env,
opts?: FrameworkOptions,
) {
return await createFrameworkApp(config, args ?? (process.env as Env), opts);
return await createFrameworkApp(config, args ?? (process.env as Env));
}
function getCleanRequest(req: Request, cleanRequest: NextjsBkndConfig["cleanRequest"]) {
@@ -41,10 +40,9 @@ function getCleanRequest(req: Request, cleanRequest: NextjsBkndConfig["cleanRequ
export function serve<Env = NextjsEnv>(
{ cleanRequest, ...config }: NextjsBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: FrameworkOptions,
) {
return async (req: Request) => {
const app = await getApp(config, args, opts);
const app = await getApp(config, args);
const request = getCleanRequest(req, cleanRequest);
return app.fetch(request);
};

View File

@@ -2,7 +2,7 @@ import path from "node:path";
import { serve as honoServe } from "@hono/node-server";
import { serveStatic } from "@hono/node-server/serve-static";
import { registerLocalMediaAdapter } from "adapter/node/storage";
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
import { config as $config, type App } from "bknd";
import { $console } from "bknd/utils";
@@ -18,7 +18,6 @@ export type NodeBkndConfig<Env = NodeEnv> = RuntimeBkndConfig<Env> & {
export async function createApp<Env = NodeEnv>(
{ distPath, relativeDistPath, ...config }: NodeBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
const root = path.relative(
process.cwd(),
@@ -36,19 +35,17 @@ export async function createApp<Env = NodeEnv>(
},
// @ts-ignore
args ?? { env: process.env },
opts,
);
}
export function createHandler<Env = NodeEnv>(
config: NodeBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
let app: App | undefined;
return async (req: Request) => {
if (!app) {
app = await createApp(config, args ?? (process.env as Env), opts);
app = await createApp(config, args ?? (process.env as Env));
}
return app.fetch(req);
};
@@ -57,13 +54,12 @@ export function createHandler<Env = NodeEnv>(
export function serve<Env = NodeEnv>(
{ port = $config.server.default_port, hostname, listener, ...config }: NodeBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: RuntimeOptions,
) {
honoServe(
{
port,
hostname,
fetch: createHandler(config, args, opts),
fetch: createHandler(config, args),
},
(connInfo) => {
$console.log(`Server is running on http://localhost:${connInfo.port}`);

View File

@@ -10,6 +10,6 @@ afterAll(enableConsoleLog);
describe("react-router adapter", () => {
adapterTestSuite(bunTestRunner, {
makeApp: rr.getApp,
makeHandler: (c, a, o) => (request: Request) => rr.serve(c, a?.env, o)({ request }),
makeHandler: (c, a) => (request: Request) => rr.serve(c, a?.env)({ request }),
});
});

View File

@@ -1,5 +1,4 @@
import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter";
import type { FrameworkOptions } from "adapter";
type ReactRouterEnv = NodeJS.ProcessEnv;
type ReactRouterFunctionArgs = {
@@ -10,17 +9,15 @@ export type ReactRouterBkndConfig<Env = ReactRouterEnv> = FrameworkBkndConfig<En
export async function getApp<Env = ReactRouterEnv>(
config: ReactRouterBkndConfig<Env>,
args: Env = {} as Env,
opts?: FrameworkOptions,
) {
return await createFrameworkApp(config, args ?? process.env, opts);
return await createFrameworkApp(config, args ?? process.env);
}
export function serve<Env = ReactRouterEnv>(
config: ReactRouterBkndConfig<Env> = {},
args: Env = {} as Env,
opts?: FrameworkOptions,
) {
return async (fnArgs: ReactRouterFunctionArgs) => {
return (await getApp(config, args, opts)).fetch(fnArgs.request);
return (await getApp(config, args)).fetch(fnArgs.request);
};
}

View File

@@ -1,7 +1,7 @@
import { serveStatic } from "@hono/node-server/serve-static";
import { type DevServerOptions, default as honoViteDevServer } from "@hono/vite-dev-server";
import type { App } from "bknd";
import { type RuntimeBkndConfig, createRuntimeApp, type FrameworkOptions } from "bknd/adapter";
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { devServerConfig } from "./dev-server-config";
import type { MiddlewareHandler } from "hono";
@@ -30,7 +30,6 @@ ${addBkndContext ? "<!-- BKND_CONTEXT -->" : ""}
async function createApp<ViteEnv>(
config: ViteBkndConfig<ViteEnv> = {},
env: ViteEnv = {} as ViteEnv,
opts: FrameworkOptions = {},
): Promise<App> {
registerLocalMediaAdapter();
return await createRuntimeApp(
@@ -47,18 +46,13 @@ async function createApp<ViteEnv>(
],
},
env,
opts,
);
}
export function serve<ViteEnv>(
config: ViteBkndConfig<ViteEnv> = {},
args?: ViteEnv,
opts?: FrameworkOptions,
) {
export function serve<ViteEnv>(config: ViteBkndConfig<ViteEnv> = {}, args?: ViteEnv) {
return {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
const app = await createApp(config, env, opts);
const app = await createApp(config, env);
return app.fetch(request, env, ctx);
},
};