mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
added fallback route to server, created extensive setup instructions in docs
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"bin": "./dist/cli/index.js",
|
||||
"version": "0.4.0-rc2",
|
||||
"version": "0.4.0-rc3",
|
||||
"scripts": {
|
||||
"build:all": "NODE_ENV=production bun run build.ts --minify --types --clean && bun run build:cli",
|
||||
"dev": "vite",
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as SystemPermissions from "modules/permissions";
|
||||
import { AdminController, type AdminControllerOptions } from "modules/server/AdminController";
|
||||
import { SystemController } from "modules/server/SystemController";
|
||||
|
||||
export type AppPlugin = (app: App) => void;
|
||||
export type AppPlugin = (app: App) => Promise<void> | void;
|
||||
|
||||
abstract class AppEvent<A = {}> extends Event<{ app: App } & A> {}
|
||||
export class AppConfigUpdatedEvent extends AppEvent {
|
||||
@@ -93,7 +93,7 @@ export class App {
|
||||
|
||||
// load plugins
|
||||
if (this.plugins.length > 0) {
|
||||
this.plugins.forEach((plugin) => plugin(this));
|
||||
await Promise.all(this.plugins.map((plugin) => plugin(this)));
|
||||
}
|
||||
|
||||
//console.log("emitting built", options);
|
||||
|
||||
@@ -7,7 +7,6 @@ export async function getCached(config: CloudflareBkndConfig, { env, ctx }: Cont
|
||||
if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings");
|
||||
const key = config.key ?? "app";
|
||||
|
||||
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
|
||||
const cachedConfig = await kv.get(key);
|
||||
const initialConfig = cachedConfig ? JSON.parse(cachedConfig) : undefined;
|
||||
|
||||
@@ -15,28 +14,31 @@ export async function getCached(config: CloudflareBkndConfig, { env, ctx }: Cont
|
||||
ctx.waitUntil(kv!.put(key, JSON.stringify(__config)));
|
||||
}
|
||||
|
||||
const app = await createRuntimeApp({
|
||||
...create_config,
|
||||
initialConfig,
|
||||
onBuilt: async (app) => {
|
||||
app.module.server.client.get("/__bknd/cache", async (c) => {
|
||||
await kv.delete(key);
|
||||
return c.json({ message: "Cache cleared" });
|
||||
});
|
||||
await config.onBuilt?.(app);
|
||||
const app = await createRuntimeApp(
|
||||
{
|
||||
...config,
|
||||
initialConfig,
|
||||
onBuilt: async (app) => {
|
||||
app.module.server.client.get("/__bknd/cache", async (c) => {
|
||||
await kv.delete(key);
|
||||
return c.json({ message: "Cache cleared" });
|
||||
});
|
||||
await config.onBuilt?.(app);
|
||||
},
|
||||
beforeBuild: async (app) => {
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppConfigUpdatedEvent,
|
||||
async ({ params: { app } }) => {
|
||||
saveConfig(app.toJSON(true));
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
await config.beforeBuild?.(app);
|
||||
},
|
||||
adminOptions: { html: config.html }
|
||||
},
|
||||
beforeBuild: async (app) => {
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppConfigUpdatedEvent,
|
||||
async ({ params: { app } }) => {
|
||||
saveConfig(app.toJSON(true));
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
await config.beforeBuild?.(app);
|
||||
},
|
||||
adminOptions: { html: config.html }
|
||||
});
|
||||
env
|
||||
);
|
||||
|
||||
if (!cachedConfig) {
|
||||
saveConfig(app.toJSON(true));
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { App } from "bknd";
|
||||
import type { CloudflareBkndConfig, Context } from "../index";
|
||||
|
||||
export async function makeApp(config: CloudflareBkndConfig, { env }: Context) {
|
||||
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
|
||||
return await createRuntimeApp({
|
||||
...config,
|
||||
...create_config,
|
||||
adminOptions: config.html ? { html: config.html } : undefined
|
||||
});
|
||||
return await createRuntimeApp(
|
||||
{
|
||||
...config,
|
||||
adminOptions: config.html ? { html: config.html } : undefined
|
||||
},
|
||||
env
|
||||
);
|
||||
}
|
||||
|
||||
export async function getFresh(config: CloudflareBkndConfig, ctx: Context) {
|
||||
|
||||
@@ -4,15 +4,16 @@ import type { MiddlewareHandler } from "hono";
|
||||
import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter";
|
||||
import type { AdminControllerOptions } from "modules/server/AdminController";
|
||||
|
||||
type BaseExternalBkndConfig = CreateAppConfig & {
|
||||
export type BkndConfig<Env = any> = CreateAppConfig & {
|
||||
app?: CreateAppConfig | ((env: Env) => CreateAppConfig);
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
beforeBuild?: (app: App) => Promise<void>;
|
||||
buildConfig?: Parameters<App["build"]>[0];
|
||||
};
|
||||
|
||||
export type FrameworkBkndConfig = BaseExternalBkndConfig;
|
||||
export type FrameworkBkndConfig<Env = any> = BkndConfig<Env>;
|
||||
|
||||
export type RuntimeBkndConfig = BaseExternalBkndConfig & {
|
||||
export type RuntimeBkndConfig<Env = any> = BkndConfig<Env> & {
|
||||
distPath?: string;
|
||||
};
|
||||
|
||||
@@ -44,8 +45,27 @@ export function registerLocalMediaAdapter() {
|
||||
registries.media.register("local", StorageLocalAdapter);
|
||||
}
|
||||
|
||||
export async function createFrameworkApp(config: FrameworkBkndConfig): Promise<App> {
|
||||
const app = App.create(config);
|
||||
export function makeConfig<Env = any>(config: BkndConfig<Env>, env?: Env): CreateAppConfig {
|
||||
let additionalConfig: CreateAppConfig = {};
|
||||
if ("app" in config && config.app) {
|
||||
if (typeof config.app === "function") {
|
||||
if (!env) {
|
||||
throw new Error("env is required when config.app is a function");
|
||||
}
|
||||
additionalConfig = config.app(env);
|
||||
} else {
|
||||
additionalConfig = config.app;
|
||||
}
|
||||
}
|
||||
|
||||
return { ...config, ...additionalConfig };
|
||||
}
|
||||
|
||||
export async function createFrameworkApp<Env = any>(
|
||||
config: FrameworkBkndConfig,
|
||||
env?: Env
|
||||
): Promise<App> {
|
||||
const app = App.create(makeConfig(config, env));
|
||||
|
||||
if (config.onBuilt) {
|
||||
app.emgr.onEvent(
|
||||
@@ -63,21 +83,24 @@ export async function createFrameworkApp(config: FrameworkBkndConfig): Promise<A
|
||||
return app;
|
||||
}
|
||||
|
||||
export async function createRuntimeApp({
|
||||
serveStatic,
|
||||
registerLocalMedia,
|
||||
adminOptions,
|
||||
...config
|
||||
}: RuntimeBkndConfig & {
|
||||
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
|
||||
registerLocalMedia?: boolean;
|
||||
adminOptions?: AdminControllerOptions | false;
|
||||
}): Promise<App> {
|
||||
export async function createRuntimeApp<Env = any>(
|
||||
{
|
||||
serveStatic,
|
||||
registerLocalMedia,
|
||||
adminOptions,
|
||||
...config
|
||||
}: RuntimeBkndConfig & {
|
||||
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
|
||||
registerLocalMedia?: boolean;
|
||||
adminOptions?: AdminControllerOptions | false;
|
||||
},
|
||||
env?: Env
|
||||
): Promise<App> {
|
||||
if (registerLocalMedia) {
|
||||
registerLocalMediaAdapter();
|
||||
}
|
||||
|
||||
const app = App.create(config);
|
||||
const app = App.create(makeConfig(config, env));
|
||||
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
|
||||
@@ -1,24 +1,40 @@
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { type RuntimeBkndConfig, createRuntimeApp } from "adapter";
|
||||
import type { CreateAppConfig } from "bknd";
|
||||
import type { App } from "bknd";
|
||||
|
||||
export type ViteBkndConfig<Env = any> = RuntimeBkndConfig & {
|
||||
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
|
||||
export type ViteBkndConfig<Env = any> = RuntimeBkndConfig<Env> & {
|
||||
setAdminHtml?: boolean;
|
||||
forceDev?: boolean;
|
||||
html?: string;
|
||||
};
|
||||
|
||||
async function createApp(config: ViteBkndConfig, env: any) {
|
||||
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
|
||||
return await createRuntimeApp({
|
||||
...create_config,
|
||||
adminOptions: config.setAdminHtml
|
||||
? { html: config.html, forceDev: config.forceDev }
|
||||
: undefined,
|
||||
serveStatic: ["/assets/*", serveStatic({ root: config.distPath ?? "./" })]
|
||||
});
|
||||
export function addViteScript(html: string, addBkndContext: boolean = true) {
|
||||
return html.replace(
|
||||
"</head>",
|
||||
`<script type="module">
|
||||
import RefreshRuntime from "/@react-refresh"
|
||||
RefreshRuntime.injectIntoGlobalHook(window)
|
||||
window.$RefreshReg$ = () => {}
|
||||
window.$RefreshSig$ = () => (type) => type
|
||||
window.__vite_plugin_react_preamble_installed__ = true
|
||||
</script>
|
||||
<script type="module" src="/@vite/client"></script>
|
||||
${addBkndContext ? "<!-- BKND_CONTEXT -->" : ""}
|
||||
</head>`
|
||||
);
|
||||
}
|
||||
|
||||
async function createApp(config: ViteBkndConfig, env?: any) {
|
||||
return await createRuntimeApp(
|
||||
{
|
||||
...config,
|
||||
adminOptions: config.setAdminHtml
|
||||
? { html: config.html, forceDev: config.forceDev }
|
||||
: undefined,
|
||||
serveStatic: ["/assets/*", serveStatic({ root: config.distPath ?? "./" })]
|
||||
},
|
||||
env
|
||||
);
|
||||
}
|
||||
|
||||
export async function serveFresh(config: ViteBkndConfig) {
|
||||
|
||||
@@ -74,6 +74,21 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
})
|
||||
);
|
||||
|
||||
// add an initial fallback route
|
||||
this.client.use("/", async (c, next) => {
|
||||
await next();
|
||||
// if not finalized or giving a 404
|
||||
if (!c.finalized || c.res.status === 404) {
|
||||
// double check it's root
|
||||
if (new URL(c.req.url).pathname === "/") {
|
||||
c.res = undefined;
|
||||
c.res = Response.json({
|
||||
bknd: "hello world!"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.client.onError((err, c) => {
|
||||
//throw err;
|
||||
console.error(err);
|
||||
@@ -82,21 +97,6 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
return err;
|
||||
}
|
||||
|
||||
/*if (isDebug()) {
|
||||
console.log("accept", c.req.header("Accept"));
|
||||
if (c.req.header("Accept") === "application/json") {
|
||||
const stack = err.stack;
|
||||
|
||||
if ("toJSON" in err && typeof err.toJSON === "function") {
|
||||
return c.json({ ...err.toJSON(), stack }, 500);
|
||||
}
|
||||
|
||||
return c.json({ message: String(err), stack }, 500);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (err instanceof Exception) {
|
||||
console.log("---is exception", err.code);
|
||||
return c.json(err.toJSON(), err.code as any);
|
||||
@@ -107,32 +107,6 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
this.setBuilt();
|
||||
}
|
||||
|
||||
/*setAdminHtml(html: string) {
|
||||
this.admin_html = html;
|
||||
const basepath = (String(this.config.admin.basepath) + "/").replace(/\/+$/, "/");
|
||||
|
||||
const allowed_prefix = basepath + "auth";
|
||||
const login_path = basepath + "auth/login";
|
||||
|
||||
this.client.get(basepath + "*", async (c, next) => {
|
||||
const path = new URL(c.req.url).pathname;
|
||||
if (!path.startsWith(allowed_prefix)) {
|
||||
console.log("guard check permissions");
|
||||
try {
|
||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.admin);
|
||||
} catch (e) {
|
||||
return c.redirect(login_path);
|
||||
}
|
||||
}
|
||||
|
||||
return c.html(this.admin_html!);
|
||||
});
|
||||
}
|
||||
|
||||
getAdminHtml() {
|
||||
return this.admin_html;
|
||||
}*/
|
||||
|
||||
override toJSON(secrets?: boolean) {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user