diff --git a/app/build.ts b/app/build.ts index ebbf33a..bbd4631 100644 --- a/app/build.ts +++ b/app/build.ts @@ -49,111 +49,117 @@ if (types && !watch) { /** * Building backend and general API */ -await tsup.build({ - minify, - sourcemap, - watch, - entry: ["src/index.ts", "src/data/index.ts", "src/core/index.ts", "src/core/utils/index.ts"], - outDir: "dist", - external: ["bun:test", "@libsql/client"], - metafile: true, - platform: "browser", - format: ["esm"], - splitting: false, - treeshake: true, - loader: { - ".svg": "dataurl" - }, - onSuccess: async () => { - delayTypes(); - } -}); +async function buildApi() { + await tsup.build({ + minify, + sourcemap, + watch, + entry: ["src/index.ts", "src/data/index.ts", "src/core/index.ts", "src/core/utils/index.ts"], + outDir: "dist", + external: ["bun:test", "@libsql/client"], + metafile: true, + platform: "browser", + format: ["esm"], + splitting: false, + treeshake: true, + loader: { + ".svg": "dataurl" + }, + onSuccess: async () => { + delayTypes(); + } + }); +} /** * Building UI for direct imports */ -await tsup.build({ - minify, - sourcemap, - watch, - entry: ["src/ui/index.ts", "src/ui/client/index.ts", "src/ui/main.css", "src/ui/styles.css"], - outDir: "dist/ui", - external: [ - "bun:test", - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - "use-sync-external-store", - /codemirror/, - "@xyflow/react", - "@mantine/core" - ], - metafile: true, - platform: "browser", - format: ["esm"], - splitting: false, - bundle: true, - treeshake: true, - loader: { - ".svg": "dataurl" - }, - esbuildOptions: (options) => { - options.logLevel = "silent"; - }, - onSuccess: async () => { - delayTypes(); - } -}); +async function buildUi() { + await tsup.build({ + minify, + sourcemap, + watch, + entry: ["src/ui/index.ts", "src/ui/client/index.ts", "src/ui/main.css", "src/ui/styles.css"], + outDir: "dist/ui", + external: [ + "bun:test", + "react", + "react-dom", + "react/jsx-runtime", + "react/jsx-dev-runtime", + "use-sync-external-store", + /codemirror/, + "@xyflow/react", + "@mantine/core" + ], + metafile: true, + platform: "browser", + format: ["esm"], + splitting: false, + bundle: true, + treeshake: true, + loader: { + ".svg": "dataurl" + }, + esbuildOptions: (options) => { + options.logLevel = "silent"; + }, + onSuccess: async () => { + delayTypes(); + } + }); +} /** * Building UI Elements * - tailwind-merge is mocked, no exclude * - ui/client is external, and after built replaced with "bknd/client" */ -await tsup.build({ - minify, - sourcemap, - watch, - entry: ["src/ui/elements/index.ts"], - outDir: "dist/ui/elements", - external: [ - "ui/client", - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - "use-sync-external-store" - ], - metafile: true, - platform: "browser", - format: ["esm"], - splitting: false, - bundle: true, - treeshake: true, - loader: { - ".svg": "dataurl" - }, - esbuildOptions: (options) => { - options.alias = { - // not important for elements, mock to reduce bundle - "tailwind-merge": "./src/ui/elements/mocks/tailwind-merge.ts" - }; - }, - onSuccess: async () => { - // manually replace ui/client with bknd/client - const path = "./dist/ui/elements/index.js"; - const bundle = await Bun.file(path).text(); - await Bun.write(path, bundle.replaceAll("ui/client", "bknd/client")); +async function buildUiElements() { + await tsup.build({ + minify, + sourcemap, + watch, + entry: ["src/ui/elements/index.ts"], + outDir: "dist/ui/elements", + external: [ + "ui/client", + "react", + "react-dom", + "react/jsx-runtime", + "react/jsx-dev-runtime", + "use-sync-external-store" + ], + metafile: true, + platform: "browser", + format: ["esm"], + splitting: false, + bundle: true, + treeshake: true, + loader: { + ".svg": "dataurl" + }, + esbuildOptions: (options) => { + options.alias = { + // not important for elements, mock to reduce bundle + "tailwind-merge": "./src/ui/elements/mocks/tailwind-merge.ts" + }; + }, + onSuccess: async () => { + // manually replace ui/client with bknd/client + const path = "./dist/ui/elements/index.js"; + const bundle = await Bun.file(path).text(); + await Bun.write(path, bundle.replaceAll("ui/client", "bknd/client")); - delayTypes(); - } -}); + delayTypes(); + } + }); +} /** * Building adapters */ -function baseConfig(adapter: string): tsup.Options { +function baseConfig(adapter: string, overrides: Partial = {}): tsup.Options { return { minify, sourcemap, @@ -162,47 +168,61 @@ function baseConfig(adapter: string): tsup.Options { format: ["esm"], platform: "neutral", outDir: `dist/adapter/${adapter}`, + metafile: true, + splitting: false, + onSuccess: async () => { + delayTypes(); + }, + ...overrides, define: { - __isDev: "0" + __isDev: "0", + ...overrides.define }, external: [ /^cloudflare*/, /^@?(hono|libsql).*?/, /^(bknd|react|next|node).*?/, - /.*\.(html)$/ - ], - metafile: true, - splitting: false, - onSuccess: async () => { - delayTypes(); - } + /.*\.(html)$/, + ...(Array.isArray(overrides.external) ? overrides.external : []) + ] }; } -// base adapter handles -await tsup.build({ - ...baseConfig(""), - entry: ["src/adapter/index.ts"], - outDir: "dist/adapter" -}); +async function buildAdapters() { + // base adapter handles + await tsup.build({ + ...baseConfig(""), + entry: ["src/adapter/index.ts"], + outDir: "dist/adapter" + }); -// specific adatpers -await tsup.build(baseConfig("remix")); -await tsup.build(baseConfig("bun")); -await tsup.build(baseConfig("astro")); -await tsup.build(baseConfig("cloudflare")); + // specific adatpers + await tsup.build(baseConfig("remix")); + await tsup.build(baseConfig("bun")); + await tsup.build(baseConfig("astro")); + await tsup.build( + baseConfig("cloudflare", { + external: [/^kysely/] + }) + ); -await tsup.build({ - ...baseConfig("vite"), - platform: "node" -}); + await tsup.build({ + ...baseConfig("vite"), + platform: "node" + }); -await tsup.build({ - ...baseConfig("nextjs"), - platform: "node" -}); + await tsup.build({ + ...baseConfig("nextjs"), + platform: "node" + }); -await tsup.build({ - ...baseConfig("node"), - platform: "node" -}); + await tsup.build({ + ...baseConfig("node"), + platform: "node" + }); +} + +await buildApi(); +await buildUi(); +await buildUiElements(); +await buildAdapters(); diff --git a/app/package.json b/app/package.json index 71ca15f..c4f4f56 100644 --- a/app/package.json +++ b/app/package.json @@ -76,6 +76,7 @@ "clsx": "^2.1.1", "esbuild-postcss": "^0.0.4", "jotai": "^2.10.1", + "kysely-d1": "^0.3.0", "open": "^10.1.0", "openapi-types": "^12.1.3", "postcss": "^8.4.47", diff --git a/app/src/adapter/cloudflare/D1Connection.ts b/app/src/adapter/cloudflare/D1Connection.ts new file mode 100644 index 0000000..810cca8 --- /dev/null +++ b/app/src/adapter/cloudflare/D1Connection.ts @@ -0,0 +1,63 @@ +/// + +import { KyselyPluginRunner, SqliteConnection, SqliteIntrospector } from "bknd/data"; +import type { QB } from "data/connection/Connection"; +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): DatabaseIntrospector { + return new SqliteIntrospector(db, { + excludeTables: ["_cf_KV"] + }); + } +} + +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: [...Queries] + ): Promise<{ + [K in keyof Queries]: Awaited>; + }> { + 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; + } +} diff --git a/app/src/media/storage/adapters/StorageR2Adapter.ts b/app/src/adapter/cloudflare/StorageR2Adapter.ts similarity index 72% rename from app/src/media/storage/adapters/StorageR2Adapter.ts rename to app/src/adapter/cloudflare/StorageR2Adapter.ts index 911fe37..aedeb10 100644 --- a/app/src/media/storage/adapters/StorageR2Adapter.ts +++ b/app/src/adapter/cloudflare/StorageR2Adapter.ts @@ -1,6 +1,47 @@ -import { isDebug } from "core"; -import type { FileBody, StorageAdapter } from "../Storage"; -import { guessMimeType } from "../mime-types"; +import { registries } from "bknd"; +import { isDebug } from "bknd/core"; +import { StringEnum, Type } from "bknd/utils"; +import type { FileBody, StorageAdapter } from "media/storage/Storage"; +import { guess } from "media/storage/mime-types-tiny"; +import { getBindings } from "./bindings"; + +export function makeSchema(bindings: string[] = []) { + return Type.Object( + { + binding: bindings.length > 0 ? StringEnum(bindings) : Type.Optional(Type.String()) + }, + { title: "R2", description: "Cloudflare R2 storage" } + ); +} + +export function registerMedia(env: Record) { + const r2_bindings = getBindings(env, "R2Bucket"); + + registries.media.register( + "r2", + class extends StorageR2Adapter { + constructor(private config: any) { + const binding = r2_bindings.find((b) => b.key === config.binding); + if (!binding) { + throw new Error(`No R2Bucket found with key ${config.binding}`); + } + + super(binding?.value); + } + + override getSchema() { + return makeSchema(r2_bindings.map((b) => b.key)); + } + + override toJSON() { + return { + ...super.toJSON(), + config: this.config + }; + } + } + ); +} /** * Adapter for R2 storage @@ -14,7 +55,7 @@ export class StorageR2Adapter implements StorageAdapter { } getSchema() { - return undefined; + return makeSchema(); } async putObject(key: string, body: FileBody) { @@ -47,7 +88,8 @@ export class StorageR2Adapter implements StorageAdapter { async getObject(key: string, headers: Headers): Promise { let object: R2ObjectBody | null; const responseHeaders = new Headers({ - "Accept-Ranges": "bytes" + "Accept-Ranges": "bytes", + "Content-Type": guess(key) }); //console.log("getObject:headers", headersToObject(headers)); @@ -97,10 +139,9 @@ export class StorageR2Adapter implements StorageAdapter { if (!metadata || Object.keys(metadata).length === 0) { // guessing is especially required for dev environment (miniflare) metadata = { - contentType: guessMimeType(object.key) + contentType: guess(object.key) }; } - //console.log("writeHttpMetadata", object.httpMetadata, metadata); for (const [key, value] of Object.entries(metadata)) { const camelToDash = key.replace(/([A-Z])/g, "-$1").toLowerCase(); @@ -115,7 +156,7 @@ export class StorageR2Adapter implements StorageAdapter { } return { - type: String(head.httpMetadata?.contentType ?? "application/octet-stream"), + type: String(head.httpMetadata?.contentType ?? guess(key)), size: head.size }; } diff --git a/app/src/adapter/cloudflare/bindings.ts b/app/src/adapter/cloudflare/bindings.ts new file mode 100644 index 0000000..491d0a8 --- /dev/null +++ b/app/src/adapter/cloudflare/bindings.ts @@ -0,0 +1,32 @@ +export type BindingTypeMap = { + D1Database: D1Database; + KVNamespace: KVNamespace; + DurableObjectNamespace: DurableObjectNamespace; + R2Bucket: R2Bucket; +}; + +export type GetBindingType = keyof BindingTypeMap; +export type BindingMap = { key: string; value: BindingTypeMap[T] }; + +export function getBindings(env: any, type: T): BindingMap[] { + const bindings: BindingMap[] = []; + for (const key in env) { + try { + if (env[key] && (env[key] as any).constructor.name === type) { + bindings.push({ + key, + value: env[key] as BindingTypeMap[T] + }); + } + } catch (e) {} + } + return bindings; +} + +export function getBinding(env: any, type: T): BindingMap { + const bindings = getBindings(env, type); + if (bindings.length === 0) { + throw new Error(`No ${type} found in bindings`); + } + return bindings[0] as BindingMap; +} diff --git a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts index 3a4044b..4486609 100644 --- a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts +++ b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts @@ -1,6 +1,11 @@ -import type { FrameworkBkndConfig } from "bknd/adapter"; +/// + +import { type FrameworkBkndConfig, makeConfig } from "bknd/adapter"; import { Hono } from "hono"; import { serveStatic } from "hono/cloudflare-workers"; +import { D1Connection } from "./D1Connection"; +import { registerMedia } from "./StorageR2Adapter"; +import { getBinding } from "./bindings"; import { getCached } from "./modes/cached"; import { getDurable } from "./modes/durable"; import { getFresh, getWarm } from "./modes/fresh"; @@ -10,6 +15,7 @@ export type CloudflareBkndConfig = FrameworkBkndConfig> bindings?: (args: Context) => { kv?: KVNamespace; dobj?: DurableObjectNamespace; + db?: D1Database; }; static?: "kv" | "assets"; key?: string; @@ -26,7 +32,39 @@ export type Context = { ctx: ExecutionContext; }; -export function serve(config: CloudflareBkndConfig) { +let media_registered: boolean = false; +export function makeCfConfig(config: CloudflareBkndConfig, context: Context) { + if (!media_registered) { + registerMedia(context.env as any); + media_registered = true; + } + + const appConfig = makeConfig(config, context); + const bindings = config.bindings?.(context); + if (!appConfig.connection) { + let db: D1Database | undefined; + if (bindings?.db) { + console.log("Using database from bindings"); + db = bindings.db; + } else if (Object.keys(context.env ?? {}).length > 0) { + const binding = getBinding(context.env, "D1Database"); + if (binding) { + console.log(`Using database from env "${binding.key}"`); + db = binding.value; + } + } + + if (db) { + appConfig.connection = new D1Connection({ binding: db }); + } else { + throw new Error("No database connection given"); + } + } + + return appConfig; +} + +export function serve(config: CloudflareBkndConfig = {}) { return { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const url = new URL(request.url); @@ -61,8 +99,6 @@ export function serve(config: CloudflareBkndConfig) { } } - config.setAdminHtml = config.setAdminHtml && !!config.manifest; - const context = { request, env, ctx } as Context; const mode = config.mode ?? "warm"; diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index c2dd1c5..a10dc53 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -1,4 +1,18 @@ +import { D1Connection, type D1ConnectionConfig } from "./D1Connection"; + export * from "./cloudflare-workers.adapter"; export { makeApp, getFresh, getWarm } from "./modes/fresh"; export { getCached } from "./modes/cached"; export { DurableBkndApp, getDurable } from "./modes/durable"; +export { D1Connection, type D1ConnectionConfig }; +export { + getBinding, + getBindings, + type BindingTypeMap, + type GetBindingType, + type BindingMap +} from "./bindings"; + +export function d1(config: D1ConnectionConfig) { + return new D1Connection(config); +} diff --git a/app/src/adapter/cloudflare/modes/cached.ts b/app/src/adapter/cloudflare/modes/cached.ts index a367e5d..48f6926 100644 --- a/app/src/adapter/cloudflare/modes/cached.ts +++ b/app/src/adapter/cloudflare/modes/cached.ts @@ -1,6 +1,6 @@ import { App } from "bknd"; import { createRuntimeApp } from "bknd/adapter"; -import type { CloudflareBkndConfig, Context } from "../index"; +import { type CloudflareBkndConfig, type Context, makeCfConfig } from "../index"; export async function getCached(config: CloudflareBkndConfig, { env, ctx, ...args }: Context) { const { kv } = config.bindings?.(env)!; @@ -16,7 +16,7 @@ export async function getCached(config: CloudflareBkndConfig, { env, ctx, ...arg const app = await createRuntimeApp( { - ...config, + ...makeCfConfig(config, { env, ctx, ...args }), initialConfig, onBuilt: async (app) => { app.module.server.client.get("/__bknd/cache", async (c) => { diff --git a/app/src/adapter/cloudflare/modes/fresh.ts b/app/src/adapter/cloudflare/modes/fresh.ts index ef40987..7a2af67 100644 --- a/app/src/adapter/cloudflare/modes/fresh.ts +++ b/app/src/adapter/cloudflare/modes/fresh.ts @@ -1,11 +1,11 @@ import type { App } from "bknd"; import { createRuntimeApp } from "bknd/adapter"; -import type { CloudflareBkndConfig, Context } from "../index"; +import { type CloudflareBkndConfig, type Context, makeCfConfig } from "../index"; export async function makeApp(config: CloudflareBkndConfig, ctx: Context) { return await createRuntimeApp( { - ...config, + ...makeCfConfig(config, ctx), adminOptions: config.html ? { html: config.html } : undefined }, ctx diff --git a/app/src/data/connection/LibsqlConnection.ts b/app/src/data/connection/LibsqlConnection.ts index e60fa32..ab7db32 100644 --- a/app/src/data/connection/LibsqlConnection.ts +++ b/app/src/data/connection/LibsqlConnection.ts @@ -74,7 +74,6 @@ export class LibsqlConnection extends SqliteConnection { }> { const stms: InStatement[] = queries.map((q) => { const compiled = q.compile(); - //console.log("compiled", compiled.sql, compiled.parameters); return { sql: compiled.sql, args: compiled.parameters as any[] diff --git a/app/src/data/connection/SqliteIntrospector.ts b/app/src/data/connection/SqliteIntrospector.ts index cf68816..f86887e 100644 --- a/app/src/data/connection/SqliteIntrospector.ts +++ b/app/src/data/connection/SqliteIntrospector.ts @@ -5,7 +5,7 @@ import type { ExpressionBuilder, Kysely, SchemaMetadata, - TableMetadata, + TableMetadata } from "kysely"; import { DEFAULT_MIGRATION_LOCK_TABLE, DEFAULT_MIGRATION_TABLE, sql } from "kysely"; import type { ConnectionIntrospector, IndexMetadata } from "./Connection"; @@ -62,7 +62,7 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro seqno: number; cid: number; name: string; - }>`pragma_index_info(${index})`.as("index_info"), + }>`pragma_index_info(${index})`.as("index_info") ) .select(["seqno", "cid", "name"]) .orderBy("cid") @@ -74,8 +74,8 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro isUnique: isUnique, columns: columns.map((col) => ({ name: col.name, - order: col.seqno, - })), + order: col.seqno + })) }; } @@ -87,7 +87,7 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro } async getTables( - options: DatabaseMetadataOptions = { withInternalKyselyTables: false }, + options: DatabaseMetadataOptions = { withInternalKyselyTables: false } ): Promise { let query = this.#db .selectFrom("sqlite_master") @@ -99,7 +99,7 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro if (!options.withInternalKyselyTables) { query = query.where( - this.excludeTables([DEFAULT_MIGRATION_TABLE, DEFAULT_MIGRATION_LOCK_TABLE]), + this.excludeTables([DEFAULT_MIGRATION_TABLE, DEFAULT_MIGRATION_LOCK_TABLE]) ); } if (this._excludeTables.length > 0) { @@ -107,17 +107,19 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro } const tables = await query.execute(); + console.log("tables", tables); return Promise.all(tables.map(({ name }) => this.#getTableMetadata(name))); } async getMetadata(options?: DatabaseMetadataOptions): Promise { return { - tables: await this.getTables(options), + tables: await this.getTables(options) }; } async #getTableMetadata(table: string): Promise { const db = this.#db; + console.log("get table metadata", table); // Get the SQL that was used to create the table. const tableDefinition = await db @@ -142,7 +144,7 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro type: string; notnull: 0 | 1; dflt_value: any; - }>`pragma_table_info(${table})`.as("table_info"), + }>`pragma_table_info(${table})`.as("table_info") ) .select(["name", "type", "notnull", "dflt_value"]) .orderBy("cid") @@ -157,8 +159,8 @@ export class SqliteIntrospector implements DatabaseIntrospector, ConnectionIntro isNullable: !col.notnull, isAutoIncrementing: col.name === autoIncrementCol, hasDefaultValue: col.dflt_value != null, - comment: undefined, - })), + comment: undefined + })) }; } } diff --git a/app/src/data/index.ts b/app/src/data/index.ts index a5de079..e63707e 100644 --- a/app/src/data/index.ts +++ b/app/src/data/index.ts @@ -18,6 +18,8 @@ export { Connection } from "./connection/Connection"; export { LibsqlConnection, type LibSqlCredentials } from "./connection/LibsqlConnection"; export { SqliteConnection } from "./connection/SqliteConnection"; export { SqliteLocalConnection } from "./connection/SqliteLocalConnection"; +export { SqliteIntrospector } from "./connection/SqliteIntrospector"; +export { KyselyPluginRunner } from "./plugins/KyselyPluginRunner"; export { constructEntity, constructRelation } from "./schema/constructor"; diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index 564b008..dda2cf6 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -40,7 +40,8 @@ export class AppMedia extends Module { let adapter: StorageAdapter; try { const { type, config } = this.config.adapter; - adapter = new (registry.get(type as any).cls)(config as any); + const cls = registry.get(type as any).cls; + adapter = new cls(config as any); this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr); this.setBuilt(); @@ -53,8 +54,6 @@ export class AppMedia extends Module { index(media).on(["path"], true).on(["reference"]); }) ); - - this.setBuilt(); } catch (e) { console.error(e); throw new Error( diff --git a/app/src/media/media-schema.ts b/app/src/media/media-schema.ts index f196c79..229b108 100644 --- a/app/src/media/media-schema.ts +++ b/app/src/media/media-schema.ts @@ -16,8 +16,8 @@ export function buildMediaSchema() { config: adapter.schema }, { - title: adapter.schema.title ?? name, - description: adapter.schema.description, + title: adapter.schema?.title ?? name, + description: adapter.schema?.description, additionalProperties: false } ); diff --git a/app/src/ui/modals/index.tsx b/app/src/ui/modals/index.tsx index 9869158..9b15848 100644 --- a/app/src/ui/modals/index.tsx +++ b/app/src/ui/modals/index.tsx @@ -22,11 +22,7 @@ declare module "@mantine/modals" { } export function BkndModalsProvider({ children }) { - return ( - - {children} - - ); + return {children}; } function open( diff --git a/app/src/ui/routes/media/media.settings.tsx b/app/src/ui/routes/media/media.settings.tsx index 68b9b04..f6eb4e8 100644 --- a/app/src/ui/routes/media/media.settings.tsx +++ b/app/src/ui/routes/media/media.settings.tsx @@ -1,4 +1,4 @@ -import { IconBrandAws, IconCloud, IconServer } from "@tabler/icons-react"; +import { IconBrandAws, IconBrandCloudflare, IconCloud, IconServer } from "@tabler/icons-react"; import { isDebug } from "core"; import { autoFormatString } from "core/utils"; import { twMerge } from "tailwind-merge"; @@ -113,10 +113,15 @@ const RootFormError = () => { ); }; -const Icons = [IconBrandAws, IconCloud, IconServer]; +const Icons = { + s3: IconBrandAws, + cloudinary: IconCloud, + local: IconServer, + r2: IconBrandCloudflare +}; -const AdapterIcon = ({ index }: { index: number }) => { - const Icon = Icons[index]; +const AdapterIcon = ({ type }: { type: string }) => { + const Icon = Icons[type]; if (!Icon) return null; return ; }; @@ -142,7 +147,7 @@ function Adapters() { )} >
- +
{autoFormatString(schema.title)} diff --git a/bun.lockb b/bun.lockb index 87d85d6..ff10b8a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/examples/cloudflare-worker/package.json b/examples/cloudflare-worker/package.json index 689ee95..55526c4 100644 --- a/examples/cloudflare-worker/package.json +++ b/examples/cloudflare-worker/package.json @@ -11,7 +11,8 @@ "cf-typegen": "wrangler types" }, "dependencies": { - "bknd": "workspace:*" + "bknd": "workspace:*", + "kysely-d1": "^0.3.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241106.0", diff --git a/examples/cloudflare-worker/src/index.ts b/examples/cloudflare-worker/src/index.ts index 628c490..d349c50 100644 --- a/examples/cloudflare-worker/src/index.ts +++ b/examples/cloudflare-worker/src/index.ts @@ -1,14 +1,9 @@ +/// + import { serve } from "bknd/adapter/cloudflare"; export default serve({ - app: (args) => ({ - connection: { - type: "libsql", - config: { - url: "http://localhost:8080" - } - } - }), + mode: "fresh", onBuilt: async (app) => { app.modules.server.get("/custom", (c) => c.json({ hello: "world" })); } diff --git a/examples/cloudflare-worker/wrangler.toml b/examples/cloudflare-worker/wrangler.toml index 27157d0..1399d79 100644 --- a/examples/cloudflare-worker/wrangler.toml +++ b/examples/cloudflare-worker/wrangler.toml @@ -1,7 +1,7 @@ #:schema node_modules/wrangler/config-schema.json name = "bknd-cf-worker-example" main = "src/index.ts" -compatibility_date = "2024-11-06" +compatibility_date = "2025-02-04" compatibility_flags = ["nodejs_compat"] workers_dev = true minify = true @@ -10,5 +10,11 @@ assets = { directory = "../../app/dist/static" } [observability] enabled = true -#[site] -#bucket = "../../app/dist/static" \ No newline at end of file +[[d1_databases]] +binding = "DB" +database_name = "bknd-cf-example" +database_id = "7ad67953-2bbf-47fc-8696-f4517dbfe674" + +[[r2_buckets]] +binding = "BUCKET" +bucket_name = "bknd-cf-example" \ No newline at end of file