mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-03 16:46:00 +00:00
Merge remote-tracking branch 'origin/main' into feat/jsonv-refactor
# Conflicts: # bun.lock
This commit is contained in:
@@ -9,10 +9,18 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
bknd simplifies app development by providing a fully functional backend for database management, authentication, media and workflows. Being lightweight and built on Web Standards, it can be deployed nearly anywhere, including running inside your framework of choice. No more deploying multiple separate services!
|
bknd simplifies app development by providing a fully functional backend for database management, authentication, media and workflows. Being lightweight and built on Web Standards, it can be deployed nearly anywhere, including running inside your framework of choice. No more deploying multiple separate services!
|
||||||
|
* **Runtimes**: Node.js 22+, Bun 1.0+, Deno, Browser, Cloudflare Workers/Pages, Vercel, Netlify, AWS Lambda, etc.
|
||||||
|
* **Databases**:
|
||||||
|
* SQLite: LibSQL, Node SQLite, Bun SQLite, Cloudflare D1, Cloudflare Durable Objects SQLite, SQLocal
|
||||||
|
* Postgres: Vanilla Postgres, Supabase, Neon, Xata
|
||||||
|
* **Frameworks**: React, Next.js, React Router, Astro, Vite, Waku
|
||||||
|
* **Storage**: AWS S3, S3-compatible (Tigris, R2, Minio, etc.), Cloudflare R2 (binding), Cloudinary, Filesystem
|
||||||
|
|
||||||
**For documentation and examples, please visit https://docs.bknd.io.**
|
**For documentation and examples, please visit https://docs.bknd.io.**
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
> This project requires Node.js 22 or higher (because of `node:sqlite`).
|
||||||
|
>
|
||||||
> Please keep in mind that **bknd** is still under active development
|
> Please keep in mind that **bknd** is still under active development
|
||||||
> and therefore full backward compatibility is not guaranteed before reaching v1.0.0.
|
> and therefore full backward compatibility is not guaranteed before reaching v1.0.0.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.15.0-rc.8",
|
"version": "0.15.0",
|
||||||
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
||||||
"homepage": "https://bknd.io",
|
"homepage": "https://bknd.io",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Connection } from "bknd/data";
|
import type { Connection } from "bknd/data";
|
||||||
import { bunSqlite } from "../bun/connection/BunSqliteConnection";
|
import { bunSqlite } from "../bun/connection/BunSqliteConnection";
|
||||||
|
|
||||||
export function sqlite(config: { url: string }): Connection {
|
export function sqlite(config?: { url: string }): Connection {
|
||||||
return bunSqlite(config);
|
return bunSqlite(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Connection } from "bknd/data";
|
import type { Connection } from "bknd/data";
|
||||||
import { nodeSqlite } from "../node/connection/NodeSqliteConnection";
|
import { nodeSqlite } from "../node/connection/NodeSqliteConnection";
|
||||||
|
|
||||||
export function sqlite(config: { url: string }): Connection {
|
export function sqlite(config?: { url: string }): Connection {
|
||||||
return nodeSqlite(config);
|
return nodeSqlite(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,9 +334,9 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
await setSignedCookie(c, "auth", token, secret, this.cookieOptions);
|
await setSignedCookie(c, "auth", token, secret, this.cookieOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deleteAuthCookie(c: Context) {
|
private deleteAuthCookie(c: Context) {
|
||||||
$console.debug("deleting auth cookie");
|
$console.debug("deleting auth cookie");
|
||||||
await deleteCookie(c, "auth", this.cookieOptions);
|
deleteCookie(c, "auth", this.cookieOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout(c: Context<ServerEnv>) {
|
async logout(c: Context<ServerEnv>) {
|
||||||
@@ -345,9 +345,13 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
|
|
||||||
const cookie = await this.getAuthCookie(c);
|
const cookie = await this.getAuthCookie(c);
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
await this.deleteAuthCookie(c);
|
addFlashMessage(c, "Signed out", "info");
|
||||||
await addFlashMessage(c, "Signed out", "info");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// on waku, only one cookie setting is performed
|
||||||
|
// therefore adding deleting cookie at the end
|
||||||
|
// as the flash isn't that important
|
||||||
|
this.deleteAuthCookie(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: move this to a server helper
|
// @todo: move this to a server helper
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Client, Config, ResultSet } from "@libsql/client";
|
import type { Client, Config, InStatement, ResultSet, TransactionMode } from "@libsql/client";
|
||||||
import { createClient } from "libsql-stateless-easy";
|
import { createClient } from "libsql-stateless-easy";
|
||||||
import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin";
|
import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin";
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +10,12 @@ import type { QueryResult } from "kysely";
|
|||||||
export type LibsqlConnection = GenericSqliteConnection<Client>;
|
export type LibsqlConnection = GenericSqliteConnection<Client>;
|
||||||
export type LibSqlCredentials = Config;
|
export type LibSqlCredentials = Config;
|
||||||
|
|
||||||
function getClient(clientOrCredentials: Client | LibSqlCredentials): Client {
|
export type LibsqlClientFns = {
|
||||||
|
execute: (statement: InStatement) => Promise<ResultSet>;
|
||||||
|
batch: (statements: InStatement[], mode?: TransactionMode) => Promise<ResultSet[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getClient(clientOrCredentials: Client | LibSqlCredentials | LibsqlClientFns): Client {
|
||||||
if (clientOrCredentials && "url" in clientOrCredentials) {
|
if (clientOrCredentials && "url" in clientOrCredentials) {
|
||||||
const { url, authToken } = clientOrCredentials;
|
const { url, authToken } = clientOrCredentials;
|
||||||
return createClient({ url, authToken });
|
return createClient({ url, authToken });
|
||||||
@@ -19,7 +24,7 @@ function getClient(clientOrCredentials: Client | LibSqlCredentials): Client {
|
|||||||
return clientOrCredentials as Client;
|
return clientOrCredentials as Client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function libsql(config: LibSqlCredentials | Client) {
|
export function libsql(config: LibSqlCredentials | Client | LibsqlClientFns) {
|
||||||
const db = getClient(config);
|
const db = getClient(config);
|
||||||
|
|
||||||
return genericSqlite(
|
return genericSqlite(
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class AdminController extends Controller {
|
|||||||
basepath: this._options.basepath ?? "/",
|
basepath: this._options.basepath ?? "/",
|
||||||
adminBasepath: this._options.adminBasepath ?? "",
|
adminBasepath: this._options.adminBasepath ?? "",
|
||||||
assetsPath: this._options.assetsPath ?? config.server.assets_path,
|
assetsPath: this._options.assetsPath ?? config.server.assets_path,
|
||||||
theme: this._options.theme ?? "system",
|
//theme: this._options.theme ?? "system",
|
||||||
logo_return_path: this._options.logoReturnPath ?? "/",
|
logo_return_path: this._options.logoReturnPath ?? "/",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ export class AdminController extends Controller {
|
|||||||
if (isProd) {
|
if (isProd) {
|
||||||
let manifest: any;
|
let manifest: any;
|
||||||
if (this.options.assetsPath.startsWith("http")) {
|
if (this.options.assetsPath.startsWith("http")) {
|
||||||
manifest = await fetch(this.options.assetsPath + "manifest.json", {
|
manifest = await fetch(this.options.assetsPath + ".vite/manifest.json", {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function Admin({
|
|||||||
|
|
||||||
const Skeleton = ({ theme }: { theme?: any }) => {
|
const Skeleton = ({ theme }: { theme?: any }) => {
|
||||||
const t = useTheme();
|
const t = useTheme();
|
||||||
const actualTheme = theme ?? t.theme;
|
const actualTheme = theme && ["dark", "light"].includes(theme) ? theme : t.theme;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="bknd-admin" className={actualTheme + " antialiased"}>
|
<div id="bknd-admin" className={actualTheme + " antialiased"}>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Link, useLocation } from "wouter";
|
|||||||
import { IconButton } from "../../components/buttons/IconButton";
|
import { IconButton } from "../../components/buttons/IconButton";
|
||||||
import { Dropdown } from "../../components/overlay/Dropdown";
|
import { Dropdown } from "../../components/overlay/Dropdown";
|
||||||
import { useEvent } from "../../hooks/use-event";
|
import { useEvent } from "../../hooks/use-event";
|
||||||
|
import { useNavigate } from "ui/lib/routes";
|
||||||
|
|
||||||
type Breadcrumb = {
|
type Breadcrumb = {
|
||||||
label: string | Element;
|
label: string | Element;
|
||||||
@@ -17,26 +18,11 @@ export type Breadcrumbs2Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Breadcrumbs2 = ({ path: _path, backTo, onBack }: Breadcrumbs2Props) => {
|
export const Breadcrumbs2 = ({ path: _path, backTo, onBack }: Breadcrumbs2Props) => {
|
||||||
const [_, navigate] = useLocation();
|
const [, , _goBack] = useNavigate();
|
||||||
const location = window.location.pathname;
|
|
||||||
const path = Array.isArray(_path) ? _path : [_path];
|
const path = Array.isArray(_path) ? _path : [_path];
|
||||||
const loc = location.split("/").filter((v) => v !== "");
|
|
||||||
const hasBack = path.length > 1;
|
const hasBack = path.length > 1;
|
||||||
|
|
||||||
const goBack = onBack
|
const goBack = onBack ? onBack : () => _goBack({ fallback: backTo });
|
||||||
? onBack
|
|
||||||
: useEvent(() => {
|
|
||||||
if (backTo) {
|
|
||||||
navigate(backTo, { replace: true });
|
|
||||||
return;
|
|
||||||
} else if (_path.length > 0 && _path[0]?.href) {
|
|
||||||
navigate(_path[0].href, { replace: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const href = loc.slice(0, path.length + 1).join("/");
|
|
||||||
navigate(`~/${href}`, { replace: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
const crumbs = useMemo(
|
const crumbs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -185,9 +185,7 @@ function UserMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.theme) {
|
items.push(() => <UserMenuThemeToggler />);
|
||||||
items.push(() => <UserMenuThemeToggler />);
|
|
||||||
}
|
|
||||||
items.push(() => (
|
items.push(() => (
|
||||||
<div className="font-mono leading-none text-xs text-primary/50 text-center pb-1 pt-2 mt-1 border-t border-primary/5">
|
<div className="font-mono leading-none text-xs text-primary/50 text-center pb-1 pt-2 mt-1 border-t border-primary/5">
|
||||||
{getVersion()}
|
{getVersion()}
|
||||||
|
|||||||
@@ -102,13 +102,29 @@ export function useNavigate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const _url = options?.absolute ? `~/${basepath}${url}`.replace(/\/+/g, "/") : url;
|
const _url = options?.absolute ? `~/${basepath}${url}`.replace(/\/+/g, "/") : url;
|
||||||
|
const state = {
|
||||||
|
...options?.state,
|
||||||
|
referrer: location,
|
||||||
|
};
|
||||||
|
|
||||||
navigate(options?.query ? withQuery(_url, options?.query) : _url, {
|
navigate(options?.query ? withQuery(_url, options?.query) : _url, {
|
||||||
replace: options?.replace,
|
replace: options?.replace,
|
||||||
state: options?.state,
|
state,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
location,
|
location,
|
||||||
|
(opts?: { fallback?: string }) => {
|
||||||
|
const state = window.history.state;
|
||||||
|
if (state?.referrer) {
|
||||||
|
//window.history.replaceState(state, "", state.referrer);
|
||||||
|
navigate(state.referrer, { replace: true });
|
||||||
|
} else if (opts?.fallback) {
|
||||||
|
navigate(opts.fallback, { replace: true });
|
||||||
|
} else {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function DataEntityUpdateImpl({ params }) {
|
|||||||
|
|
||||||
const entityId = params.id as PrimaryFieldType;
|
const entityId = params.id as PrimaryFieldType;
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [navigate] = useNavigate();
|
const [navigate, _, _goBack] = useNavigate();
|
||||||
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
|
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
|
||||||
const targetRelations = relations.listableRelationsOf(entity);
|
const targetRelations = relations.listableRelationsOf(entity);
|
||||||
|
|
||||||
@@ -52,9 +52,8 @@ function DataEntityUpdateImpl({ params }) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function goBack() {
|
const backHref = routes.data.entity.list(entity.name);
|
||||||
window.history.go(-1);
|
const goBack = () => _goBack({ fallback: backHref });
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmitted(changeSet?: EntityData) {
|
async function onSubmitted(changeSet?: EntityData) {
|
||||||
//return;
|
//return;
|
||||||
@@ -162,10 +161,8 @@ function DataEntityUpdateImpl({ params }) {
|
|||||||
className="pl-3"
|
className="pl-3"
|
||||||
>
|
>
|
||||||
<Breadcrumbs2
|
<Breadcrumbs2
|
||||||
path={[
|
backTo={backHref}
|
||||||
{ label: entity.label, href: routes.data.entity.list(entity.name) },
|
path={[{ label: entity.label, href: backHref }, { label: `Edit #${entityId}` }]}
|
||||||
{ label: `Edit #${entityId}` },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</AppShell.SectionHeader>
|
</AppShell.SectionHeader>
|
||||||
{$q.isLoading ? (
|
{$q.isLoading ? (
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
|||||||
import { useSearch } from "ui/hooks/use-search";
|
import { useSearch } from "ui/hooks/use-search";
|
||||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||||
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
||||||
import { routes } from "ui/lib/routes";
|
import { routes, useNavigate } from "ui/lib/routes";
|
||||||
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||||
import { s } from "core/object/schema";
|
import { s } from "core/object/schema";
|
||||||
|
|
||||||
export function DataEntityCreate({ params }) {
|
export function DataEntityCreate({ params }) {
|
||||||
const { $data } = useBkndData();
|
const { $data } = useBkndData();
|
||||||
|
const [navigate, _, _goBack] = useNavigate();
|
||||||
const entity = $data.entity(params.entity as string);
|
const entity = $data.entity(params.entity as string);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
|
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
|
||||||
@@ -30,9 +31,8 @@ export function DataEntityCreate({ params }) {
|
|||||||
// @todo: use entity schema for prefilling
|
// @todo: use entity schema for prefilling
|
||||||
const search = useSearch(s.object({}), {});
|
const search = useSearch(s.object({}), {});
|
||||||
|
|
||||||
function goBack() {
|
const backHref = routes.data.entity.list(entity.name);
|
||||||
window.history.go(-1);
|
const goBack = () => _goBack({ fallback: backHref });
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmitted(changeSet?: EntityData) {
|
async function onSubmitted(changeSet?: EntityData) {
|
||||||
console.log("create:changeSet", changeSet);
|
console.log("create:changeSet", changeSet);
|
||||||
@@ -80,12 +80,7 @@ export function DataEntityCreate({ params }) {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Breadcrumbs2
|
<Breadcrumbs2 backTo={backHref} path={[{ label: entity.label }, { label: "Create" }]} />
|
||||||
path={[
|
|
||||||
{ label: entity.label, href: routes.data.entity.list(entity.name) },
|
|
||||||
{ label: "Create" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</AppShell.SectionHeader>
|
</AppShell.SectionHeader>
|
||||||
<AppShell.Scrollable key={entity.name}>
|
<AppShell.Scrollable key={entity.name}>
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "bknd",
|
"name": "bknd",
|
||||||
"version": "0.15.0-rc.8",
|
"version": "0.15.0",
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cfworker/json-schema": "^4.1.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
@@ -125,6 +125,7 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
"bknd": "workspace:*",
|
||||||
"tsdx": "^0.14.1",
|
"tsdx": "^0.14.1",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
},
|
},
|
||||||
@@ -1220,7 +1221,7 @@
|
|||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="],
|
"@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
||||||
|
|
||||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
||||||
@@ -4026,7 +4027,7 @@
|
|||||||
|
|
||||||
"@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
"@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||||
|
|
||||||
"@types/bun/bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
"@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
||||||
|
|
||||||
"@types/pg/pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="],
|
"@types/pg/pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="],
|
||||||
|
|
||||||
|
|||||||
@@ -36,4 +36,44 @@ 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:
|
To mount the data directory to the host, you can use the `-v` flag:
|
||||||
```bash
|
```bash
|
||||||
docker run -p 1337:1337 -v /path/to/data:/data bknd
|
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 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 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
|
||||||
|
```
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ import { libsql } from "bknd/data";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
connection: libsql({
|
connection: libsql({
|
||||||
url: "libsql://your-database-url.turso.io",
|
url: "libsql://<database>.turso.io",
|
||||||
authToken: "your-auth-token",
|
authToken: "<auth-token>",
|
||||||
}),
|
}),
|
||||||
} as const satisfies BkndConfig;
|
} as const satisfies BkndConfig;
|
||||||
```
|
```
|
||||||
@@ -120,11 +120,13 @@ import type { BkndConfig } from "bknd";
|
|||||||
import { libsql } from "bknd/data";
|
import { libsql } from "bknd/data";
|
||||||
import { createClient } from "@libsql/client";
|
import { createClient } from "@libsql/client";
|
||||||
|
|
||||||
|
const client = createClient({
|
||||||
|
url: "libsql://<database>.turso.io",
|
||||||
|
authToken: "<auth-token>",
|
||||||
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
connection: libsql(createClient({
|
connection: libsql(client),
|
||||||
url: "libsql://your-database-url.turso.io",
|
|
||||||
authToken: "your-auth-token",
|
|
||||||
})),
|
|
||||||
} as const satisfies BkndConfig;
|
} as const satisfies BkndConfig;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user