diff --git a/app/build.ts b/app/build.ts index bbd4631..eb35fd8 100644 --- a/app/build.ts +++ b/app/build.ts @@ -56,7 +56,7 @@ async function buildApi() { watch, entry: ["src/index.ts", "src/data/index.ts", "src/core/index.ts", "src/core/utils/index.ts"], outDir: "dist", - external: ["bun:test", "@libsql/client"], + external: ["bun:test", "@libsql/client", /^bknd/], metafile: true, platform: "browser", format: ["esm"], diff --git a/app/package.json b/app/package.json index afc7e2d..30585b1 100644 --- a/app/package.json +++ b/app/package.json @@ -49,13 +49,14 @@ "hono": "^4.6.12", "json-schema-form-react": "^0.0.2", "json-schema-library": "^10.0.0-rc7", + "json-schema-to-ts": "^3.1.1", "kysely": "^0.27.4", "liquidjs": "^10.15.0", "lodash-es": "^4.17.21", "oauth4webapi": "^2.11.1", "object-path-immutable": "^4.1.2", + "picocolors": "^1.1.1", "radix-ui": "^1.1.2", - "json-schema-to-ts": "^3.1.1", "swr": "^2.2.5" }, "devDependencies": { diff --git a/app/src/App.ts b/app/src/App.ts index cd4abc5..06917d0 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -1,4 +1,6 @@ import type { CreateUserPayload } from "auth/AppAuth"; +import { Api, type ApiOptions } from "bknd/client"; +import { $console } from "core"; import { Event } from "core/events"; import { Connection, type LibSqlCredentials, LibsqlConnection } from "data"; import type { Hono } from "hono"; @@ -69,17 +71,17 @@ export class App { // respond to events, such as "onUpdated". // this is important if multiple changes are done, and then build() is called manually 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; } - console.log("App config updated", key); + $console.log("App config updated", key); // @todo: potentially double syncing await this.build({ sync: true }); await this.emgr.emit(new AppConfigUpdatedEvent({ app: this })); }, onFirstBoot: async () => { - console.log("App first boot"); + $console.log("App first boot"); this.trigger_first_boot = true; }, onServerInit: async (server) => { @@ -177,6 +179,15 @@ export class App { async createUser(p: CreateUserPayload) { return this.module.auth.createUser(p); } + + getApi(options: Request | ApiOptions = {}) { + const fetcher = this.server.request as typeof fetch; + if (options instanceof Request) { + return new Api({ request: options, headers: options.headers, fetcher }); + } + + return new Api({ host: "http://localhost", ...options, fetcher }); + } } export function createApp(config: CreateAppConfig = {}) { @@ -187,7 +198,7 @@ export function createApp(config: CreateAppConfig = {}) { connection = config.connection; } else if (typeof config.connection === "object") { if ("type" in config.connection) { - console.warn( + $console.warn( "Using deprecated connection type 'libsql', use the 'config' object directly." ); connection = new LibsqlConnection(config.connection.config); @@ -196,10 +207,10 @@ export function createApp(config: CreateAppConfig = {}) { } } else { 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) { - console.error("Could not create connection", e); + $console.error("Could not create connection", e); } if (!connection) { diff --git a/app/src/adapter/remix/remix.adapter.ts b/app/src/adapter/remix/remix.adapter.ts index 22470bb..38b81e8 100644 --- a/app/src/adapter/remix/remix.adapter.ts +++ b/app/src/adapter/remix/remix.adapter.ts @@ -1,6 +1,5 @@ import type { App } from "bknd"; import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter"; -import { Api } from "bknd/client"; export type RemixBkndConfig = FrameworkBkndConfig; @@ -9,29 +8,30 @@ type RemixContext = { }; let app: App; +let building: boolean = false; + +export async function getApp(config: RemixBkndConfig, args?: RemixContext) { + if (building) { + while (building) { + await new Promise((resolve) => setTimeout(resolve, 5)); + } + if (app) return app; + } + + building = true; + if (!app) { + app = await createFrameworkApp(config, args); + await app.build(); + } + building = false; + return app; +} + export function serve( config: RemixBkndConfig = {} ) { return async (args: Args) => { - if (!app) { - app = await createFrameworkApp(config, args); - } + app = await createFrameworkApp(config, args); return app.fetch(args.request); }; } - -export function withApi( - handler: (args: Args, api: Api) => Promise -) { - return async (args: Args) => { - if (!args.context.api) { - args.context.api = new Api({ - host: new URL(args.request.url).origin, - headers: args.request.headers - }); - await args.context.api.verifyAuth(); - } - - return handler(args, args.context.api); - }; -} diff --git a/app/src/cli/commands/create/create.ts b/app/src/cli/commands/create/create.ts index 81a5c9a..581d572 100644 --- a/app/src/cli/commands/create/create.ts +++ b/app/src/cli/commands/create/create.ts @@ -5,6 +5,7 @@ import type { CliCommand } from "cli/types"; import { typewriter, wait } from "cli/utils/cli"; import { execAsync, getVersion } from "cli/utils/sys"; import { Option } from "commander"; +import { colorizeConsole } from "core"; import color from "picocolors"; import { overridePackageJson, updateBkndPackages } from "./npm"; import { type Template, templates } from "./templates"; @@ -47,6 +48,7 @@ function errorOutro() { } async function action(options: { template?: string; dir?: string; integration?: string }) { + colorizeConsole(console); console.log(""); const downloadOpts = { diff --git a/app/src/cli/commands/debug.ts b/app/src/cli/commands/debug.ts index 77a668c..167e0ed 100644 --- a/app/src/cli/commands/debug.ts +++ b/app/src/cli/commands/debug.ts @@ -36,7 +36,6 @@ const subjects = { }; async function action(subject: string) { - console.log("debug", { subject }); if (subject in subjects) { await subjects[subject](); } else { diff --git a/app/src/cli/commands/run/run.ts b/app/src/cli/commands/run/run.ts index 2f078ad..c4e5442 100644 --- a/app/src/cli/commands/run/run.ts +++ b/app/src/cli/commands/run/run.ts @@ -2,9 +2,8 @@ import type { Config } from "@libsql/client/node"; import { App, type CreateAppConfig } from "App"; import { StorageLocalAdapter } from "adapter/node"; import type { CliBkndConfig, CliCommand } from "cli/types"; -import { replaceConsole } from "cli/utils/cli"; import { Option } from "commander"; -import { config } from "core"; +import { colorizeConsole, config } from "core"; import dotenv from "dotenv"; import { registries } from "modules/registries"; import c from "picocolors"; @@ -112,7 +111,7 @@ async function action(options: { dbToken?: string; server: Platform; }) { - replaceConsole(); + colorizeConsole(console); const configFilePath = await getConfigPath(options.config); let app: App | undefined = undefined; diff --git a/app/src/cli/utils/cli.ts b/app/src/cli/utils/cli.ts index 6e3a284..5b0d8e7 100644 --- a/app/src/cli/utils/cli.ts +++ b/app/src/cli/utils/cli.ts @@ -1,6 +1,3 @@ -import { isDebug } from "core"; -import c from "picocolors"; -import type { Formatter } from "picocolors/types"; const _SPEEDUP = process.env.LOCAL; const DEFAULT_WAIT = _SPEEDUP ? 0 : 250; @@ -57,31 +54,3 @@ 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; -} diff --git a/app/src/core/console.ts b/app/src/core/console.ts new file mode 100644 index 0000000..6bcb637 --- /dev/null +++ b/app/src/core/console.ts @@ -0,0 +1,105 @@ +import colors from "picocolors"; + +function hasColors() { + try { + // biome-ignore lint/style/useSingleVarDeclarator: + const p = process || {}, + argv = p.argv || [], + env = p.env || {}; + return ( + !(!!env.NO_COLOR || argv.includes("--no-color")) && + // biome-ignore lint/complexity/useOptionalChain: + (!!env.FORCE_COLOR || + argv.includes("--color") || + p.platform === "win32" || + ((p.stdout || {}).isTTY && env.TERM !== "dumb") || + !!env.CI) + ); + } catch (e) { + return false; + } +} + +const originalConsoles = { + error: console.error, + warn: console.warn, + info: console.info, + log: console.log, + debug: console.debug +} as typeof console; + +function __tty(type: any, args: any[]) { + const has = hasColors(); + const styles = { + error: { + prefix: colors.red, + args: colors.red + }, + warn: { + prefix: colors.yellow, + args: colors.yellow + }, + info: { + prefix: colors.cyan + }, + log: { + prefix: colors.gray + }, + debug: { + prefix: colors.yellow + } + } as const; + const prefix = styles[type].prefix( + `[${type.toUpperCase()}]${has ? " ".repeat(5 - type.length) : ""}` + ); + const _args = args.map((a) => + "args" in styles[type] && has && typeof a === "string" ? styles[type].args(a) : a + ); + return originalConsoles[type](prefix, ..._args); +} + +export type TConsoleSeverity = keyof typeof originalConsoles; +const severities = Object.keys(originalConsoles) as TConsoleSeverity[]; + +let enabled = [...severities]; + +export function disableConsole(severities: TConsoleSeverity[] = enabled) { + enabled = enabled.filter((s) => !severities.includes(s)); +} + +export function enableConsole() { + enabled = [...severities]; +} + +export const $console = new Proxy( + {}, + { + get: (_, prop) => { + if (prop in originalConsoles && enabled.includes(prop as TConsoleSeverity)) { + return (...args: any[]) => __tty(prop, args); + } + return () => null; + } + } +) as typeof console; + +export async function withDisabledConsole( + fn: () => Promise, + sev?: TConsoleSeverity[] +): Promise { + disableConsole(sev); + try { + const result = await fn(); + enableConsole(); + return result; + } catch (e) { + enableConsole(); + throw e; + } +} + +export function colorizeConsole(con: typeof console) { + for (const [key] of Object.entries(originalConsoles)) { + con[key] = $console[key]; + } +} diff --git a/app/src/core/index.ts b/app/src/core/index.ts index 330e9fe..9f13b20 100644 --- a/app/src/core/index.ts +++ b/app/src/core/index.ts @@ -26,6 +26,8 @@ export { } from "./object/query/query"; export { Registry, type Constructor } from "./registry/Registry"; +export * from "./console"; + // compatibility export type Middleware = MiddlewareHandler; export interface ClassController { diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index 92e2f2a..8ddb10f 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -1,4 +1,5 @@ import type { DB as DefaultDB, PrimaryFieldType } from "core"; +import { $console } from "core"; import { type EmitsEvents, EventManager } from "core/events"; import { type SelectQueryBuilder, sql } from "kysely"; import { cloneDeep } from "lodash-es"; @@ -161,7 +162,7 @@ export class Repository { const entity = this.entity; const compiled = qb.compile(); - //console.log("performQuery", compiled.sql, compiled.parameters); + //$console.log("performQuery", compiled.sql, compiled.parameters); const start = performance.now(); const selector = (as = "count") => this.conn.fn.countAll().as(as); @@ -180,7 +181,7 @@ export class Repository BKND - {/*