updated nextjs example with app router

This commit is contained in:
dswbx
2025-02-27 20:09:03 +01:00
parent 2d5da63eb2
commit 09074f6591
36 changed files with 334 additions and 284 deletions

View File

@@ -48,7 +48,7 @@ describe("App", () => {
const todos = await app.getApi().data.readMany("todos");
expect(todos.length).toBe(2);
expect(todos[0].title).toBe("ctx");
expect(todos[1].title).toBe("api");
expect(todos[0]?.title).toBe("ctx");
expect(todos[1]?.title).toBe("api");
});
});

View File

@@ -71,16 +71,19 @@ async function buildApi() {
});
}
async function rewriteClient(path: string) {
const bundle = await Bun.file(path).text();
await Bun.write(path, '"use client";\n' + bundle.replaceAll("ui/client", "bknd/client"));
}
/**
* Building UI for direct imports
*/
async function buildUi() {
await tsup.build({
const base = {
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",
@@ -104,7 +107,24 @@ async function buildUi() {
esbuildOptions: (options) => {
options.logLevel = "silent";
},
} satisfies tsup.Options;
await tsup.build({
...base,
entry: ["src/ui/index.ts", "src/ui/main.css", "src/ui/styles.css"],
outDir: "dist/ui",
onSuccess: async () => {
await rewriteClient("./dist/ui/index.js");
delayTypes();
},
});
await tsup.build({
...base,
entry: ["src/ui/client/index.ts"],
outDir: "dist/ui/client",
onSuccess: async () => {
await rewriteClient("./dist/ui/client/index.js");
delayTypes();
},
});
@@ -146,11 +166,7 @@ async function buildUiElements() {
};
},
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"));
await rewriteClient("./dist/ui/elements/index.js");
delayTypes();
},
});

View File

