add: agnostic web compliant adapter

This commit is contained in:
2026-04-03 13:48:50 +05:30
parent 8a90b9356b
commit 54aae56734
41 changed files with 1534 additions and 0 deletions
+5
View File
@@ -330,6 +330,11 @@ async function buildAdapters() {
platform: "node",
}),
tsup.build({
...baseConfig("web"),
platform: "neutral",
}),
tsup.build({
...baseConfig("sveltekit"),
platform: "node",
+3
View File
@@ -15,6 +15,9 @@ const configs = {
nextjs: {
base_path: "/admin",
},
meta: {
base_path: "/admin",
},
nuxt: {
base_path: "/admin",
},
+6
View File
@@ -233,6 +233,11 @@
"import": "./dist/adapter/nextjs/index.js",
"require": "./dist/adapter/nextjs/index.js"
},
"./adapter/web": {
"types": "./dist/types/adapter/web/index.d.ts",
"import": "./dist/adapter/web/index.js",
"require": "./dist/adapter/web/index.js"
},
"./adapter/nuxt": {
"types": "./dist/types/adapter/nuxt/index.d.ts",
"import": "./dist/adapter/nuxt/index.js",
@@ -292,6 +297,7 @@
"adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"],
"adapter/vite": ["./dist/types/adapter/vite/index.d.ts"],
"adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"],
"adapter/web": ["./dist/types/adapter/web/index.d.ts"],
"adapter/nuxt": ["./dist/types/adapter/nuxt/index.d.ts"],
"adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"],
"adapter/bun": ["./dist/types/adapter/bun/index.d.ts"],
+1
View File
@@ -0,0 +1 @@
export * from "./web.adapter";
+103
View File
@@ -0,0 +1,103 @@
import { afterAll, beforeAll, describe, test, expect } from "bun:test";
import { createBknd } from "./web.adapter";
import { disableConsoleLog, enableConsoleLog } from "core/utils";
import { adapterTestSuite } from "adapter/adapter-test-suite";
import { bunTestRunner } from "adapter/bun/test";
beforeAll(disableConsoleLog);
afterAll(enableConsoleLog);
describe("web adapter via createBknd", () => {
adapterTestSuite(bunTestRunner, {
makeApp: (options, args) => createBknd({ mode: "api", options }, args).getApp(),
makeHandler: (options, args) => createBknd({ mode: "api", options: options ?? {} }, args).serve(),
});
// ------------------------ MODE API ------------------------
test("caches app instance", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } });
const app1 = await bknd.getApp();
const app2 = await bknd.getApp();
expect(app1).toBe(app2);
});
test("getApi returns api", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } });
const api = await bknd.getApi();
expect(api).toBeDefined();
});
test("uses createFrameworkApp ", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } });
const app = await bknd.getApp();
expect(app).toBeDefined();
expect(app.isBuilt()).toBe(true);
});
test("serve returns a fetch handler", async () => {
const bknd = createBknd({ mode: "api", options: { connection: { url: ":memory:" } } });
const handler = bknd.serve();
const res = await handler(new Request("http://localhost:3000/api/system/config"));
expect(res.status).toBe(200);
});
});
// ------------------------ MODE STANDALONE ------------------------
describe("web adapter via createBknd in standalone mode", () => {
adapterTestSuite(bunTestRunner, {
makeApp: (options, args) => createBknd({ mode: "standalone", options }, args).getApp(),
makeHandler: (options, args) => createBknd({ mode: "standalone", options: options ?? {} }, args).serve(),
});
test("caches app instance", async () => {
const bknd = createBknd({ mode: "standalone", options: { connection: { url: ":memory:" } } });
const app1 = await bknd.getApp();
const app2 = await bknd.getApp();
expect(app1).toBe(app2);
});
test("getApi returns api", async () => {
const bknd = createBknd({ mode: "standalone", options: { connection: { url: ":memory:" } } });
const api = await bknd.getApi();
expect(api).toBeDefined();
});
test("uses createRuntimeApp", async () => {
const bknd = createBknd({
mode: "standalone",
options: {
connection: { url: ":memory:" },
adminOptions: { adminBasepath: "/admin" },
}
});
const app = await bknd.getApp();
expect(app).toBeDefined();
expect(app.isBuilt()).toBe(true);
});
test("serve returns a fetch handler", async () => {
const bknd = createBknd({
mode: "standalone",
options: {
connection: { url: ":memory:" },
adminOptions: { adminBasepath: "/admin" },
}
});
const app = await bknd.getApp();
expect(app.isBuilt()).toBe(true);
});
test("check admin route", async () => {
const bknd = createBknd({
mode: "standalone",
options: {
connection: { url: ":memory:" },
adminOptions: { adminBasepath: "/admin" },
}
});
const handler = bknd.serve();
const res = await handler(new Request("http://localhost:3000/admin"));
expect(res.status).toBe(200);
});
});
+68
View File
@@ -0,0 +1,68 @@
import {
createFrameworkApp,
createRuntimeApp,
type FrameworkBkndConfig,
type RuntimeBkndConfig,
} from "bknd/adapter";
import { $console } from "core/utils";
import type { App } from "App";
export type AdapterModeWithOptions<Env = Record<string, string | undefined>> =
| {
mode: "standalone";
options: RuntimeBkndConfig<Env>;
}
| {
mode: "api";
options: FrameworkBkndConfig<Env>;
};
export function createBknd<Env>(config: AdapterModeWithOptions<Env>, env?: Env) {
let appPromise: Promise<App> | undefined;
const { mode, options } = config;
async function getApp(): Promise<App> {
if (!appPromise) {
if (mode === "standalone") {
if (!options.serveStatic && !options.adminOptions) {
$console.warn(
"adminOptions provided without serveStatic — admin UI assets may not be served. " +
"See `serveStatic`, `serveStaticViaImport`, or add a `package.json` script that runs `bknd copy-assets --out {relative_static_assets_directory_path}`.",
);
}
appPromise = createRuntimeApp(options, env);
} else {
appPromise = createFrameworkApp(options, env);
}
}
return appPromise;
}
async function getApi(opts?: { headers?: Headers; verify?: boolean }) {
const app = await getApp();
if (opts?.verify) {
const api = app.getApi({ headers: opts.headers });
await api.verifyAuth();
return api;
}
return app.getApi();
}
function serve() {
return async (req: Request) => {
const app = await getApp();
return app.fetch(req);
};
}
return { getApp, getApi, serve };
}
/** Utility type to determine the config type based on mode,
* Usage `Config<"standalone">` or `Config<"api">`
*/
export type Config<T extends AdapterModeWithOptions["mode"]> = Extract<
Parameters<typeof createBknd>[0],
{ mode: T }
>['options'];