diff --git a/app/src/media/api/MediaApi.ts b/app/src/media/api/MediaApi.ts index db94b03..d925d4d 100644 --- a/app/src/media/api/MediaApi.ts +++ b/app/src/media/api/MediaApi.ts @@ -1,11 +1,14 @@ import type { FileListObject } from "media/storage/Storage"; import { type BaseModuleApiOptions, + type FetchPromise, + type ResponseObject, ModuleApi, type PrimaryFieldType, type TInput, } from "modules/ModuleApi"; import type { ApiFetcher } from "Api"; +import type { DB, FileUploadedEventData } from "bknd"; export type MediaApiOptions = BaseModuleApiOptions & { upload_fetcher: ApiFetcher; @@ -67,14 +70,14 @@ export class MediaApi extends ModuleApi { return new Headers(); } - protected uploadFile( + protected uploadFile( body: File | Blob | ReadableStream | Buffer, opts?: { filename?: string; path?: TInput; _init?: Omit; }, - ) { + ): FetchPromise> { const headers = { "Content-Type": "application/octet-stream", ...(opts?._init?.headers || {}), @@ -106,10 +109,10 @@ export class MediaApi extends ModuleApi { throw new Error("Invalid filename"); } - return this.post(opts?.path ?? ["upload", name], body, init); + return this.post(opts?.path ?? ["upload", name], body, init); } - async upload( + async upload( item: Request | Response | string | File | Blob | ReadableStream | Buffer, opts: { filename?: string; @@ -124,12 +127,12 @@ export class MediaApi extends ModuleApi { if (!res.ok || !res.body) { throw new Error("Failed to fetch file"); } - return this.uploadFile(res.body, opts); + return this.uploadFile(res.body, opts); } else if (item instanceof Response) { if (!item.body) { throw new Error("Invalid response"); } - return this.uploadFile(item.body, { + return this.uploadFile(item.body, { ...(opts ?? {}), _init: { ...(opts._init ?? {}), @@ -141,7 +144,7 @@ export class MediaApi extends ModuleApi { }); } - return this.uploadFile(item, opts); + return this.uploadFile(item, opts); } async uploadToEntity( @@ -153,7 +156,7 @@ export class MediaApi extends ModuleApi { _init?: Omit; fetcher?: typeof fetch; }, - ) { + ): Promise> { return this.upload(item, { ...opts, path: ["entity", entity, id, field], diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index b77b57d..f53798c 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -1,8 +1,14 @@ -import type { DB, PrimaryFieldType, EntityData, RepoQueryIn } from "bknd"; +import type { + DB, + PrimaryFieldType, + EntityData, + RepoQueryIn, + RepositoryResult, + ResponseObject, + ModuleApi, +} from "bknd"; import { objectTransform, encodeSearch } from "bknd/utils"; -import type { RepositoryResult } from "data/entities"; import type { Insertable, Selectable, Updateable } from "kysely"; -import type { FetchPromise, ModuleApi, ResponseObject } from "modules/ModuleApi"; import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr"; import { type Api, useApi } from "ui/client"; @@ -108,7 +114,7 @@ export function makeKey( ); } -interface UseEntityQueryReturn< +export interface UseEntityQueryReturn< Entity extends keyof DB | string, Id extends PrimaryFieldType | undefined = undefined, Data = Entity extends keyof DB ? Selectable : EntityData, @@ -136,11 +142,11 @@ export const useEntityQuery = < const fetcher = () => read(query ?? {}); type T = Awaited>; - const swr = useSWR(options?.enabled === false ? null : key, fetcher as any, { + const swr = useSWR(options?.enabled === false ? null : key, fetcher as any, { revalidateOnFocus: false, keepPreviousData: true, ...options, - }); + }) as ReturnType>; const mutateFn = async (id?: PrimaryFieldType) => { const entityKey = makeKey(api, entity as string, id); diff --git a/app/vite.dev.ts b/app/vite.dev.ts index 6f74ff1..255fe26 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -1,4 +1,4 @@ -import { readFile } from "node:fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; import { serveStatic } from "@hono/node-server/serve-static"; import { showRoutes } from "hono/dev"; import { App, registries, type CreateAppConfig } from "./src"; @@ -9,6 +9,7 @@ import { $console } from "core/utils/console"; import { createClient } from "@libsql/client"; import util from "node:util"; import { d1Sqlite } from "adapter/cloudflare/connection/D1Connection"; +import { slugify } from "./src/core/utils/strings"; util.inspect.defaultOptions.depth = 5; registries.media.register("local", StorageLocalAdapter); @@ -21,16 +22,19 @@ $console.debug("Using db type", dbType); let dbUrl = import.meta.env.VITE_DB_URL ?? ":memory:"; const example = import.meta.env.VITE_EXAMPLE; -if (example) { - const configPath = `.configs/${example}.json`; - $console.debug("Loading config from", configPath); - const exampleConfig = JSON.parse(await readFile(configPath, "utf-8")); - config.config = exampleConfig; - dbUrl = `file:.configs/${example}.db`; +async function loadExampleConfig() { + if (example) { + const configPath = `.configs/${example}.json`; + $console.debug("Loading config from", configPath); + const exampleConfig = JSON.parse(await readFile(configPath, "utf-8")); + config.config = exampleConfig; + dbUrl = `file:.configs/${example}.db`; + } } switch (dbType) { case "libsql": { + await loadExampleConfig(); $console.debug("Using libsql connection", dbUrl); const authToken = import.meta.env.VITE_DB_LIBSQL_TOKEN; config.connection = libsql( @@ -43,15 +47,48 @@ switch (dbType) { } case "d1": { $console.debug("Using d1 connection"); + const wranglerConfig = { + name: "vite-dev", + main: "src/index.ts", + compatibility_date: "2025-08-03", + compatibility_flags: ["nodejs_compat"], + d1_databases: [ + { + binding: "DB", + database_name: "vite-dev", + database_id: "00000000-0000-0000-0000-000000000000", + }, + ], + r2_buckets: [ + { + binding: "BUCKET", + bucket_name: "vite-dev", + }, + ], + }; + let configPath = ".configs/vite.wrangler.json"; + if (example) { + const name = slugify(example); + configPath = `.configs/${slugify(example)}.wrangler.json`; + const exists = await readFile(configPath, "utf-8"); + if (!exists) { + wranglerConfig.name = name; + wranglerConfig.d1_databases[0]!.database_name = name; + wranglerConfig.d1_databases[0]!.database_id = crypto.randomUUID(); + wranglerConfig.r2_buckets[0]!.bucket_name = name; + await writeFile(configPath, JSON.stringify(wranglerConfig, null, 2)); + } + } const { getPlatformProxy } = await import("wrangler"); const platformProxy = await getPlatformProxy({ - configPath: "./vite.wrangler.json", + configPath, }); config.connection = d1Sqlite({ binding: platformProxy.env.DB as any }); break; } default: { + await loadExampleConfig(); $console.debug("Using node-sqlite connection", dbUrl); config.connection = nodeSqlite({ url: dbUrl }); break;