mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-03 08:36:01 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e81636ea95 | |||
| 37c36e5d07 |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: "1.3.1"
|
bun-version: "1.2.22"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: ./app
|
working-directory: ./app
|
||||||
|
|||||||
+1
-2
@@ -27,8 +27,7 @@ packages/media/.env
|
|||||||
.npmrc
|
.npmrc
|
||||||
/.verdaccio
|
/.verdaccio
|
||||||
.idea
|
.idea
|
||||||
.vscode/*
|
.vscode
|
||||||
!.vscode/settings.json
|
|
||||||
.git_old
|
.git_old
|
||||||
docker/tmp
|
docker/tmp
|
||||||
.debug
|
.debug
|
||||||
|
|||||||
Vendored
-19
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
|
||||||
"biome.enabled": true,
|
|
||||||
"editor.defaultFormatter": "biomejs.biome",
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports.biome": "explicit",
|
|
||||||
"source.fixAll.biome": "explicit"
|
|
||||||
},
|
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
|
||||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
|
||||||
"**/dist/**",
|
|
||||||
"**/node_modules/**/dist/**",
|
|
||||||
"**/node_modules/**/!(src|lib|esm)/**" // optional, stricter
|
|
||||||
],
|
|
||||||
"typescript.preferences.includePackageJsonAutoImports": "on",
|
|
||||||
"typescript.tsserver.watchOptions": {
|
|
||||||
"excludeDirectories": ["**/dist", "**/node_modules/**/dist"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"typescript.preferences.includePackageJsonAutoImports": "off",
|
|
||||||
"typescript.suggest.autoImports": true,
|
|
||||||
"typescript.preferences.importModuleSpecifier": "relative",
|
|
||||||
"search.exclude": {
|
|
||||||
"**/dist/**": true,
|
|
||||||
"**/node_modules/**": true
|
|
||||||
},
|
|
||||||
"files.exclude": {
|
|
||||||
"**/dist/**": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -108,7 +108,7 @@ describe("App tests", async () => {
|
|||||||
expect(Array.from(app.plugins.keys())).toEqual(["test"]);
|
expect(Array.from(app.plugins.keys())).toEqual(["test"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("drivers", async () => {
|
test.only("drivers", async () => {
|
||||||
const called: string[] = [];
|
const called: string[] = [];
|
||||||
const app = new App(dummyConnection, undefined, {
|
const app = new App(dummyConnection, undefined, {
|
||||||
drivers: {
|
drivers: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const mockedBackend = new Hono()
|
|||||||
.get("/file/:name", async (c) => {
|
.get("/file/:name", async (c) => {
|
||||||
const { name } = c.req.param();
|
const { name } = c.req.param();
|
||||||
const file = Bun.file(`${assetsPath}/${name}`);
|
const file = Bun.file(`${assetsPath}/${name}`);
|
||||||
return new Response(new File([await file.bytes()], name, { type: file.type }), {
|
return new Response(file, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": file.type,
|
"Content-Type": file.type,
|
||||||
"Content-Length": file.size.toString(),
|
"Content-Length": file.size.toString(),
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ describe("repros", async () => {
|
|||||||
expect(app.em.entities.map((e) => e.name)).toEqual(["media", "test"]);
|
expect(app.em.entities.map((e) => e.name)).toEqual(["media", "test"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("verify inversedBy", async () => {
|
test.only("verify inversedBy", async () => {
|
||||||
const schema = proto.em(
|
const schema = proto.em(
|
||||||
{
|
{
|
||||||
products: proto.entity("products", {
|
products: proto.entity("products", {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import v7 from "./samples/v7.json";
|
|||||||
import v8 from "./samples/v8.json";
|
import v8 from "./samples/v8.json";
|
||||||
import v8_2 from "./samples/v8-2.json";
|
import v8_2 from "./samples/v8-2.json";
|
||||||
import v9 from "./samples/v9.json";
|
import v9 from "./samples/v9.json";
|
||||||
|
import v10 from "./samples/v10.json";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";
|
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";
|
||||||
|
import { CURRENT_VERSION } from "modules/db/migrations";
|
||||||
|
|
||||||
beforeAll(() => disableConsoleLog());
|
beforeAll(() => disableConsoleLog());
|
||||||
afterAll(enableConsoleLog);
|
afterAll(enableConsoleLog);
|
||||||
@@ -61,7 +63,7 @@ async function getRawConfig(
|
|||||||
return await db
|
return await db
|
||||||
.selectFrom("__bknd")
|
.selectFrom("__bknd")
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.$if(!!opts?.version, (qb) => qb.where("version", "=", opts?.version))
|
.where("version", "=", opts?.version ?? CURRENT_VERSION)
|
||||||
.$if((opts?.types?.length ?? 0) > 0, (qb) => qb.where("type", "in", opts?.types))
|
.$if((opts?.types?.length ?? 0) > 0, (qb) => qb.where("type", "in", opts?.types))
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,6 @@ describe("Migrations", () => {
|
|||||||
"^^s3.secret_access_key^^",
|
"^^s3.secret_access_key^^",
|
||||||
);
|
);
|
||||||
const [config, secrets] = (await getRawConfig(app, {
|
const [config, secrets] = (await getRawConfig(app, {
|
||||||
version: 10,
|
|
||||||
types: ["config", "secrets"],
|
types: ["config", "secrets"],
|
||||||
})) as any;
|
})) as any;
|
||||||
|
|
||||||
@@ -129,4 +130,15 @@ describe("Migrations", () => {
|
|||||||
"^^s3.secret_access_key^^",
|
"^^s3.secret_access_key^^",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("migration from 10 to 11", async () => {
|
||||||
|
expect(v10.version).toBe(10);
|
||||||
|
expect(v10.data.entities.test.fields.title.config.fillable).toEqual(["read", "update"]);
|
||||||
|
|
||||||
|
const app = await createVersionedApp(v10);
|
||||||
|
|
||||||
|
expect(app.version()).toBeGreaterThan(10);
|
||||||
|
const [config] = (await getRawConfig(app, { types: ["config"] })) as any;
|
||||||
|
expect(config.json.data.entities.test.fields.title.config.fillable).toEqual(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
{
|
||||||
|
"version": 10,
|
||||||
|
"server": {
|
||||||
|
"cors": {
|
||||||
|
"origin": "*",
|
||||||
|
"allow_methods": ["GET", "POST", "PATCH", "PUT", "DELETE"],
|
||||||
|
"allow_headers": [
|
||||||
|
"Content-Type",
|
||||||
|
"Content-Length",
|
||||||
|
"Authorization",
|
||||||
|
"Accept"
|
||||||
|
],
|
||||||
|
"allow_credentials": true
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"enabled": true,
|
||||||
|
"path": "/api/system/mcp",
|
||||||
|
"logLevel": "warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"basepath": "/api/data",
|
||||||
|
"default_primary_format": "integer",
|
||||||
|
"entities": {
|
||||||
|
"test": {
|
||||||
|
"type": "regular",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"format": "integer",
|
||||||
|
"fillable": false,
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": ["read", "update"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "enum",
|
||||||
|
"config": {
|
||||||
|
"default_value": "INACTIVE",
|
||||||
|
"options": {
|
||||||
|
"type": "strings",
|
||||||
|
"values": ["INACTIVE", "SUBSCRIBED", "UNSUBSCRIBED"]
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "date",
|
||||||
|
"config": {
|
||||||
|
"type": "datetime",
|
||||||
|
"required": true,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"type": "jsonschema",
|
||||||
|
"config": {
|
||||||
|
"default_from_schema": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"one": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "regular",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"format": "integer",
|
||||||
|
"fillable": false,
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"type": "system",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"format": "integer",
|
||||||
|
"fillable": false,
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"type": "boolean",
|
||||||
|
"config": {
|
||||||
|
"default_value": false,
|
||||||
|
"hidden": true,
|
||||||
|
"fillable": ["create"],
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mime_type": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "number",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"hidden": true,
|
||||||
|
"fillable": ["create"],
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"etag": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modified_at": {
|
||||||
|
"type": "date",
|
||||||
|
"config": {
|
||||||
|
"type": "datetime",
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity_id": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "json",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {},
|
||||||
|
"indices": {
|
||||||
|
"idx_unique_media_path": {
|
||||||
|
"entity": "media",
|
||||||
|
"fields": ["path"],
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
"idx_media_reference": {
|
||||||
|
"entity": "media",
|
||||||
|
"fields": ["reference"],
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
"idx_media_entity_id": {
|
||||||
|
"entity": "media",
|
||||||
|
"fields": ["entity_id"],
|
||||||
|
"unique": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": false,
|
||||||
|
"basepath": "/api/auth",
|
||||||
|
"entity_name": "users",
|
||||||
|
"allow_register": true,
|
||||||
|
"jwt": {
|
||||||
|
"secret": "",
|
||||||
|
"alg": "HS256",
|
||||||
|
"fields": ["id", "email", "role"]
|
||||||
|
},
|
||||||
|
"cookie": {
|
||||||
|
"path": "/",
|
||||||
|
"sameSite": "lax",
|
||||||
|
"secure": true,
|
||||||
|
"httpOnly": true,
|
||||||
|
"expires": 604800,
|
||||||
|
"partitioned": false,
|
||||||
|
"renew": true,
|
||||||
|
"pathSuccess": "/",
|
||||||
|
"pathLoggedOut": "/"
|
||||||
|
},
|
||||||
|
"strategies": {
|
||||||
|
"password": {
|
||||||
|
"type": "password",
|
||||||
|
"enabled": true,
|
||||||
|
"config": {
|
||||||
|
"hashing": "sha256"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"guard": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"roles": {}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"flows": {
|
||||||
|
"basepath": "/api/flows",
|
||||||
|
"flows": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
import pkg from "./package.json" with { type: "json" };
|
import pkg from "./package.json" with { type: "json" };
|
||||||
import c from "picocolors";
|
import c from "picocolors";
|
||||||
import { formatNumber } from "bknd/utils";
|
import { formatNumber } from "bknd/utils";
|
||||||
|
import * as esbuild from "esbuild";
|
||||||
|
|
||||||
const deps = Object.keys(pkg.dependencies);
|
const deps = Object.keys(pkg.dependencies);
|
||||||
const external = ["jsonv-ts/*", "wrangler", "bknd", "bknd/*", ...deps];
|
const external = ["jsonv-ts/*", "wrangler", "bknd", "bknd/*", ...deps];
|
||||||
|
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
const result = await esbuild.build({
|
||||||
|
entryPoints: ["./src/cli/index.ts"],
|
||||||
|
outdir: "./dist/cli",
|
||||||
|
platform: "node",
|
||||||
|
minify: true,
|
||||||
|
format: "esm",
|
||||||
|
metafile: true,
|
||||||
|
bundle: true,
|
||||||
|
external,
|
||||||
|
define: {
|
||||||
|
__isDev: "0",
|
||||||
|
__version: JSON.stringify(pkg.version),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await Bun.write("./dist/cli/metafile-esm.json", JSON.stringify(result.metafile, null, 2));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await Bun.build({
|
const result = await Bun.build({
|
||||||
entrypoints: ["./src/cli/index.ts"],
|
entrypoints: ["./src/cli/index.ts"],
|
||||||
target: "node",
|
target: "node",
|
||||||
|
|||||||
+2
-13
@@ -1,4 +1,3 @@
|
|||||||
/** biome-ignore-all lint/suspicious/noConsole: . */
|
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
import * as tsup from "tsup";
|
import * as tsup from "tsup";
|
||||||
import pkg from "./package.json" with { type: "json" };
|
import pkg from "./package.json" with { type: "json" };
|
||||||
@@ -97,7 +96,6 @@ async function buildApi() {
|
|||||||
metafile: true,
|
metafile: true,
|
||||||
target: "esnext",
|
target: "esnext",
|
||||||
platform: "browser",
|
platform: "browser",
|
||||||
removeNodeProtocol: false,
|
|
||||||
format: ["esm"],
|
format: ["esm"],
|
||||||
splitting: false,
|
splitting: false,
|
||||||
loader: {
|
loader: {
|
||||||
@@ -230,7 +228,6 @@ function baseConfig(adapter: string, overrides: Partial<tsup.Options> = {}): tsu
|
|||||||
outDir: `dist/adapter/${adapter}`,
|
outDir: `dist/adapter/${adapter}`,
|
||||||
metafile: true,
|
metafile: true,
|
||||||
splitting: false,
|
splitting: false,
|
||||||
removeNodeProtocol: false,
|
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
delayTypes();
|
delayTypes();
|
||||||
oldConsole.log(c.cyan("[Adapter]"), adapter || "base", c.green("built"));
|
oldConsole.log(c.cyan("[Adapter]"), adapter || "base", c.green("built"));
|
||||||
@@ -268,7 +265,7 @@ async function buildAdapters() {
|
|||||||
tsup.build(baseConfig("react-router")),
|
tsup.build(baseConfig("react-router")),
|
||||||
tsup.build(
|
tsup.build(
|
||||||
baseConfig("bun", {
|
baseConfig("bun", {
|
||||||
external: [/^bun:.*/],
|
external: [/^bun\:.*/],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
tsup.build(baseConfig("astro")),
|
tsup.build(baseConfig("astro")),
|
||||||
@@ -287,14 +284,6 @@ async function buildAdapters() {
|
|||||||
external: [/bknd/, "wrangler", "node:process"],
|
external: [/bknd/, "wrangler", "node:process"],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
tsup.build(
|
|
||||||
baseConfig("cloudflare/vite", {
|
|
||||||
target: "esnext",
|
|
||||||
entry: ["src/adapter/cloudflare/vite.ts"],
|
|
||||||
outDir: "dist/adapter/cloudflare",
|
|
||||||
metafile: false,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
|
|
||||||
tsup.build({
|
tsup.build({
|
||||||
...baseConfig("vite"),
|
...baseConfig("vite"),
|
||||||
@@ -331,7 +320,7 @@ async function buildAdapters() {
|
|||||||
entry: ["src/adapter/sqlite/bun.ts"],
|
entry: ["src/adapter/sqlite/bun.ts"],
|
||||||
outDir: "dist/adapter/sqlite",
|
outDir: "dist/adapter/sqlite",
|
||||||
metafile: false,
|
metafile: false,
|
||||||
external: [/^bun:.*/],
|
external: [/^bun\:.*/],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[install]
|
||||||
|
#registry = "http://localhost:4873"
|
||||||
|
|
||||||
|
[test]
|
||||||
|
coverageSkipTestFiles = true
|
||||||
|
console.depth = 10
|
||||||
+49
-59
@@ -13,7 +13,7 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/bknd-io/bknd/issues"
|
"url": "https://github.com/bknd-io/bknd/issues"
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.3.1",
|
"packageManager": "bun@1.2.22",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.13"
|
"node": ">=22.13"
|
||||||
},
|
},
|
||||||
@@ -49,98 +49,93 @@
|
|||||||
"license": "FSL-1.1-MIT",
|
"license": "FSL-1.1-MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cfworker/json-schema": "^4.1.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
"@codemirror/lang-html": "^6.4.11",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-json": "^6.0.2",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@hono/swagger-ui": "^0.5.2",
|
"@hono/swagger-ui": "^0.5.1",
|
||||||
"@mantine/core": "^7.17.1",
|
"@mantine/core": "^7.17.1",
|
||||||
"@mantine/hooks": "^7.17.1",
|
"@mantine/hooks": "^7.17.1",
|
||||||
"@tanstack/react-form": "^1.0.5",
|
"@tanstack/react-form": "^1.0.5",
|
||||||
"@uiw/react-codemirror": "^4.25.2",
|
"@uiw/react-codemirror": "^4.23.10",
|
||||||
"@xyflow/react": "^12.9.2",
|
"@xyflow/react": "^12.4.4",
|
||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.2",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.13",
|
||||||
"fast-xml-parser": "^5.3.1",
|
"fast-xml-parser": "^5.0.8",
|
||||||
"hono": "4.10.4",
|
"hono": "4.8.3",
|
||||||
"json-schema-library": "10.0.0-rc7",
|
"json-schema-library": "10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
"jsonv-ts": "0.9.3",
|
"jsonv-ts": "0.9.1",
|
||||||
"kysely": "0.28.8",
|
"kysely": "0.27.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"object-path-immutable": "^4.1.2",
|
"object-path-immutable": "^4.1.2",
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"radix-ui": "^1.1.3",
|
"radix-ui": "^1.1.3",
|
||||||
"swr": "^2.3.6",
|
"picocolors": "^1.1.1",
|
||||||
"use-sync-external-store": "^1.6.0",
|
"swr": "^2.3.3"
|
||||||
"zustand": "^4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.922.0",
|
"@aws-sdk/client-s3": "^3.758.0",
|
||||||
"@bluwy/giget-core": "^0.1.6",
|
"@bluwy/giget-core": "^0.1.2",
|
||||||
"@clack/prompts": "^0.11.0",
|
"@clack/prompts": "^0.11.0",
|
||||||
"@cloudflare/vitest-pool-workers": "^0.10.4",
|
"@cloudflare/vitest-pool-workers": "^0.9.3",
|
||||||
"@cloudflare/workers-types": "^4.20251014.0",
|
"@cloudflare/workers-types": "^4.20250606.0",
|
||||||
"@dagrejs/dagre": "^1.1.4",
|
"@dagrejs/dagre": "^1.1.4",
|
||||||
"@hono/vite-dev-server": "^0.23.0",
|
"@hono/vite-dev-server": "^0.21.0",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^4.1.3",
|
||||||
"@libsql/client": "^0.15.15",
|
"@libsql/client": "^0.15.9",
|
||||||
"@mantine/modals": "^7.17.1",
|
"@mantine/modals": "^7.17.1",
|
||||||
"@mantine/notifications": "^7.17.1",
|
"@mantine/notifications": "^7.17.1",
|
||||||
"@playwright/test": "^1.56.1",
|
"@playwright/test": "^1.51.1",
|
||||||
"@rjsf/core": "5.22.2",
|
"@rjsf/core": "5.22.2",
|
||||||
"@rjsf/utils": "5.22.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@tabler/icons-react": "3.35.0",
|
"@tabler/icons-react": "3.18.0",
|
||||||
"@tailwindcss/postcss": "^4.1.16",
|
"@tailwindcss/postcss": "^4.0.12",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.0.12",
|
||||||
"@tanstack/react-store": "^0.8.0",
|
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^22.13.10",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@vitejs/plugin-react": "^5.1.0",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"@vitest/coverage-v8": "3.0.9",
|
"@vitest/coverage-v8": "^3.0.9",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"commander": "^14.0.2",
|
"dotenv": "^16.4.7",
|
||||||
"dotenv": "^17.2.3",
|
|
||||||
"jotai": "^2.12.2",
|
"jotai": "^2.12.2",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.0.0",
|
||||||
|
"kysely-d1": "^0.3.0",
|
||||||
"kysely-generic-sqlite": "^1.2.1",
|
"kysely-generic-sqlite": "^1.2.1",
|
||||||
"libsql": "^0.5.22",
|
|
||||||
"libsql-stateless-easy": "^1.8.0",
|
"libsql-stateless-easy": "^1.8.0",
|
||||||
"miniflare": "^4.20251011.2",
|
"open": "^10.1.0",
|
||||||
"open": "^10.2.0",
|
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"posthog-js-lite": "^3.6.0",
|
"posthog-js-lite": "^3.4.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-icons": "5.5.0",
|
"react-icons": "5.2.1",
|
||||||
"react-json-view-lite": "^2.5.0",
|
"react-json-view-lite": "^2.4.1",
|
||||||
"sql-formatter": "^15.6.10",
|
"sql-formatter": "^15.4.11",
|
||||||
"tailwind-merge": "^3.0.2",
|
"tailwind-merge": "^3.0.2",
|
||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.0.12",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tsc-alias": "^1.8.16",
|
"tsc-alias": "^1.8.11",
|
||||||
"tsup": "^8.5.0",
|
"tsup": "^8.4.0",
|
||||||
"tsx": "^4.20.6",
|
"tsx": "^4.19.3",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^11.1.0",
|
||||||
"vite": "^7.1.12",
|
"vite": "^6.3.5",
|
||||||
"vite-plugin-circular-dependency": "^0.5.0",
|
"vite-plugin-circular-dependency": "^0.5.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "3.0.9",
|
"vitest": "^3.0.9",
|
||||||
"wouter": "^3.7.1",
|
"wouter": "^3.6.0",
|
||||||
"wrangler": "^4.45.4"
|
"wrangler": "^4.37.1",
|
||||||
|
"miniflare": "^4.20250913.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@hono/node-server": "^1.19.6"
|
"@hono/node-server": "^1.14.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=19",
|
"react": ">=19",
|
||||||
@@ -214,11 +209,6 @@
|
|||||||
"import": "./dist/adapter/cloudflare/proxy.js",
|
"import": "./dist/adapter/cloudflare/proxy.js",
|
||||||
"require": "./dist/adapter/cloudflare/proxy.js"
|
"require": "./dist/adapter/cloudflare/proxy.js"
|
||||||
},
|
},
|
||||||
"./adapter/cloudflare/vite": {
|
|
||||||
"types": "./dist/types/adapter/cloudflare/vite.d.ts",
|
|
||||||
"import": "./dist/adapter/cloudflare/vite.js",
|
|
||||||
"require": "./dist/adapter/cloudflare/vite.js"
|
|
||||||
},
|
|
||||||
"./adapter": {
|
"./adapter": {
|
||||||
"types": "./dist/types/adapter/index.d.ts",
|
"types": "./dist/types/adapter/index.d.ts",
|
||||||
"import": "./dist/adapter/index.js"
|
"import": "./dist/adapter/index.js"
|
||||||
|
|||||||
+2
-3
@@ -5,6 +5,7 @@ import type { em as prototypeEm } from "data/prototype";
|
|||||||
import { Connection } from "data/connection/Connection";
|
import { Connection } from "data/connection/Connection";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
import {
|
import {
|
||||||
|
type InitialModuleConfigs,
|
||||||
type ModuleConfigs,
|
type ModuleConfigs,
|
||||||
type Modules,
|
type Modules,
|
||||||
ModuleManager,
|
ModuleManager,
|
||||||
@@ -380,10 +381,8 @@ export class App<
|
|||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
for (const { name, result } of results) {
|
for (const { name, result } of results) {
|
||||||
if (result) {
|
if (result) {
|
||||||
|
$console.log(`[Plugin:${name}] schema`);
|
||||||
ctx.helper.ensureSchema(result);
|
ctx.helper.ensureSchema(result);
|
||||||
if (ctx.flags.sync_required) {
|
|
||||||
$console.log(`[Plugin:${name}] schema, sync required`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
|
import { type RuntimeBkndConfig, createRuntimeApp } from "bknd/adapter";
|
||||||
import { registerLocalMediaAdapter } from ".";
|
import { registerLocalMediaAdapter } from ".";
|
||||||
import { config, type App } from "bknd";
|
import { config, type App } from "bknd";
|
||||||
|
import type { ServeOptions } from "bun";
|
||||||
import { serveStatic } from "hono/bun";
|
import { serveStatic } from "hono/bun";
|
||||||
|
|
||||||
type BunEnv = Bun.Env;
|
type BunEnv = Bun.Env;
|
||||||
export type BunBkndConfig<Env = BunEnv> = RuntimeBkndConfig<Env> &
|
export type BunBkndConfig<Env = BunEnv> = RuntimeBkndConfig<Env> & Omit<ServeOptions, "fetch">;
|
||||||
Omit<Bun.Serve.Options<undefined, string>, "fetch">;
|
|
||||||
|
|
||||||
export async function createApp<Env = BunEnv>(
|
export async function createApp<Env = BunEnv>(
|
||||||
{ distPath, serveStatic: _serveStatic, ...config }: BunBkndConfig<Env> = {},
|
{ distPath, serveStatic: _serveStatic, ...config }: BunBkndConfig<Env> = {},
|
||||||
@@ -58,7 +60,7 @@ export function serve<Env = BunEnv>(
|
|||||||
args: Env = Bun.env as Env,
|
args: Env = Bun.env as Env,
|
||||||
) {
|
) {
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
...(serveOptions as any),
|
...serveOptions,
|
||||||
port,
|
port,
|
||||||
fetch: createHandler(
|
fetch: createHandler(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { inspect } from "node:util";
|
||||||
|
|
||||||
export type BindingTypeMap = {
|
export type BindingTypeMap = {
|
||||||
D1Database: D1Database;
|
D1Database: D1Database;
|
||||||
KVNamespace: KVNamespace;
|
KVNamespace: KVNamespace;
|
||||||
@@ -8,40 +10,21 @@ export type BindingTypeMap = {
|
|||||||
export type GetBindingType = keyof BindingTypeMap;
|
export type GetBindingType = keyof BindingTypeMap;
|
||||||
export type BindingMap<T extends GetBindingType> = { key: string; value: BindingTypeMap[T] };
|
export type BindingMap<T extends GetBindingType> = { key: string; value: BindingTypeMap[T] };
|
||||||
|
|
||||||
export const bindingMatchers = {
|
|
||||||
// D1Database instance doesn't stringify to [object D1Database] (yet)
|
|
||||||
D1Database: (value: object): value is D1Database => {
|
|
||||||
if (typeof value !== "object" || value === null) return false;
|
|
||||||
if (String(value) === "[object D1Database]") return true;
|
|
||||||
if (Object.keys(value).length !== 2) return false;
|
|
||||||
return ["alwaysPrimarySession", "fetcher"].every((key) => key in value);
|
|
||||||
},
|
|
||||||
KVNamespace: (value: object): value is KVNamespace =>
|
|
||||||
String(value) === "[object KvNamespace]" && Object.keys(value).length === 0,
|
|
||||||
DurableObjectNamespace: (value: object): value is DurableObjectNamespace =>
|
|
||||||
String(value) === "[object DurableObjectNamespace]" && Object.keys(value).length === 0,
|
|
||||||
R2Bucket: (value: object): value is R2Bucket =>
|
|
||||||
String(value) === "[object R2Bucket]" && Object.keys(value).length === 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getBindings<T extends GetBindingType>(env: any, type: T): BindingMap<T>[] {
|
export function getBindings<T extends GetBindingType>(env: any, type: T): BindingMap<T>[] {
|
||||||
const bindings: BindingMap<T>[] = [];
|
const bindings: BindingMap<T>[] = [];
|
||||||
if (!(type in bindingMatchers)) {
|
|
||||||
console.error(`No binding matcher for type ${type}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const matcher = bindingMatchers[type];
|
|
||||||
|
|
||||||
for (const key in env) {
|
for (const key in env) {
|
||||||
const value = env[key];
|
try {
|
||||||
if (typeof value !== "object" || value === null) continue;
|
if (
|
||||||
if (!matcher(value)) continue;
|
(env[key] as any).constructor.name === type ||
|
||||||
|
String(env[key]) === `[object ${type}]` ||
|
||||||
bindings.push({
|
inspect(env[key]).includes(type)
|
||||||
key,
|
) {
|
||||||
value,
|
bindings.push({
|
||||||
} as any);
|
key,
|
||||||
|
value: env[key] as BindingTypeMap[T],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { describe, afterAll, test, expect, beforeAll } from "vitest";
|
|
||||||
import { Miniflare } from "miniflare";
|
|
||||||
import { getBindings, type GetBindingType } from "./bindings";
|
|
||||||
|
|
||||||
let mf: Miniflare | undefined;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
mf = new Miniflare({
|
|
||||||
modules: true,
|
|
||||||
script: `import { DurableObject } from "cloudflare:workers";
|
|
||||||
export default { async fetch() { return new Response(null); } }
|
|
||||||
export class TestObject extends DurableObject {}
|
|
||||||
export class TestObject2 extends DurableObject {}`,
|
|
||||||
r2Buckets: ["BUCKET"],
|
|
||||||
d1Databases: ["DB"],
|
|
||||||
kvNamespaces: ["KV"],
|
|
||||||
durableObjects: {
|
|
||||||
DO_SQLITE: { className: "TestObject", useSQLite: true },
|
|
||||||
DO_KV: { className: "TestObject2" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
afterAll(() => {
|
|
||||||
mf?.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("bindings", () => {
|
|
||||||
test("extracts", async () => {
|
|
||||||
const env = {
|
|
||||||
...(await mf!.getBindings()),
|
|
||||||
some: "string",
|
|
||||||
another: 123,
|
|
||||||
array: [1, 2, 3],
|
|
||||||
};
|
|
||||||
|
|
||||||
const $getBindingKeys = (type: GetBindingType) => {
|
|
||||||
const bindings = getBindings(env, type);
|
|
||||||
const keys = bindings?.map((b) => b.key);
|
|
||||||
if (!keys || keys.length === 0) {
|
|
||||||
console.error(`No ${type} binding found`, { bindings });
|
|
||||||
throw new Error(`No ${type} binding found`);
|
|
||||||
}
|
|
||||||
return keys;
|
|
||||||
};
|
|
||||||
|
|
||||||
expect($getBindingKeys("D1Database")).toEqual(["DB"]);
|
|
||||||
expect($getBindingKeys("KVNamespace")).toEqual(["KV"]);
|
|
||||||
expect($getBindingKeys("DurableObjectNamespace")).toEqual(["DO_SQLITE", "DO_KV"]);
|
|
||||||
expect($getBindingKeys("R2Bucket")).toEqual(["BUCKET"]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -16,14 +16,10 @@ export {
|
|||||||
type GetBindingType,
|
type GetBindingType,
|
||||||
type BindingMap,
|
type BindingMap,
|
||||||
} from "./bindings";
|
} from "./bindings";
|
||||||
export {
|
export { constants, makeConfig, type CloudflareContext } from "./config";
|
||||||
constants,
|
|
||||||
makeConfig,
|
|
||||||
type CloudflareContext,
|
|
||||||
registerAsyncsExecutionContext,
|
|
||||||
} from "./config";
|
|
||||||
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
||||||
export { registries } from "bknd";
|
export { registries } from "bknd";
|
||||||
|
export { devFsVitePlugin, devFsWrite } from "./vite";
|
||||||
|
|
||||||
// for compatibility with old code
|
// for compatibility with old code
|
||||||
export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
|
export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export function registerMedia(
|
|||||||
env: Record<string, any>,
|
env: Record<string, any>,
|
||||||
registries: typeof $registries = $registries,
|
registries: typeof $registries = $registries,
|
||||||
) {
|
) {
|
||||||
if (registries.media.has("r2")) return;
|
|
||||||
const r2_bindings = getBindings(env, "R2Bucket");
|
const r2_bindings = getBindings(env, "R2Bucket");
|
||||||
|
|
||||||
registries.media.register(
|
registries.media.register(
|
||||||
@@ -173,7 +172,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
await this.bucket.delete(this.getKey(key));
|
await this.bucket.delete(this.getKey(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectUrl(): string {
|
getObjectUrl(key: string): string {
|
||||||
throw new Error("Method getObjectUrl not implemented.");
|
throw new Error("Method getObjectUrl not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +183,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON(secrets?: boolean) {
|
||||||
return {
|
return {
|
||||||
type: this.getName(),
|
type: this.getName(),
|
||||||
config: {},
|
config: {},
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// biome-ignore-all lint: .
|
|
||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
import { writeFile as nodeWriteFile } from "node:fs/promises";
|
import { writeFile as nodeWriteFile } from "node:fs/promises";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
@@ -49,6 +48,7 @@ export function devFsVitePlugin({
|
|||||||
// Skip our own debug output
|
// Skip our own debug output
|
||||||
if (output.includes("[dev-fs-plugin]") || output.includes("[dev-fs-polyfill]")) {
|
if (output.includes("[dev-fs-plugin]") || output.includes("[dev-fs-polyfill]")) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
// biome-ignore lint/style/noArguments: <explanation>
|
||||||
return originalStdoutWrite.apply(process.stdout, arguments);
|
return originalStdoutWrite.apply(process.stdout, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +178,7 @@ export function devFsVitePlugin({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
// biome-ignore lint:
|
||||||
return originalStdoutWrite.apply(process.stdout, arguments);
|
return originalStdoutWrite.apply(process.stdout, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createFrameworkApp, type FrameworkBkndConfig } from "bknd/adapter";
|
import { createFrameworkApp, type FrameworkBkndConfig } from "bknd/adapter";
|
||||||
import { isNode } from "bknd/utils";
|
import { isNode } from "bknd/utils";
|
||||||
// @ts-expect-error next is not installed
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
type NextjsEnv = NextApiRequest["env"];
|
type NextjsEnv = NextApiRequest["env"];
|
||||||
@@ -19,9 +18,7 @@ function getCleanRequest(req: Request, cleanRequest: NextjsBkndConfig["cleanRequ
|
|||||||
if (!cleanRequest) return req;
|
if (!cleanRequest) return req;
|
||||||
|
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
cleanRequest?.searchParams?.forEach((k) => {
|
cleanRequest?.searchParams?.forEach((k) => url.searchParams.delete(k));
|
||||||
url.searchParams.delete(k);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isNode()) {
|
if (isNode()) {
|
||||||
return new Request(url.toString(), {
|
return new Request(url.toString(), {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { resendEmail } from "./resend";
|
|||||||
const ALL_TESTS = !!process.env.ALL_TESTS;
|
const ALL_TESTS = !!process.env.ALL_TESTS;
|
||||||
|
|
||||||
describe.skipIf(ALL_TESTS)("resend", () => {
|
describe.skipIf(ALL_TESTS)("resend", () => {
|
||||||
it("should throw on failed", async () => {
|
it.only("should throw on failed", async () => {
|
||||||
const driver = resendEmail({ apiKey: "invalid" } as any);
|
const driver = resendEmail({ apiKey: "invalid" } as any);
|
||||||
expect(driver.send("foo@bar.com", "Test", "Test")).rejects.toThrow();
|
expect(driver.send("foo@bar.com", "Test", "Test")).rejects.toThrow();
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-2
@@ -73,9 +73,8 @@ export const env = <
|
|||||||
onValid?: (valid: R) => void;
|
onValid?: (valid: R) => void;
|
||||||
},
|
},
|
||||||
): R extends undefined ? Fallback : R => {
|
): R extends undefined ? Fallback : R => {
|
||||||
const source = opts?.source ?? (typeof process !== "undefined" ? process?.env : {});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const source = opts?.source ?? process.env;
|
||||||
const c = envs[key];
|
const c = envs[key];
|
||||||
const g = source[c.key];
|
const g = source[c.key];
|
||||||
const v = c.validate(g) as any;
|
const v = c.validate(g) as any;
|
||||||
|
|||||||
@@ -120,14 +120,17 @@ export function patternMatch(target: string, pattern: RegExp | string): boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function slugify(str: string): string {
|
export function slugify(str: string): string {
|
||||||
return String(str)
|
return (
|
||||||
.normalize("NFKD") // split accented characters into their base characters and diacritical marks
|
String(str)
|
||||||
.replace(/[\u0300-\u036f]/g, "") // remove all the accents, which happen to be all in the \u03xx UNICODE block.
|
.normalize("NFKD") // split accented characters into their base characters and diacritical marks
|
||||||
.trim() // trim leading or trailing whitespace
|
// biome-ignore lint/suspicious/noMisleadingCharacterClass: <explanation>
|
||||||
.toLowerCase() // convert to lowercase
|
.replace(/[\u0300-\u036f]/g, "") // remove all the accents, which happen to be all in the \u03xx UNICODE block.
|
||||||
.replace(/[^a-z0-9 -]/g, "") // remove non-alphanumeric characters
|
.trim() // trim leading or trailing whitespace
|
||||||
.replace(/\s+/g, "-") // replace spaces with hyphens
|
.toLowerCase() // convert to lowercase
|
||||||
.replace(/-+/g, "-"); // remove consecutive hyphens
|
.replace(/[^a-z0-9 -]/g, "") // remove non-alphanumeric characters
|
||||||
|
.replace(/\s+/g, "-") // replace spaces with hyphens
|
||||||
|
.replace(/-+/g, "-") // remove consecutive hyphens
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function truncate(str: string, length = 50, end = "..."): string {
|
export function truncate(str: string, length = 50, end = "..."): string {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
type Simplify,
|
type Simplify,
|
||||||
sql,
|
sql,
|
||||||
} from "kysely";
|
} from "kysely";
|
||||||
import type { jsonArrayFrom, jsonBuildObject, jsonObjectFrom } from "kysely/helpers/sqlite";
|
|
||||||
import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector";
|
import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector";
|
||||||
import type { DB } from "bknd";
|
import type { DB } from "bknd";
|
||||||
import type { Constructor } from "core/registry/Registry";
|
import type { Constructor } from "core/registry/Registry";
|
||||||
@@ -71,9 +70,15 @@ export type IndexSpec = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DbFunctions = {
|
export type DbFunctions = {
|
||||||
jsonObjectFrom: typeof jsonObjectFrom;
|
jsonObjectFrom<O>(expr: SelectQueryBuilderExpression<O>): RawBuilder<Simplify<O> | null>;
|
||||||
jsonArrayFrom: typeof jsonArrayFrom;
|
jsonArrayFrom<O>(expr: SelectQueryBuilderExpression<O>): RawBuilder<Simplify<O>[]>;
|
||||||
jsonBuildObject: typeof jsonBuildObject;
|
jsonBuildObject<O extends Record<string, Expression<unknown>>>(
|
||||||
|
obj: O,
|
||||||
|
): RawBuilder<
|
||||||
|
Simplify<{
|
||||||
|
[K in keyof O]: O[K] extends Expression<infer V> ? V : never;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConnQuery = CompiledQuery | Compilable;
|
export type ConnQuery = CompiledQuery | Compilable;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export type LibsqlClientFns = {
|
|||||||
function getClient(clientOrCredentials: Client | LibSqlCredentials | LibsqlClientFns): Client {
|
function getClient(clientOrCredentials: Client | LibSqlCredentials | LibsqlClientFns): Client {
|
||||||
if (clientOrCredentials && "url" in clientOrCredentials) {
|
if (clientOrCredentials && "url" in clientOrCredentials) {
|
||||||
const { url, authToken } = clientOrCredentials;
|
const { url, authToken } = clientOrCredentials;
|
||||||
return createClient({ url, authToken }) as unknown as Client;
|
return createClient({ url, authToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientOrCredentials as Client;
|
return clientOrCredentials as Client;
|
||||||
|
|||||||
@@ -136,8 +136,10 @@ export class Entity<
|
|||||||
.map((field) => (alias ? `${alias}.${field.name} as ${field.name}` : field.name));
|
.map((field) => (alias ? `${alias}.${field.name} as ${field.name}` : field.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
getFillableFields(context?: TActionContext, include_virtual?: boolean): Field[] {
|
getFillableFields(context?: "create" | "update", include_virtual?: boolean): Field[] {
|
||||||
return this.getFields(include_virtual).filter((field) => field.isFillable(context));
|
return this.getFields({ virtual: include_virtual }).filter((field) =>
|
||||||
|
field.isFillable(context),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRequiredFields(): Field[] {
|
getRequiredFields(): Field[] {
|
||||||
@@ -189,9 +191,15 @@ export class Entity<
|
|||||||
return this.fields.findIndex((field) => field.name === name) !== -1;
|
return this.fields.findIndex((field) => field.name === name) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFields(include_virtual: boolean = false): Field[] {
|
getFields({
|
||||||
if (include_virtual) return this.fields;
|
virtual = false,
|
||||||
return this.fields.filter((f) => !f.isVirtual());
|
primary = true,
|
||||||
|
}: { virtual?: boolean; primary?: boolean } = {}): Field[] {
|
||||||
|
return this.fields.filter((f) => {
|
||||||
|
if (!virtual && f.isVirtual()) return false;
|
||||||
|
if (!primary && f instanceof PrimaryField) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addField(field: Field) {
|
addField(field: Field) {
|
||||||
@@ -231,7 +239,7 @@ export class Entity<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = this.getFillableFields(context, false);
|
const fields = this.getFillableFields(context as any, false);
|
||||||
|
|
||||||
if (options?.ignoreUnknown !== true) {
|
if (options?.ignoreUnknown !== true) {
|
||||||
const field_names = fields.map((f) => f.name);
|
const field_names = fields.map((f) => f.name);
|
||||||
@@ -275,7 +283,7 @@ export class Entity<
|
|||||||
fields = this.getFillableFields(options.context);
|
fields = this.getFillableFields(options.context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fields = this.getFields(true);
|
fields = this.getFields({ virtual: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const _fields = Object.fromEntries(fields.map((field) => [field.name, field]));
|
const _fields = Object.fromEntries(fields.map((field) => [field.name, field]));
|
||||||
|
|||||||
@@ -83,8 +83,10 @@ export class Mutator<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we should never get here, but just to be sure (why?)
|
// we should never get here, but just to be sure (why?)
|
||||||
if (!field.isFillable(context)) {
|
if (!field.isFillable(context as any)) {
|
||||||
throw new Error(`Field "${key}" is not fillable on entity "${entity.name}"`);
|
throw new Error(
|
||||||
|
`Field "${key}" of entity "${entity.name}" is not fillable on context "${context}"`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// transform from field
|
// transform from field
|
||||||
|
|||||||
@@ -26,11 +26,19 @@ export const baseFieldConfigSchema = s
|
|||||||
.strictObject({
|
.strictObject({
|
||||||
label: s.string(),
|
label: s.string(),
|
||||||
description: s.string(),
|
description: s.string(),
|
||||||
required: s.boolean({ default: false }),
|
required: s.boolean({ default: DEFAULT_REQUIRED }),
|
||||||
fillable: s.anyOf([
|
fillable: s.anyOf(
|
||||||
s.boolean({ title: "Boolean" }),
|
[
|
||||||
s.array(s.string({ enum: ActionContext }), { title: "Context", uniqueItems: true }),
|
s.boolean({ title: "Boolean" }),
|
||||||
]),
|
s.array(s.string({ enum: ["create", "update"] }), {
|
||||||
|
title: "Context",
|
||||||
|
uniqueItems: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
default: DEFAULT_FILLABLE,
|
||||||
|
},
|
||||||
|
),
|
||||||
hidden: s.anyOf([
|
hidden: s.anyOf([
|
||||||
s.boolean({ title: "Boolean" }),
|
s.boolean({ title: "Boolean" }),
|
||||||
// @todo: tmp workaround
|
// @todo: tmp workaround
|
||||||
@@ -103,7 +111,7 @@ export abstract class Field<
|
|||||||
return this.config?.default_value;
|
return this.config?.default_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
isFillable(context?: TActionContext): boolean {
|
isFillable(context?: "create" | "update"): boolean {
|
||||||
if (Array.isArray(this.config.fillable)) {
|
if (Array.isArray(this.config.fillable)) {
|
||||||
return context ? this.config.fillable.includes(context) : DEFAULT_FILLABLE;
|
return context ? this.config.fillable.includes(context) : DEFAULT_FILLABLE;
|
||||||
}
|
}
|
||||||
@@ -165,7 +173,7 @@ export abstract class Field<
|
|||||||
// @todo: add field level validation
|
// @todo: add field level validation
|
||||||
isValid(value: any, context: TActionContext): boolean {
|
isValid(value: any, context: TActionContext): boolean {
|
||||||
if (typeof value !== "undefined") {
|
if (typeof value !== "undefined") {
|
||||||
return this.isFillable(context);
|
return this.isFillable(context as any);
|
||||||
} else if (context === "create") {
|
} else if (context === "create") {
|
||||||
return !this.isRequired();
|
return !this.isRequired();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export function fieldTestSuite(
|
|||||||
const _config = {
|
const _config = {
|
||||||
..._requiredConfig,
|
..._requiredConfig,
|
||||||
required: false,
|
required: false,
|
||||||
|
fillable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function fieldJson(field: Field) {
|
function fieldJson(field: Field) {
|
||||||
@@ -116,10 +117,7 @@ export function fieldTestSuite(
|
|||||||
|
|
||||||
expect(fieldJson(fillable)).toEqual({
|
expect(fieldJson(fillable)).toEqual({
|
||||||
type: noConfigField.type,
|
type: noConfigField.type,
|
||||||
config: {
|
config: _config,
|
||||||
..._config,
|
|
||||||
fillable: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(fieldJson(required)).toEqual({
|
expect(fieldJson(required)).toEqual({
|
||||||
@@ -150,7 +148,6 @@ export function fieldTestSuite(
|
|||||||
type: requiredAndDefault.type,
|
type: requiredAndDefault.type,
|
||||||
config: {
|
config: {
|
||||||
..._config,
|
..._config,
|
||||||
fillable: true,
|
|
||||||
required: true,
|
required: true,
|
||||||
default_value: config.defaultValue,
|
default_value: config.defaultValue,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export class SchemaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getIntrospectionFromEntity(entity: Entity): IntrospectedTable {
|
getIntrospectionFromEntity(entity: Entity): IntrospectedTable {
|
||||||
const fields = entity.getFields(false);
|
const fields = entity.getFields({ virtual: false });
|
||||||
const indices = this.em.getIndicesOf(entity);
|
const indices = this.em.getIndicesOf(entity);
|
||||||
|
|
||||||
// this is intentionally setting values to defaults, like "nullable" and "default"
|
// this is intentionally setting values to defaults, like "nullable" and "default"
|
||||||
|
|||||||
+13
-11
@@ -23,17 +23,26 @@ export type HybridMode<AdapterConfig extends BkndConfig> = AdapterConfig extends
|
|||||||
? BkndModeConfig<Args, Merge<BkndHybridModeOptions & AdapterConfig>>
|
? BkndModeConfig<Args, Merge<BkndHybridModeOptions & AdapterConfig>>
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export function hybrid<Args>(hybridConfig: HybridBkndConfig<Args>): BkndConfig<Args> {
|
export function hybrid<Args>({
|
||||||
|
configFilePath = "bknd-config.json",
|
||||||
|
...rest
|
||||||
|
}: HybridBkndConfig<Args>): BkndConfig<Args> {
|
||||||
return {
|
return {
|
||||||
|
...rest,
|
||||||
|
config: undefined,
|
||||||
app: async (args) => {
|
app: async (args) => {
|
||||||
const {
|
const {
|
||||||
config: appConfig,
|
config: appConfig,
|
||||||
isProd,
|
isProd,
|
||||||
plugins,
|
plugins,
|
||||||
syncSchemaOptions,
|
syncSchemaOptions,
|
||||||
} = await makeModeConfig(hybridConfig, args);
|
} = await makeModeConfig(
|
||||||
|
{
|
||||||
const configFilePath = appConfig.configFilePath ?? "bknd-config.json";
|
...rest,
|
||||||
|
configFilePath,
|
||||||
|
},
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
if (appConfig?.options?.mode && appConfig?.options?.mode !== "db") {
|
if (appConfig?.options?.mode && appConfig?.options?.mode !== "db") {
|
||||||
$console.warn("You should not set a different mode than `db` when using hybrid mode");
|
$console.warn("You should not set a different mode than `db` when using hybrid mode");
|
||||||
@@ -71,13 +80,6 @@ export function hybrid<Args>(hybridConfig: HybridBkndConfig<Args>): BkndConfig<A
|
|||||||
skipValidation: isProd,
|
skipValidation: isProd,
|
||||||
// secrets are required for hybrid mode
|
// secrets are required for hybrid mode
|
||||||
secrets: appConfig.secrets,
|
secrets: appConfig.secrets,
|
||||||
onModulesBuilt: async (ctx) => {
|
|
||||||
if (ctx.flags.sync_required && !isProd && syncSchemaOptions.force) {
|
|
||||||
$console.log("[hybrid] syncing schema");
|
|
||||||
await ctx.em.schema().sync(syncSchemaOptions);
|
|
||||||
}
|
|
||||||
await appConfig?.options?.manager?.onModulesBuilt?.(ctx);
|
|
||||||
},
|
|
||||||
...appConfig?.options?.manager,
|
...appConfig?.options?.manager,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mark, stripMark, $console, s, setPath } from "bknd/utils";
|
import { mark, stripMark, $console, s, SecretSchema, setPath } from "bknd/utils";
|
||||||
import { BkndError } from "core/errors";
|
import { BkndError } from "core/errors";
|
||||||
import * as $diff from "core/object/diff";
|
import * as $diff from "core/object/diff";
|
||||||
import type { Connection } from "data/connection";
|
import type { Connection } from "data/connection";
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { transformObject } from "bknd/utils";
|
import { transformObject } from "bknd/utils";
|
||||||
import type { Kysely } from "kysely";
|
import type { Kysely } from "kysely";
|
||||||
import { set } from "lodash-es";
|
import { set } from "lodash-es";
|
||||||
|
import type { InitialModuleConfigs } from "modules/ModuleManager";
|
||||||
|
|
||||||
export type MigrationContext = {
|
export type MigrationContext = {
|
||||||
db: Kysely<any>;
|
db: Kysely<any>;
|
||||||
@@ -107,6 +108,29 @@ export const migrations: Migration[] = [
|
|||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// change field.config.fillable to only "create" and "update"
|
||||||
|
version: 11,
|
||||||
|
up: async (config: InitialModuleConfigs) => {
|
||||||
|
const { data, ...rest } = config;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
entities: transformObject(data?.entities ?? {}, (entity) => {
|
||||||
|
return {
|
||||||
|
...entity,
|
||||||
|
fields: transformObject(entity?.fields ?? {}, (field) => {
|
||||||
|
const fillable = field!.config?.fillable;
|
||||||
|
if (!fillable || typeof fillable === "boolean") return field;
|
||||||
|
return { ...field, config: { ...field!.config, fillable: true } };
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const CURRENT_VERSION = migrations[migrations.length - 1]?.version ?? 0;
|
export const CURRENT_VERSION = migrations[migrations.length - 1]?.version ?? 0;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const Group = <E extends ElementType = "div">({
|
|||||||
{...props}
|
{...props}
|
||||||
data-role="group"
|
data-role="group"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex flex-col gap-1.5 w-full",
|
"flex flex-col gap-1.5 has-disabled:cursor-not-allowed w-full",
|
||||||
as === "fieldset" && "border border-primary/10 p-3 rounded-md",
|
as === "fieldset" && "border border-primary/10 p-3 rounded-md",
|
||||||
as === "fieldset" && error && "border-red-500",
|
as === "fieldset" && error && "border-red-500",
|
||||||
error && "text-red-500",
|
error && "text-red-500",
|
||||||
@@ -97,7 +97,7 @@ export const Input = forwardRef<HTMLInputElement, React.ComponentProps<"input">>
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"bg-muted/40 h-11 rounded-md py-2.5 px-4 outline-none w-full disabled:cursor-not-allowed",
|
"bg-muted/40 h-11 rounded-md py-2.5 px-4 outline-none w-full disabled:cursor-not-allowed",
|
||||||
disabledOrReadonly && "bg-muted/50 text-primary/50",
|
disabledOrReadonly && "bg-muted/50 text-primary/50 cursor-not-allowed",
|
||||||
!disabledOrReadonly &&
|
!disabledOrReadonly &&
|
||||||
"focus:bg-muted focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all",
|
"focus:bg-muted focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all",
|
||||||
props.className,
|
props.className,
|
||||||
@@ -154,7 +154,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, React.ComponentProps<"te
|
|||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"bg-muted/40 min-h-11 rounded-md py-2.5 px-4 focus:bg-muted outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all disabled:bg-muted/50 disabled:text-primary/50",
|
"bg-muted/40 min-h-11 rounded-md py-2.5 px-4 focus:bg-muted outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all disabled:bg-muted/50 disabled:text-primary/50 disabled:cursor-not-allowed",
|
||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -214,7 +214,7 @@ export const BooleanInput = forwardRef<HTMLInputElement, React.ComponentProps<"i
|
|||||||
{...props}
|
{...props}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-all disabled:opacity-70 scale-150 ml-1"
|
className="outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-all disabled:opacity-70 disabled:cursor-not-allowed scale-150 ml-1"
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onChange={handleCheck}
|
onChange={handleCheck}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
@@ -260,7 +260,6 @@ export const Switch = forwardRef<
|
|||||||
props.disabled && "opacity-50 !cursor-not-allowed",
|
props.disabled && "opacity-50 !cursor-not-allowed",
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(bool) => {
|
onCheckedChange={(bool) => {
|
||||||
console.log("setting", bool);
|
|
||||||
props.onChange?.({ target: { value: bool } });
|
props.onChange?.({ target: { value: bool } });
|
||||||
}}
|
}}
|
||||||
{...(props as any)}
|
{...(props as any)}
|
||||||
@@ -294,7 +293,7 @@ export const Select = forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"bg-muted/40 focus:bg-muted rounded-md py-2.5 px-4 outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all disabled:bg-muted/50 disabled:text-primary/50",
|
"bg-muted/40 focus:bg-muted rounded-md py-2.5 px-4 outline-none focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all disabled:bg-muted/50 disabled:text-primary/50 disabled:cursor-not-allowed",
|
||||||
"appearance-none h-11 w-full",
|
"appearance-none h-11 w-full",
|
||||||
!props.multiple && "border-r-8 border-r-transparent",
|
!props.multiple && "border-r-8 border-r-transparent",
|
||||||
props.className,
|
props.className,
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ import type { RelationField } from "data/relations";
|
|||||||
import { useEntityAdminOptions } from "ui/options";
|
import { useEntityAdminOptions } from "ui/options";
|
||||||
|
|
||||||
// simplify react form types 🤦
|
// simplify react form types 🤦
|
||||||
|
export type FormApi = ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any>;
|
||||||
// biome-ignore format: ...
|
// biome-ignore format: ...
|
||||||
export type FormApi = ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any, any, any>;
|
export type TFieldApi = FieldApi<any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any>;
|
||||||
// biome-ignore format: ...
|
|
||||||
export type TFieldApi = FieldApi<any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any>;
|
|
||||||
|
|
||||||
type EntityFormProps = {
|
type EntityFormProps = {
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
@@ -45,7 +44,7 @@ export function EntityForm({
|
|||||||
className,
|
className,
|
||||||
action,
|
action,
|
||||||
}: EntityFormProps) {
|
}: EntityFormProps) {
|
||||||
const fields = entity.getFillableFields(action, true);
|
const fields = entity.getFields({ virtual: true, primary: false });
|
||||||
const options = useEntityAdminOptions(entity, action);
|
const options = useEntityAdminOptions(entity, action);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,10 +92,6 @@ export function EntityForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!field.isFillable(action)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _key = `${entity.name}-${field.name}-${key}`;
|
const _key = `${entity.name}-${field.name}-${key}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -128,7 +123,7 @@ export function EntityForm({
|
|||||||
<EntityFormField
|
<EntityFormField
|
||||||
field={field}
|
field={field}
|
||||||
fieldApi={props}
|
fieldApi={props}
|
||||||
disabled={fieldsDisabled}
|
disabled={fieldsDisabled || !field.isFillable(action)}
|
||||||
tabIndex={key + 1}
|
tabIndex={key + 1}
|
||||||
action={action}
|
action={action}
|
||||||
data={data}
|
data={data}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function useEntityForm({
|
|||||||
}: EntityFormProps) {
|
}: EntityFormProps) {
|
||||||
const data = initialData ?? {};
|
const data = initialData ?? {};
|
||||||
// @todo: check if virtual must be filtered
|
// @todo: check if virtual must be filtered
|
||||||
const fields = entity.getFillableFields(action, true);
|
const fields = entity.getFields({ virtual: true, primary: false });
|
||||||
|
|
||||||
// filter defaultValues to only contain fillable fields
|
// filter defaultValues to only contain fillable fields
|
||||||
const defaultValues = getDefaultValues(fields, data);
|
const defaultValues = getDefaultValues(fields, data);
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ function DataEntityUpdateImpl({ params }) {
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
fieldsDisabled={fieldsDisabled}
|
fieldsDisabled={fieldsDisabled}
|
||||||
data={data ?? undefined}
|
data={data ?? undefined}
|
||||||
Form={Form as any}
|
Form={Form}
|
||||||
action="update"
|
action="update"
|
||||||
className="flex flex-grow flex-col gap-3 p-3"
|
className="flex flex-grow flex-col gap-3 p-3"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function DataEntityCreate({ params }) {
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
fieldsDisabled={fieldsDisabled}
|
fieldsDisabled={fieldsDisabled}
|
||||||
data={search.value}
|
data={search.value}
|
||||||
Form={Form as any}
|
Form={Form}
|
||||||
action="create"
|
action="create"
|
||||||
className="flex flex-grow flex-col gap-3 p-3"
|
className="flex flex-grow flex-col gap-3 p-3"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { JSONSchema } from "json-schema-to-ts";
|
import type { JSONSchema7 } from "json-schema";
|
||||||
import { omitKeys, type s } from "bknd/utils";
|
import { omitKeys, type s } from "bknd/utils";
|
||||||
|
|
||||||
export function extractSchema<
|
export function extractSchema<
|
||||||
@@ -10,7 +10,7 @@ export function extractSchema<
|
|||||||
config: Config,
|
config: Config,
|
||||||
keys: Keys[],
|
keys: Keys[],
|
||||||
): [
|
): [
|
||||||
Exclude<JSONSchema, boolean | null | undefined>,
|
JSONSchema7,
|
||||||
Partial<Config>,
|
Partial<Config>,
|
||||||
{
|
{
|
||||||
[K in Keys]: {
|
[K in Keys]: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import path from "node:path";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tsconfigPaths()],
|
plugins: [tsconfigPaths()],
|
||||||
test: {
|
test: {
|
||||||
projects: ["**/*.vitest.config.ts", "**/*/vitest.config.ts"],
|
projects: ["**/*.vitest.config.ts"],
|
||||||
include: ["**/*.vi-test.ts", "**/*.vitest.ts"],
|
include: ["**/*.vi-test.ts", "**/*.vitest.ts"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-29
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.3/schema.json",
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
"assist": { "actions": { "source": { "organizeImports": "off" } } },
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"defaultBranch": "main"
|
"defaultBranch": "main"
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"indentStyle": "space",
|
"indentStyle": "space"
|
||||||
"formatWithErrors": true
|
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
@@ -29,36 +30,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"includes": [
|
"ignore": [
|
||||||
"**",
|
"**/node_modules/**",
|
||||||
"!!**/node_modules",
|
"node_modules/**",
|
||||||
"!!**/.cache",
|
"**/.cache/**",
|
||||||
"!!**/.wrangler",
|
"**/.wrangler/**",
|
||||||
"!!**/build",
|
"**/build/**",
|
||||||
"!!**/dist",
|
"**/dist/**",
|
||||||
"!!**/data.sqld",
|
"**/data.sqld/**",
|
||||||
"!!**/data.sqld",
|
"data.sqld/**",
|
||||||
"!!**/public",
|
"public/**",
|
||||||
"!!**/.history"
|
".history/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"includes": ["**"],
|
"ignore": ["**/*.spec.ts"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": true,
|
||||||
"a11y": {},
|
"a11y": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
"correctness": {
|
"correctness": {
|
||||||
"useExhaustiveDependencies": "off",
|
"useExhaustiveDependencies": "off",
|
||||||
"noUnreachable": "warn",
|
"noUnreachable": "warn",
|
||||||
"noChildrenProp": "off",
|
"noChildrenProp": "off",
|
||||||
"noSwitchDeclarations": "warn",
|
"noSwitchDeclarations": "warn"
|
||||||
"noUnusedVariables": {
|
|
||||||
"options": {
|
|
||||||
"ignoreRestSiblings": true
|
|
||||||
},
|
|
||||||
"level": "warn"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"complexity": {
|
"complexity": {
|
||||||
"noUselessFragments": "warn",
|
"noUselessFragments": "warn",
|
||||||
@@ -73,11 +70,7 @@
|
|||||||
"noArrayIndexKey": "off",
|
"noArrayIndexKey": "off",
|
||||||
"noImplicitAnyLet": "warn",
|
"noImplicitAnyLet": "warn",
|
||||||
"noConfusingVoidType": "off",
|
"noConfusingVoidType": "off",
|
||||||
"noConsole": {
|
"noConsoleLog": "warn"
|
||||||
"level": "warn",
|
|
||||||
"options": { "allow": ["error", "info"] }
|
|
||||||
},
|
|
||||||
"noTsIgnore": "off"
|
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"noDangerouslySetInnerHtml": "off"
|
"noDangerouslySetInnerHtml": "off"
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[install]
|
|
||||||
#linker = "hoisted"
|
|
||||||
@@ -164,7 +164,7 @@ The [Cloudflare Vite Plugin](https://developers.cloudflare.com/workers/vite-plug
|
|||||||
To fix this, bknd exports a Vite plugin that provides filesystem access during development. You can use it by adding the following to your `vite.config.ts` file:
|
To fix this, bknd exports a Vite plugin that provides filesystem access during development. You can use it by adding the following to your `vite.config.ts` file:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { devFsVitePlugin } from "bknd/adapter/cloudflare/vite";
|
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [devFsVitePlugin()], // [!code highlight]
|
plugins: [devFsVitePlugin()], // [!code highlight]
|
||||||
@@ -174,7 +174,7 @@ export default defineConfig({
|
|||||||
Now to use this polyfill, you can use the `devFsWrite` function to write files to the filesystem.
|
Now to use this polyfill, you can use the `devFsWrite` function to write files to the filesystem.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { devFsWrite } from "bknd/adapter/cloudflare/vite"; // [!code highlight]
|
import { devFsWrite } from "bknd/adapter/cloudflare"; // [!code highlight]
|
||||||
import { syncTypes } from "bknd/plugins";
|
import { syncTypes } from "bknd/plugins";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -6,15 +6,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "wrangler deploy",
|
"deploy": "wrangler deploy",
|
||||||
"dev": "wrangler dev",
|
"dev": "wrangler dev",
|
||||||
"bknd-typegen": "PROXY=1 node_modules/.bin/bknd types",
|
"bknd-typegen": "PROXY=1 npx bknd types",
|
||||||
"typegen": "wrangler types && npm run bknd-typegen",
|
"typegen": "wrangler types && npm run bknd-typegen",
|
||||||
"postinstall": "npm run typegen"
|
"predev": "npm run typegen"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bknd": "file:../../app"
|
"bknd": "file:../../app"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.2",
|
||||||
"wrangler": "^4.46.0"
|
"wrangler": "^4.34.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
"$schema": "node_modules/wrangler/config-schema.json",
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
"name": "bknd-cf-worker-example",
|
"name": "bknd-cf-worker-example",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"compatibility_date": "2025-11-05",
|
"compatibility_date": "2025-08-03",
|
||||||
|
"compatibility_flags": ["nodejs_compat"],
|
||||||
"workers_dev": true,
|
"workers_dev": true,
|
||||||
"minify": true,
|
"minify": true,
|
||||||
"assets": {
|
"assets": {
|
||||||
|
|||||||
+6
-6
@@ -12,13 +12,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.3.3",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@tsconfig/strictest": "^2.0.7",
|
"@tsconfig/strictest": "^2.0.5",
|
||||||
"@types/bun": "^1.3.1",
|
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"miniflare": "^3.20250718.2",
|
"bun-types": "^1.1.18",
|
||||||
"typescript": "^5.9.3",
|
"miniflare": "^3.20240806.0",
|
||||||
"verdaccio": "^6.2.1"
|
"typescript": "^5.5.3",
|
||||||
|
"verdaccio": "^5.32.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
|
|||||||
Reference in New Issue
Block a user