mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #259 from bknd-io/release/0.17.2
Bugfix Release 0.17.2
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"bknd": {
|
||||
"url": "http://localhost:3000/mcp",
|
||||
"headers": {
|
||||
"API_KEY": "value"
|
||||
}
|
||||
"url": "http://localhost:28623/api/system/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
|
||||
@@ -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...");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ async function generate() {
|
||||
server: {
|
||||
mcp: {
|
||||
enabled: true,
|
||||
path: "/mcp",
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -43,7 +43,7 @@ export type ApiOptions = {
|
||||
} & (
|
||||
| {
|
||||
token?: string;
|
||||
user?: TApiUser;
|
||||
user?: TApiUser | null;
|
||||
}
|
||||
| {
|
||||
request: Request;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
13
bun.lock
13
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=="],
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
157
docs/mcp.json
157
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user