From 734235c798337fe2cfba81c298d4e27790c2f151 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 5 Sep 2025 09:17:08 +0200 Subject: [PATCH 01/13] cloudflare: fix missing serve export --- app/src/adapter/cloudflare/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 24f0459..a09da7b 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -3,6 +3,7 @@ import { d1Sqlite, type D1ConnectionConfig } from "./connection/D1Connection"; export { getFresh, createApp, + serve, type CloudflareEnv, type CloudflareBkndConfig, } from "./cloudflare-workers.adapter"; From e6ef7d9ff44f713ded79595bc6fb79cf51e55c02 Mon Sep 17 00:00:00 2001 From: anton Date: Mon, 8 Sep 2025 04:06:24 +0200 Subject: [PATCH 02/13] Add UUID validation utility Add uuidValidate function to validate UUID strings and check their version. Supports validation for v4 and v7 UUIDs using the uuid library's validate and version functions. --- app/src/core/utils/uuid.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/core/utils/uuid.ts b/app/src/core/utils/uuid.ts index e3112e2..99511af 100644 --- a/app/src/core/utils/uuid.ts +++ b/app/src/core/utils/uuid.ts @@ -1,10 +1,16 @@ -import { v4, v7 } from "uuid"; +import { v4, v7, validate, version as uuidVersion } from "uuid"; // generates v4 export function uuid(): string { - return v4(); + return v4(); } +// generates v7 export function uuidv7(): string { - return v7(); + return v7(); +} + +// validate uuid +export function uuidValidate(uuid: string, version: 4 | 7): boolean { + return validate(uuid) && uuidVersion(uuid) === version; } From a7f6d45ca92d9d4bf3a3fb9286aa1a3c7b3522b4 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sun, 14 Sep 2025 13:43:45 +0200 Subject: [PATCH 03/13] fix raw node execution by making sure import attributes stick on builds --- README.md | 2 +- app/build.ts | 6 ++++-- app/package.json | 2 +- app/src/adapter/index.ts | 6 ++++-- app/src/modules/server/AdminController.tsx | 14 +++++++++----- bun.lock | 6 +++--- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ab56a80..8761a0e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ bknd simplifies app development by providing a fully functional backend for data **For documentation and examples, please visit https://docs.bknd.io.** > [!WARNING] -> This project requires Node.js 22 or higher (because of `node:sqlite`). +> This project requires Node.js 22.13 or higher (because of `node:sqlite`). > > Please keep in mind that **bknd** is still under active development > and therefore full backward compatibility is not guaranteed before reaching v1.0.0. diff --git a/app/build.ts b/app/build.ts index 2d0caa9..eb15450 100644 --- a/app/build.ts +++ b/app/build.ts @@ -86,10 +86,10 @@ async function buildApi() { outDir: "dist", external: [...external], metafile: true, - platform: "browser", + target: "esnext", + platform: "neutral", format: ["esm"], splitting: false, - treeshake: true, loader: { ".svg": "dataurl", }, @@ -245,6 +245,8 @@ async function buildAdapters() { // base adapter handles tsup.build({ ...baseConfig(""), + target: "esnext", + platform: "neutral", entry: ["src/adapter/index.ts"], outDir: "dist/adapter", }), diff --git a/app/package.json b/app/package.json index c398fa5..aea0395 100644 --- a/app/package.json +++ b/app/package.json @@ -15,7 +15,7 @@ }, "packageManager": "bun@1.2.19", "engines": { - "node": ">=22" + "node": ">=22.13" }, "scripts": { "dev": "BKND_CLI_LOG_LEVEL=debug vite", diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 91ffcf7..17827f7 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -173,7 +173,9 @@ export function serveStaticViaImport(opts?: { manifest?: Manifest }) { return async (c: Context, next: Next) => { if (!files) { const manifest = - opts?.manifest || ((await import("bknd/dist/manifest.json")).default as Manifest); + opts?.manifest || + ((await import("bknd/dist/manifest.json", { with: { type: "json" } })) + .default as Manifest); files = Object.values(manifest).flatMap((asset) => [asset.file, ...(asset.css || [])]); } @@ -181,7 +183,7 @@ export function serveStaticViaImport(opts?: { manifest?: Manifest }) { if (files.includes(path)) { try { const content = await import(/* @vite-ignore */ `bknd/static/${path}?raw`, { - assert: { type: "text" }, + with: { type: "text" }, }).then((m) => m.default); if (content) { diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index d15eefe..a68519d 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -11,6 +11,7 @@ import { css, Style } from "hono/css"; import { Controller } from "modules/Controller"; import * as SystemPermissions from "modules/permissions"; import type { TApiUser } from "Api"; +import type { Manifest } from "vite"; const htmlBkndContextReplace = ""; @@ -32,6 +33,7 @@ export type AdminControllerOptions = { debugRerenders?: boolean; theme?: "dark" | "light" | "system"; logoReturnPath?: string; + manifest?: Manifest; }; export class AdminController extends Controller { @@ -194,8 +196,10 @@ export class AdminController extends Controller { }; if (isProd) { - let manifest: any; - if (this.options.assetsPath.startsWith("http")) { + let manifest: Manifest; + if (this.options.manifest) { + manifest = this.options.manifest; + } else if (this.options.assetsPath.startsWith("http")) { manifest = await fetch(this.options.assetsPath + ".vite/manifest.json", { headers: { Accept: "application/json", @@ -204,14 +208,14 @@ export class AdminController extends Controller { } else { // @ts-ignore manifest = await import("bknd/dist/manifest.json", { - assert: { type: "json" }, + with: { type: "json" }, }).then((res) => res.default); } try { // @todo: load all marked as entry (incl. css) - assets.js = manifest["src/ui/main.tsx"].file; - assets.css = manifest["src/ui/main.tsx"].css[0] as any; + assets.js = manifest["src/ui/main.tsx"]?.file!; + assets.css = manifest["src/ui/main.tsx"]?.css?.[0] as any; } catch (e) { $console.warn("Couldn't find assets in manifest", e); } diff --git a/bun.lock b/bun.lock index d9765f6..c7350b2 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.17.0-rc.1", + "version": "0.17.1", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", @@ -1232,7 +1232,7 @@ "@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.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], + "@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -4078,7 +4078,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=="], - "@types/bun/bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], + "@types/bun/bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="], "@typescript-eslint/experimental-utils/eslint-utils": ["eslint-utils@2.1.0", "", { "dependencies": { "eslint-visitor-keys": "^1.1.0" } }, "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg=="], From dd9617598f52b94fb0b7986cdb67aaac91ba2ba7 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sun, 14 Sep 2025 14:07:56 +0200 Subject: [PATCH 04/13] bump Dockerfile to use bknd 0.17.1 Updated the default bknd version to 0.17.1 and included the `jsonv-ts` dependency in the Dockerfile. --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 132289a..c946b6a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app # define bknd version to be used as: # `docker build --build-arg VERSION= -t bknd .` -ARG VERSION=0.13.0 +ARG VERSION=0.17.1 # Install & copy required cli RUN npm install --omit=dev bknd@${VERSION} @@ -16,10 +16,10 @@ FROM node:24-alpine WORKDIR /app -# Install pm2 and libsql +# Install required dependencies RUN npm install -g pm2 RUN echo '{"type":"module"}' > package.json -RUN npm install @libsql/client +RUN npm install jsonv-ts @libsql/client # Create volume and init args VOLUME /data From 62368c691a22a7734842acba9ebcf60e8bb8b113 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sun, 14 Sep 2025 16:01:37 +0200 Subject: [PATCH 05/13] postgres: bump 0.17.1 and improve custom connection API Aligned connection constructors to include an explicit name parameter, updated documentation, and streamlined connection methods for consistency. Adjusted dependencies and cleaned unused references. --- app/src/data/entities/EntityManager.ts | 3 + bun.lock | 13 +---- .../docs/(documentation)/usage/database.mdx | 4 +- packages/postgres/README.md | 6 +- packages/postgres/package.json | 3 +- packages/postgres/src/PgPostgresConnection.ts | 3 +- packages/postgres/src/PostgresConnection.ts | 22 ++++--- packages/postgres/src/PostgresIntrospector.ts | 2 +- packages/postgres/src/PostgresJsConnection.ts | 4 +- packages/postgres/src/custom.ts | 13 +++-- packages/postgres/test/suite.ts | 55 ++++++++++++------ packages/postgres/tsconfig.json | 58 ++++++++++--------- 12 files changed, 108 insertions(+), 78 deletions(-) diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index 544c8ad..36168f8 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -258,6 +258,9 @@ export class EntityManager { // @todo: centralize and add tests hydrate(entity_name: string, _data: EntityData[]) { + if (!Array.isArray(_data) || _data.length === 0) { + return []; + } const entity = this.entity(entity_name); const data: EntityData[] = []; diff --git a/bun.lock b/bun.lock index d9765f6..9e26bd2 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.17.0-rc.1", + "version": "0.17.1", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", @@ -151,7 +151,6 @@ "bknd": "workspace:*", "kysely-neon": "^1.3.0", "tsup": "^8.4.0", - "typescript": "^5.8.2", }, "optionalDependencies": { "kysely": "^0.27.6", @@ -1232,7 +1231,7 @@ "@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.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], + "@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -3832,10 +3831,6 @@ "@bknd/plasmic/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "@bknd/postgres/@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], - - "@bknd/postgres/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "@bknd/sqlocal/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "@bundled-es-modules/cookie/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], @@ -4078,7 +4073,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=="], - "@types/bun/bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], + "@types/bun/bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="], "@typescript-eslint/experimental-utils/eslint-utils": ["eslint-utils@2.1.0", "", { "dependencies": { "eslint-visitor-keys": "^1.1.0" } }, "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg=="], @@ -4684,8 +4679,6 @@ "@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg=="], - "@bknd/postgres/@types/bun/bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], - "@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], "@cloudflare/vitest-pool-workers/miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], diff --git a/docs/content/docs/(documentation)/usage/database.mdx b/docs/content/docs/(documentation)/usage/database.mdx index febffdd..b09587f 100644 --- a/docs/content/docs/(documentation)/usage/database.mdx +++ b/docs/content/docs/(documentation)/usage/database.mdx @@ -224,7 +224,7 @@ Example using `@neondatabase/serverless`: import { createCustomPostgresConnection } from "@bknd/postgres"; import { NeonDialect } from "kysely-neon"; -const neon = createCustomPostgresConnection(NeonDialect); +const neon = createCustomPostgresConnection("neon", NeonDialect); serve({ connection: neon({ @@ -247,7 +247,7 @@ const xata = new client({ branch: process.env.XATA_BRANCH, }); -const xataConnection = createCustomPostgresConnection(XataDialect, { +const xataConnection = createCustomPostgresConnection("xata", XataDialect, { supports: { batching: false, }, diff --git a/packages/postgres/README.md b/packages/postgres/README.md index b66395b..ed5d1e6 100644 --- a/packages/postgres/README.md +++ b/packages/postgres/README.md @@ -59,7 +59,7 @@ You can create a custom kysely postgres dialect by using the `createCustomPostgr ```ts import { createCustomPostgresConnection } from "@bknd/postgres"; -const connection = createCustomPostgresConnection(MyDialect)({ +const connection = createCustomPostgresConnection("my_postgres_dialect", MyDialect)({ // your custom dialect configuration supports: { batching: true @@ -75,7 +75,7 @@ const connection = createCustomPostgresConnection(MyDialect)({ import { createCustomPostgresConnection } from "@bknd/postgres"; import { NeonDialect } from "kysely-neon"; -const connection = createCustomPostgresConnection(NeonDialect)({ +const connection = createCustomPostgresConnection("neon", NeonDialect)({ connectionString: process.env.NEON, }); ``` @@ -94,7 +94,7 @@ const xata = new client({ branch: process.env.XATA_BRANCH, }); -const connection = createCustomPostgresConnection(XataDialect, { +const connection = createCustomPostgresConnection("xata", XataDialect, { supports: { batching: false, }, diff --git a/packages/postgres/package.json b/packages/postgres/package.json index 6d77c10..e822af4 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -31,8 +31,7 @@ "@xata.io/kysely": "^0.2.1", "bknd": "workspace:*", "kysely-neon": "^1.3.0", - "tsup": "^8.4.0", - "typescript": "^5.8.2" + "tsup": "^8.4.0" }, "tsup": { "entry": ["src/index.ts"], diff --git a/packages/postgres/src/PgPostgresConnection.ts b/packages/postgres/src/PgPostgresConnection.ts index 85a5c84..c96e693 100644 --- a/packages/postgres/src/PgPostgresConnection.ts +++ b/packages/postgres/src/PgPostgresConnection.ts @@ -1,12 +1,13 @@ import { Kysely, PostgresDialect } from "kysely"; import { PostgresIntrospector } from "./PostgresIntrospector"; import { PostgresConnection, plugins } from "./PostgresConnection"; -import { customIntrospector } from "bknd/data"; +import { customIntrospector } from "bknd"; import $pg from "pg"; export type PgPostgresConnectionConfig = $pg.PoolConfig; export class PgPostgresConnection extends PostgresConnection { + override name = "pg"; private pool: $pg.Pool; constructor(config: PgPostgresConnectionConfig) { diff --git a/packages/postgres/src/PostgresConnection.ts b/packages/postgres/src/PostgresConnection.ts index 9ea1d2c..ff67991 100644 --- a/packages/postgres/src/PostgresConnection.ts +++ b/packages/postgres/src/PostgresConnection.ts @@ -1,4 +1,11 @@ -import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } from "bknd/data"; +import { + Connection, + type DbFunctions, + type FieldSpec, + type SchemaResponse, + type ConnQuery, + type ConnQueryResults, +} from "bknd"; import { ParseJSONResultsPlugin, type ColumnDataType, @@ -13,12 +20,13 @@ export type QB = SelectQueryBuilder; export const plugins = [new ParseJSONResultsPlugin()]; -export abstract class PostgresConnection extends Connection { +export abstract class PostgresConnection extends Connection { protected override readonly supported = { batching: true, + softscans: true, }; - constructor(kysely: Kysely, fn?: Partial, _plugins?: KyselyPlugin[]) { + constructor(kysely: Kysely, fn?: Partial, _plugins?: KyselyPlugin[]) { super( kysely, fn ?? { @@ -73,13 +81,9 @@ export abstract class PostgresConnection extends Connection { ]; } - protected override async batch( - queries: [...Queries], - ): Promise<{ - [K in keyof Queries]: Awaited>; - }> { + override async executeQueries(...qbs: O): Promise> { return this.kysely.transaction().execute(async (trx) => { - return Promise.all(queries.map((q) => trx.executeQuery(q).then((r) => r.rows))); + return Promise.all(qbs.map((q) => trx.executeQuery(q))); }) as any; } } diff --git a/packages/postgres/src/PostgresIntrospector.ts b/packages/postgres/src/PostgresIntrospector.ts index 82b75ba..4b1c928 100644 --- a/packages/postgres/src/PostgresIntrospector.ts +++ b/packages/postgres/src/PostgresIntrospector.ts @@ -1,5 +1,5 @@ import { type SchemaMetadata, sql } from "kysely"; -import { BaseIntrospector } from "bknd/data"; +import { BaseIntrospector } from "bknd"; type PostgresSchemaSpec = { name: string; diff --git a/packages/postgres/src/PostgresJsConnection.ts b/packages/postgres/src/PostgresJsConnection.ts index 1ab1fe4..deff210 100644 --- a/packages/postgres/src/PostgresJsConnection.ts +++ b/packages/postgres/src/PostgresJsConnection.ts @@ -1,13 +1,15 @@ import { Kysely } from "kysely"; import { PostgresIntrospector } from "./PostgresIntrospector"; import { PostgresConnection, plugins } from "./PostgresConnection"; -import { customIntrospector } from "bknd/data"; +import { customIntrospector } from "bknd"; import { PostgresJSDialect } from "kysely-postgres-js"; import $postgresJs, { type Sql, type Options, type PostgresType } from "postgres"; export type PostgresJsConfig = Options>; export class PostgresJsConnection extends PostgresConnection { + override name = "postgres-js"; + private postgres: Sql; constructor(opts: { postgres: Sql }) { diff --git a/packages/postgres/src/custom.ts b/packages/postgres/src/custom.ts index b7369f1..9d626a0 100644 --- a/packages/postgres/src/custom.ts +++ b/packages/postgres/src/custom.ts @@ -1,9 +1,10 @@ -import type { Constructor } from "bknd/core"; -import { customIntrospector, type DbFunctions } from "bknd/data"; +import { customIntrospector, type DbFunctions } from "bknd"; import { Kysely, type Dialect, type KyselyPlugin } from "kysely"; import { plugins, PostgresConnection } from "./PostgresConnection"; import { PostgresIntrospector } from "./PostgresIntrospector"; +export type Constructor = new (...args: any[]) => T; + export type CustomPostgresConnection = { supports?: PostgresConnection["supported"]; fn?: Partial; @@ -15,17 +16,19 @@ export function createCustomPostgresConnection< T extends Constructor, C extends ConstructorParameters[0], >( + name: string, dialect: Constructor, options?: CustomPostgresConnection, -): (config: C) => PostgresConnection { +): (config: C) => PostgresConnection { const supported = { batching: true, ...((options?.supports ?? {}) as any), }; return (config: C) => - new (class extends PostgresConnection { - protected override readonly supported = supported; + new (class extends PostgresConnection { + override name = name; + override readonly supported = supported; constructor(config: C) { super( diff --git a/packages/postgres/test/suite.ts b/packages/postgres/test/suite.ts index 0d4feec..b5c1cf3 100644 --- a/packages/postgres/test/suite.ts +++ b/packages/postgres/test/suite.ts @@ -1,8 +1,11 @@ import { describe, beforeAll, afterAll, expect, it, afterEach } from "bun:test"; import type { PostgresConnection } from "../src"; -import { createApp } from "bknd"; -import * as proto from "bknd/data"; +import { createApp, em, entity, text } from "bknd"; import { disableConsoleLog, enableConsoleLog } from "bknd/utils"; +// @ts-ignore +import { connectionTestSuite } from "$bknd/data/connection/connection-test-suite"; +// @ts-ignore +import { bunTestRunner } from "$bknd/adapter/bun/test"; export type TestSuiteConfig = { createConnection: () => InstanceType; @@ -12,8 +15,9 @@ export type TestSuiteConfig = { export async function defaultCleanDatabase(connection: InstanceType) { const kysely = connection.kysely; - // drop all tables & create new schema + // drop all tables+indexes & create new schema await kysely.schema.dropSchema("public").ifExists().cascade().execute(); + await kysely.schema.dropIndex("public").ifExists().cascade().execute(); await kysely.schema.createSchema("public").execute(); } @@ -32,6 +36,23 @@ export function testSuite(config: TestSuiteConfig) { beforeAll(() => disableConsoleLog(["log", "warn", "error"])); afterAll(() => enableConsoleLog()); + // @todo: postgres seems to add multiple indexes, thus failing the test suite + /* describe("test suite", () => { + connectionTestSuite(bunTestRunner, { + makeConnection: () => { + const connection = config.createConnection(); + return { + connection, + dispose: async () => { + await cleanDatabase(connection, config); + await connection.close(); + }, + }; + }, + rawDialectDetails: [], + }); + }); */ + describe("base", () => { it("should connect to the database", async () => { const connection = config.createConnection(); @@ -73,14 +94,14 @@ export function testSuite(config: TestSuiteConfig) { }); it("should create a basic schema", async () => { - const schema = proto.em( + const schema = em( { - posts: proto.entity("posts", { - title: proto.text().required(), - content: proto.text(), + posts: entity("posts", { + title: text().required(), + content: text(), }), - comments: proto.entity("comments", { - content: proto.text(), + comments: entity("comments", { + content: text(), }), }, (fns, s) => { @@ -153,20 +174,20 @@ export function testSuite(config: TestSuiteConfig) { }); it("should support uuid", async () => { - const schema = proto.em( + const schema = em( { - posts: proto.entity( + posts: entity( "posts", { - title: proto.text().required(), - content: proto.text(), + title: text().required(), + content: text(), }, { primary_format: "uuid", }, ), - comments: proto.entity("comments", { - content: proto.text(), + comments: entity("comments", { + content: text(), }), }, (fns, s) => { @@ -187,8 +208,8 @@ export function testSuite(config: TestSuiteConfig) { // @ts-expect-error expect(config.data.entities?.posts.fields?.id.config?.format).toBe("uuid"); - const em = app.em; - const mutator = em.mutator(em.entity("posts")); + const $em = app.em; + const mutator = $em.mutator($em.entity("posts")); const data = await mutator.insertOne({ title: "Hello", content: "World" }); expect(data.data.id).toBeString(); expect(String(data.data.id).length).toBe(36); diff --git a/packages/postgres/tsconfig.json b/packages/postgres/tsconfig.json index d2359e0..5bb10f6 100644 --- a/packages/postgres/tsconfig.json +++ b/packages/postgres/tsconfig.json @@ -1,29 +1,33 @@ { - "compilerOptions": { - "composite": false, - "module": "ESNext", - "moduleResolution": "bundler", - "allowImportingTsExtensions": false, - "target": "ES2022", - "noImplicitAny": false, - "allowJs": true, - "verbatimModuleSyntax": true, - "declaration": true, - "strict": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "exactOptionalPropertyTypes": false, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": false, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "isolatedModules": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["./src/**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "composite": false, + "module": "ESNext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": false, + "target": "ES2022", + "noImplicitAny": false, + "allowJs": true, + "verbatimModuleSyntax": true, + "declaration": true, + "strict": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "exactOptionalPropertyTypes": false, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "$bknd/*": ["../../app/src/*"] + } + }, + "include": ["./src/**/*.ts"], + "exclude": ["node_modules"] } From fea2812688381d644780aa1200317ab8c7ea60ab Mon Sep 17 00:00:00 2001 From: dswbx Date: Sun, 14 Sep 2025 17:03:23 +0200 Subject: [PATCH 06/13] fix: handle numbered object conversion and update MCP tool URL Add `convertNumberedObjectToArray` utility for handling numbered object to array conversion, addressing MCP tool allOf behavior. Adjust MCP tool URL in configuration and ensure default inspect options in development environment. Minor improvement in role enumeration handling for auth. --- .cursor/mcp.json | 5 +---- app/src/auth/api/AuthController.ts | 3 ++- app/src/core/utils/objects.ts | 7 +++++++ app/src/data/api/DataController.ts | 19 +++++++++++++++++-- app/vite.dev.ts | 3 +++ 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 064ec5c..8cd1d99 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -1,10 +1,7 @@ { "mcpServers": { "bknd": { - "url": "http://localhost:3000/mcp", - "headers": { - "API_KEY": "value" - } + "url": "http://localhost:28623/api/system/mcp" } } } diff --git a/app/src/auth/api/AuthController.ts b/app/src/auth/api/AuthController.ts index 33c0df6..ba12d4a 100644 --- a/app/src/auth/api/AuthController.ts +++ b/app/src/auth/api/AuthController.ts @@ -221,6 +221,7 @@ export class AuthController extends Controller { return user; }; + const roles = Object.keys(this.auth.config.roles ?? {}); mcp.tool( // @todo: needs permission "auth_user_create", @@ -231,7 +232,7 @@ export class AuthController extends Controller { password: s.string({ minLength: 8 }), role: s .string({ - enum: Object.keys(this.auth.config.roles ?? {}), + enum: roles.length > 0 ? roles : undefined, }) .optional(), }), diff --git a/app/src/core/utils/objects.ts b/app/src/core/utils/objects.ts index 4a5e129..6f6a985 100644 --- a/app/src/core/utils/objects.ts +++ b/app/src/core/utils/objects.ts @@ -473,3 +473,10 @@ export function deepFreeze(object: T): T { return Object.freeze(object); } + +export function convertNumberedObjectToArray(obj: object): any[] | object { + if (Object.keys(obj).every((key) => Number.isInteger(Number(key)))) { + return Object.values(obj); + } + return obj; +} diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index a0d7f02..055033c 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -1,6 +1,15 @@ import type { ModuleBuildContext } from "modules"; import { Controller } from "modules/Controller"; -import { jsc, s, describeRoute, schemaToSpec, omitKeys, pickKeys, mcpTool } from "bknd/utils"; +import { + jsc, + s, + describeRoute, + schemaToSpec, + omitKeys, + pickKeys, + mcpTool, + convertNumberedObjectToArray, +} from "bknd/utils"; import * as SystemPermissions from "modules/permissions"; import type { AppDataConfig } from "../data-schema"; import type { EntityManager, EntityData } from "data/entities"; @@ -420,7 +429,13 @@ export class DataController extends Controller { if (!this.entityExists(entity)) { return this.notFound(c); } - const body = (await c.req.json()) as EntityData | EntityData[]; + + const _body = (await c.req.json()) as EntityData | EntityData[]; + // @todo: check on jsonv-ts how to handle this better + // temporary fix for numbered object to array + // this happens when the MCP tool uses the allOf function + // to transform all validation targets into a single object + const body = convertNumberedObjectToArray(_body); if (Array.isArray(body)) { const result = await this.em.mutator(entity).insertMany(body); diff --git a/app/vite.dev.ts b/app/vite.dev.ts index bee9219..1bc2309 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -9,6 +9,9 @@ import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection"; import { libsql } from "./src/data/connection/sqlite/libsql/LibsqlConnection"; import { $console } from "core/utils/console"; import { createClient } from "@libsql/client"; +import util from "node:util"; + +util.inspect.defaultOptions.depth = 5; registries.media.register("local", StorageLocalAdapter); From d2de642c172b7093301ecfcd67fc6b6272369a7c Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 09:25:32 +0200 Subject: [PATCH 07/13] fix: update build config and improve e2e adapter handling Added dynamic inclusion of dependencies as external packages and switched build platform to browser. Streamlined e2e adapter test setup, improved type safety, and standardized console output for better clarity. --- app/build.ts | 5 ++++- app/e2e/adapters.ts | 12 ++++++------ app/package.json | 8 ++++---- app/src/Api.ts | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/build.ts b/app/build.ts index eb15450..66db700 100644 --- a/app/build.ts +++ b/app/build.ts @@ -61,8 +61,11 @@ function delayTypes() { watcher_timeout = setTimeout(buildTypes, 1000); } +const dependencies = Object.keys(pkg.dependencies); + // collection of always-external packages const external = [ + ...dependencies, "bun:test", "node:test", "node:assert/strict", @@ -87,7 +90,7 @@ async function buildApi() { external: [...external], metafile: true, target: "esnext", - platform: "neutral", + platform: "browser", format: ["esm"], splitting: false, loader: { diff --git a/app/e2e/adapters.ts b/app/e2e/adapters.ts index 9ca8154..0dd009b 100644 --- a/app/e2e/adapters.ts +++ b/app/e2e/adapters.ts @@ -17,7 +17,7 @@ async function run( }); // Read from stdout - const reader = proc.stdout.getReader(); + const reader = (proc.stdout as ReadableStream).getReader(); const decoder = new TextDecoder(); // Function to read chunks @@ -30,7 +30,7 @@ async function run( const text = decoder.decode(value); if (!resolveCalled) { - console.log(c.dim(text.replace(/\n$/, ""))); + console.info(c.dim(text.replace(/\n$/, ""))); } onChunk( text, @@ -189,21 +189,21 @@ const adapters = { async function testAdapter(name: keyof typeof adapters) { const config = adapters[name]; - console.log("adapter", c.cyan(name)); + console.info("adapter", c.cyan(name)); await config.clean(); const { proc, data } = await config.start(); - console.log("proc:", proc.pid, "data:", c.cyan(data)); + console.info("proc:", proc.pid, "data:", c.cyan(data)); //proc.kill();process.exit(0); const add_env = "env" in config && config.env ? config.env : ""; await $`TEST_URL=${data} TEST_ADAPTER=${name} ${add_env} bun run test:e2e`; - console.log("DONE!"); + console.info("DONE!"); while (!proc.killed) { proc.kill("SIGINT"); await Bun.sleep(250); - console.log("Waiting for process to exit..."); + console.info("Waiting for process to exit..."); } } diff --git a/app/package.json b/app/package.json index aea0395..3277694 100644 --- a/app/package.json +++ b/app/package.json @@ -30,7 +30,7 @@ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly && tsc-alias", "updater": "bun x npm-check-updates -ui", "cli": "LOCAL=1 bun src/cli/index.ts", - "prepublishOnly": "bun run types && bun run test && bun run test:node && bun run test:e2e && bun run build:all && cp ../README.md ./", + "prepublishOnly": "bun run types && bun run test && bun run test:node && VITE_DB_URL=:memory: bun run test:e2e && bun run build:all && cp ../README.md ./", "postpublish": "rm -f README.md", "test": "ALL_TESTS=1 bun test --bail", "test:all": "bun run test && bun run test:node", @@ -39,8 +39,8 @@ "test:adapters": "bun test src/adapter/**/*.adapter.spec.ts --bail", "test:coverage": "ALL_TESTS=1 bun test --bail --coverage", "test:vitest:coverage": "vitest run --coverage", - "test:e2e": "VITE_DB_URL=:memory: playwright test", - "test:e2e:adapters": "VITE_DB_URL=:memory: bun run e2e/adapters.ts", + "test:e2e": "playwright test", + "test:e2e:adapters": "bun run e2e/adapters.ts", "test:e2e:ui": "VITE_DB_URL=:memory: playwright test --ui", "test:e2e:debug": "VITE_DB_URL=:memory: playwright test --debug", "test:e2e:report": "VITE_DB_URL=:memory: playwright show-report", @@ -71,6 +71,7 @@ "oauth4webapi": "^2.11.1", "object-path-immutable": "^4.1.2", "radix-ui": "^1.1.3", + "picocolors": "^1.1.1", "swr": "^2.3.3" }, "devDependencies": { @@ -108,7 +109,6 @@ "libsql-stateless-easy": "^1.8.0", "open": "^10.1.0", "openapi-types": "^12.1.3", - "picocolors": "^1.1.1", "postcss": "^8.5.3", "postcss-preset-mantine": "^1.17.0", "postcss-simple-vars": "^7.0.1", diff --git a/app/src/Api.ts b/app/src/Api.ts index d9fb8e4..28d2ef0 100644 --- a/app/src/Api.ts +++ b/app/src/Api.ts @@ -43,7 +43,7 @@ export type ApiOptions = { } & ( | { token?: string; - user?: TApiUser; + user?: TApiUser | null; } | { request: Request; From f23135322897e8584f9ef32ac9e0377656cce296 Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 09:31:01 +0200 Subject: [PATCH 08/13] bump version to 0.17.2-rc.1 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 3277694..6fdfa72 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.17.1", + "version": "0.17.2-rc.1", "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", "repository": { From 9a1589da0be0faca667c87f4c039abf38a2d3582 Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 10:07:34 +0200 Subject: [PATCH 09/13] bump @bknd/postgres version to 0.2.0 --- packages/postgres/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/postgres/package.json b/packages/postgres/package.json index e822af4..036d6a9 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -1,6 +1,6 @@ { "name": "@bknd/postgres", - "version": "0.1.0", + "version": "0.2.0", "type": "module", "main": "dist/index.js", "module": "dist/index.js", From 1ef5bf7d15242275a5fbaec870c16e362a1c6de0 Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 10:09:51 +0200 Subject: [PATCH 10/13] fix: adjust mcp schema validation and add default path support Updated `mcp.json` to enhance validation by supporting `anyOf` schema for `entity` properties and refined `role` typing. Introduced default `path` property for MCP system API and aligned build assets configuration accordingly. --- app/internal/docs.build-assets.ts | 1 + docs/mcp.json | 157 ++++++++++++++++++++++-------- 2 files changed, 116 insertions(+), 42 deletions(-) diff --git a/app/internal/docs.build-assets.ts b/app/internal/docs.build-assets.ts index 127def7..730367f 100644 --- a/app/internal/docs.build-assets.ts +++ b/app/internal/docs.build-assets.ts @@ -7,6 +7,7 @@ async function generate() { server: { mcp: { enabled: true, + path: "/mcp", }, }, auth: { diff --git a/docs/mcp.json b/docs/mcp.json index df16c4b..7075b1e 100644 --- a/docs/mcp.json +++ b/docs/mcp.json @@ -35,8 +35,7 @@ "minLength": 8 }, "role": { - "type": "string", - "enum": [] + "type": "string" } }, "required": [ @@ -134,10 +133,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -161,10 +167,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -194,10 +207,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -235,10 +255,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -276,10 +303,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" } @@ -297,10 +331,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -334,10 +375,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -410,10 +458,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -462,10 +517,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -494,10 +556,17 @@ ], "properties": { "entity": { - "type": "string", - "enum": [ - "users", - "media" + "anyOf": [ + { + "type": "string", + "enum": [ + "users", + "media" + ] + }, + { + "type": "string" + } ], "$target": "param" }, @@ -3994,6 +4063,10 @@ "enabled": { "type": "boolean", "default": false + }, + "path": { + "type": "string", + "default": "/api/system/mcp" } } } From 655bb241a80d20f7c60db38878f08a01cb968c8f Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 10:14:14 +0200 Subject: [PATCH 11/13] bump version to 0.17.2 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 6fdfa72..98d9426 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.17.2-rc.1", + "version": "0.17.2", "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", "repository": { From 77c85bfd5cf903ed788590cba7e39d511bbc312e Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 12:41:49 +0200 Subject: [PATCH 12/13] update docs: add `bknd.config.ts` example and clarify cloudflare setup Added a detailed example to `bknd.config.ts` documentation showcasing database connection and custom routes. Updated Cloudflare guide with clearer sections, marking modern assets usage as recommended and workers sites as legacy. --- .../docs/(documentation)/extending/config.mdx | 43 +++++++++++++------ .../integration/(runtimes)/cloudflare.mdx | 5 ++- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/content/docs/(documentation)/extending/config.mdx b/docs/content/docs/(documentation)/extending/config.mdx index f473f5c..8f81579 100644 --- a/docs/content/docs/(documentation)/extending/config.mdx +++ b/docs/content/docs/(documentation)/extending/config.mdx @@ -40,24 +40,49 @@ export type BkndConfig = CreateAppConfig & { onBuilt?: (app: App) => Promise; // passed as the first argument to the `App.build` method buildConfig?: Parameters[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. +## Example + +Here is an example of a configuration file that specifies a database connection, registers a plugin, add custom routes using [Hono](https://hono.dev/) and performs a [Kysely](https://kysely.dev/) query. + +```typescript +import type { BkndConfig } from "bknd/adapter"; +import { showRoutes } from "bknd/plugins"; + +export default { + connection: { + url: process.env.DB_URL ?? "file:data.db", + }, + onBuilt: async (app) => { + // `app.server` is a Hono instance + const hono = app.server; + hono.get("/hello", (c) => c.text("Hello World")); + + // for complex queries, you can use Kysely directly + const db = app.connection.kysely; + hono.get("/custom_query", async (c) => { + return c.json(await db.selectFrom("pages").selectAll().execute()); + }); + }, + options: { + plugins: [showRoutes()], + }, +} satisfies BkndConfig; +``` + ### `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. +The `app` property is a function that returns a `CreateAppConfig` object. It allows accessing the adapter specific environment variables. This is especially useful when using the [Cloudflare Workers](/integration/cloudflare) runtime, where the environment variables are only available inside the request handler. ```typescript import type { BkndConfig } from "bknd/adapter"; export default { - app: ({ env }) => ({ + app: (env) => ({ connection: { url: env.DB_URL, }, @@ -104,12 +129,6 @@ export default { }; ``` -### `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. diff --git a/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx b/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx index 6ca9f25..fc227a7 100644 --- a/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx +++ b/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx @@ -81,7 +81,7 @@ your browser. 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 +### Assets (recommended) Make sure your assets point to the static assets included in the bknd package: @@ -89,7 +89,7 @@ Make sure your assets point to the static assets included in the bknd package: assets = { directory = "node_modules/bknd/dist/static" } ``` -### Workers Sites +### Workers Sites (legacy) Make sure your site points to the static assets included in the bknd package: @@ -108,6 +108,7 @@ export default serve({ app: () => ({ /* ... */ }), + assets: "kv", // [!code highlight] manifest, // [!code highlight] }); ``` From ddfc3e599feb8d8c0b4dae94a47e34a6ae99f723 Mon Sep 17 00:00:00 2001 From: dswbx Date: Mon, 15 Sep 2025 14:36:00 +0200 Subject: [PATCH 13/13] docs: corrected `app` function parameter signature --- .../integration/(runtimes)/cloudflare.mdx | 10 +++++----- docs/content/docs/(documentation)/usage/cli.mdx | 2 +- docs/content/docs/(documentation)/usage/database.mdx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx b/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx index fc227a7..1157e06 100644 --- a/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx +++ b/docs/content/docs/(documentation)/integration/(runtimes)/cloudflare.mdx @@ -52,17 +52,17 @@ export default serve(); // manually specifying a D1 binding: export default serve({ - app: ({ env }) => d1({ binding: env.D1_BINDING }), + app: (env) => d1({ binding: env.D1_BINDING }), }); // or specify binding using `bindings` export default serve({ - bindings: ({ env }) => ({ db: env.D1_BINDING }), + bindings: (env) => ({ db: env.D1_BINDING }), }); // or use LibSQL export default serve({ - app: ({ env }) => ({ url: env.DB_URL }), + app: (env) => ({ url: env.DB_URL }), }); ``` @@ -199,7 +199,7 @@ import { d1 } from "bknd/adapter/cloudflare"; import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy"; export default withPlatformProxy({ - app: ({ env }) => ({ + app: (env) => ({ connection: d1({ binding: env.DB }), }), }); @@ -217,7 +217,7 @@ Instead, it's recommended to split this configuration into separate files, e.g. import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare"; export default { - app: ({ env }) => ({ + app: (env) => ({ connection: d1({ binding: env.DB }), }), } satisfies CloudflareBkndConfig; diff --git a/docs/content/docs/(documentation)/usage/cli.mdx b/docs/content/docs/(documentation)/usage/cli.mdx index 2ccd875..ea81bee 100644 --- a/docs/content/docs/(documentation)/usage/cli.mdx +++ b/docs/content/docs/(documentation)/usage/cli.mdx @@ -86,7 +86,7 @@ export default { url: "file:data.db", }, // or use the `app` function which passes the environment variables - app: ({ env }) => ({ + app: (env) => ({ connection: { url: env.DB_URL, }, diff --git a/docs/content/docs/(documentation)/usage/database.mdx b/docs/content/docs/(documentation)/usage/database.mdx index b09587f..4608d62 100644 --- a/docs/content/docs/(documentation)/usage/database.mdx +++ b/docs/content/docs/(documentation)/usage/database.mdx @@ -147,7 +147,7 @@ To manually specify which D1 database to take, you can specify it explicitly: import { serve, d1 } from "bknd/adapter/cloudflare"; export default serve({ - app: ({ env }) => d1({ binding: env.D1_BINDING }), + app: (env) => d1({ binding: env.D1_BINDING }), }); ```