Refactor: Rename api mode to headless

The 'api' mode is being renamed to 'headless' to better reflect its
purpose: serving the API without the admin UI. This change aligns with
the existing framework concept of a headless backend.

The 'standalone' mode is renamed to 'admin' to indicate that it serves
both the API and the admin UI.
This commit is contained in:
Cameron Pak
2026-04-06 07:55:08 -05:00
parent 9e0382389e
commit dfae51c368
4 changed files with 30 additions and 27 deletions
@@ -9,33 +9,33 @@ afterAll(enableConsoleLog);
describe("universal adapter via createBknd", () => { describe("universal adapter via createBknd", () => {
adapterTestSuite(bunTestRunner, { adapterTestSuite(bunTestRunner, {
makeApp: (options, args) => createBknd({ mode: "api", options }, args).getApp(), makeApp: (options, args) => createBknd({ mode: "headless", options }, args).getApp(),
makeHandler: (options, args) => createBknd({ mode: "api", options: options ?? {} }, args).serve(), makeHandler: (options, args) => createBknd({ mode: "headless", options: options ?? {} }, args).serve(),
}); });
// ------------------------ MODE API ------------------------ // ------------------------ MODE HEADLESS ------------------------
test("caches app instance", async () => { test("caches app instance", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } }); const bknd = createBknd({ mode: "headless", options: { connection: { url: ":memory:" } } });
const app1 = await bknd.getApp(); const app1 = await bknd.getApp();
const app2 = await bknd.getApp(); const app2 = await bknd.getApp();
expect(app1).toBe(app2); expect(app1).toBe(app2);
}); });
test("getApi returns api", async () => { test("getApi returns api", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } }); const bknd = createBknd({ mode: "headless", options: { connection: { url: ":memory:" } } });
const api = await bknd.getApi(); const api = await bknd.getApi();
expect(api).toBeDefined(); expect(api).toBeDefined();
}); });
test("uses createFrameworkApp ", async () => { test("uses createFrameworkApp in headless mode", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } }); const bknd = createBknd({ mode: "headless", options: { connection: { url: ":memory:" } } });
const app = await bknd.getApp(); const app = await bknd.getApp();
expect(app).toBeDefined(); expect(app).toBeDefined();
expect(app.isBuilt()).toBe(true); expect(app.isBuilt()).toBe(true);
}); });
test("serve returns a fetch handler", async () => { test("serve returns a fetch handler", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } }); const bknd = createBknd({ mode: "headless", options: { connection: { url: ":memory:" } } });
const handler = bknd.serve(); const handler = bknd.serve();
const res = await handler(new Request("http://localhost:3000/api/system/config")); const res = await handler(new Request("http://localhost:3000/api/system/config"));
expect(res.status).toBe(200); expect(res.status).toBe(200);
@@ -43,29 +43,29 @@ describe("universal adapter via createBknd", () => {
}); });
// ------------------------ MODE STANDALONE ------------------------ // ------------------------ MODE ADMIN ------------------------
describe("universal adapter via createBknd in standalone mode", () => { describe("universal adapter via createBknd in admin mode", () => {
adapterTestSuite(bunTestRunner, { adapterTestSuite(bunTestRunner, {
makeApp: (options, args) => createBknd({ mode: "standalone", options }, args).getApp(), makeApp: (options, args) => createBknd({ mode: "admin", options }, args).getApp(),
makeHandler: (options, args) => createBknd({ mode: "standalone", options: options ?? {} }, args).serve(), makeHandler: (options, args) => createBknd({ mode: "admin", options: options ?? {} }, args).serve(),
}); });
test("caches app instance", async () => { test("caches app instance", async () => {
const bknd = createBknd({ mode: "standalone", options: { connection: { url: ":memory:" } } }); const bknd = createBknd({ mode: "admin", options: { connection: { url: ":memory:" } } });
const app1 = await bknd.getApp(); const app1 = await bknd.getApp();
const app2 = await bknd.getApp(); const app2 = await bknd.getApp();
expect(app1).toBe(app2); expect(app1).toBe(app2);
}); });
test("getApi returns api", async () => { test("getApi returns api", async () => {
const bknd = createBknd({ mode: "standalone", options: { connection: { url: ":memory:" } } }); const bknd = createBknd({ mode: "admin", options: { connection: { url: ":memory:" } } });
const api = await bknd.getApi(); const api = await bknd.getApi();
expect(api).toBeDefined(); expect(api).toBeDefined();
}); });
test("uses createRuntimeApp", async () => { test("uses createRuntimeApp in admin mode", async () => {
const bknd = createBknd({ const bknd = createBknd({
mode: "standalone", mode: "admin",
options: { options: {
connection: { url: ":memory:" }, connection: { url: ":memory:" },
adminOptions: { adminBasepath: "/admin" }, adminOptions: { adminBasepath: "/admin" },
@@ -78,7 +78,7 @@ describe("universal adapter via createBknd in standalone mode", () => {
test("serve returns a fetch handler", async () => { test("serve returns a fetch handler", async () => {
const bknd = createBknd({ const bknd = createBknd({
mode: "standalone", mode: "admin",
options: { options: {
connection: { url: ":memory:" }, connection: { url: ":memory:" },
adminOptions: { adminBasepath: "/admin" }, adminOptions: { adminBasepath: "/admin" },
@@ -91,7 +91,7 @@ describe("universal adapter via createBknd in standalone mode", () => {
test("check admin route", async () => { test("check admin route", async () => {
const bknd = createBknd({ const bknd = createBknd({
mode: "standalone", mode: "admin",
options: { options: {
connection: { url: ":memory:" }, connection: { url: ":memory:" },
adminOptions: { adminBasepath: "/admin" }, adminOptions: { adminBasepath: "/admin" },
@@ -9,11 +9,13 @@ import type { App } from "App";
export type AdapterModeWithOptions<Env = Record<string, string | undefined>> = export type AdapterModeWithOptions<Env = Record<string, string | undefined>> =
| { | {
mode: "standalone"; /** Serves the API along with the admin UI and static assets */
mode: "admin";
options: RuntimeBkndConfig<Env>; options: RuntimeBkndConfig<Env>;
} }
| { | {
mode: "api"; /** Serves the API only, without the admin UI — use when your framework handles rendering */
mode: "headless";
options: FrameworkBkndConfig<Env>; options: FrameworkBkndConfig<Env>;
}; };
@@ -24,7 +26,7 @@ export function createBknd<Env>(config: AdapterModeWithOptions<Env>, env?: Env)
async function getApp(): Promise<App> { async function getApp(): Promise<App> {
if (!appPromise) { if (!appPromise) {
if (mode === "standalone") { if (mode === "admin") {
if (options.adminOptions && !options.serveStatic) { if (options.adminOptions && !options.serveStatic) {
$console.warn( $console.warn(
"adminOptions provided without serveStatic — admin UI assets may not be served. " + "adminOptions provided without serveStatic — admin UI assets may not be served. " +
@@ -59,9 +61,10 @@ export function createBknd<Env>(config: AdapterModeWithOptions<Env>, env?: Env)
return { getApp, getApi, serve }; return { getApp, getApi, serve };
} }
/** Utility type to determine the config type based on mode, /**
* Usage `Config<"standalone">` or `Config<"api">` * Utility type to determine the config type based on mode,
*/ * Usage `Config<"standalone">` or `Config<"api">`
*/
export type Config<T extends AdapterModeWithOptions["mode"]> = Extract< export type Config<T extends AdapterModeWithOptions["mode"]> = Extract<
Parameters<typeof createBknd>[0], Parameters<typeof createBknd>[0],
{ mode: T } { mode: T }
+1 -1
View File
@@ -57,4 +57,4 @@ export default {
assetsPath: "/admin/", assetsPath: "/admin/",
logoReturnPath: "../..", logoReturnPath: "../..",
}, },
} satisfies Config<"standalone">; } satisfies Config<"admin">;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createBknd } from "bknd/adapter/universal";
import bkndConfig from "../../bknd.config"; import bkndConfig from "../../bknd.config";
import type { EnvGetter } from "@builder.io/qwik-city/middleware/request-handler"; import type { EnvGetter } from "@builder.io/qwik-city/middleware/request-handler";
export const getApp = async (env?: EnvGetter) => createBknd({ mode: "standalone", options: bkndConfig }, env); export const getApp = async (env?: EnvGetter) => createBknd({ mode: "admin", options: bkndConfig }, env);
export const handler = async (req: Request, env?: EnvGetter) => { export const handler = async (req: Request, env?: EnvGetter) => {
return (await getApp(env)).serve()(req); return (await getApp(env)).serve()(req);