mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Release 0.16 (#196)
* initial refactor * fixes * test secrets extraction * updated lock * fix secret schema * updated schemas, fixed tests, skipping flow tests for now * added validator for rjsf, hook form via standard schema * removed @sinclair/typebox * remove unneeded vite dep * fix jsonv literal on Field.tsx * fix schema import path * fix schema modals * fix schema modals * fix json field form, replaced auth form * initial waku * finalize waku example * fix jsonv-ts version * fix schema updates with falsy values * fix media api to respect options' init, improve types * checking media controller test * checking media controller test * checking media controller test * clean up mediacontroller test * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` (#214) * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` * fix server test * fix data api (updated jsonv-ts) * enhance cloudflare image optimization plugin with new options and explain endpoint (#215) * feat: add ability to serve static by using dynamic imports (#197) * feat: add ability to serve static by using dynamic imports * serveStaticViaImport: make manifest optional * serveStaticViaImport: add error log * refactor/imports (#217) * refactored core and core/utils imports * refactored core and core/utils imports * refactored media imports * refactored auth imports * refactored data imports * updated package json exports, fixed mm config * fix tests * feat/deno (#219) * update bun version * fix module manager's em reference * add basic deno example * finalize * docs: fumadocs migration (#185) * feat(docs): initialize documentation structure with Fumadocs * feat(docs): remove home route and move /docs route to /route * feat(docs): add redirect to /start page * feat(docs): migrate Getting Started chapters * feat(docs): migrate Usage and Extending chapters * feat(callout): add CalloutCaution, CalloutDanger, CalloutInfo, and CalloutPositive * feat(layout): add Discord and GitHub links to documentation layout * feat(docs): add integration chapters draft * feat(docs): add modules chapters draft * refactor(mdx-components): remove unused Icon import * refactor(StackBlitz): enhance type safety by using unknown instead of any * refactor(layout): update navigation mode to 'top' in layout configuration * feat(docs): add @iconify/react package * docs(mdx-components): add Icon component to MDX components list * feat(docs): update Next.js integration guide * feat(docs): update React Router integration guide * feat(docs): update Astro integration guide * feat(docs): update Vite integration guide * fix(docs): update package manager initialization commands * feat(docs): migrate Modules chapters * chore(docs): update package.json with new devDependencies * feat(docs): migrate Integration Runtimes chapters * feat(docs): update Database usage chapter * feat(docs): restructure documentation paths * chore(docs): clean up unused imports and files in documentation * style(layout): revert navigation mode to previous state * fix(docs): routing for documentation structure * feat(openapi): add API documentation generation from OpenAPI schema * feat(docs): add icons to documentation pages * chore(dependencies): remove unused content-collections packages * fix(types): fix type error for attachFile in source.ts * feat(redirects): update root redirect destination to '/start' * feat(search): add static search functionality * chore(dependencies): update fumadocs-core and fumadocs-ui to latest versions * feat(search): add Powered by Orama link * feat(generate-openapi): add error handling for missing OpenAPI schema * feat(scripts): add OpenAPI generation to build process * feat(config): enable dynamic redirects and rewrites in development mode * feat(layout): add GitHub token support for improved API rate limits * feat(redirects): add 301 redirects for cloudflare pages * feat(docs): add Vercel redirects configuration * feat(config): enable standalone output for development environment * chore(layout): adjust layout settings * refactor(package): clean up ajv dependency versions * feat(docs): add twoslash support * refactor(layout): update DocsLayout import and navigation configuration * chore(layout): clean up layout.tsx by commenting out GithubInfo * fix(Search): add locale to search initialization * chore(package): update fumadocs and orama to latest versions * docs: add menu items descriptions * feat(layout): add GitHub URL to the layout component * feat(docs): add AutoTypeTable component to MDX components * feat(app): implement AutoTypeTable rendering for AppEvents type * docs(layout): switch callouts back to default components * fix(config): use __filename and __dirname for module paths * docs: add note about node.js 22 requirement * feat(styles): add custom color variables for light and dark themes * docs: add S3 setup instructions for media module * docs: fix typos and indentation in media module docs * docs: add local media adapter example for Node.js * docs(media): add S3/R2 URL format examples and fix typo * docs: add cross-links to initial config and seeding sections * indent numbered lists content, clarified media serve locations * fix mediacontroller tests * feat(layout): add AnimatedGridPattern component for dynamic background * style(layout): configure fancy ToC style ('clerk') * fix(AnimatedGridPattern): correct strokeDasharray type * docs: actualize docs * feat: add favicon * style(cloudflare): format code examples * feat(layout): add Github and Discord footer icons * feat(footer): add SVG social media icons for GitHub and Discord * docs: adjusted auto type table, added llm functions * added static deployment to cloudflare workers * docs: change cf redirects to proxy *.mdx instead of redirecting --------- Co-authored-by: dswbx <dennis.senn@gmx.ch> Co-authored-by: cameronapak <cameronandrewpak@gmail.com> * build: improve build script * add missing exports, fix EntityTypescript imports * media: Dropzone: add programmatic upload, additional events, loading state * schema object: disable extended defaults to allow empty config values * Feat/new docs deploy (#224) * test * try fixing pm * try fixing pm * fix docs on imports, export events correctly --------- Co-authored-by: Tim Seriakov <59409712+timseriakov@users.noreply.github.com> Co-authored-by: cameronapak <cameronandrewpak@gmail.com>
This commit is contained in:
159
docs/content/docs/(documentation)/extending/config.mdx
Normal file
159
docs/content/docs/(documentation)/extending/config.mdx
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
title: bknd.config.ts
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
The central configuration file to extend bknd should be placed in the root of your project, so that the [CLI](/usage/cli#using-configuration-file-bknd-config) can automatically pick it up. It allows to:
|
||||
|
||||
- define your database connection centrally
|
||||
- pass in [initial configuration](/usage/database#initial-structure) or [data seeds](/usage/database#seeding-the-database) when booting the first time
|
||||
- add plugins to the app
|
||||
- hook into system events
|
||||
- define custom routes and endpoints
|
||||
|
||||
A simple example of a `bknd.config.ts` file:
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
The `BkndConfig` extends the [`CreateAppConfig`](/usage/introduction#configuration-createappconfig) type with the following properties:
|
||||
|
||||
{/* <AutoTypeTable path="../app/src/adapter/index.ts" name="BkndConfig" /> */}
|
||||
|
||||
|
||||
```typescript
|
||||
export type BkndConfig = CreateAppConfig & {
|
||||
// return the app configuration as object or from a function
|
||||
app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
|
||||
// called before the app is built
|
||||
beforeBuild?: (app: App) => Promise<void>;
|
||||
// called after the app has been built
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
// passed as the first argument to the `App.build` method
|
||||
buildConfig?: Parameters<App["build"]>[0];
|
||||
// force the app to be recreated
|
||||
force?: boolean;
|
||||
// the id of the app, defaults to `app`
|
||||
id?: string;
|
||||
};
|
||||
```
|
||||
|
||||
The supported configuration file extensions are `js`, `ts`, `mjs`, `cjs` and `json`. Throughout the documentation, we'll use `ts` for the file extension.
|
||||
|
||||
### `app` (CreateAppConfig)
|
||||
|
||||
The `app` property is a function that returns a `CreateAppConfig` object. It allows to pass in the environment variables to the configuration object.
|
||||
|
||||
```typescript
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
app: ({ env }) => ({
|
||||
connection: {
|
||||
url: env.DB_URL,
|
||||
},
|
||||
}),
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
See [Database](/usage/database) for more information on how to configure the database connection.
|
||||
|
||||
### `beforeBuild`
|
||||
|
||||
The `beforeBuild` property is an async function that is called before the app is built. It allows to modify the app instance that may influence the build process.
|
||||
|
||||
```typescript
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
beforeBuild: async (app: App) => {
|
||||
// do something that has to happen before the app is built
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `onBuilt`
|
||||
|
||||
The `onBuilt` property is an async function that is called after the app has been built. It allows to hook into the app after it has been built. This is useful for defining event listeners or register custom routes, as both the event manager and the server are recreated during the build process.
|
||||
|
||||
```typescript
|
||||
import { type App, AppEvents } from "bknd";
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
onBuilt: async (app: App) => {
|
||||
console.log("App built", app.version());
|
||||
|
||||
// registering an event listener
|
||||
app.emgr.onEvent(AppEvents.AppRequest, (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
|
||||
// registering a custom route
|
||||
app.server.get("/hello", (c) => c.text("Hello World"));
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `force` & `id`
|
||||
|
||||
The `force` property is a boolean that forces the app to be recreated. This is mainly useful for serverless environments where the execution environment is re-used, and you may or may not want to recreate the app on every request.
|
||||
|
||||
The `id` property is the reference in a cache map. You may create multiple instances of apps in the same process by using different ids (e.g. multi tenant applications).
|
||||
|
||||
## Framework & Runtime configuration
|
||||
|
||||
Depending on which framework or runtime you're using to run bknd, the configuration object will extend the `BkndConfig` type with additional properties.
|
||||
|
||||
### `RuntimeBkndConfig`
|
||||
|
||||
[Runtime adapters](/integration/runtime) need additional configuration to serve static assets for the admin UI.
|
||||
|
||||
```typescript
|
||||
export type RuntimeBkndConfig<Args = any> = BkndConfig<Args> & {
|
||||
// the path to the dist folder to serve static assets for the admin UI
|
||||
distPath?: string;
|
||||
// custom middleware to serve static assets for the admin UI
|
||||
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
|
||||
// options for the admin UI
|
||||
adminOptions?: AdminControllerOptions | false;
|
||||
};
|
||||
```
|
||||
|
||||
### `FrameworkBkndConfig`
|
||||
|
||||
[Framework adapters](/integration/framework) may need additional configuration based on the framework's requirements. For example, the `NextjsBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
type NextjsEnv = NextApiRequest["env"];
|
||||
export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
|
||||
cleanRequest?: { searchParams?: string[] };
|
||||
};
|
||||
```
|
||||
|
||||
Next.js adds the mounted path to the request object, so that the `cleanRequest` property can be used to remove the mounted path from the request URL. See other frameworks for more information on how to configure them.
|
||||
|
||||
## Using the configuration file
|
||||
|
||||
The configuration file is automatically picked up if you're using the [CLI](/usage/cli). This allows interacting with your application using the `bknd` command. For example, you can run the following command in the root of your project to start an instance:
|
||||
|
||||
```bash
|
||||
npx bknd run
|
||||
```
|
||||
|
||||
When serving your application, you need to make sure to import the contents of your configuration file. If you're using Next.js for example, it's recommended to follow these steps:
|
||||
|
||||
1. create a `bknd.config.ts` file in the root of your project which defines the connection to the database, adds event listeners and custom routes.
|
||||
2. create a `bknd.ts` file inside your app folder which exports helper functions to instantiate the bknd instance and retrieve the API.
|
||||
3. create a catch-all route file at `src/api/[[...bknd]]/route.ts` which serves the bknd API.
|
||||
|
||||
This way, your application and the CLI are using the same configuration.
|
||||
141
docs/content/docs/(documentation)/extending/events.mdx
Normal file
141
docs/content/docs/(documentation)/extending/events.mdx
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Events & Hooks
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
bknd comes with a powerful built-in event system that allows you to hook into the app lifecycle and extend its functionality. You can hook into these events in two ways:
|
||||
|
||||
- `async`: Your listener is not blocking the main execution flow. E.g. on Cloudflare Workers, by default, the `ExecutionContext`'s `waitUntil` method is used so that the listeners runs after the response is sent.
|
||||
- `sync`: Your listener is blocking the main execution flow. This allows to abort the request in your custom conditions. Some events also allow to return a modified event payload.
|
||||
|
||||
<Callout type="info">
|
||||
Don't mistake `async` with JavaScript's `async` keyword. The `async` keyword
|
||||
is used to indicate that the listener is not blocking the main execution flow.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
### Listening to events
|
||||
|
||||
You can listen to events by using the `EventManager` exposed at `app.emgr`. To register a listener, you can use either of these methods:
|
||||
|
||||
- `onEvent`: Register a listener for a specific event with a typed event.
|
||||
- `on`: Register a listener for an event by its slug.
|
||||
- `onAny`: Register a listener for all events.
|
||||
|
||||
```typescript
|
||||
import { createApp, AppEvents } from "bknd";
|
||||
const app = createApp();
|
||||
|
||||
app.emgr.onEvent(AppEvents.AppRequest, async (event) => {
|
||||
// ^? AppRequest
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
|
||||
app.emgr.on("app-request", async (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
|
||||
app.emgr.onAny(async (event) => {
|
||||
console.log("Event received", event.slug);
|
||||
});
|
||||
```
|
||||
|
||||
You may want to register your listeners inside [`bknd.config.ts`](/extending/config) to make sure they are registered before the app is built:
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import { AppEvents } from "bknd";
|
||||
|
||||
export default {
|
||||
onBuilt: (app) => {
|
||||
app.emgr.onEvent(AppEvents.AppRequest, async (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Setting a mode
|
||||
|
||||
By default, listeners are registered as `async` listeners, meaning that the listener is not blocking the main execution flow. You can change this by passing the mode as the third argument:
|
||||
|
||||
```typescript
|
||||
app.emgr.onEvent(
|
||||
AppEvents.AppRequest,
|
||||
async (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
},
|
||||
{ mode: "sync" },
|
||||
);
|
||||
```
|
||||
|
||||
This works for all three methods.
|
||||
|
||||
## App Events
|
||||
|
||||
These events are emitted by the `App` class and are available on the `AppEvents` object.
|
||||
|
||||
```typescript
|
||||
import { AppEvents } from "bknd";
|
||||
```
|
||||
|
||||
Available events:
|
||||
|
||||
{/* <AutoTypeTable path="../app/src/App.ts" name="AppEvents" /> */}
|
||||
| Event | Params | Description |
|
||||
|:------|:--------|:------------|
|
||||
| `AppConfigUpdatedEvent` | `{ app: App }` | Emitted when the app configuration is updated |
|
||||
| `AppBuiltEvent` | `{ app: App }` | Emitted when the app is built |
|
||||
| `AppFirstBoot` | `{ app: App }` | Emitted when the app is first booted |
|
||||
| `AppRequest` | `{ app: App, request: Request }` | Emitted when a request is received |
|
||||
| `AppBeforeResponse` | `{ app: App, request: Request, response: Response }` | Emitted before a response is sent |
|
||||
|
||||
## Database Events
|
||||
|
||||
These events are emitted by the `Database` class and are available on the `DatabaseEvents` object. These are divided by events triggered by the `Mutator` and `Repository` classes.
|
||||
|
||||
```typescript
|
||||
import { DatabaseEvents } from "bknd";
|
||||
```
|
||||
|
||||
### Mutator Events
|
||||
|
||||
These events are emitted during database mutations (insert, update, delete operations).
|
||||
|
||||
| Event | Params | Description |
|
||||
| :-------------------- | :-------------------------------------------------------------------------------------- | :---------------------------------------------------------- |
|
||||
| `MutatorInsertBefore` | `{ entity: Entity, data: EntityData }` | Emitted before inserting a new record. Can modify the data. |
|
||||
| `MutatorInsertAfter` | `{ entity: Entity, data: EntityData, changed: EntityData }` | Emitted after inserting a new record. |
|
||||
| `MutatorUpdateBefore` | `{ entity: Entity, entityId: PrimaryFieldType, data: EntityData }` | Emitted before updating a record. Can modify the data. |
|
||||
| `MutatorUpdateAfter` | `{ entity: Entity, entityId: PrimaryFieldType, data: EntityData, changed: EntityData }` | Emitted after updating a record. |
|
||||
| `MutatorDeleteBefore` | `{ entity: Entity, entityId: PrimaryFieldType }` | Emitted before deleting a record. |
|
||||
| `MutatorDeleteAfter` | `{ entity: Entity, entityId: PrimaryFieldType, data: EntityData }` | Emitted after deleting a record. |
|
||||
|
||||
### Repository Events
|
||||
|
||||
These events are emitted during database queries (find operations).
|
||||
|
||||
| Event | Params | Description |
|
||||
| :------------------------- | :--------------------------------------------------------- | :--------------------------------------- |
|
||||
| `RepositoryFindOneBefore` | `{ entity: Entity, options: RepoQuery }` | Emitted before finding a single record. |
|
||||
| `RepositoryFindOneAfter` | `{ entity: Entity, options: RepoQuery, data: EntityData }` | Emitted after finding a single record. |
|
||||
| `RepositoryFindManyBefore` | `{ entity: Entity, options: RepoQuery }` | Emitted before finding multiple records. |
|
||||
| `RepositoryFindManyAfter` | `{ entity: Entity, options: RepoQuery, data: EntityData }` | Emitted after finding multiple records. |
|
||||
|
||||
## Media Events
|
||||
|
||||
These events are emitted by the `Storage` class and are available on the `MediaEvents` object.
|
||||
|
||||
```typescript
|
||||
import { MediaEvents } from "bknd";
|
||||
```
|
||||
|
||||
| Event | Params | Description |
|
||||
| :------------------ | :--------------------------------------- | :----------------------------------------------------------------------- |
|
||||
| `FileUploadedEvent` | `{ file: FileBody } & FileUploadPayload` | Emitted when a file is successfully uploaded. Can modify the event data. |
|
||||
| `FileDeletedEvent` | `{ name: string }` | Emitted when a file is deleted. |
|
||||
| `FileAccessEvent` | `{ name: string }` | Emitted when a file is accessed. |
|
||||
|
||||
## Auth Events
|
||||
|
||||
Coming soon.
|
||||
@@ -0,0 +1,229 @@
|
||||
---
|
||||
title: "Astro"
|
||||
description: "Run bknd inside Astro"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Astro and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new Astro CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i astro
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Astro project by following the [official guide](https://docs.astro.build/en/install-and-setup/), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
<Callout type="info">
|
||||
The guide below assumes you're using Astro v4. We've experienced issues with
|
||||
Astro DB using v5, see [this
|
||||
issue](https://github.com/withastro/astro/issues/12474).
|
||||
</Callout>
|
||||
|
||||
For the Astro integration to work, you also need to [add the react integration](https://docs.astro.build/en/guides/integrations-guide/react/):
|
||||
|
||||
```bash
|
||||
npx astro add react
|
||||
```
|
||||
|
||||
You also need to make sure to set the output to `server` in your Astro config:
|
||||
|
||||
```js {6}
|
||||
// astro.config.mjs
|
||||
import { defineConfig } from "astro/config";
|
||||
import react from "@astrojs/react";
|
||||
|
||||
export default defineConfig({
|
||||
output: "server", // [!code highlight]
|
||||
integrations: [react()],
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
If you don't want to use React with Astro, there is also an option to serve
|
||||
the bknd Admin UI statically using Astro's middleware. In case you're
|
||||
interested in this, feel free to reach out in
|
||||
[Discord](https://discord.gg/952SFk8Tb8) or open an [issue on
|
||||
GitHub](https://github.com/bknd-io/bknd/issues/new).
|
||||
</Callout>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { AstroBkndConfig } from "bknd/adapter/astro";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies AstroBkndConfig;
|
||||
```
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd. The `AstroBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
type AstroEnv = NodeJS.ProcessEnv;
|
||||
export type AstroBkndConfig<Env = AstroEnv> = FrameworkBkndConfig<Env>;
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the `bknd.config.ts` file:
|
||||
|
||||
```ts title="src/bknd.ts"
|
||||
import type { AstroGlobal } from "astro";
|
||||
import { getApp as getBkndApp } from "bknd/adapter/astro";
|
||||
import config from "../bknd.config";
|
||||
|
||||
export { config };
|
||||
|
||||
export async function getApp() {
|
||||
return await getBkndApp(config);
|
||||
}
|
||||
|
||||
export async function getApi(
|
||||
astro: AstroGlobal,
|
||||
opts?: { mode: "static" } | { mode?: "dynamic"; verify?: boolean },
|
||||
) {
|
||||
const app = await getApp();
|
||||
if (opts?.mode !== "static" && opts?.verify) {
|
||||
const api = app.getApi({ headers: astro.request.headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
}
|
||||
```
|
||||
|
||||
Create a new catch-all route at `src/pages/api/[...api].ts`.
|
||||
|
||||
```ts title="src/pages/api/[...api].ts"
|
||||
import { serve } from "bknd/adapter/astro";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const ALL = serve({
|
||||
connection: {
|
||||
// location of your local Astro DB
|
||||
// make sure to use a remote URL in production
|
||||
url: "file:.astro/content.db",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide. In the
|
||||
special case of astro, you may also use your Astro DB credentials since it's also using LibSQL
|
||||
under the hood. Refer to the [Astro DB documentation](https://docs.astro.build/en/guides/astro-db/) for more information.
|
||||
|
||||
## Enabling the Admin UI
|
||||
|
||||
Create a new catch-all route at `src/pages/admin/[...admin].astro`:
|
||||
|
||||
```jsx title="src/pages/admin/[...admin].astro"
|
||||
---
|
||||
import { Admin } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
import { getApi } from "bknd/adapter/astro";
|
||||
|
||||
const api = await getApi(Astro, { mode: "dynamic" });
|
||||
const user = api.getUser();
|
||||
|
||||
export const prerender = false;
|
||||
---
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<Admin
|
||||
withProvider={{ user }}
|
||||
config={{
|
||||
basepath: "/admin",
|
||||
color_scheme: "dark",
|
||||
logo_return_path: "/../"
|
||||
}}
|
||||
client:only
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You use the API in both static and SSR pages. Just note that on static pages, authentication
|
||||
might not work as expected, because Cookies are not available in the static context.
|
||||
|
||||
Here is an example of using the API in static context:
|
||||
|
||||
```jsx
|
||||
---
|
||||
import { getApi } from "bknd/adapter/astro";
|
||||
const api = await getApi(Astro);
|
||||
const { data } = await api.data.readMany("todos");
|
||||
---
|
||||
|
||||
<ul>
|
||||
{data.map((todo) => (
|
||||
<li>{todo.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
```
|
||||
|
||||
On SSR pages, you can also access the authenticated user:
|
||||
|
||||
```jsx
|
||||
---
|
||||
import { getApi } from "bknd/adapter/astro";
|
||||
const api = await getApi(Astro, { mode: "dynamic" });
|
||||
const user = api.getUser();
|
||||
const { data } = await api.data.readMany("todos");
|
||||
|
||||
export const prerender = false;
|
||||
---
|
||||
|
||||
{user
|
||||
? <p>Logged in as <b>{user.email}</b>.</p>
|
||||
: <p>Not authenticated.</p>}
|
||||
<ul>
|
||||
{data.map((todo) => (
|
||||
<li>{todo.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
```
|
||||
|
||||
Check the [astro repository example](https://github.com/bknd-io/bknd/tree/main/examples/astro)
|
||||
for more implementation details or a [fully working example using Astro DB](https://github.com/dswbx/bknd-astro-example).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["nextjs", "react-router", "astro", "vite"]
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: "Next.js"
|
||||
description: "Run bknd inside Next.js"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Next.js and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter.
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new Next.js CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i nextjs
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Next.js project by following the [official guide](https://nextjs.org/docs/pages/api-reference/cli/create-next-app), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { NextjsBkndConfig } from "bknd/adapter/nextjs";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies NextjsBkndConfig;
|
||||
```
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd. The `NextjsBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
type NextjsEnv = NextApiRequest["env"];
|
||||
export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
|
||||
cleanRequest?: { searchParams?: string[] };
|
||||
};
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the `bknd.config.ts` file:
|
||||
|
||||
```ts title="src/bknd.ts"
|
||||
import {
|
||||
type NextjsBkndConfig,
|
||||
getApp as getBkndApp,
|
||||
} from "bknd/adapter/nextjs";
|
||||
import { headers } from "next/headers";
|
||||
import config from "../bknd.config";
|
||||
|
||||
export { config };
|
||||
|
||||
export async function getApp() {
|
||||
return await getBkndApp(config, process.env);
|
||||
}
|
||||
|
||||
export async function getApi(opts?: { verify?: boolean }) {
|
||||
const app = await getApp();
|
||||
if (opts?.verify) {
|
||||
const api = app.getApi({ headers: await headers() });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Now to expose the API, create a catch-all route file at `src/api/[[...bknd]]/route.ts`:
|
||||
|
||||
```ts title="src/api/[[...bknd]]/route.ts"
|
||||
import { config } from "@/bknd";
|
||||
import { serve } from "bknd/adapter/nextjs";
|
||||
|
||||
// optionally, you can set the runtime to edge for better performance
|
||||
export const runtime = "edge";
|
||||
|
||||
const handler = serve({
|
||||
...config,
|
||||
cleanRequest: {
|
||||
// depending on what name you used for the catch-all route,
|
||||
// you need to change this to clean it from the request.
|
||||
searchParams: ["bknd"],
|
||||
},
|
||||
});
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
export const PUT = handler;
|
||||
export const PATCH = handler;
|
||||
export const DELETE = handler;
|
||||
```
|
||||
|
||||
## Enabling the Admin UI
|
||||
|
||||
Create a page at `admin/[[...admin]]/page.tsx`:
|
||||
|
||||
```tsx title="admin/[[...admin]]/page.tsx"
|
||||
import { Admin } from "bknd/ui";
|
||||
import { getApi } from "@/bknd";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default async function AdminPage() {
|
||||
// make sure to verify auth using headers
|
||||
const api = await getApi({ verify: true });
|
||||
|
||||
return (
|
||||
<Admin
|
||||
withProvider={{ user: api.getUser() }}
|
||||
config={{
|
||||
basepath: "/admin",
|
||||
logo_return_path: "/../",
|
||||
color_scheme: "system",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You can use the `getApi` helper function we've already set up to fetch and mutate in static pages and server components:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
import { getApi } from "@/bknd";
|
||||
|
||||
export default async function Home() {
|
||||
const api = await getApi();
|
||||
const { data: todos } = await api.data.readMany("todos", { limit: 5 });
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{todos.map((todo) => (
|
||||
<li key={String(todo.id)}>{todo.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: "React Router"
|
||||
description: "Run bknd inside React Router"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with React Router and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new React Router CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i react-router
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new React Router project by following the [official guide](https://reactrouter.com/start/framework/installation), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { ReactRouterBkndConfig } from "bknd/adapter/react-router";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies ReactRouterBkndConfig;
|
||||
```
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd. The `ReactRouterBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
type ReactRouterEnv = NodeJS.ProcessEnv;
|
||||
type ReactRouterFunctionArgs = {
|
||||
request: Request;
|
||||
};
|
||||
export type ReactRouterBkndConfig<Env = ReactRouterEnv> =
|
||||
FrameworkBkndConfig<Env>;
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the `bknd.config.ts` file:
|
||||
|
||||
```ts title="app/bknd.ts"
|
||||
import {
|
||||
type ReactRouterBkndConfig,
|
||||
getApp as getBkndApp,
|
||||
} from "bknd/adapter/react-router";
|
||||
import config from "../bknd.config";
|
||||
|
||||
export { config };
|
||||
|
||||
// you may adjust this function depending on your runtime environment.
|
||||
// e.g. when deploying to cloudflare workers, you'd want the FunctionArgs to be passed in
|
||||
// to resolve environment variables
|
||||
export async function getApp() {
|
||||
return await getBkndApp(config, process.env as any);
|
||||
}
|
||||
|
||||
export async function getApi(
|
||||
args?: { request: Request },
|
||||
opts?: { verify?: boolean },
|
||||
) {
|
||||
const app = await getApp();
|
||||
if (opts?.verify) {
|
||||
const api = app.getApi({ headers: args?.request.headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Create a new api splat route file at `app/routes/api.$.ts`:
|
||||
|
||||
```ts title="app/routes/api.$.ts"
|
||||
import { getApp } from "~/bknd";
|
||||
|
||||
const handler = async (args: { request: Request }) => {
|
||||
const app = await getApp();
|
||||
return app.fetch(args.request);
|
||||
};
|
||||
|
||||
export const loader = handler;
|
||||
export const action = handler;
|
||||
```
|
||||
|
||||
## Enabling the Admin UI
|
||||
|
||||
Create a new splat route file at `app/routes/admin.$.tsx`:
|
||||
|
||||
```tsx title="app/routes/admin.$.tsx"
|
||||
import { lazy, Suspense, useSyncExternalStore } from "react";
|
||||
import { type LoaderFunctionArgs, useLoaderData } from "react-router";
|
||||
import { getApi } from "~/bknd";
|
||||
|
||||
const Admin = lazy(() =>
|
||||
import("bknd/ui").then((mod) => ({ default: mod.Admin })),
|
||||
);
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export const loader = async (args: LoaderFunctionArgs) => {
|
||||
const api = await getApi(args, { verify: true });
|
||||
return {
|
||||
user: api.getUser(),
|
||||
};
|
||||
};
|
||||
|
||||
export default function AdminPage() {
|
||||
const { user } = useLoaderData<typeof loader>();
|
||||
// derived from https://github.com/sergiodxa/remix-utils
|
||||
// @ts-ignore
|
||||
const hydrated = useSyncExternalStore(
|
||||
() => {},
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
if (!hydrated) return null;
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<Admin
|
||||
withProvider={{ user }}
|
||||
config={{ basepath: "/admin", logo_return_path: "/../" }}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You can use the `getApi` helper function we've already set up to fetch and mutate:
|
||||
|
||||
```tsx title="app/routes/_index.tsx"
|
||||
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
|
||||
import { getApi } from "~/bknd";
|
||||
|
||||
export const loader = async (args: LoaderFunctionArgs) => {
|
||||
// use authentication from request
|
||||
const api = await getApi(args, { verify: true });
|
||||
const { data } = await api.data.readMany("todos");
|
||||
return { data, user: api.getUser() };
|
||||
};
|
||||
|
||||
export default function Index() {
|
||||
const { data, user } = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Data</h1>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<h1>User</h1>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
title: "Vite"
|
||||
description: "Run bknd inside Vite"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Vite is a powerful toolkit to accelerate your local development.
|
||||
|
||||
## Installation
|
||||
|
||||
Create a new vite project by following the [official guide](https://vite.dev/guide/#scaffolding-your-first-vite-project)
|
||||
and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
Additionally, install required dependencies:
|
||||
|
||||
```bash
|
||||
npm install @hono/vite-dev-server
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { ViteBkndConfig } from "bknd/adapter/vite";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies ViteBkndConfig;
|
||||
```
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd.
|
||||
The `ViteBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
export type ViteEnv = NodeJS.ProcessEnv;
|
||||
export type ViteBkndConfig<Env = ViteEnv> = RuntimeBkndConfig<Env> & {};
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
To serve the **bknd** API, you first have to create a local server file for you vite environment.
|
||||
Create a `server.ts` file:
|
||||
|
||||
```typescript title="server.ts"
|
||||
import { serve } from "bknd/adapter/vite";
|
||||
import config from "./bknd.config";
|
||||
|
||||
export default serve(config);
|
||||
```
|
||||
|
||||
You can also run your vite server in `mode: "fresh"`, this will re-create the app on every fetch.
|
||||
This is only useful for when working on the `bknd` repository directly.
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Next, adjust your `vite.config.ts` to look like the following:
|
||||
|
||||
```ts title="vite.config.ts"
|
||||
import { devServer } from "bknd/adapter/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
tsconfigPaths(),
|
||||
devServer({
|
||||
// point to your previously created server file
|
||||
entry: "./server.ts",
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Now you can start your application using `npm run dev`. Now opening http://localhost:5174/
|
||||
looks like an empty project. That's because we only registered the API, head over to
|
||||
http://localhost:5174/api/system/config to see **bknd** respond.
|
||||
|
||||
## Serve the Admin UI
|
||||
|
||||
After adding the API, you can easily add the Admin UI by simply returning it in your `App.tsx`.
|
||||
Replace all of its content with the following:
|
||||
|
||||
```tsx title="App.tsx"
|
||||
import { Admin } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default function App() {
|
||||
return <Admin withProvider />;
|
||||
}
|
||||
```
|
||||
|
||||
Now http://localhost:5174/ should give you the Admin UI.
|
||||
|
||||
## Customizations
|
||||
|
||||
This is just the bare minimum and may not always fulfill your requirements. There are a few
|
||||
options you can make use of to adjust it according to your setup.
|
||||
|
||||
### Use custom HTML to serve the Admin UI
|
||||
|
||||
There might be cases you want to be sure to be in control over the HTML that is being used.
|
||||
`bknd` generates it automatically, but you use your own one as follows:
|
||||
|
||||
```typescript title="server.ts"
|
||||
import { serve, addViteScript } from "bknd/adapter/vite";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import config from "./bknd.config";
|
||||
|
||||
let html = await readFile("./index.html", "utf-8");
|
||||
|
||||
// then add it as an option
|
||||
export default serve({
|
||||
...config,
|
||||
adminOptions: {
|
||||
html: addViteScript(html),
|
||||
// optionally, you can change the base path for the admin UI
|
||||
adminBasePath: "/admin",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The vite scripts has to be added manually currently, as adding them automatically with
|
||||
`@hono/vite-dev-server` is buggy. This may change in the future.
|
||||
|
||||
### Use a custom entry point
|
||||
|
||||
By default, the entry point `/src/main.tsx` is used and should fit most cases. If that's not you,
|
||||
you can supply a different one like so:
|
||||
|
||||
```typescript title="server.ts"
|
||||
import { serve } from "bknd/adapter/vite";
|
||||
import config from "./bknd.config";
|
||||
|
||||
// the configuration given is optional
|
||||
export default serve({
|
||||
...config,
|
||||
adminOptions: {
|
||||
forceDev: {
|
||||
mainPath: "/src/special.tsx",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
130
docs/content/docs/(documentation)/integration/(runtimes)/aws.mdx
Normal file
130
docs/content/docs/(documentation)/integration/(runtimes)/aws.mdx
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: "AWS Lambda"
|
||||
description: "Run bknd inside AWS Lambda"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with AWS Lambda and bknd you can either install the package manually and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new Bun CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i aws
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new AWS Lambda project and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Serve the API
|
||||
|
||||
To serve the API, you can use the `serveLambda` function of the AWS Lambda adapter.
|
||||
|
||||
```tsx title="index.mjs"
|
||||
import { serveLambda } from "bknd/adapter/aws";
|
||||
import { libsql } from "bknd";
|
||||
|
||||
export const handler = serveLambda({
|
||||
connection: libsql({
|
||||
url: "libsql://your-database-url.turso.io",
|
||||
authToken: "your-auth-token",
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Although the runtime would support database as a file, we don't recommend it. You'd need to also bundle the native dependencies which increases the deployment size and cold start time. Instead, we recommend you to use [LibSQL on Turso](/usage/database#sqlite-using-libsql-on-turso).
|
||||
|
||||
## Serve the Admin UI
|
||||
|
||||
Lambda functions should be as small as possible. Therefore, the static files for the admin panel should not be served from node_modules like with the Node adapter.
|
||||
|
||||
Instead, we recommend to copy the static files and bundle them with the lambda function. To copy the static files, you can use the `copy-assets` command:
|
||||
|
||||
```bash
|
||||
npx bknd copy-assets --out static
|
||||
```
|
||||
|
||||
This will copy the static files to the `static` directory and then serve them from there:
|
||||
|
||||
```tsx title="index.mjs"
|
||||
import { serveLambda } from "bknd/adapter/aws";
|
||||
|
||||
export const handler = serveLambda({
|
||||
connection: {
|
||||
url: process.env.DB_URL!,
|
||||
authToken: process.env.DB_AUTH_TOKEN!,
|
||||
},
|
||||
assets: {
|
||||
// [!code highlight]
|
||||
mode: "local", // [!code highlight]
|
||||
root: "./static", // [!code highlight]
|
||||
}, // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
To deploy a lambda function, you could follow these steps:
|
||||
|
||||
1. Create an IAM role with a trust policy that allows lambda to assume the role.
|
||||
2. Attach the `AWSLambdaBasicExecutionRole` policy to the role.
|
||||
3. Bundle the lambda function with the static files (e.g. using esbuild)
|
||||
4. Create a zip file with the bundled lambda function
|
||||
5. Create a lambda function
|
||||
6. Create a function URL for the lambda function & make it publicly accessible (optional)
|
||||
|
||||
Depending on your use case, you may want to skip step 6 and use the AWS API Gateway to serve the lambda function. Here is an [example deployment script](https://github.com/bknd-io/bknd/blob/main/examples/aws-lambda/deploy.sh) which creates the AWS resources described above, bundles the lambda function and uploads it.
|
||||
|
||||
### Using the CLI starter
|
||||
|
||||
The CLI starter example includes a basic build script that creates the required AWS resources, copies the static files, bundles the lambda function and uploads it. To deploy the lambda function, you can run:
|
||||
|
||||
```bash
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
To make adjustments to the lambda function created (e.g. architecture, memory, timeout, etc.) you can edit the head section of the `deploy.sh` script.
|
||||
|
||||
```sh title="deploy.sh"
|
||||
# cat deploy.sh | head -12
|
||||
FUNCTION_NAME="bknd-lambda"
|
||||
ROLE_NAME="bknd-lambda-execution-role"
|
||||
RUNTIME="nodejs22.x"
|
||||
HANDLER="index.handler"
|
||||
ARCHITECTURE="arm64" # or "x86_64"
|
||||
MEMORY="1024" # in MB, 128 is the minimum
|
||||
TIMEOUT="30"
|
||||
ENTRY_FILE="index.mjs"
|
||||
ZIP_FILE="lambda.zip"
|
||||
# ...
|
||||
```
|
||||
|
||||
To clean up AWS resources created by the deployment script, you can run:
|
||||
|
||||
```bash
|
||||
npm run clean
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: "Bun"
|
||||
description: "Run bknd inside Bun"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Bun and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
## CLI Starter
|
||||
|
||||
Create a new Bun CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i bun
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Bun project and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Serve the API & static files
|
||||
|
||||
The `serve` function of the Bun adapter makes sure to also serve the static files required for
|
||||
the admin panel.
|
||||
|
||||
```tsx title="index.ts"
|
||||
import { serve } from "bknd/adapter/bun";
|
||||
|
||||
// if the configuration is omitted, it uses an in-memory database
|
||||
serve({
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Run the application using Bun by executing:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
@@ -0,0 +1,274 @@
|
||||
---
|
||||
title: "Cloudflare"
|
||||
description: "Run bknd inside Cloudflare Worker"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Cloudflare Workers and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new Cloudflare CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i cloudflare
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new cloudflare worker project by following the [official guide](https://developers.cloudflare.com/workers/get-started/guide/), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Serve the API
|
||||
|
||||
If you don't choose anything specific, the following code will use the `warm` mode and uses the first D1 binding it finds. See the
|
||||
chapter [Using a different mode](#using-a-different-mode) for available modes.
|
||||
|
||||
```ts title="src/index.ts"
|
||||
import { serve, d1 } from "bknd/adapter/cloudflare";
|
||||
|
||||
// scans your environment for the first D1 binding it finds
|
||||
export default serve();
|
||||
|
||||
// manually specifying a D1 binding:
|
||||
export default serve<Env>({
|
||||
app: ({ env }) => d1({ binding: env.D1_BINDING }),
|
||||
});
|
||||
|
||||
// or specify binding using `bindings`
|
||||
export default serve<Env>({
|
||||
bindings: ({ env }) => ({ db: env.D1_BINDING }),
|
||||
});
|
||||
|
||||
// or use LibSQL
|
||||
export default serve<Env>({
|
||||
app: ({ env }) => ({ url: env.DB_URL }),
|
||||
});
|
||||
```
|
||||
|
||||
For more information about the connection object when using LibSQL, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Now run the worker:
|
||||
|
||||
```bash
|
||||
wrangler dev
|
||||
```
|
||||
|
||||
And confirm it works by opening [http://localhost:8787](http://localhost:8787) in
|
||||
your browser.
|
||||
|
||||
## Serve the Admin UI
|
||||
|
||||
Now in order to also server the static admin files, you have to modify the `wrangler.toml` to include the static assets. You can do so by either serving the static using the new [Assets feature](https://developers.cloudflare.com/workers/static-assets/), or the deprecated [Workers Site](https://developers.cloudflare.com/workers/configuration/sites/configuration/).
|
||||
|
||||
### Assets
|
||||
|
||||
Make sure your assets point to the static assets included in the bknd package:
|
||||
|
||||
```toml title="wrangler.toml"
|
||||
assets = { directory = "node_modules/bknd/dist/static" }
|
||||
```
|
||||
|
||||
### Workers Sites
|
||||
|
||||
Make sure your site points to the static assets included in the bknd package:
|
||||
|
||||
```toml title="wrangler.toml"
|
||||
[site]
|
||||
bucket = "node_modules/bknd/dist/static"
|
||||
```
|
||||
|
||||
And then modify the worker entry as follows:
|
||||
|
||||
```ts title="src/index.ts"
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
import manifest from "__STATIC_CONTENT_MANIFEST"; // [!code highlight]
|
||||
|
||||
export default serve<Env>({
|
||||
app: () => ({
|
||||
/* ... */
|
||||
}),
|
||||
manifest, // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
## Adding custom routes
|
||||
|
||||
You can also add custom routes by defining them after the app has been built, like so:
|
||||
|
||||
```ts
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve<Env>({
|
||||
// ...
|
||||
onBuilt: async (app) => {
|
||||
// [!code highlight]
|
||||
app.server.get("/hello", (c) => c.json({ hello: "world" })); // [!code highlight]
|
||||
}, // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
The property `app.server` is a [Hono](https://hono.dev/) instance, you can literally anything you can do with Hono.
|
||||
|
||||
## Using a different mode
|
||||
|
||||
With the Cloudflare Workers adapter, you're being offered to 4 modes to choose from (default:
|
||||
`warm`):
|
||||
|
||||
| Mode | Description | Use Case |
|
||||
| :-------- | :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fresh` | On every request, the configuration gets refetched, app built and then served. | Ideal if you don't want to deal with eviction, KV or Durable Objects. |
|
||||
| `warm` | It tries to keep the built app in memory for as long as possible, and rebuilds if evicted. | Better response times, should be the default choice. |
|
||||
| `cache` | The configuration is fetched from KV to reduce the initial roundtrip to the database. | Generally faster response times with irregular access patterns. |
|
||||
| `durable` | The bknd app is ran inside a Durable Object and can be configured to stay alive. | Slowest boot time, but fastest responses. Can be kept alive for as long as you want, giving similar response times as server instances. |
|
||||
|
||||
### Modes: `fresh` and `warm`
|
||||
|
||||
To use either `fresh` or `warm`, all you have to do is adding the desired mode to `cloudflare.
|
||||
mode`, like so:
|
||||
|
||||
```ts
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve({
|
||||
// ...
|
||||
mode: "fresh", // mode: "fresh" | "warm" | "cache" | "durable"
|
||||
});
|
||||
```
|
||||
|
||||
### Mode: `cache`
|
||||
|
||||
For the cache mode to work, you also need to specify the KV to be used. For this, use the
|
||||
`bindings` property:
|
||||
|
||||
```ts
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve<Env>({
|
||||
// ...
|
||||
mode: "cache",
|
||||
bindings: ({ env }) => ({ kv: env.KV }),
|
||||
});
|
||||
```
|
||||
|
||||
### Mode: `durable` (advanced)
|
||||
|
||||
To use the `durable` mode, you have to specify the Durable Object to extract from your
|
||||
environment, and additionally export the `DurableBkndApp` class:
|
||||
|
||||
```ts
|
||||
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";
|
||||
|
||||
export { DurableBkndApp };
|
||||
export default serve<Env>({
|
||||
// ...
|
||||
mode: "durable",
|
||||
bindings: ({ env }) => ({ dobj: env.DOBJ }),
|
||||
keepAliveSeconds: 60, // optional
|
||||
});
|
||||
```
|
||||
|
||||
Next, you need to define the Durable Object in your `wrangler.toml` file (refer to the [Durable
|
||||
Objects](https://developers.cloudflare.com/durable-objects/) documentation):
|
||||
|
||||
```toml
|
||||
[[durable_objects.bindings]]
|
||||
name = "DOBJ"
|
||||
class_name = "DurableBkndApp"
|
||||
|
||||
[[migrations]]
|
||||
tag = "v1"
|
||||
new_classes = ["DurableBkndApp"]
|
||||
```
|
||||
|
||||
Since the communication between the Worker and Durable Object is serialized, the `onBuilt`
|
||||
property won't work. To use it (e.g. to specify special routes), you need to extend from the
|
||||
`DurableBkndApp`:
|
||||
|
||||
```ts
|
||||
import type { App } from "bknd";
|
||||
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve({
|
||||
// ...
|
||||
mode: "durable",
|
||||
bindings: ({ env }) => ({ dobj: env.DOBJ }),
|
||||
keepAliveSeconds: 60, // optional
|
||||
});
|
||||
|
||||
export class CustomDurableBkndApp extends DurableBkndApp {
|
||||
async onBuilt(app: App) {
|
||||
app.modules.server.get("/custom/endpoint", (c) => c.text("Custom"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In case you've already deployed your Worker, the deploy command may complain about a new class
|
||||
being used. To fix this issue, you need to add a "rename migration":
|
||||
|
||||
```toml
|
||||
[[durable_objects.bindings]]
|
||||
name = "DOBJ"
|
||||
class_name = "CustomDurableBkndApp"
|
||||
|
||||
[[migrations]]
|
||||
tag = "v1"
|
||||
new_classes = ["DurableBkndApp"]
|
||||
|
||||
[[migrations]]
|
||||
tag = "v2"
|
||||
renamed_classes = [{from = "DurableBkndApp", to = "CustomDurableBkndApp"}]
|
||||
deleted_classes = ["DurableBkndApp"]
|
||||
```
|
||||
|
||||
## D1 Sessions (experimental)
|
||||
|
||||
D1 now supports to enable [global read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/). This allows to reduce latency by reading from the closest region. In order for this to work, D1 has to be started from a bookmark. You can enable this behavior on bknd by setting the `d1.session` property:
|
||||
|
||||
```typescript title="src/index.ts"
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve({
|
||||
// currently recommended to use "fresh" mode
|
||||
// otherwise consecutive requests will use the same bookmark
|
||||
mode: "fresh",
|
||||
// ...
|
||||
d1: {
|
||||
// enables D1 sessions
|
||||
session: true,
|
||||
// (optional) restrict the transport, options: "header" | "cookie"
|
||||
// if not specified, it supports both
|
||||
transport: "cookie",
|
||||
// (optional) choose session constraint if not bookmark present
|
||||
// options: "first-primary" | "first-unconstrained"
|
||||
first: "first-primary",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If bknd is used in a stateful user context (like in a browser), it'll automatically send the session cookie to the server to set the correct bookmark. If you need to manually set the bookmark, you can do so by setting the `x-cf-d1-session` header:
|
||||
|
||||
```bash
|
||||
curl -H "x-cf-d1-session: <bookmark>" ...
|
||||
```
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: "Docker"
|
||||
description: "Official docker image for bknd"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
# Official `bknd` Docker image
|
||||
|
||||
The docker image intentially doesn't copy any data into the image for now, so you can copy the Dockerfile and build the image anywhere.
|
||||
|
||||
Locate the Dockerfile either by pulling the [repository](https://github.com/bknd-io/bknd) and navigating to the `docker` directory, or download from [here](https://github.com/bknd-io/bknd/blob/main/docker/Dockerfile).
|
||||
|
||||
## Building the Docker image
|
||||
|
||||
To build the Docker image, run the following command:
|
||||
|
||||
```bash
|
||||
docker build -t bknd .
|
||||
```
|
||||
|
||||
If you want to override the bknd version used, you can pass a `VERSION` build argument:
|
||||
|
||||
```bash
|
||||
docker build --build-arg VERSION=<version> -t bknd .
|
||||
```
|
||||
|
||||
## Running the Docker container
|
||||
|
||||
To run the Docker container, run the following command:
|
||||
|
||||
```bash
|
||||
docker run -p 1337:1337 bknd
|
||||
```
|
||||
|
||||
You can pass the same CLI arguments (see [Using the CLI](https://docs.bknd.io/cli) guide) to the docker container as you'd do with `npx bknd run`, like so:
|
||||
|
||||
```bash
|
||||
docker run -p 1337:1337 -e ARGS="--db-url file:/data/data.db" bknd
|
||||
```
|
||||
|
||||
To mount the data directory to the host, you can use the `-v` flag:
|
||||
|
||||
```bash
|
||||
docker run -p 1337:1337 -v /path/to/data:/data bknd
|
||||
```
|
||||
|
||||
## Docker compose example
|
||||
|
||||
If you want to use docker compose and build the image directly from the git repository.
|
||||
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
bknd:
|
||||
pull_policy: build
|
||||
build: https://github.com/bknd-io/bknd.git#main:docker
|
||||
ports:
|
||||
- 1337:1337
|
||||
environment:
|
||||
ARGS: "--db-url file:/data/data.db"
|
||||
volumes:
|
||||
- ${DATA_DIR:-.}/data:/data
|
||||
```
|
||||
|
||||
The docker compose file can be extended to build a specific version of bknd.
|
||||
Extend the `build` section with `args` and `labels`.
|
||||
Inside `args`, you can pass a `VERSION` build argument, and use `labels` so the built image receives a unique identifier.
|
||||
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
bknd:
|
||||
pull_policy: build
|
||||
build:
|
||||
context: https://github.com/bknd-io/bknd.git#main:docker
|
||||
args:
|
||||
VERSION: <version>
|
||||
labels:
|
||||
- x-bknd-version=<version>
|
||||
ports:
|
||||
- 1337:1337
|
||||
environment:
|
||||
ARGS: "--db-url file:/data/data.db"
|
||||
volumes:
|
||||
- ${DATA_DIR:-.}/data:/data
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["node", "bun", "cloudflare", "aws", "docker"]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "Node"
|
||||
description: "Run bknd inside Node"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Node and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new Node CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i node
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Node project and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Serve the API & static files
|
||||
|
||||
The `serve` function of the Node adapter makes sure to also serve the static files required for
|
||||
the admin panel.
|
||||
|
||||
```tsx title="server.ts"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
|
||||
// if the configuration is omitted, it uses an in-memory database
|
||||
/** @type {import("bknd/adapter/node").NodeAdapterOptions} */
|
||||
const config = {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
};
|
||||
|
||||
serve(config);
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Run the application using node by executing:
|
||||
|
||||
```bash
|
||||
node server.js
|
||||
```
|
||||
116
docs/content/docs/(documentation)/integration/introduction.mdx
Normal file
116
docs/content/docs/(documentation)/integration/introduction.mdx
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
description: "Integrate bknd into your runtime/framework of choice"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
## Start with a Framework
|
||||
|
||||
bknd seamlessly integrates with popular frameworks, allowing you to use what you're already familar with. The following guides will help you get started with your framework of choice.
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="simple-icons:reactrouter" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="React Router"
|
||||
href="/integration/react-router"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:astro" className="text-fd-primary !size-6" />}
|
||||
title="Astro"
|
||||
href="/integration/astro"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your framework.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Start with a Runtime
|
||||
|
||||
If you prefer to use a runtime instead of a framework, you can choose from the following options. These runtimes allow you to serve the API and UI in the runtime's native server and serve the UI assets statically from `node_modules`.
|
||||
|
||||
<Cards>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:brand-nodejs" className="text-fd-primary !size-6" />}
|
||||
title="NodeJS"
|
||||
href="/integration/node"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:bun" className="text-fd-primary !size-6" />}
|
||||
title="Bun"
|
||||
href="/integration/bun"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="devicon-plain:cloudflareworkers"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Cloudflare"
|
||||
href="/integration/cloudflare"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
|
||||
title="AWS Lambda"
|
||||
href="/integration/aws"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:vitest" className="text-fd-primary !size-6" />}
|
||||
title="Vite"
|
||||
href="/integration/vite"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="streamline-logos:docker-logo-solid"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Docker"
|
||||
href="/integration/docker"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your runtime.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Overview
|
||||
|
||||
### Serving the backend (API)
|
||||
|
||||
Serve the backend as an API for any JS runtime or framework. The latter is especially handy, as it allows you to deploy your frontend and backend bundled together. Furthermore it allows adding additional logic in a way you're already familar with. Just add another route and you're good to go.
|
||||
|
||||
Here is an example of serving the API using node:
|
||||
|
||||
```js title="index.js"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
serve();
|
||||
```
|
||||
|
||||
### Serving the Admin UI
|
||||
|
||||
The admin UI allows to manage your data including full configuration of your backend using a graphical user interface. Using `vite`, your admin route looks like this:
|
||||
|
||||
```tsx title="admin.tsx"
|
||||
import { Admin } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default function AdminPage() {
|
||||
return <Admin withProvider />;
|
||||
}
|
||||
```
|
||||
33
docs/content/docs/(documentation)/meta.json
Normal file
33
docs/content/docs/(documentation)/meta.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"title": "Documentation",
|
||||
"root": true,
|
||||
"description": "Technical reference and integration",
|
||||
"icon": "Book",
|
||||
"slug": "",
|
||||
"pages": [
|
||||
"---Getting Started---",
|
||||
"start",
|
||||
"motivation",
|
||||
"---Usage---",
|
||||
"./usage/introduction",
|
||||
"./usage/database",
|
||||
"./usage/cli",
|
||||
"./usage/sdk",
|
||||
"./usage/react",
|
||||
"./usage/elements",
|
||||
"---Extending---",
|
||||
"./extending/config",
|
||||
"./extending/events",
|
||||
"---Integration---",
|
||||
"./integration/introduction",
|
||||
"./integration/(frameworks)/",
|
||||
"./integration/(runtimes)/",
|
||||
"---Modules---",
|
||||
"./modules/overview",
|
||||
"./modules/server",
|
||||
"./modules/data",
|
||||
"./modules/auth",
|
||||
"./modules/media",
|
||||
"./modules/flows"
|
||||
]
|
||||
}
|
||||
23
docs/content/docs/(documentation)/modules/auth.mdx
Normal file
23
docs/content/docs/(documentation)/modules/auth.mdx
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "Auth"
|
||||
description: "Easily implement reliable authentication strategies."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Authentication is essential for securing applications, and **bknd** provides a straightforward approach to implementing robust strategies.
|
||||
|
||||
### **Core Features**
|
||||
|
||||
- Automatically creates a `user` entity, with support for customizable fields.
|
||||
- Authenticates users based on configurable strategies.
|
||||
- Generates JWTs according to specified configurations.
|
||||
- Provides session management for maintaining user authentication state.
|
||||
|
||||
### **Supported Authentication Strategies**
|
||||
|
||||
- **Email/Password**: Supports plain and SHA-256 password hashing (bcrypt planned for future
|
||||
releases).
|
||||
- **OAuth/OIDC**: Works with providers like Google and GitHub.
|
||||
- Compatible with any specification-compliant provider.
|
||||
|
||||
With a focus on flexibility and ease of integration, bknd's authentication system offers the essentials for managing secure user access in your applications.
|
||||
47
docs/content/docs/(documentation)/modules/data.mdx
Normal file
47
docs/content/docs/(documentation)/modules/data.mdx
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "Data"
|
||||
description: "Define, query, and control your data with ease."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Data is the lifeblood of any application, and **bknd** provides the tools to handle it effortlessly. From defining entities to executing complex queries, bknd offers a developer-friendly, intuitive, and powerful data management experience.
|
||||
|
||||
### **Define Your Data**
|
||||
|
||||
Model entities with **fields** and **relationships**, automatically synced to your database.
|
||||
|
||||
- Supported field types: `primary`, `text`, `number`, `date`, `boolean`, `enum`, `json`, and `jsonschema`.
|
||||
- Relationship types: `many-to-one`, `one-to-one`, `many-to-many`, and `polymorphic`.
|
||||
- Add **indices** to enhance database performance: single, compound, and unique indices.
|
||||
|
||||
### **Entity Management**
|
||||
|
||||
Use the **EntityManager** to simplify handling your entities. Access data efficiently with the **Repository**:
|
||||
|
||||
- Select specific properties to return.
|
||||
- Define sort directions, limits, and offsets.
|
||||
- Include or join relational data seamlessly within a single query using `with` and joins.
|
||||
- Apply robust filtering with an extensive `where` object: `$eq`, `$ne`, `$isnull`, `$notnull`, `$in`, `$notin`, `$gt`, `$gte`, `$lt`, `$lte`, `$between`.
|
||||
|
||||
### **Seamless Data Manipulation with Mutators**
|
||||
|
||||
Create, update, and delete entity data with confidence.
|
||||
|
||||
- Validates data against the entity schema.
|
||||
- (coming soon) Supports relational data with flexible syntax: `$create`, `$set`, `$attach`,
|
||||
`$detach`.
|
||||
|
||||
### **Powerful Event System**
|
||||
|
||||
Hook into critical lifecycle events for fine-grained control:
|
||||
|
||||
- Repository events: `find-one-before`, `find-one-after`, `find-many-before`, `find-many-after`.
|
||||
- Mutator events: `insert-before`, `insert-after`, `update-before`, `update-after`, `delete-before`, `delete-after`.
|
||||
|
||||
### **Enhanced Database Communication**
|
||||
|
||||
The **Connection** class communicates with the database. It's based on kysely, so that it
|
||||
generally supports multiple database dialects (but currently only SQLite/LibSQL is supported).
|
||||
|
||||
Whether you're modeling simple data structures or managing complex relationships, bknd's data tools empower you to build applications with confidence and scalability.
|
||||
|
||||
40
docs/content/docs/(documentation)/modules/flows.mdx
Normal file
40
docs/content/docs/(documentation)/modules/flows.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "Flows"
|
||||
description: "Design and run workflows with seamless automation."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
<Callout type="info">
|
||||
Flows is the youngest module and its UI is still in development. For the sake
|
||||
of tech preview, we decided to exclude it from the Admin UI for now. It will
|
||||
be available very soon.
|
||||
</Callout>
|
||||
|
||||
**bknd** enables you to automate tasks and processes through flexible and powerful workflows.
|
||||
|
||||
## **Core Features**
|
||||
|
||||
### Trigger-Based Executions
|
||||
|
||||
- **Manual**: Trigger workflows manually.
|
||||
- **System Events**: Automate workflows based on system events, such as:
|
||||
- **Data Events**: Entity create, update, or delete.
|
||||
- **Auth Events**: User sign-in, sign-up, sign-out, or deletion.
|
||||
- **Media Events**: File uploads or downloads.
|
||||
- **Server Events**: Before and after request/response handling.
|
||||
- **HTTP Trigger**: Map workflows to any URL path for external triggers.
|
||||
|
||||
- **Custom Event Hooks**: Define and trigger custom events within workflows for tailored automation.
|
||||
|
||||
### Task Management
|
||||
|
||||
- Define tasks to run in sequence, parallel, or loops.
|
||||
- Enable conditional task execution based on output results.
|
||||
|
||||
### Execution Modes
|
||||
|
||||
- **Asynchronous**: Run workflows in the background.
|
||||
- **Synchronous**: Block further execution until the workflow completes, similar to traditional code execution.
|
||||
|
||||
- **Reusable Components**: Create reusable task sequences (sub-workflows) for better organization and efficiency.
|
||||
- **OpenAPI Specification (OAS) Tasks**: Upload an OpenAPI spec to execute API requests as part of a workflow.
|
||||
86
docs/content/docs/(documentation)/modules/media.mdx
Normal file
86
docs/content/docs/(documentation)/modules/media.mdx
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: "Media"
|
||||
description: "Effortlessly manage and serve all your media files."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
**bknd** provides a flexible and efficient way to handle media files, making it easy to upload, manage, and deliver content across various platforms.
|
||||
|
||||
### **Core Features**
|
||||
|
||||
- **File Uploads**: Supports direct (chunked coming soon) uploads of files up to 5GB (depending
|
||||
on your environment).
|
||||
- **Adapter-Based Design**:
|
||||
- Pre-built support for S3, S3-compatible services (e.g., R2, Tigris), and Cloudinary.
|
||||
- Extend functionality by implementing custom adapters.
|
||||
- **Entity Integration**: Attach media files directly to entities for seamless data association.
|
||||
- **Content Delivery**: Serve media over an API endpoint or directly from your provider.
|
||||
|
||||
The media tools are designed to integrate effortlessly into your workflows, offering scalability
|
||||
and control without added complexity.
|
||||
|
||||
### Pre-built support for S3
|
||||
|
||||
There are two ways to enable S3 or S3 compatible storage in bknd.
|
||||
|
||||
1. Enable via the admin UI.
|
||||
|
||||
Simply navigate to the **Media** tab of the bknd admin UI and enable the S3 media support.
|
||||
Enter your AWS S3-compatible storage Access Key, Secret Access Key, and URL.
|
||||
This will automatically configure the S3 adapter for you.
|
||||
|
||||
**URL Format Examples:**
|
||||
|
||||
- **S3**: `https://{bucket}.s3.{region}.amazonaws.com`
|
||||
- **R2 (Cloudflare)**: `https://{account_id}.r2.cloudflarestorage.com/{bucket}`
|
||||
|
||||
2. Enable programmatically.
|
||||
|
||||
To enable using a code-first approach, create an [Initial Structure](/usage/database#initial-structure) using the `initialConfig` option in your `bknd.config.ts` file.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
initialConfig: {
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "s3",
|
||||
config: {
|
||||
access_key: "<key>",
|
||||
secret_access_key: "<secret key>",
|
||||
url: "<bucket url>",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
### Local adapter for development
|
||||
|
||||
For local development and testing, you can use the local file system adapter. This is particularly useful when working with Node.js environments.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
// Register the local media adapter
|
||||
const local = registerLocalMediaAdapter();
|
||||
|
||||
export default {
|
||||
initialConfig: {
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: local({
|
||||
path: "./public/uploads", // Files will be stored in this directory
|
||||
}),
|
||||
},
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
This configuration will store uploaded files in the specified directory,
|
||||
making them accessible through your application's public path (in this case)
|
||||
or at `/api/media/file/{filename}`.
|
||||
53
docs/content/docs/(documentation)/modules/overview.mdx
Normal file
53
docs/content/docs/(documentation)/modules/overview.mdx
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "Overview"
|
||||
description: "General overview of the system"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
This backend system focuses on 4 essential building blocks which can be tightly connected:
|
||||
Data, Auth, Media and Flows.
|
||||
The main idea is to supply all baseline functionality required in order to accomplish complex
|
||||
logic and behaviors – instead of hard coding it into the system code. This way, almost any part
|
||||
of the application is customizable and additionally won't limit you to what it can do.
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="majesticons:data-line" className="text-fd-primary !size-6" />} title="Data" href="/modules/data">
|
||||
Define, query, and control your data with ease.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="meteor-icons:fingerprint" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="Auth"
|
||||
href="/modules/auth"
|
||||
>
|
||||
Easily implement reliable authentication strategies.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="flowbite:image-outline" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="Media"
|
||||
href="/modules/media"
|
||||
>
|
||||
Effortlessly manage and serve all your media files.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="hugeicons:workflow-square-03"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Flows"
|
||||
href="/modules/flows"
|
||||
>
|
||||
Design and run workflows with seamless automation.
|
||||
</Card>
|
||||
|
||||
</Cards>
|
||||
11
docs/content/docs/(documentation)/modules/server.mdx
Normal file
11
docs/content/docs/(documentation)/modules/server.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Server"
|
||||
description: "Effortlessly manage and serve all your files."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
75
docs/content/docs/(documentation)/motivation.mdx
Normal file
75
docs/content/docs/(documentation)/motivation.mdx
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: "Motivation"
|
||||
description: "Why another backend system?"
|
||||
icon: Rocket
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Creating digital products always requires developing both the backend (the logic) and the frontend (the appearance). Building a backend from scratch demands deep knowledge in areas such as authentication and database management. Using a backend framework can speed up initial development, but it still requires ongoing effort to work within its constraints (e.g., _"how to do X with Y?"_), which can quickly slow you down. Choosing a backend system is a tough decision, as you might not be aware of its limitations until you encounter them.
|
||||
|
||||
<CalloutPositive title="The solution">
|
||||
A backend system that only assumes and implements primitive details,
|
||||
integrates into multiple environments, and adheres to industry standards.
|
||||
</CalloutPositive>
|
||||
|
||||
For the sake of brevity, let's assume you are looking for a "backend system" rather than dealing with custom implementations yourself. Let's identify the most common challenges:
|
||||
|
||||
1. Database lock-in
|
||||
2. Environment and framework lock-in
|
||||
3. Deviation from standards (such as `X-Auth` headers for authentication)
|
||||
4. _Wrong-for-your-use-case_ implementations
|
||||
5. Complex self-hosting
|
||||
|
||||
## Database lock-in
|
||||
|
||||
As the developer of a backend system, you must make tough decisions, one of which is choosing which database(s) to support. To simplify development, many systems lock you into a single database, leveraging its advanced features.
|
||||
|
||||
But isn't the database known to be the hardest part to scale? Isn't more logic moving to the application layer? Haven't NoSQL databases proven this? If you're like me, you may have dipped your toes into the NoSQL world only to quickly return to SQL. SQL is known, predictable, and safe. But what if we could have both? NoSQL offers flexibility and scalability, yet querying it is tedious due to vendor-specific implementations.
|
||||
|
||||
To get the best of both worlds, bknd focuses on the weakest SQL database (SQLite), treating it as a data store and query interface. Schema details and enforcement are moved to the application layer, making it easy to adjust a default value or property length. The added benefit is that any SQL database could theoretically work the same way, and since it's all TypeScript, the same validation logic can be used on both the client and server sides–you can validate your data before it even reaches your server. It even works without database-enforced referential integrity, as the integrity checks occur on the application layer. This opens the door to NewSQL systems like PlanetScale.
|
||||
|
||||
## Environment and framework lock-in
|
||||
|
||||
There are backend systems that embed themselves into a specific React framework. This works well until you realize it doesn't support your preferred framework or the new hyped one you're considering switching to. Just like database choices, decisions must be made. The easiest path is to select a single option and let people live with it.
|
||||
|
||||
Alternatively, you could develop for the weakest environment (workerd) by strictly using Web APIs, avoiding shortcuts, and implementing certain logic manually because the go-to package is using Node APIs. This isn't always fun, but it's essential. The benefit? It works anywhere JavaScript does.
|
||||
|
||||
bknd is the only backend system that not only works with any JavaScript framework but also integrates directly into it. It runs within the framework, enabling a single deployment for your entire app.
|
||||
|
||||
_"But isn't it ironic that it forces a JavaScript environment?"_ you might ask. And you're right, but it also allows running standalone via CLI or Docker.
|
||||
|
||||
## Deviation from standards
|
||||
|
||||
One of the biggest frustrations I've encountered is when software vendors choose custom headers for authentication or implement query parameters in a format they find more suitable—such as unencoded JSON for simplicity. When you are in full control, it's tempting to use a more suitable format, or just use an auth-ish name for the header property–after all, it's just a header, right?
|
||||
|
||||
The issue is that users may rely on HTTP clients that offer built-in authentication methods, which won't include your custom solution. Custom `SearchParams` implementations might be convenient, but translating them across different environments and languages can be challenging without trial and error.
|
||||
|
||||
bknd strives to adhere to web standards as much as possible while offering handy alternatives. Here's an example of the `select` search parameter for retrieving a list of entities:
|
||||
|
||||
```bash
|
||||
/api/data/todos?select=id&select=name # web standard
|
||||
/api/data/todos?select=id,name # handy alternative
|
||||
```
|
||||
|
||||
If you ever find an instance where bknd isn't adhering to standards or could be improved, please feel free to [file an issue](https://github.com/bknd-io/bknd/issues/new). Your feedback is greatly appreciated!
|
||||
|
||||
## Wrong-for-your-use-case implementations
|
||||
|
||||
If you've ever developed a social chat application, you likely discovered the extensive feature depth required—features we often take for granted. Things like socket connections for single and group chats, partial loading, asset attachments, and emoji reactions. Even more frustrating, these features make the app being considered incomplete until delivered.
|
||||
|
||||
The same applies to backend systems. Features such as email sending, password resets, and image transformations are expected. Worse still, you'll receive feedback requesting different email verification methods—PIN codes instead of links, 4-digit codes versus 6-digit ones, or UUIDs like Axiom uses.
|
||||
|
||||
Since it's impossible to satisfy all requirements, why implement them at all? _"Because people expect it."_ That's fair. But technically, email verification is not a core backend feature—it's business logic. Setting it up involves:
|
||||
|
||||
1. Adding a `code` and `verified` field to the users' entity and generating a random code on creation.
|
||||
2. Creating an endpoint to accept the code, retrieve the authenticated user, check the code, clear it, and mark the user as verified.
|
||||
|
||||
Additional security measures, such as short-lived tokens, can be added, but the concept remains simple.
|
||||
|
||||
Instead of hardcoding such features, bknd offers a powerful event system that supports asynchronous (like webhooks) and synchronous execution, blocking further actions if needed. With integrated workflows (UI coming soon), you can listen to and react to system events, and even map them to endpoints. Since workflows, like everything else in bknd, are JSON-serializable, they're easy to export and import.
|
||||
|
||||
## Complex self-hosting
|
||||
|
||||
Finally, hosting. It's a business advantage if your system is highly sought after but difficult to self-host, forcing users to opt for your cloud service. The truth is, if it's hard for users, it's also hard for the vendor, which drives up costs.
|
||||
|
||||
If you know how to deploy your Next.js, Remix, or Astro application, you can deploy bknd. It's straightforward to deploy using Cloudflare Workers/Pages or with just 28 lines of a Dockerfile. No PhD required.
|
||||
158
docs/content/docs/(documentation)/start.mdx
Normal file
158
docs/content/docs/(documentation)/start.mdx
Normal file
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: Introduction
|
||||
icon: Album
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { examples } from "@/app/_components/StackBlitz";
|
||||
|
||||
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
|
||||
|
||||
## Preview
|
||||
|
||||
Here is a preview of **bknd** in StackBlitz:
|
||||
|
||||
<StackBlitz {...examples.adminRich} />
|
||||
|
||||
<Accordions>
|
||||
<Accordion title="What's going on?">
|
||||
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).
|
||||
|
||||
</Accordion>
|
||||
</Accordions>
|
||||
|
||||
## Quickstart
|
||||
|
||||
Enter the following command to spin up an instance:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm','bun' ]}>
|
||||
|
||||
```bash tab="npm"
|
||||
npx bknd run
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bunx bknd run
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
To learn more about the CLI, check out the [Using the CLI](/usage/cli) guide.
|
||||
|
||||
## Start with a Framework/Runtime
|
||||
|
||||
Start by using the integration guide for these popular frameworks/runtimes. There will be more in the future, so stay tuned!
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="simple-icons:reactrouter" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="React Router"
|
||||
href="/integration/react-router"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:astro" className="text-fd-primary !size-6" />}
|
||||
title="Astro"
|
||||
href="/integration/astro"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:brand-nodejs" className="text-fd-primary !size-6" />}
|
||||
title="NodeJS"
|
||||
href="/integration/node"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="devicon-plain:cloudflareworkers"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Cloudflare"
|
||||
href="/integration/cloudflare"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:bun" className="text-fd-primary !size-6" />}
|
||||
title="Bun"
|
||||
href="/integration/bun"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
|
||||
title="AWS Lambda"
|
||||
href="/integration/aws"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:vitest" className="text-fd-primary !size-6" />}
|
||||
title="Vite"
|
||||
href="/integration/vite"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="streamline-logos:docker-logo-solid"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Docker"
|
||||
href="/integration/docker"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your runtime or framework.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Use your favorite SQL Database
|
||||
|
||||
The following databases are currently supported. Request a new integration if your favorite is missing.
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="simple-icons:sqlite" className="text-fd-primary !size-6" />} title="SQLite" href="/usage/database#database" />
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:turso" className="text-fd-primary !size-6" />}
|
||||
title="Turso/LibSQL"
|
||||
href="/usage/database#sqlite-using-libsql-on-turso"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="lineicons:postgresql" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="PostgreSQL"
|
||||
href="/usage/database#postgresql"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="streamline-plump:database"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Cloudflare D1"
|
||||
href="/usage/database#cloudflare-d1"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a new database integration.
|
||||
</Card>
|
||||
</Cards>
|
||||
206
docs/content/docs/(documentation)/usage/cli.mdx
Normal file
206
docs/content/docs/(documentation)/usage/cli.mdx
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
title: "Using the CLI"
|
||||
description: "How to start a bknd instance using the CLI."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
The bknd package includes a command-line interface (CLI) that allows you to run a bknd instance and perform various tasks.
|
||||
|
||||
```
|
||||
npx bknd
|
||||
```
|
||||
|
||||
Here is the output:
|
||||
|
||||
```
|
||||
$ npx bknd
|
||||
Usage: bknd [options] [command]
|
||||
|
||||
⚡ bknd cli v0.16.0
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
user <action> create/update users, or generate a token (auth)
|
||||
types [options] generate types
|
||||
schema [options] get schema
|
||||
run [options] run an instance
|
||||
debug <subject> debug bknd
|
||||
create [options] create a new project
|
||||
copy-assets [options] copy static assets
|
||||
config [options] get default config
|
||||
help [command] display help for command
|
||||
```
|
||||
|
||||
## Starting an instance (`run`)
|
||||
|
||||
To see all available `run` options, execute `npx bknd run --help`.
|
||||
|
||||
```
|
||||
$ npx bknd run --help
|
||||
Usage: bknd run [options]
|
||||
|
||||
Options:
|
||||
-p, --port <port> port to run on (default: 1337, env: PORT)
|
||||
-m, --memory use in-memory database
|
||||
-c, --config <config> config file
|
||||
--db-url <db> database url, can be any valid libsql url
|
||||
--db-token <db> database token
|
||||
--server <server> server type (choices: "node", "bun", default: "bun")
|
||||
--no-open don't open browser window on start
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
To order in which the connection is determined is as follows:
|
||||
|
||||
1. `--db-url`
|
||||
2. `--config` or reading the filesystem looking for `bknd.config.[js|ts|mjs|cjs|json]`
|
||||
3. `--memory`
|
||||
4. Environment variables `DB_URL` and `DB_TOKEN` in `.env` or `.dev.vars`
|
||||
5. Fallback to file-based database `data.db`
|
||||
|
||||
### File-based database
|
||||
|
||||
By default, a file-based database `data.db` is used when running without any arguments. You can specify a different file name or path using the `--db-url` option. The database file will be created in the current working directory if it does not exist.
|
||||
|
||||
```
|
||||
npx bknd run --db-url file:data.db
|
||||
```
|
||||
|
||||
### Using configuration file (`bknd.config.*`)
|
||||
|
||||
You can create a configuration file on the working directory that automatically gets picked up: `bknd.config.[js|ts|mjs|cjs|json]`
|
||||
|
||||
Here is an example of a `bknd.config.ts` file:
|
||||
|
||||
```ts
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
// you can either specify the connection directly
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
// or use the `app` function which passes the environment variables
|
||||
app: ({ env }) => ({
|
||||
connection: {
|
||||
url: env.DB_URL,
|
||||
},
|
||||
}),
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
The `app` function is useful if you need a cross-platform way to access the environment variables. For example, on Cloudflare Workers, you can only access environment variables inside a request handler. If you're exclusively using a node-like environment, it's safe to access the environment variables directly from `process.env`.
|
||||
|
||||
If you're using `npx bknd run`, make sure to create a file in a file format that `node` can load, otherwise you may run into an error that the file couldn't be found:
|
||||
|
||||
```sh
|
||||
[INF] 2025-03-28 18:02:21 Using config from bknd.config.ts
|
||||
[ERR] 2025-03-28 18:02:21 Failed to load config: Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'bknd.config.ts' imported from [...]
|
||||
at packageResolve (node:internal/modules/esm/resolve:857:9)
|
||||
at [...] {
|
||||
code: 'ERR_MODULE_NOT_FOUND'
|
||||
}
|
||||
```
|
||||
|
||||
If you still want to use a `.ts` extension, you can start the CLI e.g. using `node` (>=v22.6.0):
|
||||
|
||||
```sh
|
||||
node --experimental-strip-types node_modules/.bin/bknd run
|
||||
```
|
||||
|
||||
Or with `tsx`:
|
||||
|
||||
```sh
|
||||
npx tsx node_modules/.bin/bknd run
|
||||
```
|
||||
|
||||
### Turso/LibSQL database
|
||||
|
||||
To start an instance with a Turso/LibSQL database, run the following:
|
||||
|
||||
```
|
||||
npx bknd run --db-url libsql://your-db.turso.io --db-token <your-token>
|
||||
```
|
||||
|
||||
The `--db-token` option is optional and only required if the database is protected.
|
||||
|
||||
### In-memory database
|
||||
|
||||
To start an instance with an ephemeral in-memory database, run the following:
|
||||
|
||||
```
|
||||
npx bknd run --memory
|
||||
```
|
||||
|
||||
Keep in mind that the database is not persisted and will be lost when the process is terminated.
|
||||
|
||||
## Generating types (`types`)
|
||||
|
||||
To see all available `types` options, execute `npx bknd types --help`.
|
||||
|
||||
```
|
||||
$ npx bknd types --help
|
||||
Usage: bknd types [options]
|
||||
|
||||
generate types
|
||||
|
||||
Options:
|
||||
-o, --outfile <outfile> output file (default: "bknd-types.d.ts")
|
||||
--no-write do not write to file
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
To generate types for the database, run the following:
|
||||
|
||||
```
|
||||
npx bknd types
|
||||
```
|
||||
|
||||
This will generate types for your database schema in `bknd-types.d.ts`. The generated file could look like this:
|
||||
|
||||
```typescript bknd-types.d.ts
|
||||
import type { DB } from "bknd";
|
||||
import type { Insertable, Selectable, Updateable, Generated } from "kysely";
|
||||
|
||||
declare global {
|
||||
type BkndEntity<T extends keyof DB> = Selectable<DB[T]>;
|
||||
type BkndEntityCreate<T extends keyof DB> = Insertable<DB[T]>;
|
||||
type BkndEntityUpdate<T extends keyof DB> = Updateable<DB[T]>;
|
||||
}
|
||||
|
||||
export interface Todos {
|
||||
id: Generated<number>;
|
||||
title?: string;
|
||||
done?: boolean;
|
||||
}
|
||||
|
||||
interface Database {
|
||||
todos: Todos;
|
||||
}
|
||||
|
||||
declare module "bknd" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to add the generated file in your `tsconfig.json` file:
|
||||
|
||||
```json tsconfig.json
|
||||
{
|
||||
"include": ["bknd-types.d.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
You can then use the types by importing them from `bknd`:
|
||||
|
||||
```typescript
|
||||
import type { DB } from "bknd";
|
||||
|
||||
type Todo = DB["todos"];
|
||||
```
|
||||
|
||||
All bknd methods that involve your database schema will be automatically typed.
|
||||
|
||||
412
docs/content/docs/(documentation)/usage/database.mdx
Normal file
412
docs/content/docs/(documentation)/usage/database.mdx
Normal file
@@ -0,0 +1,412 @@
|
||||
---
|
||||
title: "Database"
|
||||
description: "Choosing the right database configuration"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
In order to use **bknd**, you need to prepare access information to your database and potentially install additional dependencies. Connections to the database are managed using Kysely. Therefore, all [its dialects](https://kysely.dev/docs/dialects) are theoretically supported.
|
||||
|
||||
Currently supported and tested databases are:
|
||||
|
||||
- SQLite (embedded): Node.js SQLite, Bun SQLite, LibSQL, SQLocal
|
||||
- SQLite (remote): Turso, Cloudflare D1
|
||||
- Postgres: Vanilla Postgres, Supabase, Neon, Xata
|
||||
|
||||
By default, bknd will try to use a SQLite database in-memory. Depending on your runtime, a different SQLite implementation will be used.
|
||||
|
||||
## Defining the connection
|
||||
|
||||
There are mainly 3 ways to define the connection to your database, when
|
||||
|
||||
1. creating an app using `App.create()` or `createApp()`
|
||||
2. creating an app using a [Framework or Runtime adapter](/integration/introduction)
|
||||
3. starting a quick instance using the [CLI](/usage/cli#using-configuration-file-bknd-config)
|
||||
|
||||
When creating an app using `App.create()` or `createApp()`, you can pass a connection object in the configuration object.
|
||||
|
||||
```typescript title="app.ts"
|
||||
import { createApp } from "bknd";
|
||||
import { sqlite } from "bknd/adapter/sqlite";
|
||||
|
||||
// a connection is required when creating an app like this
|
||||
const app = createApp({
|
||||
connection: sqlite({ url: ":memory:" }),
|
||||
});
|
||||
```
|
||||
|
||||
When using an adapter, or using the CLI, bknd will automatically try to use a SQLite implementation depending on the runtime:
|
||||
|
||||
```javascript title="app.js"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
|
||||
serve({
|
||||
// connection is optional, but recommended
|
||||
connection: { url: "file:data.db" },
|
||||
});
|
||||
```
|
||||
|
||||
You can also pass a connection instance to the `connection` property to explictly use a specific connection.
|
||||
|
||||
```javascript title="app.js"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
import { sqlite } from "bknd/adapter/sqlite";
|
||||
|
||||
serve({
|
||||
connection: sqlite({ url: "file:data.db" }),
|
||||
});
|
||||
```
|
||||
|
||||
If you're using [`bknd.config.*`](/extending/config), you can specify the connection on the exported object.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd";
|
||||
|
||||
export default {
|
||||
connection: { url: "file:data.db" },
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
Throughout the documentation, it is assumed you use `bknd.config.ts` to define your connection.
|
||||
|
||||
## SQLite
|
||||
|
||||
### Using config object
|
||||
|
||||
<Callout type="warn">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
The `sqlite` adapter is automatically resolved based on the runtime.
|
||||
|
||||
| Runtime | Adapter | In-Memory | File | Remote |
|
||||
| ------------------------------ | ------------- | --------- | ---- | ------ |
|
||||
| Node.js | `node:sqlite` | ✅ | ✅ | ❌ |
|
||||
| Bun | `bun:sqlite` | ✅ | ✅ | ❌ |
|
||||
| Cloudflare Worker/Browser/Edge | `libsql` | 🟠 | 🟠 | ✅ |
|
||||
|
||||
The bundled version of the `libsql` connection only works with remote databases. However, you can pass in a `Client` from `@libsql/client`, see [LibSQL](#libsql) for more details.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd";
|
||||
|
||||
// no connection is required, bknd will use a SQLite database in-memory
|
||||
// this does not work on edge environments!
|
||||
export default {} as const satisfies BkndConfig;
|
||||
|
||||
// or explicitly in-memory
|
||||
export default {
|
||||
connection: { url: ":memory:" },
|
||||
} as const satisfies BkndConfig;
|
||||
|
||||
// or explicitly as a file
|
||||
export default {
|
||||
connection: { url: "file:<path/to/your/database.db>" },
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
### LibSQL
|
||||
|
||||
Turso offers a SQLite-fork called LibSQL that runs a server around your SQLite database. The edge-version of the adapter is included in the bundle (remote only):
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import { libsql, type BkndConfig } from "bknd";
|
||||
|
||||
export default {
|
||||
connection: libsql({
|
||||
url: "libsql://<database>.turso.io",
|
||||
authToken: "<auth-token>",
|
||||
}),
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
If you wish to use LibSQL as file, in-memory or make use of [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction), you have to pass in the `Client` from `@libsql/client`:
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import { libsql, type BkndConfig } from "bknd";
|
||||
import { createClient } from "@libsql/client";
|
||||
|
||||
const client = createClient({
|
||||
url: "libsql://<database>.turso.io",
|
||||
authToken: "<auth-token>",
|
||||
});
|
||||
|
||||
export default {
|
||||
connection: libsql(client),
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
### Cloudflare D1
|
||||
|
||||
Using the [Cloudflare Adapter](/integration/cloudflare), you can choose to use a D1 database binding. To do so, you only need to add a D1 database to your `wrangler.toml` and it'll pick up automatically.
|
||||
|
||||
To manually specify which D1 database to take, you can specify it explicitly:
|
||||
|
||||
```ts
|
||||
import { serve, d1 } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve<Env>({
|
||||
app: ({ env }) => d1({ binding: env.D1_BINDING }),
|
||||
});
|
||||
```
|
||||
|
||||
### SQLocal
|
||||
|
||||
To use bknd with `sqlocal` for a offline expierence, you need to install the `@bknd/sqlocal` package. You can do so by running the following command:
|
||||
|
||||
```bash
|
||||
npm install @bknd/sqlocal
|
||||
```
|
||||
|
||||
This package uses `sqlocal` under the hood. Consult the [sqlocal documentation](https://sqlocal.dallashoffman.com/guide/setup) for connection options:
|
||||
|
||||
```js
|
||||
import { createApp } from "bknd";
|
||||
import { SQLocalConnection } from "@bknd/sqlocal";
|
||||
|
||||
const app = createApp({
|
||||
connection: new SQLocalConnection({
|
||||
databasePath: ":localStorage:",
|
||||
verbose: true,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
To use bknd with Postgres, you need to install the `@bknd/postgres` package. You can do so by running the following command:
|
||||
|
||||
```bash
|
||||
npm install @bknd/postgres
|
||||
```
|
||||
|
||||
You can connect to your Postgres database using `pg` or `postgres` dialects. Additionally, you may also define your custom connection.
|
||||
|
||||
### Using `pg`
|
||||
|
||||
To establish a connection to your database, you can use any connection options available on the [`pg`](https://node-postgres.com/apis/client) package.
|
||||
|
||||
```js
|
||||
import { serve } from "bknd/adapter/node";
|
||||
import { pg } from "@bknd/postgres";
|
||||
|
||||
/** @type {import("bknd/adapter/node").NodeBkndConfig} */
|
||||
const config = {
|
||||
connection: pg({
|
||||
connectionString: "postgresql://user:password@localhost:5432/database",
|
||||
}),
|
||||
};
|
||||
|
||||
serve(config);
|
||||
```
|
||||
|
||||
### Using `postgres`
|
||||
|
||||
To establish a connection to your database, you can use any connection options available on the [`postgres`](https://github.com/porsager/postgres) package.
|
||||
|
||||
```js
|
||||
import { serve } from "bknd/adapter/node";
|
||||
import { postgresJs } from "@bknd/postgres";
|
||||
|
||||
serve({
|
||||
connection: postgresJs("postgresql://user:password@localhost:5432/database"),
|
||||
});
|
||||
```
|
||||
|
||||
### Using custom connection
|
||||
|
||||
Several Postgres hosting providers offer their own clients to connect to their database, e.g. suitable for serverless environments.
|
||||
|
||||
Example using `@neondatabase/serverless`:
|
||||
|
||||
```js
|
||||
import { createCustomPostgresConnection } from "@bknd/postgres";
|
||||
import { NeonDialect } from "kysely-neon";
|
||||
|
||||
const neon = createCustomPostgresConnection(NeonDialect);
|
||||
|
||||
serve({
|
||||
connection: neon({
|
||||
connectionString: process.env.NEON,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Example using `@xata.io/client`:
|
||||
|
||||
```js
|
||||
import { createCustomPostgresConnection } from "@bknd/postgres";
|
||||
import { XataDialect } from "@xata.io/kysely";
|
||||
import { buildClient } from "@xata.io/client";
|
||||
|
||||
const client = buildClient();
|
||||
const xata = new client({
|
||||
databaseURL: process.env.XATA_URL,
|
||||
apiKey: process.env.XATA_API_KEY,
|
||||
branch: process.env.XATA_BRANCH,
|
||||
});
|
||||
|
||||
const xataConnection = createCustomPostgresConnection(XataDialect, {
|
||||
supports: {
|
||||
batching: false,
|
||||
},
|
||||
});
|
||||
|
||||
serve({
|
||||
connection: xataConnection({ xata }),
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Connection
|
||||
|
||||
Creating a custom connection is as easy as extending the `Connection` class and passing constructing a Kysely instance.
|
||||
|
||||
```ts
|
||||
import { createApp, Connection } from "bknd";
|
||||
import { Kysely } from "kysely";
|
||||
|
||||
class CustomConnection extends Connection {
|
||||
constructor() {
|
||||
const kysely = new Kysely(/* ... */);
|
||||
super(kysely);
|
||||
}
|
||||
}
|
||||
|
||||
const connection = new CustomConnection();
|
||||
|
||||
// e.g. and then, create an instance
|
||||
const app = createApp({ connection });
|
||||
```
|
||||
|
||||
## Initial Structure
|
||||
|
||||
To provide an initial database structure, you can pass `initialConfig` to the creation of an app. This will only be used if there isn't an existing configuration found in the database given. Here is a quick example:
|
||||
|
||||
<Callout type="info">
|
||||
The initial structure is only respected if the database is empty! If you made
|
||||
updates, ensure to delete the database first, or perform updates through the
|
||||
Admin UI.
|
||||
</Callout>
|
||||
|
||||
```typescript
|
||||
import { createApp, em, entity, text, number } from "bknd";
|
||||
|
||||
const schema = em(
|
||||
{
|
||||
posts: entity("posts", {
|
||||
// "id" is automatically added
|
||||
title: text().required(),
|
||||
slug: text().required(),
|
||||
content: text(),
|
||||
views: number(),
|
||||
}),
|
||||
comments: entity("comments", {
|
||||
content: text(),
|
||||
}),
|
||||
|
||||
// relations and indices are defined separately.
|
||||
// the first argument are the helper functions, the second the entities.
|
||||
},
|
||||
({ relation, index }, { posts, comments }) => {
|
||||
relation(comments).manyToOne(posts);
|
||||
// relation as well as index can be chained!
|
||||
index(posts).on(["title"]).on(["slug"], true);
|
||||
},
|
||||
);
|
||||
|
||||
// to get a type from your schema, use:
|
||||
type Database = (typeof schema)["DB"];
|
||||
// type Database = {
|
||||
// posts: {
|
||||
// id: number;
|
||||
// title: string;
|
||||
// content: string;
|
||||
// views: number;
|
||||
// },
|
||||
// comments: {
|
||||
// id: number;
|
||||
// content: string;
|
||||
// }
|
||||
// }
|
||||
|
||||
// pass the schema to the app
|
||||
const app = createApp({
|
||||
connection: {
|
||||
/* ... */
|
||||
},
|
||||
initialConfig: {
|
||||
data: schema.toJSON(),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Note that we didn't add relational fields directly to the entity, but instead defined them afterwards. That is because the relations are managed outside the entity scope to have an unified expierence for all kinds of relations (e.g. many-to-many).
|
||||
|
||||
<Callout type="info">
|
||||
Defined relations are currently not part of the produced types for the
|
||||
structure. We're working on that, but in the meantime, you can define them
|
||||
manually.
|
||||
</Callout>
|
||||
|
||||
### Type completion
|
||||
|
||||
To get type completion, there are two options:
|
||||
|
||||
1. Use the CLI to [generate the types](/usage/cli#generating-types-types)
|
||||
2. If you have an initial structure created with the prototype functions, you can extend the `DB` interface with your own schema.
|
||||
|
||||
All entity related functions use the types defined in `DB` from `bknd`. To get type completion, you can extend that interface with your own schema:
|
||||
|
||||
```typescript
|
||||
import { em } from "bknd";
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
const schema = em({
|
||||
/* ... */
|
||||
});
|
||||
|
||||
type Database = (typeof schema)["DB"];
|
||||
declare module "bknd" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
|
||||
const api = new Api({
|
||||
/* ... */
|
||||
});
|
||||
const { data: posts } = await api.data.readMany("posts", {});
|
||||
// `posts` is now typed as Database["posts"]
|
||||
```
|
||||
|
||||
The type completion is available for the API as well as all provided [React hooks](/usage/react).
|
||||
|
||||
### Seeding the database
|
||||
|
||||
To seed your database with initial data, you can pass a `seed` function to the configuration. It
|
||||
provides the `ModuleBuildContext` as the first argument.
|
||||
|
||||
<Callout type="info">
|
||||
Note that the seed function will only be executed on app's first boot. If a
|
||||
configuration already exists in the database, it will not be executed.
|
||||
</Callout>
|
||||
|
||||
```typescript
|
||||
import { createApp, type ModuleBuildContext } from "bknd";
|
||||
|
||||
const app = createApp({
|
||||
connection: {
|
||||
/* ... */
|
||||
},
|
||||
initialConfig: {
|
||||
/* ... */
|
||||
},
|
||||
options: {
|
||||
seed: async (ctx: ModuleBuildContext) => {
|
||||
await ctx.em.mutator("posts").insertMany([
|
||||
{ title: "First post", slug: "first-post", content: "..." },
|
||||
{ title: "Second post", slug: "second-post" },
|
||||
]);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
151
docs/content/docs/(documentation)/usage/elements.mdx
Normal file
151
docs/content/docs/(documentation)/usage/elements.mdx
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: "React Elements"
|
||||
description: "Speed up your frontend development"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Not only creating and maintaing a backend is time-consuming, but also integrating it into your frontend can be a hassle. With `bknd/elements`, you can easily add media uploads and authentication forms to your app without having to figure out API details.
|
||||
|
||||
<Callout type="info">
|
||||
In order to use these exported elements, make sure to wrap your app inside
|
||||
`ClientProvider`. See the [React Setup](/usage/react#setup) for more
|
||||
information.
|
||||
</Callout>
|
||||
|
||||
## Media
|
||||
|
||||
### Media.Dropzone
|
||||
|
||||
The `Media.Dropzone` element allows retrieving from and uploading media items to your bknd instance. Without any properties specified, it will behave similar to your media library inside the bknd Admin UI. Here is how to get the last 10 items:
|
||||
|
||||
```tsx
|
||||
import { Media } from "bknd/elements";
|
||||
|
||||
export default function MediaGallery() {
|
||||
return <Media.Dropzone query={{ limit: 10, sort: "-id" }} />;
|
||||
}
|
||||
```
|
||||
|
||||
Since you can also upload media to a specific entity, you can also point that `Dropzone` to it. Here is an example of a single user avatar that gets overwritten on re-upload:
|
||||
|
||||
```tsx
|
||||
import { Media } from "bknd/elements";
|
||||
|
||||
export default function UserAvatar() {
|
||||
return (
|
||||
<Media.Dropzone
|
||||
entity={{ name: "users", id: 1, field: "avatar" }}
|
||||
maxItems={1}
|
||||
overwrite
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
- `initialItems?: xMediaFieldSchema[]`: Initial items to display, must be an array of media objects.
|
||||
- `entity?: { name: string; id: number; field: string }`: If given, the initial media items fetched will be from this entity.
|
||||
- `query?: RepoQueryIn`: Query to filter the media items.
|
||||
- `overwrite?: boolean`: If true, the media item will be overwritten on entity media uploads if limit was reached.
|
||||
- `maxItems?: number`: Maximum number of media items that can be uploaded.
|
||||
- `autoUpload?: boolean`: If true, the media items will be uploaded automatically.
|
||||
- `onRejected?: (files: FileWithPath[]) => void`: Callback when a file is rejected.
|
||||
- `onDeleted?: (file: FileState) => void`: Callback when a file is deleted.
|
||||
- `onUploaded?: (file: FileState) => void`: Callback when a file is uploaded.
|
||||
- `placeholder?: { show?: boolean; text?: string }`: Placeholder text to show when no media items are present.
|
||||
|
||||
#### Customize Rendering
|
||||
|
||||
You can also customize the rendering of the media items and its uploading by passing a react element as a child. Here is an example of a custom `Media.Dropzone` that renders an user avatar (styled using tailwind):
|
||||
|
||||
```tsx
|
||||
import { Media, useMediaDropzone, useMediaDropzoneState } from "bknd/elements";
|
||||
|
||||
export default function CustomUserAvatar() {
|
||||
return (
|
||||
<Media.Dropzone
|
||||
entity={{ name: "users", id: 1, field: "avatar" }}
|
||||
maxItems={1}
|
||||
overwrite
|
||||
>
|
||||
<CustomUserAvatar />
|
||||
</Media.Dropzone>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomUserAvatar() {
|
||||
const {
|
||||
wrapperRef,
|
||||
inputProps,
|
||||
showPlaceholder,
|
||||
actions: { openFileInput },
|
||||
} = useMediaDropzone();
|
||||
const {
|
||||
files: [file],
|
||||
isOver,
|
||||
isOverAccepted,
|
||||
} = useMediaDropzoneState();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="size-32 rounded-full border border-gray-200 flex justify-center items-center leading-none overflow-hidden"
|
||||
>
|
||||
<div className="hidden">
|
||||
<input {...inputProps} />
|
||||
</div>
|
||||
{showPlaceholder && (
|
||||
<>{isOver && isOverAccepted ? "let it drop" : "drop here"}</>
|
||||
)}
|
||||
{file && (
|
||||
<Media.Preview
|
||||
file={file}
|
||||
className="object-cover w-full h-full"
|
||||
onClick={openFileInput}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Auth
|
||||
|
||||
Adding authentication to your app with bknd is as easy as adding a `<form method="POST" />` with an action pointing to the action (`login` or `register`) to the strategy you want to use, e.g. for the password strategy, use `/api/auth/password/login`. But to make it even easier, you can use the `Auth.*` elements.
|
||||
|
||||
### `Auth.Screen`
|
||||
|
||||
The `Auth.Screen` element is a wrapper around the `Auth.Form` element that provides a full page screen. The current layout is admittedly very basic, but there will be more customization options in the future.
|
||||
|
||||
```tsx
|
||||
import { Auth } from "bknd/elements";
|
||||
|
||||
export default function LoginScreen() {
|
||||
return <Auth.Screen action="login" />;
|
||||
}
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
Note that this component doesn't require any strategy-specific information, as it gathers it itself.
|
||||
|
||||
- `action: "login" | "register"`: The action to perform.
|
||||
- `method?: "POST" | "GET"`: The method to use for the form.
|
||||
|
||||
### `Auth.Form`
|
||||
|
||||
If you only wish to render the form itself without the screen, you can use the `Auth.Form` element. Unlike the `Auth.Screen`, this element requires the `strategy` prop to be set to the strategy you want to use. You can either specify it manually, use use the exported hook `useAuthStrategies()` for fetch them from your bknd instance.
|
||||
|
||||
```tsx
|
||||
import { Auth, useAuthStrategies } from "bknd/elements";
|
||||
|
||||
export default function LoginForm() {
|
||||
const { strategies, basepath, loading } = useAuthStrategies();
|
||||
if (loading) return null;
|
||||
|
||||
return (
|
||||
<Auth.Form action="login" strategies={strategies} basepath={basepath} />
|
||||
);
|
||||
}
|
||||
```
|
||||
228
docs/content/docs/(documentation)/usage/introduction.mdx
Normal file
228
docs/content/docs/(documentation)/usage/introduction.mdx
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
description: "Setting up bknd"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
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.
|
||||
2. Use a runtime like [Node](/integration/node), [Bun](/integration/bun) or
|
||||
[Cloudflare](/integration/cloudflare) (workerd). This will run the API and UI in the runtime's
|
||||
native server and serves the UI assets statically from `node_modules`.
|
||||
3. Run it inside your React framework of choice like [Next.js](/integration/nextjs),
|
||||
[Astro](/integration/astro) or [Remix](/integration/remix).
|
||||
|
||||
There is also a fourth option, which is running it inside a
|
||||
[Docker container](/integration/docker). This is essentially a wrapper around the CLI.
|
||||
|
||||
## Basic setup
|
||||
|
||||
Regardless of the method you choose, at the end all adapters come down to the actual
|
||||
instantiation of the `App`, which in raw looks like this:
|
||||
|
||||
```typescript
|
||||
import { createApp, type CreateAppConfig } from "bknd";
|
||||
|
||||
// create the app
|
||||
const config = {
|
||||
/* ... */
|
||||
} satisfies CreateAppConfig;
|
||||
const app = createApp(config);
|
||||
|
||||
// build the app
|
||||
await app.build();
|
||||
|
||||
// export for Web API compliant envs
|
||||
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`)
|
||||
|
||||
The `CreateAppConfig` 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 { Config } from "@libsql/client";
|
||||
|
||||
type AppPlugin = (app: App) => Promise<void> | void;
|
||||
type ManagerOptions = {
|
||||
basePath?: string;
|
||||
trustFetched?: boolean;
|
||||
onFirstBoot?: () => Promise<void>;
|
||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||
};
|
||||
|
||||
type CreateAppConfig = {
|
||||
connection?: Connection | Config;
|
||||
initialConfig?: InitialModuleConfigs;
|
||||
options?: {
|
||||
plugins?: AppPlugin[];
|
||||
manager?: ManagerOptions;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### `connection`
|
||||
|
||||
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: "<url>",
|
||||
authToken: "<token>",
|
||||
};
|
||||
```
|
||||
|
||||
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`
|
||||
|
||||
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
|
||||
to the latest version upon boot. The default configuration looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"admin": {
|
||||
"basepath": "",
|
||||
"color_scheme": "light",
|
||||
"logo_return_path": "/"
|
||||
},
|
||||
"cors": {
|
||||
"origin": "*",
|
||||
"allow_methods": ["GET", "POST", "PATCH", "PUT", "DELETE"],
|
||||
"allow_headers": [
|
||||
"Content-Type",
|
||||
"Content-Length",
|
||||
"Authorization",
|
||||
"Accept"
|
||||
]
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"basepath": "/api/data",
|
||||
"entities": {},
|
||||
"relations": {},
|
||||
"indices": {}
|
||||
},
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"basepath": "/api/auth",
|
||||
"entity_name": "users",
|
||||
"allow_register": true,
|
||||
"jwt": {
|
||||
"secret": "",
|
||||
"alg": "HS256",
|
||||
"fields": ["id", "email", "role"]
|
||||
},
|
||||
"cookie": {
|
||||
"path": "/",
|
||||
"sameSite": "lax",
|
||||
"secure": true,
|
||||
"httpOnly": true,
|
||||
"expires": 604800,
|
||||
"renew": true,
|
||||
"pathSuccess": "/",
|
||||
"pathLoggedOut": "/"
|
||||
},
|
||||
"strategies": {
|
||||
"password": {
|
||||
"type": "password",
|
||||
"config": {
|
||||
"hashing": "sha256"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {}
|
||||
},
|
||||
"media": {
|
||||
"enabled": false,
|
||||
"basepath": "/api/media",
|
||||
"entity_name": "media",
|
||||
"storage": {}
|
||||
},
|
||||
"flows": {
|
||||
"basepath": "/api/flows",
|
||||
"flows": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use the CLI to get the default configuration:
|
||||
|
||||
```sh
|
||||
npx bknd config --pretty
|
||||
```
|
||||
|
||||
To validate your configuration against a JSON schema, you can also dump the schema using the CLI:
|
||||
|
||||
```sh
|
||||
npx bknd schema
|
||||
```
|
||||
|
||||
To create an initial data structure, you can use helpers [described here](/usage/database#initial-structure).
|
||||
|
||||
### `options.plugins`
|
||||
|
||||
The `plugins` property is an array of functions that are called after the app has been built,
|
||||
but before its event is emitted. This is useful for adding custom routes or other functionality.
|
||||
A simple plugin that adds a custom route looks like this:
|
||||
|
||||
```ts
|
||||
import type { AppPlugin } from "bknd";
|
||||
|
||||
export const myPlugin: AppPlugin = (app) => {
|
||||
app.server.get("/hello", (c) => c.json({ hello: "world" }));
|
||||
};
|
||||
```
|
||||
|
||||
Since each plugin has full access to the `app` instance, it can add routes, modify the database
|
||||
structure, add custom middlewares, respond to or add events, etc. Plugins are very powerful, so
|
||||
make sure to only run trusted ones.
|
||||
|
||||
### `options.seed`
|
||||
|
||||
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
|
||||
type ModuleBuildContext = {
|
||||
connection: Connection;
|
||||
server: Hono;
|
||||
em: EntityManager;
|
||||
emgr: EventManager;
|
||||
guard: Guard;
|
||||
};
|
||||
|
||||
const seed = async (ctx: ModuleBuildContext) => {
|
||||
// seed the database
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false },
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
### `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<void>`): A function that is called when the app is booted for
|
||||
the first time.
|
||||
240
docs/content/docs/(documentation)/usage/react.mdx
Normal file
240
docs/content/docs/(documentation)/usage/react.mdx
Normal file
@@ -0,0 +1,240 @@
|
||||
---
|
||||
title: "SDK (React)"
|
||||
description: "Use the bknd SDK for React"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
There are 4 useful hooks to work with your backend:
|
||||
|
||||
1. simple hooks which are solely based on the [API](/usage/sdk):
|
||||
- [`useApi`](#useapi)
|
||||
- [`useEntity`](#useentity)
|
||||
2. query hooks that wraps the API in [SWR](https://swr.vercel.app/):
|
||||
- [`useApiQuery`](#useapiquery)
|
||||
- [`useEntityQuery`](#useentityquery)
|
||||
|
||||
## Setup
|
||||
|
||||
In order to use them, make sure you wrap your `<App />` inside `<ClientProvider />`, so that these hooks point to your bknd instance:
|
||||
|
||||
```tsx
|
||||
import { ClientProvider } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
return <ClientProvider>{/* your app */}</ClientProvider>;
|
||||
}
|
||||
```
|
||||
|
||||
For all other examples below, we'll assume that your app is wrapped inside the `ClientProvider`.
|
||||
|
||||
## `useApi()`
|
||||
|
||||
To use the simple hook that returns the Api, you can use:
|
||||
|
||||
```tsx
|
||||
import { useApi } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const api = useApi();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## `useApiQuery()`
|
||||
|
||||
This hook wraps the API class in an SWR hook for convenience. You can use any API endpoint
|
||||
supported, like so:
|
||||
|
||||
```tsx
|
||||
import { useApiQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const { data, ...swr } = useApiQuery((api) => api.data.readMany("comments"));
|
||||
|
||||
if (swr.error) return <div>Error</div>;
|
||||
if (swr.isLoading) return <div>Loading...</div>;
|
||||
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
- `selector: (api: Api) => FetchPromise`
|
||||
|
||||
The first parameter is a selector function that provides an Api instance and expects an
|
||||
endpoint function to be returned.
|
||||
|
||||
- `options`: optional object that inherits from `SWRConfiguration`
|
||||
|
||||
```ts
|
||||
type Options<Data> = import("swr").SWRConfiguration & {
|
||||
enabled?: boolean;
|
||||
refine?: (data: Data) => Data | any;
|
||||
};
|
||||
```
|
||||
|
||||
* `enabled`: Determines whether this hook should trigger a fetch of the data or not.
|
||||
* `refine`: Optional refinement that is called after a response from the API has been
|
||||
|
||||
received. Useful to omit irrelevant data from the response (see example below).
|
||||
|
||||
### Using mutations
|
||||
|
||||
To query and mutate data using this hook, you can leverage the parameters returned. In the
|
||||
following example we'll also use a `refine` function as well as `revalidateOnFocus` (option from
|
||||
`SWRConfiguration`) so that our data keeps updating on window focus change.
|
||||
|
||||
```tsx
|
||||
import { useEffect, useState } from "react";
|
||||
import { useApiQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const [text, setText] = useState("");
|
||||
const { data, api, mutate, ...q } = useApiQuery(
|
||||
(api) => api.data.readOne("comments", 1),
|
||||
{
|
||||
// filter to a subset of the response
|
||||
refine: (data) => data.data,
|
||||
revalidateOnFocus: true,
|
||||
},
|
||||
);
|
||||
|
||||
const comment = data ? data : null;
|
||||
|
||||
useEffect(() => {
|
||||
setText(comment?.content ?? "");
|
||||
}, [comment]);
|
||||
|
||||
if (q.error) return <div>Error</div>;
|
||||
if (q.isLoading) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!comment) return;
|
||||
|
||||
// this will automatically revalidate the query
|
||||
await mutate(async () => {
|
||||
const res = await api.data.updateOne("comments", comment.id, {
|
||||
content: text,
|
||||
});
|
||||
return res.data;
|
||||
});
|
||||
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## `useEntity()`
|
||||
|
||||
This hook wraps the endpoints of `DataApi` and returns CRUD options as parameters:
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEntity } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const [data, setData] = useState<any>();
|
||||
const { create, read, update, _delete } = useEntity("comments", 1);
|
||||
|
||||
useEffect(() => {
|
||||
read().then(setData);
|
||||
}, []);
|
||||
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||
}
|
||||
```
|
||||
|
||||
If you only supply the entity name as string without an ID, the `read` method will fetch a list
|
||||
of entities instead of a single entry.
|
||||
|
||||
### Props
|
||||
|
||||
Following props are available when using `useEntityQuery([entity], [id?])`:
|
||||
|
||||
- `entity: string`: Specify the table name of the entity
|
||||
- `id?: number | string`: If an id given, it will fetch a single entry, otherwise a list
|
||||
|
||||
### Returned actions
|
||||
|
||||
The following actions are returned from this hook:
|
||||
|
||||
- `create: (input: object)`: Create a new entry
|
||||
- `read: (query: Partial<RepoQuery> = {})`: If an id was given,
|
||||
it returns a single item, otherwise a list
|
||||
- `update: (input: object, id?: number | string)`: If an id was given, the id parameter is
|
||||
optional. Updates the given entry partially.
|
||||
- `_delete: (id?: number | string)`: If an id was given, the id parameter is
|
||||
optional. Deletes the given entry.
|
||||
|
||||
## `useEntityQuery()`
|
||||
|
||||
This hook wraps the actions from `useEntity` around `SWR`. The previous example would look like
|
||||
this:
|
||||
|
||||
```tsx
|
||||
import { useEntityQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const { data } = useEntityQuery("comments", 1);
|
||||
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||
}
|
||||
```
|
||||
|
||||
### Using mutations
|
||||
|
||||
All actions returned from `useEntityQuery` are conveniently wrapped around the `mutate` function,
|
||||
so you don't have think about this:
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEntityQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const [text, setText] = useState("");
|
||||
const { data, update, ...q } = useEntityQuery("comments", 1);
|
||||
|
||||
const comment = data ? data : null;
|
||||
|
||||
useEffect(() => {
|
||||
setText(comment?.content ?? "");
|
||||
}, [comment]);
|
||||
|
||||
if (q.error) return <div>Error</div>;
|
||||
if (q.isLoading) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!comment) return;
|
||||
|
||||
// this will automatically revalidate the query
|
||||
await update({ content: text });
|
||||
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
243
docs/content/docs/(documentation)/usage/sdk.mdx
Normal file
243
docs/content/docs/(documentation)/usage/sdk.mdx
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
title: "SDK (TypeScript)"
|
||||
description: "Use the bknd SDK in TypeScript"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
To start using the bknd API, start by creating a new API instance:
|
||||
|
||||
```ts
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
const api = new Api();
|
||||
|
||||
// always make sure to verify auth
|
||||
await api.verifyAuth();
|
||||
```
|
||||
|
||||
The `Api` class is the main entry point for interacting with the bknd API. It provides methods
|
||||
for all available modules described below.
|
||||
|
||||
## Setup
|
||||
|
||||
You can initialize an API instance by providing the `Request` object, or manually specifying the details such as `host` and `token`.
|
||||
|
||||
### Using the `Request` object
|
||||
|
||||
The recommended way to create an API instance is by passing the current `Request` object. This will automatically point the API to your current instance and extract the token from the headers (either from cookies or `Authorization` header):
|
||||
|
||||
```ts
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
// replace this with the actual request
|
||||
let request: Request;
|
||||
|
||||
const api = new Api({ request });
|
||||
```
|
||||
|
||||
If the authentication details are contained in the current request, but you're hosting your bknd instance somewhere else, you can specify a `host` option:
|
||||
|
||||
```ts
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
// replace this with the actual request
|
||||
let request: Request;
|
||||
|
||||
const api = new Api({
|
||||
host: "https://<your-endpoint>",
|
||||
request,
|
||||
});
|
||||
```
|
||||
|
||||
### Using the `token` option
|
||||
|
||||
If you want to have an API instance that is using a different token, e.g. an admin token, you can create it by specifying the `host` and `token` option:
|
||||
|
||||
```ts
|
||||
import { Api } from "bknd/client";
|
||||
const api = new Api({
|
||||
host: "https://<your-endpoint>",
|
||||
token: "<your-token>",
|
||||
});
|
||||
```
|
||||
|
||||
### Using a local API
|
||||
|
||||
In case the place where you're using the API is the same as your bknd instance (e.g. when using it embedded in a React framework), you can specify a `fetcher` option to point to your bknd app. This way, requests won't travel over the network and instead processed locally:
|
||||
|
||||
```ts
|
||||
import type { App } from "bknd";
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
// replace this with your actual `App` instance
|
||||
let app: App;
|
||||
|
||||
const api = new Api({
|
||||
fetcher: app.server.request as typeof fetch,
|
||||
// specify `host` and `token` or `request`
|
||||
});
|
||||
```
|
||||
|
||||
## Data (`api.data`)
|
||||
|
||||
Access the `Data` specific API methods at `api.data`.
|
||||
|
||||
### `data.readMany([entity], [query])`
|
||||
|
||||
To retrieve a list of records from an entity, use the `readMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.readMany("posts");
|
||||
```
|
||||
|
||||
You can also add additional query instructions:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.readMany("posts", {
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
select: ["id", "title", "views"],
|
||||
with: {
|
||||
// join last 2 comments
|
||||
comments: {
|
||||
with: {
|
||||
// along with the comments' user
|
||||
users: {},
|
||||
},
|
||||
limit: 2,
|
||||
sort: "-id",
|
||||
},
|
||||
// also get the first 2 images, but only the path
|
||||
images: {
|
||||
select: ["path"],
|
||||
limit: 2,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
// same as '{ title: { $eg: "Hello, World!" } }'
|
||||
title: "Hello, World!",
|
||||
// only with views greater than 100
|
||||
views: {
|
||||
$gt: 100,
|
||||
},
|
||||
},
|
||||
// sort by views descending (without "-" would be ascending)
|
||||
sort: "-views",
|
||||
});
|
||||
```
|
||||
|
||||
The `with` property automatically adds the related entries to the response.
|
||||
|
||||
### `data.readOne([entity], [id])`
|
||||
|
||||
To retrieve a single record from an entity, use the `readOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.readOne("posts", 1);
|
||||
```
|
||||
|
||||
### `data.createOne([entity], [data])`
|
||||
|
||||
To create a single record of an entity, use the `createOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.createOne("posts", {
|
||||
title: "Hello, World!",
|
||||
content: "This is a test post.",
|
||||
views: 0,
|
||||
});
|
||||
```
|
||||
|
||||
### `data.createMany([entity], [data])`
|
||||
|
||||
To create many records of an entity, use the `createMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.createMany("posts", [
|
||||
{ title: "Hello, World!" },
|
||||
{ title: "Again, Hello." },
|
||||
]);
|
||||
```
|
||||
|
||||
### `data.updateOne([entity], [id], [data])`
|
||||
|
||||
To update a single record of an entity, use the `updateOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.updateOne("posts", 1, {
|
||||
views: 1,
|
||||
});
|
||||
```
|
||||
|
||||
### `data.updateMany([entity], [where], [update])`
|
||||
|
||||
To update many records of an entity, use the `updateMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.updateMany(
|
||||
"posts",
|
||||
{ views: { $gt: 1 } },
|
||||
{
|
||||
title: "viewed more than once",
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### `data.deleteOne([entity], [id])`
|
||||
|
||||
To delete a single record of an entity, use the `deleteOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.deleteOne("posts", 1);
|
||||
```
|
||||
|
||||
### `data.deleteMany([entity], [where])`
|
||||
|
||||
To delete many records of an entity, use the `deleteMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.deleteMany("posts", { views: { $lte: 1 } });
|
||||
```
|
||||
|
||||
## Auth (`api.auth`)
|
||||
|
||||
Access the `Auth` specific API methods at `api.auth`. If there is successful authentication, the
|
||||
API will automatically save the token and use it for subsequent requests.
|
||||
|
||||
### `auth.strategies()`
|
||||
|
||||
To retrieve the available authentication strategies, use the `strategies` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.strategies();
|
||||
```
|
||||
|
||||
### `auth.login([strategy], [input])`
|
||||
|
||||
To log in with a password, use the `login` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.login("password", {
|
||||
email: "...",
|
||||
password: "...",
|
||||
});
|
||||
```
|
||||
|
||||
### `auth.register([strategy], [input])`
|
||||
|
||||
To register with a password, use the `register` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.register("password", {
|
||||
email: "...",
|
||||
password: "...",
|
||||
});
|
||||
```
|
||||
|
||||
### `auth.me()`
|
||||
|
||||
To retrieve the current user, use the `me` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.me();
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Delete many
|
||||
full: true
|
||||
_openapi:
|
||||
method: DELETE
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"delete"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Delete one
|
||||
full: true
|
||||
_openapi:
|
||||
method: DELETE
|
||||
route: /api/data/entity/{entity}/{id}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}","method":"delete"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/data/getApiData.mdx
Normal file
15
docs/content/docs/api-reference/data/getApiData.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve data configuration
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Read many
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Read one
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/entity/{entity}/{id}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Read many by reference
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/entity/{entity}/{id}/{reference}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}/{reference}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve entity info
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/info/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/info/{entity}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve data schema
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/schema.json
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/schema.json","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve entity schema
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/schemas/{entity}/{context}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/schemas/{entity}/{context}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/data/getApiDataSync.mdx
Normal file
15
docs/content/docs/api-reference/data/getApiDataSync.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Sync database schema
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/sync
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/sync","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Update many
|
||||
full: true
|
||||
_openapi:
|
||||
method: PATCH
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"patch"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Update one
|
||||
full: true
|
||||
_openapi:
|
||||
method: PATCH
|
||||
route: /api/data/entity/{entity}/{id}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}","method":"patch"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Insert one or many
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Count entities
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}/fn/count
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/fn/count","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Check if entity exists
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}/fn/exists
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/fn/exists","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Query entities
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}/query
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/query","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
26
docs/content/docs/api-reference/introduction.mdx
Normal file
26
docs/content/docs/api-reference/introduction.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
description: "Example section for showcasing your API endpoints"
|
||||
icon: Album
|
||||
tags: ["openapi"]
|
||||
---
|
||||
|
||||
<Callout type="warn">
|
||||
You need to make sure to point your request to your API server.
|
||||
</Callout>
|
||||
|
||||
<Callout type="info">
|
||||
This section is a work in progress. Updates will be made soon.
|
||||
</Callout>
|
||||
|
||||
## Authentication
|
||||
|
||||
All API endpoints are authenticated using Bearer tokens and picked up from the specification file.
|
||||
|
||||
```json
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
```
|
||||
7
docs/content/docs/api-reference/meta.json
Normal file
7
docs/content/docs/api-reference/meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"title": "API Reference",
|
||||
"description": "Specs for endpoints and options",
|
||||
"icon": "Webhook",
|
||||
"root": true,
|
||||
"pages": ["introduction", "..."]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the config for a module
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/config/{module}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/config/{module}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the raw config
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/config/raw
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/config/raw","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/system/getApiSystemInfo.mdx
Normal file
15
docs/content/docs/api-reference/system/getApiSystemInfo.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the server info
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/info
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/info","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/system/getApiSystemPing.mdx
Normal file
15
docs/content/docs/api-reference/system/getApiSystemPing.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Ping the server
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/ping
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/ping","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the schema for a module
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/schema/{module}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/schema/{module}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Build the app
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/system/build
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/build","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
12
docs/content/docs/guide/(setup)/admin-ui.mdx
Normal file
12
docs/content/docs/guide/(setup)/admin-ui.mdx
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "Admin UI"
|
||||
icon: TvMinimal
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
12
docs/content/docs/guide/(setup)/introduction.mdx
Normal file
12
docs/content/docs/guide/(setup)/introduction.mdx
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
icon: Album
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
12
docs/content/docs/guide/(setup)/setup.mdx
Normal file
12
docs/content/docs/guide/(setup)/setup.mdx
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "Setup"
|
||||
icon: Bolt
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/auth/permissions.mdx
Normal file
11
docs/content/docs/guide/auth/permissions.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Permissions"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/auth/roles.mdx
Normal file
11
docs/content/docs/guide/auth/roles.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Roles"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/auth/users.mdx
Normal file
11
docs/content/docs/guide/auth/users.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Users"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/data/entities.mdx
Normal file
11
docs/content/docs/guide/data/entities.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Entities"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/data/fields.mdx
Normal file
11
docs/content/docs/guide/data/fields.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Fields"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/data/indices.mdx
Normal file
11
docs/content/docs/guide/data/indices.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Indices"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/data/relation.mdx
Normal file
11
docs/content/docs/guide/data/relation.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Relation"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/flows/introduction.mdx
Normal file
11
docs/content/docs/guide/flows/introduction.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/media/introduction.mdx
Normal file
11
docs/content/docs/guide/media/introduction.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/media/overview.mdx
Normal file
11
docs/content/docs/guide/media/overview.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Overview"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
11
docs/content/docs/guide/media/transformations.mdx
Normal file
11
docs/content/docs/guide/media/transformations.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Transformations"
|
||||
tags: ["guide"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
|
||||
27
docs/content/docs/guide/meta.json
Normal file
27
docs/content/docs/guide/meta.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"title": "User Guide",
|
||||
"root": true,
|
||||
"description": "Instructions for building with bknd",
|
||||
"icon": "Bolt",
|
||||
"pages": [
|
||||
"---User Guide---",
|
||||
"./(setup)/introduction",
|
||||
"./(setup)/setup",
|
||||
"./(setup)/admin-ui",
|
||||
"---Data---",
|
||||
"./data/entities",
|
||||
"./data/fields",
|
||||
"./data/relations",
|
||||
"./data/indices",
|
||||
"---Auth---",
|
||||
"./auth/users",
|
||||
"./auth/roles",
|
||||
"./auth/permissions",
|
||||
"---Flows---",
|
||||
"./flows/introduction",
|
||||
"---Media---",
|
||||
"./media/introduction",
|
||||
"./media/overview",
|
||||
"./media/transformations"
|
||||
]
|
||||
}
|
||||
3
docs/content/docs/meta.json
Normal file
3
docs/content/docs/meta.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["(documentation)", "guide", "api-reference"]
|
||||
}
|
||||
Reference in New Issue
Block a user