mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
adding d1 for cloudflare environments
This commit is contained in:
40
app/build.ts
40
app/build.ts
@@ -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();
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
65
app/src/adapter/cloudflare/connection/D1Connection.ts
Normal file
65
app/src/adapter/cloudflare/connection/D1Connection.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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" }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user