adding d1 for cloudflare environments

This commit is contained in:
dswbx
2025-02-10 17:46:22 +01:00
parent 1e5c0dbc22
commit be39d1c374
12 changed files with 264 additions and 151 deletions

View File

@@ -49,6 +49,7 @@ if (types && !watch) {
/** /**
* Building backend and general API * Building backend and general API
*/ */
async function buildApi() {
await tsup.build({ await tsup.build({
minify, minify,
sourcemap, sourcemap,
@@ -68,10 +69,12 @@ await tsup.build({
delayTypes(); delayTypes();
} }
}); });
}
/** /**
* Building UI for direct imports * Building UI for direct imports
*/ */
async function buildUi() {
await tsup.build({ await tsup.build({
minify, minify,
sourcemap, sourcemap,
@@ -105,12 +108,14 @@ await tsup.build({
delayTypes(); delayTypes();
} }
}); });
}
/** /**
* Building UI Elements * Building UI Elements
* - tailwind-merge is mocked, no exclude * - tailwind-merge is mocked, no exclude
* - ui/client is external, and after built replaced with "bknd/client" * - ui/client is external, and after built replaced with "bknd/client"
*/ */
async function buildUiElements() {
await tsup.build({ await tsup.build({
minify, minify,
sourcemap, sourcemap,
@@ -149,11 +154,12 @@ await tsup.build({
delayTypes(); delayTypes();
} }
}); });
}
/** /**
* Building adapters * Building adapters
*/ */
function baseConfig(adapter: string): tsup.Options { function baseConfig(adapter: string, overrides: Partial<tsup.Options> = {}): tsup.Options {
return { return {
minify, minify,
sourcemap, sourcemap,
@@ -162,23 +168,27 @@ function baseConfig(adapter: string): tsup.Options {
format: ["esm"], format: ["esm"],
platform: "neutral", platform: "neutral",
outDir: `dist/adapter/${adapter}`, outDir: `dist/adapter/${adapter}`,
metafile: true,
splitting: false,
onSuccess: async () => {
delayTypes();
},
...overrides,
define: { define: {
__isDev: "0" __isDev: "0",
...overrides.define
}, },
external: [ external: [
/^cloudflare*/, /^cloudflare*/,
/^@?(hono|libsql).*?/, /^@?(hono|libsql).*?/,
/^(bknd|react|next|node).*?/, /^(bknd|react|next|node).*?/,
/.*\.(html)$/ /.*\.(html)$/,
], ...(Array.isArray(overrides.external) ? overrides.external : [])
metafile: true, ]
splitting: false,
onSuccess: async () => {
delayTypes();
}
}; };
} }
async function buildAdapters() {
// base adapter handles // base adapter handles
await tsup.build({ await tsup.build({
...baseConfig(""), ...baseConfig(""),
@@ -190,7 +200,11 @@ await tsup.build({
await tsup.build(baseConfig("remix")); await tsup.build(baseConfig("remix"));
await tsup.build(baseConfig("bun")); await tsup.build(baseConfig("bun"));
await tsup.build(baseConfig("astro")); await tsup.build(baseConfig("astro"));
await tsup.build(baseConfig("cloudflare")); await tsup.build(
baseConfig("cloudflare", {
external: [/^kysely/]
})
);
await tsup.build({ await tsup.build({
...baseConfig("vite"), ...baseConfig("vite"),
@@ -206,3 +220,9 @@ await tsup.build({
...baseConfig("node"), ...baseConfig("node"),
platform: "node" platform: "node"
}); });
}
await buildApi();
await buildUi();
await buildUiElements();
await buildAdapters();

View File

@@ -76,6 +76,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"esbuild-postcss": "^0.0.4", "esbuild-postcss": "^0.0.4",
"jotai": "^2.10.1", "jotai": "^2.10.1",
"kysely-d1": "^0.3.0",
"open": "^10.1.0", "open": "^10.1.0",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"postcss": "^8.4.47", "postcss": "^8.4.47",

View File

@@ -1,6 +1,9 @@
import type { FrameworkBkndConfig } from "bknd/adapter"; /// <reference types="@cloudflare/workers-types" />
import { type FrameworkBkndConfig, makeConfig } from "bknd/adapter";
import { Hono } from "hono"; import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers"; import { serveStatic } from "hono/cloudflare-workers";
import { D1Connection } from "./connection/D1Connection";
import { getCached } from "./modes/cached"; import { getCached } from "./modes/cached";
import { getDurable } from "./modes/durable"; import { getDurable } from "./modes/durable";
import { getFresh, getWarm } from "./modes/fresh"; import { getFresh, getWarm } from "./modes/fresh";
@@ -10,6 +13,7 @@ export type CloudflareBkndConfig<Env = any> = FrameworkBkndConfig<Context<Env>>
bindings?: (args: Context<Env>) => { bindings?: (args: Context<Env>) => {
kv?: KVNamespace; kv?: KVNamespace;
dobj?: DurableObjectNamespace; dobj?: DurableObjectNamespace;
db?: D1Database;
}; };
static?: "kv" | "assets"; static?: "kv" | "assets";
key?: string; key?: string;
@@ -26,7 +30,7 @@ export type Context<Env = any> = {
ctx: ExecutionContext; ctx: ExecutionContext;
}; };
export function serve<Env = any>(config: CloudflareBkndConfig<Env>) { export function serve<Env = any>(config: CloudflareBkndConfig<Env> = {}) {
return { return {
async fetch(request: Request, env: Env, ctx: ExecutionContext) { async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url); const url = new URL(request.url);
@@ -61,20 +65,46 @@ export function serve<Env = any>(config: CloudflareBkndConfig<Env>) {
} }
} }
config.setAdminHtml = config.setAdminHtml && !!config.manifest;
const context = { request, env, ctx } as Context; const context = { request, env, ctx } as Context;
const mode = config.mode ?? "warm"; const mode = config.mode ?? "warm";
const appConfig = makeConfig(config, context);
const bindings = config.bindings?.(context);
if (!appConfig.connection) {
let db: D1Database | undefined;
if (bindings && "db" in bindings && bindings.db) {
console.log("Using database from bindings");
db = bindings.db;
} else if (env && Object.keys(env).length > 0) {
// try to find a database in env
for (const key in env) {
try {
// @ts-ignore
if (env[key].constructor.name === "D1Database") {
console.log(`Using database from env "${key}"`);
db = env[key] as D1Database;
break;
}
} catch (e) {}
}
}
if (db) {
appConfig.connection = new D1Connection({ binding: db });
} else {
throw new Error("No database connection given");
}
}
switch (mode) { switch (mode) {
case "fresh": case "fresh":
return await getFresh(config, context); return await getFresh(appConfig, context);
case "warm": case "warm":
return await getWarm(config, context); return await getWarm(appConfig, context);
case "cache": case "cache":
return await getCached(config, context); return await getCached(appConfig, context);
case "durable": case "durable":
return await getDurable(config, context); return await getDurable(appConfig, context);
default: default:
throw new Error(`Unknown mode ${mode}`); throw new Error(`Unknown mode ${mode}`);
} }

View File

@@ -0,0 +1,65 @@
/// <reference types="@cloudflare/workers-types" />
import { SqliteConnection } from "bknd/data";
import { KyselyPluginRunner } from "data";
import type { QB } from "data/connection/Connection";
import { SqliteIntrospector } from "data/connection/SqliteIntrospector";
import { type DatabaseIntrospector, Kysely, ParseJSONResultsPlugin } from "kysely";
import { D1Dialect } from "kysely-d1";
export type D1ConnectionConfig = {
binding: D1Database;
};
class CustomD1Dialect extends D1Dialect {
override createIntrospector(db: Kysely<any>): DatabaseIntrospector {
return new SqliteIntrospector(db, {
excludeTables: [""]
});
}
}
export class D1Connection extends SqliteConnection {
constructor(private config: D1ConnectionConfig) {
const plugins = [new ParseJSONResultsPlugin()];
const kysely = new Kysely({
dialect: new CustomD1Dialect({ database: config.binding }),
plugins
});
super(kysely, {}, plugins);
}
override supportsBatching(): boolean {
return true;
}
override supportsIndices(): boolean {
return true;
}
protected override async batch<Queries extends QB[]>(
queries: [...Queries]
): Promise<{
[K in keyof Queries]: Awaited<ReturnType<Queries[K]["execute"]>>;
}> {
const db = this.config.binding;
const res = await db.batch(
queries.map((q) => {
const { sql, parameters } = q.compile();
return db.prepare(sql).bind(...parameters);
})
);
// let it run through plugins
const kyselyPlugins = new KyselyPluginRunner(this.plugins);
const data: any = [];
for (const r of res) {
const rows = await kyselyPlugins.transformResultRows(r.results);
data.push(rows);
}
return data;
}
}

View File

@@ -1,4 +1,11 @@
import { D1Connection, type D1ConnectionConfig } from "./connection/D1Connection";
export * from "./cloudflare-workers.adapter"; export * from "./cloudflare-workers.adapter";
export { makeApp, getFresh, getWarm } from "./modes/fresh"; export { makeApp, getFresh, getWarm } from "./modes/fresh";
export { getCached } from "./modes/cached"; export { getCached } from "./modes/cached";
export { DurableBkndApp, getDurable } from "./modes/durable"; export { DurableBkndApp, getDurable } from "./modes/durable";
export { D1Connection, type D1ConnectionConfig };
export function d1(config: D1ConnectionConfig) {
return new D1Connection(config);
}

View File

@@ -74,7 +74,6 @@ export class LibsqlConnection extends SqliteConnection {
}> { }> {
const stms: InStatement[] = queries.map((q) => { const stms: InStatement[] = queries.map((q) => {
const compiled = q.compile(); const compiled = q.compile();
//console.log("compiled", compiled.sql, compiled.parameters);
return { return {
sql: compiled.sql, sql: compiled.sql,
args: compiled.parameters as any[] args: compiled.parameters as any[]

View File

@@ -18,6 +18,8 @@ export { Connection } from "./connection/Connection";
export { LibsqlConnection, type LibSqlCredentials } from "./connection/LibsqlConnection"; export { LibsqlConnection, type LibSqlCredentials } from "./connection/LibsqlConnection";
export { SqliteConnection } from "./connection/SqliteConnection"; export { SqliteConnection } from "./connection/SqliteConnection";
export { SqliteLocalConnection } from "./connection/SqliteLocalConnection"; export { SqliteLocalConnection } from "./connection/SqliteLocalConnection";
export { SqliteIntrospector } from "./connection/SqliteIntrospector";
export { KyselyPluginRunner } from "./plugins/KyselyPluginRunner";
export { constructEntity, constructRelation } from "./schema/constructor"; export { constructEntity, constructRelation } from "./schema/constructor";

View File

@@ -22,11 +22,7 @@ declare module "@mantine/modals" {
} }
export function BkndModalsProvider({ children }) { export function BkndModalsProvider({ children }) {
return ( return <ModalsProvider modals={modals}>{children}</ModalsProvider>;
<ModalsProvider modals={modals} modalProps={{ className: "bknd-admin" }}>
{children}
</ModalsProvider>
);
} }
function open<Modal extends keyof typeof modals>( function open<Modal extends keyof typeof modals>(

BIN
bun.lockb

Binary file not shown.

View File

@@ -11,7 +11,8 @@
"cf-typegen": "wrangler types" "cf-typegen": "wrangler types"
}, },
"dependencies": { "dependencies": {
"bknd": "workspace:*" "bknd": "workspace:*",
"kysely-d1": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20241106.0", "@cloudflare/workers-types": "^4.20241106.0",

View File

@@ -1,15 +1,5 @@
/// <reference types="@cloudflare/workers-types" />
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
export default serve({ export default serve();
app: (args) => ({
connection: {
type: "libsql",
config: {
url: "http://localhost:8080"
}
}
}),
onBuilt: async (app) => {
app.modules.server.get("/custom", (c) => c.json({ hello: "world" }));
}
});

View File

@@ -10,5 +10,7 @@ assets = { directory = "../../app/dist/static" }
[observability] [observability]
enabled = true enabled = true
#[site] [[d1_databases]]
#bucket = "../../app/dist/static" binding = "DB"
database_name = "bknd-cf-example"
database_id = "7ad67953-2bbf-47fc-8696-f4517dbfe674"