import { createContext, lazy, Suspense, useContext, useEffect, useState, type ReactNode, } from "react"; import { checksum } from "bknd/utils"; import { App, registries, sqlocal, type BkndConfig } from "bknd"; import { Route, Router, Switch } from "wouter"; import { ClientProvider } from "bknd/client"; import { SQLocalKysely } from "sqlocal/kysely"; import type { ClientConfig, DatabasePath } from "sqlocal"; import { OpfsStorageAdapter } from "bknd/adapter/browser"; import type { BkndAdminConfig } from "bknd/ui"; const Admin = lazy(() => Promise.all([ import("bknd/ui"), // @ts-ignore import("bknd/dist/styles.css"), ]).then(([mod]) => ({ default: mod.Admin, })), ); function safeViewTransition(fn: () => void) { if (document.startViewTransition) { document.startViewTransition(fn); } else { fn(); } } export type BrowserBkndConfig = Omit< BkndConfig, "connection" | "app" > & { adminConfig?: BkndAdminConfig; connection?: ClientConfig | DatabasePath; }; export type BkndBrowserAppProps = { children: ReactNode; header?: ReactNode; loading?: ReactNode; notFound?: ReactNode; } & BrowserBkndConfig; const BkndBrowserAppContext = createContext<{ app: App; hash: string; }>(undefined!); export function BkndBrowserApp({ children, adminConfig, header, loading, notFound, ...config }: BkndBrowserAppProps) { const [app, setApp] = useState(undefined); const [hash, setHash] = useState(""); const adminRoutePath = (adminConfig?.basepath ?? "") + "/*?"; async function onBuilt(app: App) { safeViewTransition(async () => { setApp(app); setHash(await checksum(app.toJSON())); }); } useEffect(() => { setup({ ...config, adminConfig }) .then((app) => onBuilt(app as any)) .catch(console.error); }, []); if (!app) { return ( loading ?? (
Loading...
) ); } return ( {header} {children} {notFound ?? (
404
)}
); } export function useApp() { return useContext(BkndBrowserAppContext); } const Center = (props: React.HTMLAttributes) => (
); let initialized = false; async function setup(config: BrowserBkndConfig = {}) { if (initialized) return; initialized = true; registries.media.register("opfs", OpfsStorageAdapter); const app = App.create({ ...config, // @ts-ignore connection: sqlocal(new SQLocalKysely(config.connection ?? ":localStorage:")), }); await config.beforeBuild?.(app); await app.build({ sync: true }); await config.onBuilt?.(app); return app; }