mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge branch 'release/0.8' into refactor/data-api-entity-prefix
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.8.0-rc.5",
|
"version": "0.8.0-rc.6",
|
||||||
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
||||||
"homepage": "https://bknd.io",
|
"homepage": "https://bknd.io",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ export class AppFirstBoot extends AppEvent {
|
|||||||
}
|
}
|
||||||
export const AppEvents = { AppConfigUpdatedEvent, AppBuiltEvent, AppFirstBoot } as const;
|
export const AppEvents = { AppConfigUpdatedEvent, AppBuiltEvent, AppFirstBoot } as const;
|
||||||
|
|
||||||
|
export type AppOptions = {
|
||||||
|
plugins?: AppPlugin[];
|
||||||
|
manager?: Omit<ModuleManagerOptions, "initial" | "onUpdated">;
|
||||||
|
};
|
||||||
export type CreateAppConfig = {
|
export type CreateAppConfig = {
|
||||||
connection?:
|
connection?:
|
||||||
| Connection
|
| Connection
|
||||||
@@ -36,8 +40,7 @@ export type CreateAppConfig = {
|
|||||||
}
|
}
|
||||||
| LibSqlCredentials;
|
| LibSqlCredentials;
|
||||||
initialConfig?: InitialModuleConfigs;
|
initialConfig?: InitialModuleConfigs;
|
||||||
plugins?: AppPlugin[];
|
options?: AppOptions;
|
||||||
options?: Omit<ModuleManagerOptions, "initial" | "onUpdated">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppConfig = InitialModuleConfigs;
|
export type AppConfig = InitialModuleConfigs;
|
||||||
@@ -47,32 +50,33 @@ export class App {
|
|||||||
static readonly Events = AppEvents;
|
static readonly Events = AppEvents;
|
||||||
adminController?: AdminController;
|
adminController?: AdminController;
|
||||||
private trigger_first_boot = false;
|
private trigger_first_boot = false;
|
||||||
|
private plugins: AppPlugin[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private connection: Connection,
|
private connection: Connection,
|
||||||
_initialConfig?: InitialModuleConfigs,
|
_initialConfig?: InitialModuleConfigs,
|
||||||
private plugins: AppPlugin[] = [],
|
private options?: AppOptions
|
||||||
moduleManagerOptions?: ModuleManagerOptions
|
|
||||||
) {
|
) {
|
||||||
|
this.plugins = options?.plugins ?? [];
|
||||||
this.modules = new ModuleManager(connection, {
|
this.modules = new ModuleManager(connection, {
|
||||||
...moduleManagerOptions,
|
...(options?.manager ?? {}),
|
||||||
initial: _initialConfig,
|
initial: _initialConfig,
|
||||||
onUpdated: async (key, config) => {
|
onUpdated: async (key, config) => {
|
||||||
// if the EventManager was disabled, we assume we shouldn't
|
// if the EventManager was disabled, we assume we shouldn't
|
||||||
// respond to events, such as "onUpdated".
|
// respond to events, such as "onUpdated".
|
||||||
// this is important if multiple changes are done, and then build() is called manually
|
// this is important if multiple changes are done, and then build() is called manually
|
||||||
if (!this.emgr.enabled) {
|
if (!this.emgr.enabled) {
|
||||||
console.warn("[APP] config updated, but event manager is disabled, skip.");
|
console.warn("App config updated, but event manager is disabled, skip.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[APP] config updated", key);
|
console.log("App config updated", key);
|
||||||
// @todo: potentially double syncing
|
// @todo: potentially double syncing
|
||||||
await this.build({ sync: true });
|
await this.build({ sync: true });
|
||||||
await this.emgr.emit(new AppConfigUpdatedEvent({ app: this }));
|
await this.emgr.emit(new AppConfigUpdatedEvent({ app: this }));
|
||||||
},
|
},
|
||||||
onFirstBoot: async () => {
|
onFirstBoot: async () => {
|
||||||
console.log("[APP] first boot");
|
console.log("App first boot");
|
||||||
this.trigger_first_boot = true;
|
this.trigger_first_boot = true;
|
||||||
},
|
},
|
||||||
onServerInit: async (server) => {
|
onServerInit: async (server) => {
|
||||||
@@ -106,8 +110,6 @@ export class App {
|
|||||||
|
|
||||||
await this.emgr.emit(new AppBuiltEvent({ app: this }));
|
await this.emgr.emit(new AppBuiltEvent({ app: this }));
|
||||||
|
|
||||||
server.all("/api/*", async (c) => c.notFound());
|
|
||||||
|
|
||||||
// first boot is set from ModuleManager when there wasn't a config table
|
// first boot is set from ModuleManager when there wasn't a config table
|
||||||
if (this.trigger_first_boot) {
|
if (this.trigger_first_boot) {
|
||||||
this.trigger_first_boot = false;
|
this.trigger_first_boot = false;
|
||||||
@@ -183,7 +185,7 @@ export function createApp(config: CreateAppConfig = {}) {
|
|||||||
} else if (typeof config.connection === "object") {
|
} else if (typeof config.connection === "object") {
|
||||||
if ("type" in config.connection) {
|
if ("type" in config.connection) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[WARN] Using deprecated connection type 'libsql', use the 'config' object directly."
|
"Using deprecated connection type 'libsql', use the 'config' object directly."
|
||||||
);
|
);
|
||||||
connection = new LibsqlConnection(config.connection.config);
|
connection = new LibsqlConnection(config.connection.config);
|
||||||
} else {
|
} else {
|
||||||
@@ -191,7 +193,7 @@ export function createApp(config: CreateAppConfig = {}) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
connection = new LibsqlConnection({ url: ":memory:" });
|
connection = new LibsqlConnection({ url: ":memory:" });
|
||||||
console.warn("[!] No connection provided, using in-memory database");
|
console.warn("No connection provided, using in-memory database");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not create connection", e);
|
console.error("Could not create connection", e);
|
||||||
@@ -201,5 +203,5 @@ export function createApp(config: CreateAppConfig = {}) {
|
|||||||
throw new Error("Invalid connection");
|
throw new Error("Invalid connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new App(connection, config.initialConfig, config.plugins, config.options);
|
return new App(connection, config.initialConfig, config.options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ export function serve({
|
|||||||
distPath,
|
distPath,
|
||||||
connection,
|
connection,
|
||||||
initialConfig,
|
initialConfig,
|
||||||
plugins,
|
|
||||||
options,
|
options,
|
||||||
port = config.server.default_port,
|
port = config.server.default_port,
|
||||||
onBuilt,
|
onBuilt,
|
||||||
@@ -44,7 +43,6 @@ export function serve({
|
|||||||
const app = await createApp({
|
const app = await createApp({
|
||||||
connection,
|
connection,
|
||||||
initialConfig,
|
initialConfig,
|
||||||
plugins,
|
|
||||||
options,
|
options,
|
||||||
onBuilt,
|
onBuilt,
|
||||||
buildConfig,
|
buildConfig,
|
||||||
|
|||||||
@@ -1,20 +1,45 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import url from "node:url";
|
import url from "node:url";
|
||||||
|
import { createApp } from "App";
|
||||||
|
import { getConnectionCredentialsFromEnv } from "cli/commands/run/platform";
|
||||||
import { getDistPath, getRelativeDistPath, getRootPath } from "cli/utils/sys";
|
import { getDistPath, getRelativeDistPath, getRootPath } from "cli/utils/sys";
|
||||||
|
import { Argument } from "commander";
|
||||||
|
import { showRoutes } from "hono/dev";
|
||||||
import type { CliCommand } from "../types";
|
import type { CliCommand } from "../types";
|
||||||
|
|
||||||
export const debug: CliCommand = (program) => {
|
export const debug: CliCommand = (program) => {
|
||||||
program
|
program
|
||||||
.command("debug")
|
.command("debug")
|
||||||
.description("debug path resolution")
|
.description("debug bknd")
|
||||||
.action(() => {
|
.addArgument(new Argument("<subject>", "subject to debug").choices(Object.keys(subjects)))
|
||||||
console.log("paths", {
|
.action(action);
|
||||||
rootpath: getRootPath(),
|
|
||||||
distPath: getDistPath(),
|
|
||||||
relativeDistPath: getRelativeDistPath(),
|
|
||||||
cwd: process.cwd(),
|
|
||||||
dir: path.dirname(url.fileURLToPath(import.meta.url)),
|
|
||||||
resolvedPkg: path.resolve(getRootPath(), "package.json")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const subjects = {
|
||||||
|
paths: async () => {
|
||||||
|
console.log("[PATHS]", {
|
||||||
|
rootpath: getRootPath(),
|
||||||
|
distPath: getDistPath(),
|
||||||
|
relativeDistPath: getRelativeDistPath(),
|
||||||
|
cwd: process.cwd(),
|
||||||
|
dir: path.dirname(url.fileURLToPath(import.meta.url)),
|
||||||
|
resolvedPkg: path.resolve(getRootPath(), "package.json")
|
||||||
|
});
|
||||||
|
},
|
||||||
|
routes: async () => {
|
||||||
|
console.log("[APP ROUTES]");
|
||||||
|
const credentials = getConnectionCredentialsFromEnv();
|
||||||
|
const app = createApp({ connection: credentials });
|
||||||
|
await app.build();
|
||||||
|
showRoutes(app.server);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function action(subject: string) {
|
||||||
|
console.log("debug", { subject });
|
||||||
|
if (subject in subjects) {
|
||||||
|
await subjects[subject]();
|
||||||
|
} else {
|
||||||
|
console.error("Invalid subject: ", subject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export async function attachServeStatic(app: any, platform: Platform) {
|
|||||||
|
|
||||||
export async function startServer(server: Platform, app: any, options: { port: number }) {
|
export async function startServer(server: Platform, app: any, options: { port: number }) {
|
||||||
const port = options.port;
|
const port = options.port;
|
||||||
console.log(`(using ${server} serve)`);
|
console.log(`Using ${server} serve`);
|
||||||
|
|
||||||
switch (server) {
|
switch (server) {
|
||||||
case "node": {
|
case "node": {
|
||||||
@@ -54,7 +54,7 @@ export async function startServer(server: Platform, app: any, options: { port: n
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `http://localhost:${port}`;
|
const url = `http://localhost:${port}`;
|
||||||
console.log(`Server listening on ${url}`);
|
console.info("Server listening on", url);
|
||||||
await open(url);
|
await open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import type { Config } from "@libsql/client/node";
|
|||||||
import { App, type CreateAppConfig } from "App";
|
import { App, type CreateAppConfig } from "App";
|
||||||
import { StorageLocalAdapter } from "adapter/node";
|
import { StorageLocalAdapter } from "adapter/node";
|
||||||
import type { CliBkndConfig, CliCommand } from "cli/types";
|
import type { CliBkndConfig, CliCommand } from "cli/types";
|
||||||
|
import { replaceConsole } from "cli/utils/cli";
|
||||||
import { Option } from "commander";
|
import { Option } from "commander";
|
||||||
import { config } from "core";
|
import { config } from "core";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { registries } from "modules/registries";
|
import { registries } from "modules/registries";
|
||||||
|
import c from "picocolors";
|
||||||
import {
|
import {
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
type Platform,
|
type Platform,
|
||||||
@@ -27,6 +29,13 @@ export const run: CliCommand = (program) => {
|
|||||||
.default(config.server.default_port)
|
.default(config.server.default_port)
|
||||||
.argParser((v) => Number.parseInt(v))
|
.argParser((v) => Number.parseInt(v))
|
||||||
)
|
)
|
||||||
|
.addOption(
|
||||||
|
new Option("-m, --memory", "use in-memory database").conflicts([
|
||||||
|
"config",
|
||||||
|
"db-url",
|
||||||
|
"db-token"
|
||||||
|
])
|
||||||
|
)
|
||||||
.addOption(new Option("-c, --config <config>", "config file"))
|
.addOption(new Option("-c, --config <config>", "config file"))
|
||||||
.addOption(
|
.addOption(
|
||||||
new Option("--db-url <db>", "database url, can be any valid libsql url").conflicts(
|
new Option("--db-url <db>", "database url, can be any valid libsql url").conflicts(
|
||||||
@@ -97,33 +106,44 @@ export async function makeConfigApp(config: CliBkndConfig, platform?: Platform)
|
|||||||
|
|
||||||
async function action(options: {
|
async function action(options: {
|
||||||
port: number;
|
port: number;
|
||||||
|
memory?: boolean;
|
||||||
config?: string;
|
config?: string;
|
||||||
dbUrl?: string;
|
dbUrl?: string;
|
||||||
dbToken?: string;
|
dbToken?: string;
|
||||||
server: Platform;
|
server: Platform;
|
||||||
}) {
|
}) {
|
||||||
|
replaceConsole();
|
||||||
const configFilePath = await getConfigPath(options.config);
|
const configFilePath = await getConfigPath(options.config);
|
||||||
|
|
||||||
let app: App | undefined = undefined;
|
let app: App | undefined = undefined;
|
||||||
if (options.dbUrl) {
|
if (options.dbUrl) {
|
||||||
|
console.info("Using connection from", c.cyan("--db-url"));
|
||||||
const connection = options.dbUrl
|
const connection = options.dbUrl
|
||||||
? { url: options.dbUrl, authToken: options.dbToken }
|
? { url: options.dbUrl, authToken: options.dbToken }
|
||||||
: undefined;
|
: undefined;
|
||||||
app = await makeApp({ connection, server: { platform: options.server } });
|
app = await makeApp({ connection, server: { platform: options.server } });
|
||||||
} else if (configFilePath) {
|
} else if (configFilePath) {
|
||||||
console.log("[INFO] Using config from:", configFilePath);
|
console.info("Using config from", c.cyan(configFilePath));
|
||||||
const config = (await import(configFilePath).then((m) => m.default)) as CliBkndConfig;
|
const config = (await import(configFilePath).then((m) => m.default)) as CliBkndConfig;
|
||||||
app = await makeConfigApp(config, options.server);
|
app = await makeConfigApp(config, options.server);
|
||||||
|
} else if (options.memory) {
|
||||||
|
console.info("Using", c.cyan("in-memory"), "connection");
|
||||||
|
app = await makeApp({ server: { platform: options.server } });
|
||||||
} else {
|
} else {
|
||||||
const credentials = getConnectionCredentialsFromEnv();
|
const credentials = getConnectionCredentialsFromEnv();
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
console.log("[INFO] Using connection from environment");
|
console.info("Using connection from env", c.cyan(credentials.url));
|
||||||
app = await makeConfigApp({ app: { connection: credentials } }, options.server);
|
app = await makeConfigApp({ app: { connection: credentials } }, options.server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
app = await makeApp({ server: { platform: options.server } });
|
const connection = { url: "file:data.db" } as Config;
|
||||||
|
console.info("Using connection", c.cyan(connection.url));
|
||||||
|
app = await makeApp({
|
||||||
|
connection,
|
||||||
|
server: { platform: options.server }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await startServer(options.server, app, { port: options.port });
|
await startServer(options.server, app, { port: options.port });
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { isDebug } from "core";
|
||||||
|
import c from "picocolors";
|
||||||
|
import type { Formatter } from "picocolors/types";
|
||||||
const _SPEEDUP = process.env.LOCAL;
|
const _SPEEDUP = process.env.LOCAL;
|
||||||
|
|
||||||
const DEFAULT_WAIT = _SPEEDUP ? 0 : 250;
|
const DEFAULT_WAIT = _SPEEDUP ? 0 : 250;
|
||||||
@@ -54,3 +57,31 @@ export async function* typewriter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ifString(args: any[], c: Formatter) {
|
||||||
|
return args.map((a) => (typeof a === "string" ? c(a) : a));
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalConsole = {
|
||||||
|
log: console.log,
|
||||||
|
info: console.info,
|
||||||
|
debug: console.debug,
|
||||||
|
warn: console.warn,
|
||||||
|
error: console.error
|
||||||
|
};
|
||||||
|
|
||||||
|
export const $console = {
|
||||||
|
log: (...args: any[]) => originalConsole.info(c.gray("[LOG] "), ...ifString(args, c.dim)),
|
||||||
|
info: (...args: any[]) => originalConsole.info(c.cyan("[INFO] "), ...args),
|
||||||
|
debug: (...args: any[]) => isDebug() && originalConsole.info(c.yellow("[DEBUG]"), ...args),
|
||||||
|
warn: (...args: any[]) => originalConsole.info(c.yellow("[WARN] "), ...ifString(args, c.yellow)),
|
||||||
|
error: (...args: any[]) => originalConsole.info(c.red("[ERROR]"), ...ifString(args, c.red))
|
||||||
|
};
|
||||||
|
|
||||||
|
export function replaceConsole() {
|
||||||
|
console.log = $console.log;
|
||||||
|
console.info = $console.info;
|
||||||
|
console.debug = $console.debug;
|
||||||
|
console.warn = $console.warn;
|
||||||
|
console.error = $console.error;
|
||||||
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export class DataController extends Controller {
|
|||||||
//console.log("request", c.req.raw);
|
//console.log("request", c.req.raw);
|
||||||
const { entity, context } = c.req.param();
|
const { entity, context } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
console.log("not found", entity, definedEntities);
|
console.warn("not found:", entity, definedEntities);
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
}
|
}
|
||||||
const _entity = this.em.entity(entity);
|
const _entity = this.em.entity(entity);
|
||||||
@@ -228,7 +228,7 @@ export class DataController extends Controller {
|
|||||||
//console.log("request", c.req.raw);
|
//console.log("request", c.req.raw);
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
console.log("not found", entity, definedEntities);
|
console.warn("not found:", entity, definedEntities);
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
}
|
}
|
||||||
const options = c.req.valid("query") as RepoQuery;
|
const options = c.req.valid("query") as RepoQuery;
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ export class AppFlows extends Module<typeof flowsConfigSchema> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hono.all("*", (c) => c.notFound());
|
||||||
|
|
||||||
this.ctx.server.route(this.config.basepath, hono);
|
this.ctx.server.route(this.config.basepath, hono);
|
||||||
|
|
||||||
// register flows
|
// register flows
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { html } from "hono/html";
|
|||||||
import { Fragment } from "hono/jsx";
|
import { Fragment } from "hono/jsx";
|
||||||
import { Controller } from "modules/Controller";
|
import { Controller } from "modules/Controller";
|
||||||
import * as SystemPermissions from "modules/permissions";
|
import * as SystemPermissions from "modules/permissions";
|
||||||
|
import type { AppTheme } from "modules/server/AppServer";
|
||||||
|
|
||||||
const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
|
const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
|
||||||
|
|
||||||
@@ -246,7 +247,7 @@ export class AdminController extends Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = (theme: "light" | "dark" = "light") => {
|
const style = (theme: AppTheme) => {
|
||||||
const base = {
|
const base = {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
@@ -271,6 +272,6 @@ const style = (theme: "light" | "dark" = "light") => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
...styles[theme]
|
...styles[theme === "light" ? "light" : "dark"]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import { cors } from "hono/cors";
|
|||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
|
|
||||||
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"];
|
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"];
|
||||||
|
const appThemes = ["dark", "light", "system"] as const;
|
||||||
|
export type AppTheme = (typeof appThemes)[number];
|
||||||
|
|
||||||
export const serverConfigSchema = Type.Object(
|
export const serverConfigSchema = Type.Object(
|
||||||
{
|
{
|
||||||
admin: Type.Object(
|
admin: Type.Object(
|
||||||
{
|
{
|
||||||
basepath: Type.Optional(Type.String({ default: "", pattern: "^(/.+)?$" })),
|
basepath: Type.Optional(Type.String({ default: "", pattern: "^(/.+)?$" })),
|
||||||
color_scheme: Type.Optional(StringEnum(["dark", "light"], { default: "light" })),
|
color_scheme: Type.Optional(StringEnum(["dark", "light", "system"])),
|
||||||
logo_return_path: Type.Optional(
|
logo_return_path: Type.Optional(
|
||||||
Type.String({
|
Type.String({
|
||||||
default: "/",
|
default: "/",
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function AdminInternal() {
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MantineProvider {...createMantineTheme(theme ?? "light")}>
|
<MantineProvider {...createMantineTheme(theme as any)}>
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<FlashMessage />
|
<FlashMessage />
|
||||||
<BkndModalsProvider>
|
<BkndModalsProvider>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Api, type ApiOptions, type TApiUser } from "Api";
|
import { Api, type ApiOptions, type TApiUser } from "Api";
|
||||||
import { isDebug } from "core";
|
import { isDebug } from "core";
|
||||||
|
import type { AppTheme } from "modules/server/AppServer";
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
const ClientContext = createContext<{ baseUrl: string; api: Api }>({
|
const ClientContext = createContext<{ baseUrl: string; api: Api }>({
|
||||||
@@ -61,7 +62,7 @@ export const useBaseUrl = () => {
|
|||||||
type BkndWindowContext = {
|
type BkndWindowContext = {
|
||||||
user?: TApiUser;
|
user?: TApiUser;
|
||||||
logout_route: string;
|
logout_route: string;
|
||||||
color_scheme?: "light" | "dark";
|
color_scheme?: AppTheme;
|
||||||
};
|
};
|
||||||
export function useBkndWindowContext(): BkndWindowContext {
|
export function useBkndWindowContext(): BkndWindowContext {
|
||||||
if (typeof window !== "undefined" && window.__BKND__) {
|
if (typeof window !== "undefined" && window.__BKND__) {
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
|
import type { AppTheme } from "modules/server/AppServer";
|
||||||
import { useBkndWindowContext } from "ui/client/ClientProvider";
|
import { useBkndWindowContext } from "ui/client/ClientProvider";
|
||||||
import { useBknd } from "ui/client/bknd";
|
import { useBknd } from "ui/client/bknd";
|
||||||
|
|
||||||
export type Theme = "light" | "dark";
|
export function useTheme(fallback: AppTheme = "system"): { theme: AppTheme } {
|
||||||
|
|
||||||
export function useTheme(fallback: Theme = "light"): { theme: Theme } {
|
|
||||||
const b = useBknd();
|
const b = useBknd();
|
||||||
const winCtx = useBkndWindowContext();
|
const winCtx = useBkndWindowContext();
|
||||||
if (b) {
|
|
||||||
if (b?.adminOverride?.color_scheme) {
|
// 1. override
|
||||||
return { theme: b.adminOverride.color_scheme };
|
// 2. config
|
||||||
} else if (!b.fallback) {
|
// 3. winCtx
|
||||||
return { theme: b.config.server.admin.color_scheme ?? fallback };
|
// 4. fallback
|
||||||
}
|
// 5. default
|
||||||
|
const override = b?.adminOverride?.color_scheme;
|
||||||
|
const config = b?.config.server.admin.color_scheme;
|
||||||
|
const win = winCtx.color_scheme;
|
||||||
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
|
||||||
|
const theme = override ?? config ?? win ?? fallback;
|
||||||
|
|
||||||
|
if (theme === "system") {
|
||||||
|
return { theme: prefersDark ? "dark" : "light" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { theme: winCtx.color_scheme ?? fallback };
|
return { theme };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ import MediaRoutes from "./media";
|
|||||||
import { Root, RootEmpty } from "./root";
|
import { Root, RootEmpty } from "./root";
|
||||||
import SettingsRoutes from "./settings";
|
import SettingsRoutes from "./settings";
|
||||||
|
|
||||||
/*const DataRoutes = lazy(() => import("./data"));
|
|
||||||
const AuthRoutes = lazy(() => import("./auth"));
|
|
||||||
const MediaRoutes = lazy(() => import("./media"));
|
|
||||||
const FlowRoutes = lazy(() => import("./flows"));
|
|
||||||
const SettingsRoutes = lazy(() => import("./settings"));*/
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const TestRoutes = lazy(() => import("./test"));
|
const TestRoutes = lazy(() => import("./test"));
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import tailwindCssAnimate from "tailwindcss-animate";
|
||||||
|
|
||||||
/** @type {import("tailwindcss").Config} */
|
/** @type {import("tailwindcss").Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ["./index.html", "./src/ui/**/*.tsx", "./src/ui/lib/mantine/theme.ts"],
|
content: ["./index.html", "./src/ui/**/*.tsx", "./src/ui/lib/mantine/theme.ts"],
|
||||||
@@ -13,5 +15,5 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")]
|
plugins: [tailwindCssAnimate]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import { serveStatic } from "@hono/node-server/serve-static";
|
import { serveStatic } from "@hono/node-server/serve-static";
|
||||||
|
import { showRoutes } from "hono/dev";
|
||||||
import { App, registries } from "./src";
|
import { App, registries } from "./src";
|
||||||
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ if (example) {
|
|||||||
|
|
||||||
let app: App;
|
let app: App;
|
||||||
const recreate = import.meta.env.VITE_APP_DISABLE_FRESH !== "1";
|
const recreate = import.meta.env.VITE_APP_DISABLE_FRESH !== "1";
|
||||||
|
let routesShown = false;
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request) {
|
async fetch(request: Request) {
|
||||||
if (!app || recreate) {
|
if (!app || recreate) {
|
||||||
@@ -44,6 +46,14 @@ export default {
|
|||||||
"sync"
|
"sync"
|
||||||
);
|
);
|
||||||
await app.build();
|
await app.build();
|
||||||
|
|
||||||
|
// log routes
|
||||||
|
if (!routesShown) {
|
||||||
|
routesShown = true;
|
||||||
|
console.log("\n\n[APP ROUTES]");
|
||||||
|
showRoutes(app.server);
|
||||||
|
console.log("-------\n\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.fetch(request);
|
return app.fetch(request);
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ The easiest to get started is using SQLite in-memory. When serving the API in th
|
|||||||
the function accepts an object with connection details. To use an in-memory database, you can either omit the object completely or explicitly use it as follows:
|
the function accepts an object with connection details. To use an in-memory database, you can either omit the object completely or explicitly use it as follows:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "libsql",
|
"url": ":memory:"
|
||||||
"config": {
|
|
||||||
"url": ":memory:"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,10 +26,7 @@ Just like the in-memory option, using a file is just as easy:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "libsql",
|
"url": "file:<path/to/your/database.db>"
|
||||||
"config": {
|
|
||||||
"url": "file:<path/to/your/database.db>"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Please note that using SQLite as a file is only supported in server environments.
|
Please note that using SQLite as a file is only supported in server environments.
|
||||||
@@ -47,10 +41,7 @@ turso dev
|
|||||||
The command will yield a URL. Use it in the connection object:
|
The command will yield a URL. Use it in the connection object:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "libsql",
|
"url": "http://localhost:8080"
|
||||||
"config": {
|
|
||||||
"url": "http://localhost:8080"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -59,11 +50,8 @@ If you want to use LibSQL on Turso, [sign up for a free account](https://turso.t
|
|||||||
connection object to your new database:
|
connection object to your new database:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "libsql",
|
"url": "libsql://your-database-url.turso.io",
|
||||||
"config": {
|
"authToken": "your-auth-token"
|
||||||
"url": "libsql://your-database-url.turso.io",
|
|
||||||
"authToken": "your-auth-token"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -44,18 +44,21 @@ import type { Connection } from "bknd/data";
|
|||||||
import type { Config } from "@libsql/client";
|
import type { Config } from "@libsql/client";
|
||||||
|
|
||||||
type AppPlugin = (app: App) => Promise<void> | void;
|
type AppPlugin = (app: App) => Promise<void> | void;
|
||||||
|
type ManagerOptions = {
|
||||||
|
basePath?: string;
|
||||||
|
trustFetched?: boolean;
|
||||||
|
onFirstBoot?: () => Promise<void>;
|
||||||
|
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
type CreateAppConfig = {
|
type CreateAppConfig = {
|
||||||
connection?:
|
connection?:
|
||||||
| Connection
|
| Connection
|
||||||
| Config;
|
| Config;
|
||||||
initialConfig?: InitialModuleConfigs;
|
initialConfig?: InitialModuleConfigs;
|
||||||
plugins?: AppPlugin[];
|
|
||||||
options?: {
|
options?: {
|
||||||
basePath?: string;
|
plugins?: AppPlugin[];
|
||||||
trustFetched?: boolean;
|
manager?: ManagerOptions
|
||||||
onFirstBoot?: () => Promise<void>;
|
|
||||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user