Compare commits

..

17 Commits

Author SHA1 Message Date
dswbx 7f8d8d26ac Merge pull request #259 from bknd-io/release/0.17.2
Bugfix Release 0.17.2
2025-09-15 10:15:14 +02:00
dswbx 655bb241a8 bump version to 0.17.2 2025-09-15 10:14:14 +02:00
dswbx 1ef5bf7d15 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.
2025-09-15 10:09:51 +02:00
dswbx 9a1589da0b bump @bknd/postgres version to 0.2.0 2025-09-15 10:07:34 +02:00
dswbx f231353228 bump version to 0.17.2-rc.1 2025-09-15 09:31:01 +02:00
dswbx d2de642c17 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.
2025-09-15 09:25:32 +02:00
dswbx 50c5383138 Merge pull request #257 from bknd-io/fix/postgres-0.17-updates
postgres: bump 0.17.1 and improve custom connection API
2025-09-14 17:10:32 +02:00
dswbx fff88a9237 Merge pull request #258 from bknd-io/fix/mcp-auth-and-insert
fix: handle numbered object conversion and update MCP tool URL
2025-09-14 17:09:55 +02:00
dswbx d768a80526 Merge pull request #256 from bknd-io/fix/docker-0.17
bump Dockerfile to use bknd 0.17.1
2025-09-14 17:09:34 +02:00
dswbx 0d79b19c66 Merge pull request #255 from bknd-io/fix/node-static-import
fix raw node execution by making sure import attributes stick on builds
2025-09-14 17:08:54 +02:00
dswbx fea2812688 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.
2025-09-14 17:03:23 +02:00
dswbx 62368c691a 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.
2025-09-14 16:01:37 +02:00
dswbx dd9617598f 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.
2025-09-14 14:07:56 +02:00
dswbx a7f6d45ca9 fix raw node execution by making sure import attributes stick on builds 2025-09-14 13:43:45 +02:00
dswbx 92656523ff Merge pull request #254 from quicujo/feat/validate-uuid
Add UUID validation utility
2025-09-14 13:19:57 +02:00
anton e6ef7d9ff4 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.
2025-09-08 05:06:42 +02:00
dswbx 210b22e307 Merge pull request #251 from bknd-io/fix/cf-adapter-exports
cloudflare: fix missing serve export
2025-09-05 09:18:36 +02:00
28 changed files with 300 additions and 156 deletions
+1 -4
View File
@@ -1,10 +1,7 @@
{
"mcpServers": {
"bknd": {
"url": "http://localhost:3000/mcp",
"headers": {
"API_KEY": "value"
}
"url": "http://localhost:28623/api/system/mcp"
}
}
}
+1 -1
View File
@@ -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.
+6 -1
View File
@@ -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",
@@ -86,10 +89,10 @@ async function buildApi() {
outDir: "dist",
external: [...external],
metafile: true,
target: "esnext",
platform: "browser",
format: ["esm"],
splitting: false,
treeshake: true,
loader: {
".svg": "dataurl",
},
@@ -245,6 +248,8 @@ async function buildAdapters() {
// base adapter handles
tsup.build({
...baseConfig(""),
target: "esnext",
platform: "neutral",
entry: ["src/adapter/index.ts"],
outDir: "dist/adapter",
}),
+6 -6
View File
@@ -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...");
}
}
+1
View File
@@ -7,6 +7,7 @@ async function generate() {
server: {
mcp: {
enabled: true,
path: "/mcp",
},
},
auth: {
+6 -6
View File
@@ -3,7 +3,7 @@
"type": "module",
"sideEffects": false,
"bin": "./dist/cli/index.js",
"version": "0.17.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": {
@@ -15,7 +15,7 @@
},
"packageManager": "bun@1.2.19",
"engines": {
"node": ">=22"
"node": ">=22.13"
},
"scripts": {
"dev": "BKND_CLI_LOG_LEVEL=debug vite",
@@ -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",
+1 -1
View File
@@ -43,7 +43,7 @@ export type ApiOptions = {
} & (
| {
token?: string;
user?: TApiUser;
user?: TApiUser | null;
}
| {
request: Request;
+4 -2
View File
@@ -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) {
+2 -1
View File
@@ -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(),
}),
+7
View File
@@ -473,3 +473,10 @@ export function deepFreeze<T extends object>(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;
}
+9 -3
View File
@@ -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;
}
+17 -2
View File
@@ -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);
+3
View File
@@ -258,6 +258,9 @@ export class EntityManager<TBD extends object = DefaultDB> {
// @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[] = [];
+9 -5
View File
@@ -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 = "<!-- BKND_CONTEXT -->";
@@ -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);
}
+3
View File
@@ -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);
+3 -10
View File
@@ -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=="],
+3 -3
View File
@@ -5,7 +5,7 @@ WORKDIR /app
# define bknd version to be used as:
# `docker build --build-arg VERSION=<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
@@ -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,
},
+115 -42
View File
@@ -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"
}
}
}
+3 -3
View File
@@ -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,
},
+2 -3
View File
@@ -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",
@@ -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"],
@@ -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) {
+13 -9
View File
@@ -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<any, any, any>;
export const plugins = [new ParseJSONResultsPlugin()];
export abstract class PostgresConnection<DB = any> extends Connection<DB> {
export abstract class PostgresConnection extends Connection {
protected override readonly supported = {
batching: true,
softscans: true,
};
constructor(kysely: Kysely<DB>, fn?: Partial<DbFunctions>, _plugins?: KyselyPlugin[]) {
constructor(kysely: Kysely<any>, fn?: Partial<DbFunctions>, _plugins?: KyselyPlugin[]) {
super(
kysely,
fn ?? {
@@ -73,13 +81,9 @@ export abstract class PostgresConnection<DB = any> extends Connection<DB> {
];
}
protected override async batch<Queries extends QB[]>(
queries: [...Queries],
): Promise<{
[K in keyof Queries]: Awaited<ReturnType<Queries[K]["execute"]>>;
}> {
override async executeQueries<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
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;
}
}
@@ -1,5 +1,5 @@
import { type SchemaMetadata, sql } from "kysely";
import { BaseIntrospector } from "bknd/data";
import { BaseIntrospector } from "bknd";
type PostgresSchemaSpec = {
name: string;
@@ -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<Record<string, PostgresType>>;
export class PostgresJsConnection extends PostgresConnection {
override name = "postgres-js";
private postgres: Sql;
constructor(opts: { postgres: Sql }) {
+8 -5
View File
@@ -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<T> = new (...args: any[]) => T;
export type CustomPostgresConnection = {
supports?: PostgresConnection["supported"];
fn?: Partial<DbFunctions>;
@@ -15,17 +16,19 @@ export function createCustomPostgresConnection<
T extends Constructor<Dialect>,
C extends ConstructorParameters<T>[0],
>(
name: string,
dialect: Constructor<Dialect>,
options?: CustomPostgresConnection,
): (config: C) => PostgresConnection<any> {
): (config: C) => PostgresConnection {
const supported = {
batching: true,
...((options?.supports ?? {}) as any),
};
return (config: C) =>
new (class extends PostgresConnection<any> {
protected override readonly supported = supported;
new (class extends PostgresConnection {
override name = name;
override readonly supported = supported;
constructor(config: C) {
super(
+38 -17
View File
@@ -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<typeof PostgresConnection>;
@@ -12,8 +15,9 @@ export type TestSuiteConfig = {
export async function defaultCleanDatabase(connection: InstanceType<typeof PostgresConnection>) {
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);
+31 -27
View File
@@ -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"]
}