@@ -3,7 +3,7 @@
"type": "module",
"sideEffects": false,
"bin": "./dist/cli/index.js",
"version": "0.9.0-rc.1",
"version": "0.9.0-rc.1-7",
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
"homepage": "https://bknd.io",
"repository": {

View File

@@ -1,4 +1,3 @@
import { Api, type ApiOptions } from "Api";
import type { CreateUserPayload } from "auth/AppAuth";
import { $console } from "core";
import { Event } from "core/events";
@@ -188,12 +187,10 @@ export class App {
return this.module.auth.createUser(p);
}
async getApi(options?: LocalApiOptions) {
getApi(options?: LocalApiOptions) {
const fetcher = this.server.request as typeof fetch;
if (options && options instanceof Request) {
const api = new Api({ request: options, headers: options.headers, fetcher });
await api.verifyAuth();
return api;
return new Api({ request: options, headers: options.headers, fetcher });
}
return new Api({ host: "http://localhost", ...(options ?? {}), fetcher });

View File

@@ -54,9 +54,9 @@ function AdminInternal() {
);
}
const Skeleton = ({ theme }: { theme?: string }) => {
const actualTheme =
(theme ?? document.querySelector("html")?.classList.contains("light")) ? "light" : "dark";
const Skeleton = ({ theme }: { theme?: any }) => {
const t = useTheme();
const actualTheme = theme ?? t.theme;
return (
<div id="bknd-admin" className={actualTheme + " antialiased"}>

View File

@@ -2,7 +2,7 @@ import type { AppTheme } from "modules/server/AppServer";
import { useBkndWindowContext } from "ui/client/ClientProvider";
import { useBknd } from "ui/client/bknd";
export function useTheme(fallback: AppTheme = "system"): { theme: AppTheme } {
export function useTheme(fallback: AppTheme = "system") {
const b = useBknd();
const winCtx = useBkndWindowContext();
@@ -14,13 +14,16 @@ export function useTheme(fallback: AppTheme = "system"): { theme: AppTheme } {
const override = b?.adminOverride?.color_scheme;
const config = b?.config.server.admin.color_scheme;
const win = winCtx.color_scheme;
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const prefersDark =
typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
const theme = override ?? config ?? win ?? fallback;
if (theme === "system") {
return { theme: prefersDark ? "dark" : "light" };
}
return { theme };
return {
theme: (theme === "system" ? (prefersDark ? "dark" : "light") : theme) as AppTheme,
prefersDark,
override,
config,
win,
};
}

View File

@@ -4,15 +4,15 @@
// there is no lifecycle or Hook in React that we can use to switch
// .current at the right timing."
// So we will have to make do with this "close enough" approach for now.
import { useInsertionEffect, useRef } from "react";
import { useEffect, useRef } from "react";
export const useEvent = <Fn>(fn: Fn | ((...args: any[]) => any) | undefined): Fn => {
const ref = useRef([fn, (...args) => ref[0](...args)]).current;
// Per Dan Abramov: useInsertionEffect executes marginally closer to the
// correct timing for ref synchronization than useLayoutEffect on React 18.
// See: https://github.com/facebook/react/pull/25881#issuecomment-1356244360
useInsertionEffect(() => {
useEffect(() => {
ref[0] = fn;
});
}, []);
return ref[1];
};

View File

@@ -45,7 +45,6 @@ export function StepEntityFields() {
const values = watch();
const updateListener = useEvent((data: TAppDataEntityFields) => {
console.log("updateListener", data);
setValue("fields", data as any);
});

View File

@@ -2,7 +2,7 @@ import { Type } from "core/utils";
import { type Entity, querySchema } from "data";
import { Fragment } from "react";
import { TbDots } from "react-icons/tb";
import { useApi, useApiQuery } from "ui/client";
import { useApiQuery } from "ui/client";
import { useBknd } from "ui/client/bknd";
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button";
@@ -83,7 +83,7 @@ export function DataEntityList({ params }) {
search.set("perPage", perPage);
}
const isUpdating = $q.isLoading && $q.isValidating;
const isUpdating = $q.isLoading || $q.isValidating;
return (
<Fragment key={entity.name}>

View File

@@ -108,9 +108,7 @@ export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsForm
useEffect(() => {
if (props?.onChange) {
console.log("----set");
watch((data: any) => {
console.log("---calling");
props?.onChange?.(toCleanValues(data));
});
}

View File

@@ -28,8 +28,9 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for commiting if needed)
# env files (can opt-in for committing if needed)
.env*
# vercel
@@ -38,4 +39,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
!test.db

View File

@@ -1,38 +1,36 @@
# bknd starter: Next.js
A minimal Next.js project with bknd integration.
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Project Structure
## Getting Started
Inside of your Next.js project, you'll see the following folders and files:
First, run the development server:
```text
/
├── public/
├── src/
│ └── pages/
│ └── admin/
│ │ └── [[...admin]].tsx
│ └── api/
│ │ └── [...route].ts
│ ├── _app.tsx
│ ├── _document.tsx
│ └── index.tsx
└── package.json
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
To update `bknd` config, check `src/pages/api/[...route].ts` and `src/pages/admin/[[...admin]].tsx`.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Commands
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
All commands are run from the root of the project, from a terminal:
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
| Command | Action |
|:--------------------------|:-------------------------------------------------|
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site |
| `npm run db` | Starts a local LibSQL database |
## Learn More
## Want to learn more?
To learn more about Next.js, take a look at the following resources:
Feel free to check [our documentation](https://docs.bknd.io/integration/nextjs) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

View File

@@ -1,18 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: true
/*transpilePackages: [
"@rjsf/core",
"@libsql/isomorphic-fetch",
"@libsql/isomorphic-ws",
"@libsql/kysely-libsql"
],
experimental: {
esmExternals: "loose"
}*/
/* config options here */
};
export default nextConfig;

View File

@@ -3,24 +3,24 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "npm run db & next dev",
"db": "turso dev --db-file test.db",
"dev": "next dev",
"dev:turbo": "next dev --turbopack",
"build": "next build",
"start": "npm run db & next start",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"bknd": "file:../../app",
"next": "15.0.2",
"react": "file:../../node_modules/react",
"react-dom": "file:../../node_modules/react-dom"
"react-dom": "file:../../node_modules/react-dom",
"next": "15.2.0"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"postcss": "^8",
"tailwindcss": "^3.4.1"
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4"
}
}

View File

@@ -1,8 +1,6 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

View File

@@ -0,0 +1,14 @@
<svg
width="578"
height="188"
viewBox="0 0 578 188"
fill="black"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M41.5 34C37.0817 34 33.5 37.5817 33.5 42V146C33.5 150.418 37.0817 154 41.5 154H158.5C162.918 154 166.5 150.418 166.5 146V42C166.5 37.5817 162.918 34 158.5 34H41.5ZM123.434 113.942C124.126 111.752 124.5 109.42 124.5 107C124.5 94.2975 114.203 84 101.5 84C99.1907 84 96.9608 84.3403 94.8579 84.9736L87.2208 65.1172C90.9181 63.4922 93.5 59.7976 93.5 55.5C93.5 49.701 88.799 45 83 45C77.201 45 72.5 49.701 72.5 55.5C72.5 61.299 77.201 66 83 66C83.4453 66 83.8841 65.9723 84.3148 65.9185L92.0483 86.0256C87.1368 88.2423 83.1434 92.1335 80.7957 96.9714L65.4253 91.1648C65.4746 90.7835 65.5 90.3947 65.5 90C65.5 85.0294 61.4706 81 56.5 81C51.5294 81 47.5 85.0294 47.5 90C47.5 94.9706 51.5294 99 56.5 99C60.0181 99 63.0648 96.9814 64.5449 94.0392L79.6655 99.7514C78.9094 102.03 78.5 104.467 78.5 107C78.5 110.387 79.2321 113.603 80.5466 116.498L69.0273 123.731C67.1012 121.449 64.2199 120 61 120C55.201 120 50.5 124.701 50.5 130.5C50.5 136.299 55.201 141 61 141C66.799 141 71.5 136.299 71.5 130.5C71.5 128.997 71.1844 127.569 70.6158 126.276L81.9667 119.149C86.0275 125.664 93.2574 130 101.5 130C110.722 130 118.677 124.572 122.343 116.737L132.747 120.899C132.585 121.573 132.5 122.276 132.5 123C132.5 127.971 136.529 132 141.5 132C146.471 132 150.5 127.971 150.5 123C150.5 118.029 146.471 114 141.5 114C138.32 114 135.525 115.649 133.925 118.139L123.434 113.942Z"
/>
<path d="M243.9 151.5C240.4 151.5 237 151 233.7 150C230.4 149 227.4 147.65 224.7 145.95C222 144.15 219.75 142.15 217.95 139.95C216.15 137.65 215 135.3 214.5 132.9L219.3 131.1L218.25 149.7H198.15V39H219.45V89.25L215.4 87.6C216 85.2 217.15 82.9 218.85 80.7C220.55 78.4 222.7 76.4 225.3 74.7C227.9 72.9 230.75 71.5 233.85 70.5C236.95 69.5 240.15 69 243.45 69C250.35 69 256.5 70.8 261.9 74.4C267.3 77.9 271.55 82.75 274.65 88.95C277.85 95.15 279.45 102.25 279.45 110.25C279.45 118.25 277.9 125.35 274.8 131.55C271.7 137.75 267.45 142.65 262.05 146.25C256.75 149.75 250.7 151.5 243.9 151.5ZM238.8 133.35C242.8 133.35 246.25 132.4 249.15 130.5C252.15 128.5 254.5 125.8 256.2 122.4C257.9 118.9 258.75 114.85 258.75 110.25C258.75 105.75 257.9 101.75 256.2 98.25C254.6 94.75 252.3 92.05 249.3 90.15C246.3 88.25 242.8 87.3 238.8 87.3C234.8 87.3 231.3 88.25 228.3 90.15C225.3 92.05 222.95 94.75 221.25 98.25C219.55 101.75 218.7 105.75 218.7 110.25C218.7 114.85 219.55 118.9 221.25 122.4C222.95 125.8 225.3 128.5 228.3 130.5C231.3 132.4 234.8 133.35 238.8 133.35ZM308.312 126.15L302.012 108.6L339.512 70.65H367.562L308.312 126.15ZM288.062 150V39H309.362V150H288.062ZM341.762 150L313.262 114.15L328.262 102.15L367.412 150H341.762ZM371.675 150V70.65H392.075L392.675 86.85L388.475 88.65C389.575 85.05 391.525 81.8 394.325 78.9C397.225 75.9 400.675 73.5 404.675 71.7C408.675 69.9 412.875 69 417.275 69C423.275 69 428.275 70.2 432.275 72.6C436.375 75 439.425 78.65 441.425 83.55C443.525 88.35 444.575 94.3 444.575 101.4V150H423.275V103.05C423.275 99.45 422.775 96.45 421.775 94.05C420.775 91.65 419.225 89.9 417.125 88.8C415.125 87.6 412.625 87.1 409.625 87.3C407.225 87.3 404.975 87.7 402.875 88.5C400.875 89.2 399.125 90.25 397.625 91.65C396.225 93.05 395.075 94.65 394.175 96.45C393.375 98.25 392.975 100.2 392.975 102.3V150H382.475C380.175 150 378.125 150 376.325 150C374.525 150 372.975 150 371.675 150ZM488.536 151.5C481.636 151.5 475.436 149.75 469.936 146.25C464.436 142.65 460.086 137.8 456.886 131.7C453.786 125.5 452.236 118.35 452.236 110.25C452.236 102.35 453.786 95.3 456.886 89.1C460.086 82.9 464.386 78 469.786 74.4C475.286 70.8 481.536 69 488.536 69C492.236 69 495.786 69.6 499.186 70.8C502.686 71.9 505.786 73.45 508.486 75.45C511.286 77.45 513.536 79.7 515.236 82.2C516.936 84.6 517.886 87.15 518.086 89.85L512.686 90.75V39H533.986V150H513.886L512.986 131.7L517.186 132.15C516.986 134.65 516.086 137.05 514.486 139.35C512.886 141.65 510.736 143.75 508.036 145.65C505.436 147.45 502.436 148.9 499.036 150C495.736 151 492.236 151.5 488.536 151.5ZM493.336 133.8C497.336 133.8 500.836 132.8 503.836 130.8C506.836 128.8 509.186 126.05 510.886 122.55C512.586 119.05 513.436 114.95 513.436 110.25C513.436 105.65 512.586 101.6 510.886 98.1C509.186 94.5 506.836 91.75 503.836 89.85C500.836 87.85 497.336 86.85 493.336 86.85C489.336 86.85 485.836 87.85 482.836 89.85C479.936 91.75 477.636 94.5 475.936 98.1C474.336 101.6 473.536 105.65 473.536 110.25C473.536 114.95 474.336 119.05 475.936 122.55C477.636 126.05 479.936 128.8 482.836 130.8C485.836 132.8 489.336 133.8 493.336 133.8Z" />
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,18 @@
import { Admin } from "bknd/ui";
import "bknd/dist/styles.css";
import { getApi } from "@/bknd";
export default async function AdminPage() {
const api = await getApi({ verify: true });
return (
<Admin
withProvider={{ user: api.getUser() }}
config={{
basepath: "/admin",
logo_return_path: "/../",
color_scheme: "system",
}}
/>
);
}

View File

@@ -0,0 +1,12 @@
import { getApp } from "@/bknd";
const handler = async (request: Request) => {
const app = await getApp();
return app.fetch(request);
};
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const PATCH = handler;
export const DELETE = handler;

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,25 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
@theme {
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-background: var(--background);
--color-foreground: var(--foreground);
}
body {
@apply bg-background text-foreground;
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -0,0 +1,32 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next & bknd App",
description: "Presented by create next app & bknd",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
{children}
</body>
</html>
);
}

View File

@@ -0,0 +1,97 @@
import Image from "next/image";
import { getApi } from "@/bknd";
export default async function Home() {
const api = await getApi();
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<div className="flex flex-row items-center ">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<div className="ml-3.5 mr-2 font-mono opacity-70">&amp;</div>
<Image
className="dark:invert"
src="/bknd.svg"
alt="bknd logo"
width={183}
height={59}
priority
/>
</div>
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
src/app/page.tsx
</code>
.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://docs.bknd.io/integration/nextjs"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
<pre className="text-sm">
{JSON.stringify(await api.data.readMany("posts"), null, 2)}
</pre>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="/ssr"
>
<Image aria-hidden src="/file.svg" alt="File icon" width={16} height={16} />
SSR
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="/admin"
>
<Image aria-hidden src="/window.svg" alt="Window icon" width={16} height={16} />
Admin
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://bknd.io"
target="_blank"
rel="noopener noreferrer"
>
<Image aria-hidden src="/globe.svg" alt="Globe icon" width={16} height={16} />
Go to bknd.io
</a>
</footer>
</div>
);
}

View File

@@ -0,0 +1,14 @@
import { getApi } from "@/bknd";
export default async function SSRPage() {
const api = await getApi({ verify: true });
const { data } = await api.data.readMany("posts");
return (
<div>
<h1>Server-Side Rendered Page</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
<pre>{JSON.stringify(api.getUser(), null, 2)}</pre>
</div>
);
}

View File

@@ -1,68 +1,27 @@
import { App, type LocalApiOptions } from "bknd";
import { type NextjsBkndConfig, getApp as getBkndApp } from "bknd/adapter/nextjs";
import { boolean, em, entity, text } from "bknd/data";
import { secureRandomString } from "bknd/utils";
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { headers } from "next/headers";
// the em() function makes it easy to create an initial schema
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 {}
}
registerLocalMediaAdapter();
export const config = {
// we can use any libsql config, and if omitted, uses in-memory
connection: {
url: "http://localhost:8080"
// make sure to use a remote URL for production!
url: "file:data.db",
},
// an initial config is only applied if the database is empty
initialConfig: {
data: schema.toJSON(),
// we're enabling auth ...
auth: {
enabled: true,
jwt: {
secret: secureRandomString(64)
}
}
},
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 }
]);
}
},
// here we can hook into the app lifecycle events ...
beforeBuild: async (app) => {
app.emgr.onEvent(
App.Events.AppFirstBoot,
async () => {
// ... to create an initial user
await app.module.auth.createUser({
email: "ds@bknd.io",
password: "12345678"
});
},
"sync"
);
}
} as const satisfies NextjsBkndConfig;
export async function getApp() {
return await getBkndApp(config);
}
export async function getApi(options?: LocalApiOptions) {
export async function getApi(opts?: { verify?: boolean }) {
const app = await getApp();
return await app.getApi(options);
if (opts?.verify) {
const api = app.getApi({ headers: await headers() });
await api.verifyAuth();
return api;
}
return app.getApi();
}

View File

@@ -1,6 +0,0 @@
import "@/styles/globals.css";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}

View File

@@ -1,13 +0,0 @@
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body className="antialiased">
<Main />
<NextScript />
</body>
</Html>
);
}

View File

@@ -1,27 +0,0 @@
import type { InferGetServerSidePropsType as InferProps } from "next";
import dynamic from "next/dynamic";
import { withApi } from "bknd/adapter/nextjs";
import "bknd/dist/styles.css";
const Admin = dynamic(() => import("bknd/ui").then((mod) => mod.Admin), {
ssr: false
});
export const getServerSideProps = withApi(async (context) => {
return {
props: {
user: context.api.getUser()
}
};
});
export default function AdminPage({ user }: InferProps<typeof getServerSideProps>) {
if (typeof document === "undefined") return null;
return (
<Admin
withProvider={{ user }}
config={{ basepath: "/admin", logo_return_path: "/../", color_scheme: "system" }}
/>
);
}

View File

@@ -1,18 +0,0 @@
import { config as bkndConfig } from "@/bknd";
import { serve } from "bknd/adapter/nextjs";
export const config = {
runtime: "edge",
// add a matcher for bknd dist to allow dynamic otherwise build may fail.
// inside this repo it's '../../app/dist/index.js', outside probably inside node_modules
// see https://github.com/vercel/next.js/issues/51401
// and https://github.com/vercel/next.js/pull/69402
unstable_allowDynamic: ["**/*.js"]
};
export default serve({
cleanRequest: {
searchParams: ["route"]
},
...bkndConfig
});

View File

@@ -1,13 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
type Data = {
name: string;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>,
) {
res.status(200).json({ name: "John Doe" });
}

View File

@@ -1,25 +0,0 @@
import { getApi } from "@/bknd";
import type { InferGetServerSidePropsType } from "next";
export const getServerSideProps = async () => {
const api = await getApi();
const { data = [] } = await api.data.readMany("todos");
const user = api.getUser();
return { props: { data, user } };
};
export default function Home({
data,
user
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<div>
<h1>Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
<h1>User</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
);
}

View File

@@ -1,21 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -1,19 +1,18 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
export default {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
},
plugins: [],
};
export default config;
},
plugins: [],
} satisfies Config;

Binary file not shown.

View File

@@ -13,10 +13,15 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}