diff --git a/app/build.ts b/app/build.ts index ee6e428..3fd35ef 100644 --- a/app/build.ts +++ b/app/build.ts @@ -308,6 +308,11 @@ async function buildAdapters() { platform: "node", }), + tsup.build({ + ...baseConfig("sveltekit"), + platform: "node", + }), + tsup.build({ ...baseConfig("node"), platform: "node", diff --git a/app/package.json b/app/package.json index b3fa2f2..577e19a 100644 --- a/app/package.json +++ b/app/package.json @@ -253,6 +253,11 @@ "import": "./dist/adapter/astro/index.js", "require": "./dist/adapter/astro/index.js" }, + "./adapter/sveltekit": { + "types": "./dist/types/adapter/sveltekit/index.d.ts", + "import": "./dist/adapter/sveltekit/index.js", + "require": "./dist/adapter/sveltekit/index.js" + }, "./adapter/aws": { "types": "./dist/types/adapter/aws/index.d.ts", "import": "./dist/adapter/aws/index.js", @@ -280,6 +285,7 @@ "adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"], "adapter/bun": ["./dist/types/adapter/bun/index.d.ts"], "adapter/node": ["./dist/types/adapter/node/index.d.ts"], + "adapter/sveltekit": ["./dist/types/adapter/sveltekit/index.d.ts"], "adapter/sqlite": ["./dist/types/adapter/sqlite/edge.d.ts"] } }, @@ -309,6 +315,8 @@ "remix", "react-router", "astro", + "sveltekit", + "svelte", "bun", "node" ] diff --git a/app/src/adapter/sveltekit/index.ts b/app/src/adapter/sveltekit/index.ts new file mode 100644 index 0000000..8f0f38e --- /dev/null +++ b/app/src/adapter/sveltekit/index.ts @@ -0,0 +1 @@ +export * from "./sveltekit.adapter"; diff --git a/app/src/adapter/sveltekit/sveltekit.adapter.spec.ts b/app/src/adapter/sveltekit/sveltekit.adapter.spec.ts new file mode 100644 index 0000000..002cf27 --- /dev/null +++ b/app/src/adapter/sveltekit/sveltekit.adapter.spec.ts @@ -0,0 +1,15 @@ +import { afterAll, beforeAll, describe } from "bun:test"; +import * as sveltekit from "./sveltekit.adapter"; +import { disableConsoleLog, enableConsoleLog } from "core/utils"; +import { adapterTestSuite } from "adapter/adapter-test-suite"; +import { bunTestRunner } from "adapter/bun/test"; + +beforeAll(disableConsoleLog); +afterAll(enableConsoleLog); + +describe("sveltekit adapter", () => { + adapterTestSuite(bunTestRunner, { + makeApp: sveltekit.getApp, + makeHandler: (c, a) => (request: Request) => sveltekit.serve(c, a)({ request }), + }); +}); diff --git a/app/src/adapter/sveltekit/sveltekit.adapter.ts b/app/src/adapter/sveltekit/sveltekit.adapter.ts new file mode 100644 index 0000000..3454d72 --- /dev/null +++ b/app/src/adapter/sveltekit/sveltekit.adapter.ts @@ -0,0 +1,23 @@ +import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter"; + +type SvelteKitEnv = NodeJS.ProcessEnv; +type TSvelteKit = { + request: Request; +}; +export type SvelteKitBkndConfig = FrameworkBkndConfig; + +export async function getApp( + config: SvelteKitBkndConfig = {}, + args: Env = process.env as Env, +) { + return await createFrameworkApp(config, args); +} + +export function serve( + config: SvelteKitBkndConfig = {}, + args: Env = process.env as Env, +) { + return async (fnArgs: TSvelteKit) => { + return (await getApp(config, args)).fetch(fnArgs.request); + }; +} diff --git a/examples/sveltekit/.gitignore b/examples/sveltekit/.gitignore new file mode 100644 index 0000000..a9edab4 --- /dev/null +++ b/examples/sveltekit/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +*.db diff --git a/examples/sveltekit/README.md b/examples/sveltekit/README.md new file mode 100644 index 0000000..9a5d66f --- /dev/null +++ b/examples/sveltekit/README.md @@ -0,0 +1,26 @@ +# bknd + SvelteKit Example + +This example shows how to integrate bknd with SvelteKit. + +## Setup + +```bash +bun install +bun run dev +``` + +## How it works + +1. **`bknd.config.ts`** - bknd configuration with database connection, schema, and seed data +2. **`src/hooks.server.ts`** - Routes `/api/*` requests to bknd +3. **`src/routes/+page.server.ts`** - Uses `getApp()` to fetch data server-side + +## API Endpoints + +- `GET /api/data/entity/todos` - List todos (requires auth) +- `POST /api/auth/password/login` - Login + +## Test Credentials + +- Email: `admin@example.com` +- Password: `password` diff --git a/examples/sveltekit/bknd.config.ts b/examples/sveltekit/bknd.config.ts new file mode 100644 index 0000000..ad1b9d3 --- /dev/null +++ b/examples/sveltekit/bknd.config.ts @@ -0,0 +1,52 @@ +import type { SvelteKitBkndConfig } from "bknd/adapter/sveltekit"; +import { em, entity, text, libsql } from "bknd"; +import { createClient } from "@libsql/client"; + +const schema = em({ + todos: entity("todos", { + title: text().required(), + done: text(), + }), +}); + +export default { + connection: libsql( + createClient({ + url: "file:data.db", + }) + ), + config: { + data: schema.toJSON(), + auth: { + enabled: true, + allow_register: true, + jwt: { + issuer: "bknd-sveltekit-example", + secret: "dev-secret-change-in-production-1234567890abcdef", + }, + roles: { + admin: { + implicit_allow: true, + }, + default: { + permissions: ["data.entity.read", "data.entity.create"], + is_default: true, + }, + }, + }, + }, + options: { + seed: async (ctx) => { + await ctx.app.module.auth.createUser({ + email: "admin@example.com", + password: "password", + role: "admin", + }); + + await ctx.em.mutator("todos").insertMany([ + { title: "Learn bknd", done: "true" }, + { title: "Build with SvelteKit", done: "false" }, + ]); + }, + }, +} as const satisfies SvelteKitBkndConfig; diff --git a/examples/sveltekit/package.json b/examples/sveltekit/package.json new file mode 100644 index 0000000..9bab319 --- /dev/null +++ b/examples/sveltekit/package.json @@ -0,0 +1,23 @@ +{ + "name": "bknd-sveltekit-example", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "svelte": "^5.0.0", + "typescript": "^5.0.0", + "vite": "^7.0.0" + }, + "dependencies": { + "bknd": "file:../../app", + "@libsql/client": "^0.15.0" + }, + "type": "module" +} diff --git a/examples/sveltekit/src/app.html b/examples/sveltekit/src/app.html new file mode 100644 index 0000000..84ffad1 --- /dev/null +++ b/examples/sveltekit/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/sveltekit/src/hooks.server.ts b/examples/sveltekit/src/hooks.server.ts new file mode 100644 index 0000000..9993b77 --- /dev/null +++ b/examples/sveltekit/src/hooks.server.ts @@ -0,0 +1,14 @@ +import type { Handle } from "@sveltejs/kit"; +import { serve } from "bknd/adapter/sveltekit"; +import config from "../bknd.config"; + +const bkndHandler = serve(config); + +export const handle: Handle = async ({ event, resolve }) => { + // Handle bknd API requests + if (event.url.pathname.startsWith("/api/")) { + return bkndHandler(event); + } + + return resolve(event); +}; diff --git a/examples/sveltekit/src/routes/+page.server.ts b/examples/sveltekit/src/routes/+page.server.ts new file mode 100644 index 0000000..6ca8ca1 --- /dev/null +++ b/examples/sveltekit/src/routes/+page.server.ts @@ -0,0 +1,14 @@ +import type { PageServerLoad } from "./$types"; +import { getApp } from "bknd/adapter/sveltekit"; +import config from "../../bknd.config"; + +export const load: PageServerLoad = async () => { + const app = await getApp(config); + const api = app.getApi(); + + const todos = await api.data.readMany("todos"); + + return { + todos: todos.data ?? [], + }; +}; diff --git a/examples/sveltekit/src/routes/+page.svelte b/examples/sveltekit/src/routes/+page.svelte new file mode 100644 index 0000000..8973d68 --- /dev/null +++ b/examples/sveltekit/src/routes/+page.svelte @@ -0,0 +1,24 @@ + + +

bknd + SvelteKit Example

+ +

Todos

+
    + {#each data.todos as todo (todo.id)} +
  • {todo.title} - {todo.done === "true" ? "Done" : "Pending"}
  • + {/each} +
+ +

API Endpoints

+ + +

+ Login credentials: admin@example.com / password +

diff --git a/examples/sveltekit/svelte.config.js b/examples/sveltekit/svelte.config.js new file mode 100644 index 0000000..3fc56b9 --- /dev/null +++ b/examples/sveltekit/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from "@sveltejs/adapter-auto"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + }, +}; + +export default config; diff --git a/examples/sveltekit/tsconfig.json b/examples/sveltekit/tsconfig.json new file mode 100644 index 0000000..4344710 --- /dev/null +++ b/examples/sveltekit/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } +} diff --git a/examples/sveltekit/vite.config.ts b/examples/sveltekit/vite.config.ts new file mode 100644 index 0000000..80864b9 --- /dev/null +++ b/examples/sveltekit/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [sveltekit()], +});