diff --git a/docs/content/docs/(documentation)/extending/plugins.mdx b/docs/content/docs/(documentation)/extending/plugins.mdx index e18606c..9d6ff59 100644 --- a/docs/content/docs/(documentation)/extending/plugins.mdx +++ b/docs/content/docs/(documentation)/extending/plugins.mdx @@ -116,6 +116,36 @@ export default { } satisfies BkndConfig; ``` +### `syncSecrets` + +A simple plugin that writes down the app secrets on boot and each build. + +```typescript title="bknd.config.ts" +import { syncSecrets } from "bknd/plugins"; +import { writeFile } from "node:fs/promises"; + +export default { + options: { + plugins: [ + syncSecrets({ + // whether to enable the plugin, make sure to disable in production + enabled: true, + // your writing function (required) + write: async (secrets) => { + // apply your writing logic, e.g. writing a template file in an env format + await writeFile( + ".env.example", + Object.entries(secrets) + .map(([key]) => `${key}=`) + .join("\n") + ); + }, + }), + ] + }, +} satisfies BkndConfig; +``` + ### `showRoutes` A simple plugin that logs the routes of your app in the console. diff --git a/docs/content/docs/(documentation)/start.mdx b/docs/content/docs/(documentation)/start.mdx index bcb0c95..8e664c6 100644 --- a/docs/content/docs/(documentation)/start.mdx +++ b/docs/content/docs/(documentation)/start.mdx @@ -6,29 +6,34 @@ tags: ["documentation"] import { Icon } from "@iconify/react"; import { examples } from "@/app/_components/StackBlitz"; +import { SquareMousePointer, Code, Blend } from 'lucide-react'; + Glad you're here! **bknd** is a lightweight, infrastructure agnostic and feature-rich backend that runs in any JavaScript environment. - Instant backend with full REST API - Built on Web Standards for maximum compatibility -- Multiple run modes (standalone, runtime, framework) -- Official API and React SDK with type-safety -- React elements for auto-configured authentication and media components +- Multiple ready-made [integrations](/integration/introduction) (standalone, runtime, framework) +- Official [API SDK](/usage/sdk) and [React SDK](/usage/react) with type-safety +- [React elements](/usage/elements) for auto-configured authentication and media components +- Built-in [MCP server](/usage/mcp/overview) for controlling your backend +- Multiple run [modes](/usage/introduction#modes) (ui-only, code-only, hybrid) ## Preview Here is a preview of **bknd** in StackBlitz: - + + + + + The example shown is starting a [node server](/integration/node) using an [in-memory database](/usage/database#sqlite-in-memory). To ensure there are a few entities defined, it is using an [initial structure](/usage/database#initial-structure) using the prototype methods. Furthermore it uses the [seed option](/usage/database#seeding-the-database) to seed some data in the structure created. - - - The example shown is starting a [node server](/integration/node) using an [in-memory database](/usage/database#sqlite-in-memory). To ensure there are a few entities defined, it is using an [initial structure](/usage/database#initial-structure) using the prototype methods. Furthermore it uses the [seed option](/usage/database#seeding-the-database) to seed some data in the structure created. + To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending). - To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending). - - - + + + ## Quickstart @@ -156,3 +161,19 @@ The following databases are currently supported. Request a new integration if yo Create a new issue to request a new database integration. + +## Choose a mode + +**bknd** supports multiple modes to suit your needs. + + + }> + Configure your backend and manage your data visually with the built-in Admin UI. + + }> + Configure your backend programmatically with a Drizzle-like API, manage your data with the Admin UI. + + }> + Configure your backend visually while in development, use a read-only configuration in production. + + \ No newline at end of file diff --git a/docs/content/docs/(documentation)/usage/cli.mdx b/docs/content/docs/(documentation)/usage/cli.mdx index ea81bee..a369d98 100644 --- a/docs/content/docs/(documentation)/usage/cli.mdx +++ b/docs/content/docs/(documentation)/usage/cli.mdx @@ -1,6 +1,7 @@ --- title: "Using the CLI" description: "How to start a bknd instance using the CLI." +icon: Terminal tags: ["documentation"] --- @@ -204,5 +205,81 @@ import type { DB } from "bknd"; type Todo = DB["todos"]; ``` -All bknd methods that involve your database schema will be automatically typed. +All bknd methods that involve your database schema will be automatically typed. You may use the [`syncTypes`](/extending/plugins/#synctypes) plugin to automatically write the types to a file. + +## Getting the configuration (`config`) + +To see all available `config` options, execute `npx bknd config --help`. + +``` +$ npx bknd config --help +Usage: bknd config [options] + +get app config + +Options: + -c, --config config file + --db-url database url, can be any valid sqlite url + --pretty pretty print + --default use default config + --secrets include secrets in output + --out output file + -h, --help display help for command +``` + +To get the configuration of your app, and to write it to a file, run the following: + +``` +npx bknd config --out appconfig.json +``` + +To get a template configuration instead, run the following: + +``` +npx bknd config --default +``` + +To automatically sync your configuration to a file, you may also use the [`syncConfig`](/extending/plugins/#syncconfig) plugin. + +## Getting the secrets (`secrets`) + +To see all available `secrets` options, execute `npx bknd secrets --help`. + +``` +$ npx bknd secrets --help +Usage: bknd secrets [options] + +get app secrets + +Options: + -c, --config config file + --db-url database url, can be any valid sqlite url + --template template output without the actual secrets + --format format output (choices: "json", "env", default: + "json") + --out output file + -h, --help display help for command +``` + +To automatically sync your secrets to a file, you may also use the [`syncSecrets`](/extending/plugins/#syncsecrets) plugin. + +## Syncing the database (`sync`) + +Sync your database can be useful when running in [`code`](/usage/introduction/#code-only-mode) mode. When you're ready to deploy, you can point to the production configuration and sync the database. Schema mutations are only applied when running with the `--force` option. + +``` +$ npx bknd sync --help +Usage: bknd sync [options] + +sync database + +Options: + -c, --config config file + --db-url database url, can be any valid sqlite url + --force perform database syncing operations + --drop include destructive DDL operations + --out output file + --sql use sql output + -h, --help display help for command +``` diff --git a/docs/content/docs/(documentation)/usage/database.mdx b/docs/content/docs/(documentation)/usage/database.mdx index bb57adc..b714ada 100644 --- a/docs/content/docs/(documentation)/usage/database.mdx +++ b/docs/content/docs/(documentation)/usage/database.mdx @@ -1,6 +1,7 @@ --- title: "Database" description: "Choosing the right database configuration" +icon: Database tags: ["documentation"] --- diff --git a/docs/content/docs/(documentation)/usage/elements.mdx b/docs/content/docs/(documentation)/usage/elements.mdx index 239687f..8df9e78 100644 --- a/docs/content/docs/(documentation)/usage/elements.mdx +++ b/docs/content/docs/(documentation)/usage/elements.mdx @@ -1,6 +1,7 @@ --- title: "React Elements" description: "Speed up your frontend development" +icon: Box tags: ["documentation"] --- diff --git a/docs/content/docs/(documentation)/usage/introduction.mdx b/docs/content/docs/(documentation)/usage/introduction.mdx index d718ab2..472d0d1 100644 --- a/docs/content/docs/(documentation)/usage/introduction.mdx +++ b/docs/content/docs/(documentation)/usage/introduction.mdx @@ -1,9 +1,13 @@ --- title: "Introduction" description: "Setting up bknd" +icon: Pin tags: ["documentation"] --- +import { TypeTable } from "fumadocs-ui/components/type-table"; +import { SquareMousePointer, Code, Blend } from 'lucide-react'; + There are several methods to get **bknd** up and running. You can choose between these options: 1. [Run it using the CLI](/usage/cli): That's the easiest and fastest way to get started. @@ -22,12 +26,12 @@ Regardless of the method you choose, at the end all adapters come down to the ac instantiation of the `App`, which in raw looks like this: ```typescript -import { createApp, type CreateAppConfig } from "bknd"; +import { createApp, type BkndConfig } from "bknd"; // create the app const config = { /* ... */ -} satisfies CreateAppConfig; +} satisfies BkndConfig; const app = createApp(config); // build the app @@ -40,13 +44,153 @@ export default app; In Web API compliant environments, all you have to do is to default exporting the app, as it implements the `Fetch` API. -## Configuration (`CreateAppConfig`) +## Modes -The `CreateAppConfig` type is the main configuration object for the `createApp` function. It has +Main project goal is to provide a backend that can be configured visually with the built-in Admin UI. However, you may instead want to configure your backend programmatically, and define your data structure with a Drizzle-like API: + + + }> + This is the default mode, it allows visual configuration and saves the configuration to the database. Expects you to deploy your backend separately from your frontend. + + }> + This mode allows you to configure your backend programmatically, and define your data structure with a Drizzle-like API. Visual configuration controls are disabled. + + }> + This mode allows you to configure your backend visually while in development, and uses the produced configuration in a code-only mode for maximum performance. + + + +In the following sections, we'll cover the different modes in more detail. The configuration properties involved are the following: + +```typescript title="bknd.config.ts" +import type { BkndConfig } from "bknd"; + +export default { + config: { /* ... */ } + options: { + mode: "db", // or "code" + manager: { + secrets: { /* ... */ }, + storeSecrets: true, + }, + } +} satisfies BkndConfig; +``` + + + +### UI-only mode + +This mode is the default mode. It allows you to configure your backend visually with the built-in Admin UI. It expects that you deploy your backend separately from your frontend, and make changes there. No configuration is needed, however, if you want to provide an initial configuration, you can do so by passing a `config` object. + +```typescript +import type { BkndConfig } from "bknd"; + +export default { + // this will only be applied if the database is empty + config: { /* ... */ }, +} satisfies BkndConfig; +``` + +### Code-only mode + +This mode allows you to configure your backend programmatically, and define your data structure with a Drizzle-like API. Visual configuration controls are disabled. + +```typescript title="bknd.config.ts" +import { type BkndConfig, em, entity, text, boolean } from "bknd"; +import { secureRandomString } from "bknd/utils"; + +const schema = em({ + todos: entity("todos", { + title: text(), + done: boolean(), + }), +}); + +export default { + // example configuration + config: { + data: schema.toJSON(), + auth: { + enabled: true, + jwt: { + secret: secureRandomString(64), + }, + } + }, + options: { + // this ensures that the provided configuration is always used + mode: "code", + }, +} satisfies BkndConfig; +``` + +### Hybrid mode + +This mode allows you to configure your backend visually while in development, and uses the produced configuration in a code-only mode for maximum performance. It gives you the best of both worlds. + +While in development, we set the mode to `"db"` where the configuration is stored in the database. When it's time to deploy, we export the configuration, and set the mode to `"code"`. While in `"db"` mode, the `config` property interprets the value as an initial configuration to use when the database is empty. + +```typescript title="bknd.config.ts" +import type { BkndConfig } from "bknd"; + +// import your produced configuration +import appConfig from "./appconfig.json" with { type: "json" }; + +export default { + config: appConfig, + options: { + mode: process.env.NODE_ENV === "development" ? "db" : "code", + manager: { + secrets: process.env + } + }, +} satisfies BkndConfig; +``` + +To keep your config, secrets and types in sync, you can either use the CLI or the plugins. + + +| Type | Plugin | CLI Command | +|----------------|-----------------------------------------------------------------------|----------------------------| +| Configuration | [`syncConfig`](/extending/plugins/#syncconfig) | [`config`](/usage/cli/#getting-the-configuration-config) | +| Secrets | [`syncSecrets`](/extending/plugins/#syncsecrets) | [`secrets`](/usage/cli/#getting-the-secrets-secrets) | +| Types | [`syncTypes`](/extending/plugins/#synctypes) | [`types`](/usage/cli/#generating-types-types) | + + + +## Configuration (`BkndConfig`) + +The `BkndConfig` type is the main configuration object for the `createApp` function. It has the following properties: ```typescript -import type { App, InitialModuleConfigs, ModuleBuildContext, Connection } from "bknd"; +import type { App, InitialModuleConfigs, ModuleBuildContext, Connection, MaybePromise } from "bknd"; import type { Config } from "@libsql/client"; type AppPlugin = (app: App) => Promise | void; @@ -57,13 +201,19 @@ type ManagerOptions = { seed?: (ctx: ModuleBuildContext) => Promise; }; -type CreateAppConfig = { +type BkndConfig = { connection?: Connection | Config; - initialConfig?: InitialModuleConfigs; + config?: InitialModuleConfigs; options?: { plugins?: AppPlugin[]; manager?: ManagerOptions; }; + app?: BkndConfig | ((args: Args) => MaybePromise>); + onBuilt?: (app: App) => Promise; + beforeBuild?: (app?: App) => Promise; + buildConfig?: { + sync?: boolean; + } }; ``` @@ -72,18 +222,31 @@ type CreateAppConfig = { The `connection` property is the main connection object to the database. It can be either an object with libsql config or the actual `Connection` class. ```ts -const connection = { - url: "", - authToken: "", -}; +// uses the default SQLite connection depending on the runtime +const connection = { url: "" }; + +// the same as above, but more explicit +import { sqlite } from "bknd/adapter/sqlite"; +const connection = sqlite({ url: "" }); + +// Node.js SQLite, default on Node.js +import { nodeSqlite } from "bknd/adapter/node"; +const connection = nodeSqlite({ url: "" }); + +// Bun SQLite, default on Bun +import { bunSqlite } from "bknd/adapter/bun"; +const connection = bunSqlite({ url: "" }); + +// LibSQL, default on Cloudflare +import { libsql } from "bknd"; +const connection = libsql({ url: "" }); ``` -Alternatively, you can pass an instance of a `Connection` class directly, -see [Custom Connection](/usage/database#custom-connection) as a reference. +See a full list of available connections in the [Database](/usage/database) section. Alternatively, you can pass an instance of a `Connection` class directly, see [Custom Connection](/usage/database#custom-connection) as a reference. If the connection object is omitted, the app will try to use an in-memory database. -### `initialConfig` +### `config` As [initial configuration](/usage/database#initial-structure), you can either pass a partial configuration object or a complete one with a version number. The version number is used to automatically migrate the configuration up @@ -157,10 +320,10 @@ to the latest version upon boot. The default configuration looks like this: } ``` -You can use the CLI to get the default configuration: +You can use the [CLI](/usage/cli/#getting-the-configuration-config) to get the default configuration: ```sh -npx bknd config --pretty +npx bknd config --default --pretty ``` To validate your configuration against a JSON schema, you can also dump the schema using the CLI: @@ -191,6 +354,10 @@ make sure to only run trusted ones. ### `options.seed` + + The seed function will only be executed on app's first boot in `"db"` mode. If a configuration already exists in the database, or in `"code"` mode, it will not be executed. + + The `seed` property is a function that is called when the app is booted for the first time. It is used to seed the database with initial data. The function is passed a `ModuleBuildContext` object: ```ts @@ -211,18 +378,3 @@ const seed = async (ctx: ModuleBuildContext) => { }; ``` -### `options.manager` - -This object is passed to the `ModuleManager` which is responsible for: - -- validating and maintaining configuration of all modules -- building all modules (data, auth, media, flows) -- maintaining the `ModuleBuildContext` used by the modules - -The `options.manager` object has the following properties: - -- `basePath` (`string`): The base path for the Hono instance. This is used to prefix all routes. -- `trustFetched` (`boolean`): If set to `true`, the app will not perform any validity checks for - the given or fetched configuration. -- `onFirstBoot` (`() => Promise`): A function that is called when the app is booted for - the first time. diff --git a/docs/content/docs/(documentation)/usage/mcp/meta.json b/docs/content/docs/(documentation)/usage/mcp/meta.json index b85e570..5482549 100644 --- a/docs/content/docs/(documentation)/usage/mcp/meta.json +++ b/docs/content/docs/(documentation)/usage/mcp/meta.json @@ -1,4 +1,5 @@ { "title": "MCP", + "icon": "Mcp", "pages": ["overview", "tools-resources"] } diff --git a/docs/content/docs/(documentation)/usage/mcp/tools-resources.mdx b/docs/content/docs/(documentation)/usage/mcp/tools-resources.mdx index 6cb798e..78e1b45 100644 --- a/docs/content/docs/(documentation)/usage/mcp/tools-resources.mdx +++ b/docs/content/docs/(documentation)/usage/mcp/tools-resources.mdx @@ -1,5 +1,5 @@ --- -title: "MCP" +title: "Tools & Resources" description: "Tools & Resources of the built-in full featured MCP server." tags: ["documentation"] --- diff --git a/docs/content/docs/(documentation)/usage/react.mdx b/docs/content/docs/(documentation)/usage/react.mdx index f5e6efd..6b58af8 100644 --- a/docs/content/docs/(documentation)/usage/react.mdx +++ b/docs/content/docs/(documentation)/usage/react.mdx @@ -1,6 +1,7 @@ --- title: "SDK (React)" description: "Use the bknd SDK for React" +icon: React tags: ["documentation"] --- diff --git a/docs/content/docs/(documentation)/usage/sdk.mdx b/docs/content/docs/(documentation)/usage/sdk.mdx index 70c194a..8f95b7f 100644 --- a/docs/content/docs/(documentation)/usage/sdk.mdx +++ b/docs/content/docs/(documentation)/usage/sdk.mdx @@ -1,6 +1,7 @@ --- title: "SDK (TypeScript)" description: "Use the bknd SDK in TypeScript" +icon: TypeScript tags: ["documentation"] --- diff --git a/docs/lib/icons.tsx b/docs/lib/icons.tsx new file mode 100644 index 0000000..b51ba6a --- /dev/null +++ b/docs/lib/icons.tsx @@ -0,0 +1,25 @@ +import { icons } from "lucide-react"; +import { TbBrandReact, TbBrandTypescript } from "react-icons/tb"; + +const McpIcon = () => ( + + ModelContextProtocol + + + +); + +export default { + ...icons, + React: TbBrandReact, + TypeScript: TbBrandTypescript, + Mcp: McpIcon, +}; diff --git a/docs/lib/source.ts b/docs/lib/source.ts index 5969b7b..d619b53 100644 --- a/docs/lib/source.ts +++ b/docs/lib/source.ts @@ -1,23 +1,29 @@ import { loader } from "fumadocs-core/source"; import { docs } from "@/.source"; import { createOpenAPI, attachFile } from "fumadocs-openapi/server"; -import { icons } from "lucide-react"; +import icons from "./icons"; import { createElement } from "react"; +import { TbBrandReact, TbBrandTypescript } from "react-icons/tb"; + +const add_icons = { + TypeScript: TbBrandTypescript, + React: TbBrandReact, +}; export const source = loader({ - baseUrl: "/", - source: docs.toFumadocsSource(), - pageTree: { - // adds a badge to each page item in page tree - attachFile, - }, - icon(icon) { - if (!icon) { - // You may set a default icon - return; - } - if (icon in icons) return createElement(icons[icon as keyof typeof icons]); - }, + baseUrl: "/", + source: docs.toFumadocsSource(), + pageTree: { + // adds a badge to each page item in page tree + attachFile, + }, + icon(icon) { + if (!icon) { + // You may set a default icon + return; + } + if (icon in icons) return createElement(icons[icon as keyof typeof icons]); + }, }); export const openapi = createOpenAPI(); diff --git a/docs/package-lock.json b/docs/package-lock.json index 8fb69ff..8f5b0af 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -23,6 +23,7 @@ "next": "15.3.5", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "tailwind-merge": "^3.3.1", "twoslash": "^0.3.2" }, @@ -10352,6 +10353,15 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/docs/package.json b/docs/package.json index 346a3be..15c5d53 100644 --- a/docs/package.json +++ b/docs/package.json @@ -31,6 +31,7 @@ "next": "15.3.5", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "tailwind-merge": "^3.3.1", "twoslash": "^0.3.2" }, diff --git a/docs/source.config.ts b/docs/source.config.ts index d91842e..bbe843c 100644 --- a/docs/source.config.ts +++ b/docs/source.config.ts @@ -1,11 +1,5 @@ import { remarkInstall } from "fumadocs-docgen"; -import { - defineConfig, - defineDocs, - frontmatterSchema, - metaSchema, -} from "fumadocs-mdx/config"; - +import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config"; import { transformerTwoslash } from "fumadocs-twoslash"; import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs"; import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins"; @@ -14,31 +8,31 @@ import { remarkAutoTypeTable } from "fumadocs-typescript"; // You can customise Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.vercel.app/docs/mdx/collections#define-docs export const docs = defineDocs({ - docs: { - schema: frontmatterSchema, - }, - meta: { - schema: metaSchema, - }, + docs: { + schema: frontmatterSchema, + }, + meta: { + schema: metaSchema, + }, }); export default defineConfig({ - mdxOptions: { - remarkPlugins: [remarkInstall, remarkAutoTypeTable], - rehypeCodeOptions: { - lazy: true, - experimentalJSEngine: true, - langs: ["ts", "js", "html", "tsx", "mdx"], - themes: { - light: "light-plus", - dark: "dark-plus", + mdxOptions: { + remarkPlugins: [remarkInstall, remarkAutoTypeTable], + rehypeCodeOptions: { + lazy: true, + experimentalJSEngine: true, + langs: ["ts", "js", "html", "tsx", "mdx"], + themes: { + light: "light-plus", + dark: "dark-plus", + }, + transformers: [ + ...(rehypeCodeDefaultOptions.transformers ?? []), + transformerTwoslash({ + typesCache: createFileSystemTypesCache(), + }), + ], }, - transformers: [ - ...(rehypeCodeDefaultOptions.transformers ?? []), - transformerTwoslash({ - typesCache: createFileSystemTypesCache(), - }), - ], - }, - }, + }, });