diff --git a/bun.lock b/bun.lock index 3218ebf..76abf42 100644 --- a/bun.lock +++ b/bun.lock @@ -143,7 +143,7 @@ "version": "0.0.1", "dependencies": { "kysely": "^0.27.6", - "pg": "^8.12.0", + "pg": "^8.14.0", }, "devDependencies": { "@types/bun": "^1.2.5", @@ -151,7 +151,7 @@ "@types/pg": "^8.11.11", "bknd": "workspace:*", "tsup": "^8.4.0", - "typescript": "^5.6.3", + "typescript": "^5.8.2", }, }, "packages/sqlocal": { @@ -167,7 +167,7 @@ "bknd": "workspace:*", "kysely": "^0.27.6", "tsup": "^8.4.0", - "typescript": "^5.6.3", + "typescript": "^5.8.2", "vitest": "^3.0.8", "webdriverio": "^9.12.0", }, diff --git a/examples/react/package.json b/examples/react/package.json index 4a0bfca..568b72d 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -10,19 +10,23 @@ "preview": "vite preview" }, "dependencies": { - "bknd": "file:../../app", "@bknd/sqlocal": "file:../../packages/sqlocal", + "bknd": "file:../../app", "react": "^19.0.0", "react-dom": "^19.0.0", - "sqlocal": "^0.14.0" + "sqlocal": "^0.14.0", + "wouter": "^3.6.0" }, "devDependencies": { + "@tailwindcss/vite": "^4.0.14", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", "globals": "^15.15.0", + "tailwindcss": "^4.0.14", "typescript": "~5.7.2", "typescript-eslint": "^8.24.1", - "vite": "^6.2.0" + "vite": "^6.2.0", + "vite-tsconfig-paths": "^5.1.4" } } diff --git a/examples/react/public/bknd.svg b/examples/react/public/bknd.svg new file mode 100644 index 0000000..182ef92 --- /dev/null +++ b/examples/react/public/bknd.svg @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx index 1c9d48a..e122556 100644 --- a/examples/react/src/App.tsx +++ b/examples/react/src/App.tsx @@ -1,9 +1,13 @@ -import { useEffect, useState } from "react"; +import { createContext, lazy, useEffect, useState, Suspense, Fragment } from "react"; import { App } from "bknd"; -import { Admin } from "bknd/ui"; -import { checksum } from "bknd/utils"; -import { em, entity, text } from "bknd/data"; +import { checksum, secureRandomString } from "bknd/utils"; +import { boolean, em, entity, text } from "bknd/data"; import { SQLocalConnection } from "@bknd/sqlocal"; +import { Route, Router, Switch } from "wouter"; +import IndexPage from "~/routes/_index"; +const Admin = lazy(() => import("~/routes/admin")); +import { Center } from "~/components/Center"; +import { ClientProvider } from "bknd/client"; import "bknd/dist/styles.css"; export default function () { @@ -11,28 +15,65 @@ export default function () { const [hash, setHash] = useState(""); async function onBuilt(app: App) { - setApp(app); - setHash(await checksum(app.toJSON())); + document.startViewTransition(async () => { + setApp(app); + setHash(await checksum(app.toJSON())); + }); } useEffect(() => { - setup({ - onBuilt, - }) + setup({ onBuilt }) .then((app) => console.log("setup", app?.version())) .catch(console.error); }, []); - if (!app) return null; + if (!app) + return ( + + Loading... + + ); return ( - // @ts-ignore - + + + ( + + + + )} + /> + + + + + + + + 404 + + + ); } +const schema = em({ + todos: entity("todos", { + title: text(), + done: boolean(), + }), +}); + +// register your schema to get automatic type completion +type Database = (typeof schema)["DB"]; +declare module "bknd/core" { + interface DB extends Database {} +} + let initialized = false; -export async function setup(opts?: { +async function setup(opts?: { beforeBuild?: (app: App) => Promise; onBuilt?: (app: App) => Promise; }) { @@ -40,17 +81,30 @@ export async function setup(opts?: { initialized = true; const connection = new SQLocalConnection({ + databasePath: ":localStorage:", verbose: true, }); const app = App.create({ connection, + // an initial config is only applied if the database is empty initialConfig: { - data: em({ - test: entity("test", { - name: text(), - }), - }).toJSON(), + data: schema.toJSON(), + }, + options: { + // the seed option is only executed if the database was empty + seed: async (ctx) => { + await ctx.em.mutator("todos").insertMany([ + { title: "Learn bknd", done: true }, + { title: "Build something cool", done: false }, + ]); + + // @todo: auth is currently not working due to POST request + /*await ctx.app.module.auth.createUser({ + email: "test@bknd.io", + password: "12345678", + });*/ + }, }, }); diff --git a/examples/react/src/components/Center.tsx b/examples/react/src/components/Center.tsx new file mode 100644 index 0000000..235d8c5 --- /dev/null +++ b/examples/react/src/components/Center.tsx @@ -0,0 +1,10 @@ +import type { ComponentProps } from "react"; + +export function Center(props: ComponentProps<"div">) { + return ( + + ); +} diff --git a/examples/react/src/main.tsx b/examples/react/src/main.tsx index d0da966..1215d5e 100644 --- a/examples/react/src/main.tsx +++ b/examples/react/src/main.tsx @@ -1,6 +1,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; +import App from "./App"; +import "./styles.css"; createRoot(document.getElementById("root")!).render( diff --git a/examples/react/src/routes/_index.tsx b/examples/react/src/routes/_index.tsx new file mode 100644 index 0000000..227ab5f --- /dev/null +++ b/examples/react/src/routes/_index.tsx @@ -0,0 +1,94 @@ +import { Center } from "~/components/Center"; +import type { App } from "bknd"; +import { useEntityQuery } from "bknd/client"; + +export default function IndexPage({ app }: { app: App }) { + const user = app.getApi().getUser(); + const limit = 5; + const { data: todos, ...$q } = useEntityQuery("todos", undefined, { + limit, + }); + // @ts-ignore + const total = todos?.body.meta.total || 0; + + return ( + + + + + local + + + + + What's next? ({total}) + + + {total > limit && ( + + {total - limit} more todo(s) hidden + + )} + + {todos?.reverse().map((todo) => ( + + + { + await $q.update({ done: !todo.done }, todo.id); + }} + /> + {todo.title} + + { + await $q._delete(todo.id); + }} + > + ❌ + + + ))} + + t.id).join()} + action={async (formData: FormData) => { + const title = formData.get("title") as string; + await $q.create({ title }); + }} + > + + + Add + + + + + + + Go to Admin ➝ + {/* + {user ? ( + + Authenticated as {user.email} + + ) : ( + Login + )} + */} + + + + ); +} diff --git a/examples/react/src/routes/admin.tsx b/examples/react/src/routes/admin.tsx new file mode 100644 index 0000000..0f390af --- /dev/null +++ b/examples/react/src/routes/admin.tsx @@ -0,0 +1,9 @@ +import { Admin, type BkndAdminProps } from "bknd/ui"; +import type { App } from "bknd"; + +export default function AdminPage({ + app, + ...props +}: Omit & { app: App }) { + return ; +} diff --git a/examples/react/src/styles.css b/examples/react/src/styles.css new file mode 100644 index 0000000..3f4f365 --- /dev/null +++ b/examples/react/src/styles.css @@ -0,0 +1,25 @@ +/* @todo: currently not working nicely */ +#app { + @import "tailwindcss"; + :root { + --background: #ffffff; + --foreground: #171717; + } + + @media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } + } + + @theme { + --color-background: var(--background); + --color-foreground: var(--foreground); + } + + width: 100%; + min-height: 100dvh; + @apply bg-background text-foreground flex; + font-family: Arial, Helvetica, sans-serif; +} diff --git a/examples/react/tsconfig.json b/examples/react/tsconfig.json index e9c7ab7..9a3582b 100644 --- a/examples/react/tsconfig.json +++ b/examples/react/tsconfig.json @@ -7,16 +7,17 @@ "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", - "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", - "strict": true, "noImplicitAny": false, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + "paths": { + "~/*": ["./src/*"] + } }, "include": ["src"] } diff --git a/examples/react/vite.config.ts b/examples/react/vite.config.ts index 542ddcd..b374507 100644 --- a/examples/react/vite.config.ts +++ b/examples/react/vite.config.ts @@ -1,5 +1,7 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; +import tsconfigPaths from "vite-tsconfig-paths"; // https://vite.dev/config/ // https://sqlocal.dallashoffman.com/guide/setup#vite-configuration @@ -9,6 +11,8 @@ export default defineConfig({ }, plugins: [ react(), + tailwindcss(), + tsconfigPaths(), { name: "configure-response-headers", configureServer: (server) => { diff --git a/packages/postgres/README.md b/packages/postgres/README.md index cb22856..e04d8a1 100644 --- a/packages/postgres/README.md +++ b/packages/postgres/README.md @@ -1,5 +1,5 @@ # Postgres adapter for `bknd` (experimental) -This packages adds an adapter to use a Postgres database with `bknd`. It is based on `pg` and the driver included in `kysely`. +This packages adds an adapter to use a Postgres database with [`bknd`](https://github.com/bknd-io/bknd). It is based on [`pg`](https://github.com/brianc/node-postgres) and the driver included in [`kysely`](https://github.com/kysely-org/kysely). ## Installation Install the adapter with: diff --git a/packages/postgres/package.json b/packages/postgres/package.json index 51d0e90..681fffe 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -5,14 +5,20 @@ "main": "dist/index.js", "module": "dist/index.js", "types": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsup", "test": "bun test", + "typecheck": "tsc --noEmit", + "updater": "bun x npm-check-updates -ui", + "prepublishOnly": "bun run typecheck && bun run test && bun run build", "docker:start": "docker run --rm --name bknd-test-postgres -d -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=bknd -p 5430:5432 postgres:17", "docker:stop": "docker stop bknd-test-postgres" }, "dependencies": { - "pg": "^8.12.0", + "pg": "^8.14.0", "kysely": "^0.27.6" }, "devDependencies": { @@ -21,7 +27,7 @@ "@types/pg": "^8.11.11", "bknd": "workspace:*", "tsup": "^8.4.0", - "typescript": "^5.6.3" + "typescript": "^5.8.2" }, "tsup": { "entry": ["src/index.ts"], @@ -30,7 +36,6 @@ "clean": true, "minify": true, "dts": true, - "metafile": true, "external": ["bknd", "pg", "kysely"] }, "files": ["dist", "README.md", "!*.map", "!metafile*.json"] diff --git a/packages/sqlocal/README.md b/packages/sqlocal/README.md index 5f4a6ff..4c09768 100644 --- a/packages/sqlocal/README.md +++ b/packages/sqlocal/README.md @@ -1,5 +1,5 @@ # SQLocal adapter for `bknd` (experimental) -This packages adds an adapter to use a SQLocal database with `bknd`. It is based on [`sqlocal`](https://github.com/DallasHoff/sqlocal) and the driver included for `kysely`. +This packages adds an adapter to use a SQLocal database with `bknd`](https://github.com/bknd-io/bknd). It is based on [`sqlocal`](https://github.com/DallasHoff/sqlocal) and the driver included for [`kysely`](https://github.com/kysely-org/kysely). ## Installation Install the adapter with: diff --git a/packages/sqlocal/package.json b/packages/sqlocal/package.json index c2c5fcc..1285c13 100644 --- a/packages/sqlocal/package.json +++ b/packages/sqlocal/package.json @@ -5,11 +5,15 @@ "main": "dist/index.js", "module": "dist/index.js", "types": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsup", - "test": "vitest", + "test": "vitest --run", + "updater": "bun x npm-check-updates -ui", "typecheck": "tsc --noEmit", - "prepublishOnly": "bun run test && bun run typecheck && bun run build" + "prepublishOnly": "bun run typecheck && bun run test && bun run build" }, "dependencies": { "sqlocal": "^0.14.0" @@ -21,7 +25,7 @@ "bknd": "workspace:*", "kysely": "^0.27.6", "tsup": "^8.4.0", - "typescript": "^5.6.3", + "typescript": "^5.8.2", "vitest": "^3.0.8", "webdriverio": "^9.12.0" }, @@ -32,7 +36,6 @@ "clean": true, "minify": true, "dts": true, - "metafile": true, "external": ["bknd", "kysely"] }, "files": ["dist", "README.md", "!*.map", "!metafile*.json"]
local
What's next? ({total})
+ Authenticated as {user.email} +