mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 04:46:05 +00:00
Release 0.16 (#196)
* initial refactor * fixes * test secrets extraction * updated lock * fix secret schema * updated schemas, fixed tests, skipping flow tests for now * added validator for rjsf, hook form via standard schema * removed @sinclair/typebox * remove unneeded vite dep * fix jsonv literal on Field.tsx * fix schema import path * fix schema modals * fix schema modals * fix json field form, replaced auth form * initial waku * finalize waku example * fix jsonv-ts version * fix schema updates with falsy values * fix media api to respect options' init, improve types * checking media controller test * checking media controller test * checking media controller test * clean up mediacontroller test * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` (#214) * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` * fix server test * fix data api (updated jsonv-ts) * enhance cloudflare image optimization plugin with new options and explain endpoint (#215) * feat: add ability to serve static by using dynamic imports (#197) * feat: add ability to serve static by using dynamic imports * serveStaticViaImport: make manifest optional * serveStaticViaImport: add error log * refactor/imports (#217) * refactored core and core/utils imports * refactored core and core/utils imports * refactored media imports * refactored auth imports * refactored data imports * updated package json exports, fixed mm config * fix tests * feat/deno (#219) * update bun version * fix module manager's em reference * add basic deno example * finalize * docs: fumadocs migration (#185) * feat(docs): initialize documentation structure with Fumadocs * feat(docs): remove home route and move /docs route to /route * feat(docs): add redirect to /start page * feat(docs): migrate Getting Started chapters * feat(docs): migrate Usage and Extending chapters * feat(callout): add CalloutCaution, CalloutDanger, CalloutInfo, and CalloutPositive * feat(layout): add Discord and GitHub links to documentation layout * feat(docs): add integration chapters draft * feat(docs): add modules chapters draft * refactor(mdx-components): remove unused Icon import * refactor(StackBlitz): enhance type safety by using unknown instead of any * refactor(layout): update navigation mode to 'top' in layout configuration * feat(docs): add @iconify/react package * docs(mdx-components): add Icon component to MDX components list * feat(docs): update Next.js integration guide * feat(docs): update React Router integration guide * feat(docs): update Astro integration guide * feat(docs): update Vite integration guide * fix(docs): update package manager initialization commands * feat(docs): migrate Modules chapters * chore(docs): update package.json with new devDependencies * feat(docs): migrate Integration Runtimes chapters * feat(docs): update Database usage chapter * feat(docs): restructure documentation paths * chore(docs): clean up unused imports and files in documentation * style(layout): revert navigation mode to previous state * fix(docs): routing for documentation structure * feat(openapi): add API documentation generation from OpenAPI schema * feat(docs): add icons to documentation pages * chore(dependencies): remove unused content-collections packages * fix(types): fix type error for attachFile in source.ts * feat(redirects): update root redirect destination to '/start' * feat(search): add static search functionality * chore(dependencies): update fumadocs-core and fumadocs-ui to latest versions * feat(search): add Powered by Orama link * feat(generate-openapi): add error handling for missing OpenAPI schema * feat(scripts): add OpenAPI generation to build process * feat(config): enable dynamic redirects and rewrites in development mode * feat(layout): add GitHub token support for improved API rate limits * feat(redirects): add 301 redirects for cloudflare pages * feat(docs): add Vercel redirects configuration * feat(config): enable standalone output for development environment * chore(layout): adjust layout settings * refactor(package): clean up ajv dependency versions * feat(docs): add twoslash support * refactor(layout): update DocsLayout import and navigation configuration * chore(layout): clean up layout.tsx by commenting out GithubInfo * fix(Search): add locale to search initialization * chore(package): update fumadocs and orama to latest versions * docs: add menu items descriptions * feat(layout): add GitHub URL to the layout component * feat(docs): add AutoTypeTable component to MDX components * feat(app): implement AutoTypeTable rendering for AppEvents type * docs(layout): switch callouts back to default components * fix(config): use __filename and __dirname for module paths * docs: add note about node.js 22 requirement * feat(styles): add custom color variables for light and dark themes * docs: add S3 setup instructions for media module * docs: fix typos and indentation in media module docs * docs: add local media adapter example for Node.js * docs(media): add S3/R2 URL format examples and fix typo * docs: add cross-links to initial config and seeding sections * indent numbered lists content, clarified media serve locations * fix mediacontroller tests * feat(layout): add AnimatedGridPattern component for dynamic background * style(layout): configure fancy ToC style ('clerk') * fix(AnimatedGridPattern): correct strokeDasharray type * docs: actualize docs * feat: add favicon * style(cloudflare): format code examples * feat(layout): add Github and Discord footer icons * feat(footer): add SVG social media icons for GitHub and Discord * docs: adjusted auto type table, added llm functions * added static deployment to cloudflare workers * docs: change cf redirects to proxy *.mdx instead of redirecting --------- Co-authored-by: dswbx <dennis.senn@gmx.ch> Co-authored-by: cameronapak <cameronandrewpak@gmail.com> * build: improve build script * add missing exports, fix EntityTypescript imports * media: Dropzone: add programmatic upload, additional events, loading state * schema object: disable extended defaults to allow empty config values * Feat/new docs deploy (#224) * test * try fixing pm * try fixing pm * fix docs on imports, export events correctly --------- Co-authored-by: Tim Seriakov <59409712+timseriakov@users.noreply.github.com> Co-authored-by: cameronapak <cameronandrewpak@gmail.com>
This commit is contained in:
3
docs/.eslintrc.json
Normal file
3
docs/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
||||
29
docs/.gitignore
vendored
Normal file
29
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# deps
|
||||
/node_modules
|
||||
|
||||
# generated content
|
||||
.contentlayer
|
||||
.content-collections
|
||||
.source
|
||||
.twoslash-cache
|
||||
|
||||
# test & build
|
||||
/coverage
|
||||
/.next/
|
||||
/out/
|
||||
/build
|
||||
*.tsbuildinfo
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
/.pnp
|
||||
.pnp.js
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# others
|
||||
.env*.local
|
||||
.vercel
|
||||
next-env.d.ts
|
||||
45
docs/README.md
Normal file
45
docs/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# bknd-docs
|
||||
|
||||
This is a Next.js application generated with
|
||||
[Create Fumadocs](https://github.com/fuma-nama/fumadocs).
|
||||
|
||||
Run development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000 with your browser to see the result.
|
||||
|
||||
## Explore
|
||||
|
||||
In the project, you can see:
|
||||
|
||||
- `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content.
|
||||
- `app/layout.config.tsx`: Shared options for layouts, optional but preferred to keep.
|
||||
|
||||
| Route | Description |
|
||||
| ------------------------- | ------------------------------------------------------ |
|
||||
| `app/(home)` | The route group for your landing page and other pages. |
|
||||
| `app/docs` | The documentation layout and pages. |
|
||||
| `app/api/search/route.ts` | The Route Handler for search. |
|
||||
|
||||
### Fumadocs MDX
|
||||
|
||||
A `source.config.ts` config file has been included, you can customise different options like frontmatter schema.
|
||||
|
||||
Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js and Fumadocs, take a look at the following
|
||||
resources:
|
||||
|
||||
- [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.
|
||||
- [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 157 KiB |
@@ -1,161 +0,0 @@
|
||||
<svg width="700" height="320" viewBox="0 0 700 320" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2862_30)">
|
||||
<rect width="700" height="320" rx="16" fill="url(#paint0_linear_2862_30)"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="white"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint1_radial_2862_30)"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint2_linear_2862_30)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M311.72 247.034C283.108 246.887 258.409 231.208 246.538 201.531C234.656 171.825 238.271 134.702 253.583 101.377C282.195 101.524 306.894 117.203 318.765 146.88C330.647 176.586 327.031 213.709 311.72 247.034Z" stroke="url(#paint3_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
|
||||
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="white"/>
|
||||
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="url(#paint4_radial_2862_30)"/>
|
||||
<path d="M393.341 171.537C376.971 210.369 343.89 237.091 305.969 246.867C286.462 212.959 282.476 170.663 298.845 131.831C315.215 92.9978 348.295 66.2765 386.217 56.5004C405.724 90.4077 409.71 132.704 393.341 171.537Z" stroke="url(#paint5_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
|
||||
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="white"/>
|
||||
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint6_radial_2862_30)"/>
|
||||
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint7_linear_2862_30)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M393.586 261.878C362.034 272.529 329.98 265.88 306.002 246.907C317.534 215.919 341.57 190.327 373.13 179.673C404.681 169.023 436.735 175.671 460.714 194.644C449.181 225.632 425.145 251.224 393.586 261.878Z" stroke="url(#paint8_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
|
||||
<g opacity="0.8" filter="url(#filter0_f_2862_30)">
|
||||
<circle cx="660" cy="-60" r="160" fill="#18E244" fill-opacity="0.4"/>
|
||||
</g>
|
||||
<g opacity="0.8" filter="url(#filter1_f_2862_30)">
|
||||
<circle cx="20" cy="213" r="160" fill="#18CAE2" fill-opacity="0.33"/>
|
||||
</g>
|
||||
<g opacity="0.8" filter="url(#filter2_f_2862_30)">
|
||||
<circle cx="660" cy="480" r="160" fill="#18E2B2" fill-opacity="0.52"/>
|
||||
</g>
|
||||
<g opacity="0.8" filter="url(#filter3_f_2862_30)">
|
||||
<circle cx="20" cy="413" r="160" fill="#4018E2" fill-opacity="0.22"/>
|
||||
</g>
|
||||
<path opacity="0.2" d="M0 50H700" stroke="url(#paint9_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.1" d="M0 82H700" stroke="url(#paint10_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.2" d="M239 0L239 320" stroke="url(#paint11_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.1" d="M271 0L271 320" stroke="url(#paint12_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.2" d="M461 0L461 320" stroke="url(#paint13_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.1" d="M429 0L429 320" stroke="url(#paint14_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.2" d="M0 271H700" stroke="url(#paint15_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<path opacity="0.1" d="M0 239H700" stroke="url(#paint16_radial_2862_30)" stroke-dasharray="4 4"/>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M0 160H700" stroke="url(#paint17_linear_2862_30)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.2">
|
||||
<path d="M511 -1L189 321" stroke="url(#paint18_linear_2862_30)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.2">
|
||||
<path d="M511 321L189 -1" stroke="url(#paint19_linear_2862_30)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<circle cx="350" cy="160" r="111" stroke="white"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<circle cx="350" cy="160" r="79" stroke="white"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_2862_30" x="260" y="-460" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_2862_30" x="-380" y="-187" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_2862_30" x="260" y="80" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_2862_30" x="-380" y="13" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_2862_30" x1="1.04308e-05" y1="320" x2="710.784" y2="26.0793" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#18E299" stop-opacity="0.09"/>
|
||||
<stop offset="0.729167" stop-color="#0D9373" stop-opacity="0.08"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(208.697 189.703) rotate(-10.029) scale(169.097 167.466)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint2_linear_2862_30" x1="306.587" y1="93.5598" x2="252.341" y2="224.228" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#18E299"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2862_30" x1="311.84" y1="123.717" x2="253.579" y2="224.761" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint4_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(313.407 243.64) rotate(-75.7542) scale(203.632 223.902)">
|
||||
<stop stop-color="#00BBBB"/>
|
||||
<stop offset="0.712616" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint5_linear_2862_30" x1="308.586" y1="102.284" x2="383.487" y2="201.169" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint6_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(311.446 249.925) rotate(-20.3524) scale(174.776 163.096)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint7_linear_2862_30" x1="395.842" y1="169.781" x2="332.121" y2="263.82" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00B1BC"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_2862_30" x1="395.842" y1="169.781" x2="370.99" y2="271.799" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint9_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 50) scale(398.125 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint10_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 82) scale(398.125 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint11_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(239 160) rotate(90) scale(182 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint12_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(271 160) rotate(90) scale(182 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint13_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(461 160) rotate(90) scale(182 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint14_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(429 160) rotate(90) scale(182 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint15_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 271) scale(398.125 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint16_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 239) scale(398.125 182)">
|
||||
<stop offset="0.348958" stop-color="#84FFD3"/>
|
||||
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint17_linear_2862_30" x1="0" y1="160" x2="700" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.1"/>
|
||||
<stop offset="0.5" stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint18_linear_2862_30" x1="511" y1="-1" x2="189" y2="321" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.1"/>
|
||||
<stop offset="0.5" stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint19_linear_2862_30" x1="511" y1="321" x2="189" y2="-0.999997" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.1"/>
|
||||
<stop offset="0.5" stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2862_30">
|
||||
<rect width="700" height="320" rx="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,155 +0,0 @@
|
||||
<svg width="700" height="320" viewBox="0 0 700 320" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2862_278)">
|
||||
<rect width="700" height="320" rx="16" fill="url(#paint0_linear_2862_278)"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="white"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint1_radial_2862_278)"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint2_linear_2862_278)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M311.72 247.034C283.108 246.887 258.409 231.208 246.538 201.531C234.656 171.825 238.271 134.702 253.583 101.377C282.195 101.524 306.894 117.203 318.765 146.88C330.647 176.586 327.031 213.709 311.72 247.034Z" stroke="url(#paint3_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
|
||||
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="white"/>
|
||||
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="url(#paint4_radial_2862_278)"/>
|
||||
<path d="M393.341 171.537C376.971 210.369 343.89 237.091 305.969 246.867C286.462 212.959 282.476 170.663 298.845 131.831C315.215 92.9978 348.295 66.2765 386.217 56.5004C405.724 90.4077 409.71 132.704 393.341 171.537Z" stroke="url(#paint5_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
|
||||
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="white"/>
|
||||
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint6_radial_2862_278)"/>
|
||||
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint7_linear_2862_278)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M393.586 261.878C362.035 272.529 329.981 265.88 306.002 246.907C317.535 215.919 341.571 190.327 373.13 179.673C404.682 169.023 436.736 175.671 460.715 194.644C449.182 225.632 425.146 251.224 393.586 261.878Z" stroke="url(#paint8_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
|
||||
<g opacity="0.8" filter="url(#filter0_f_2862_278)">
|
||||
<circle cx="660" cy="-60" r="160" fill="#18E299" fill-opacity="0.4"/>
|
||||
</g>
|
||||
<g opacity="0.8" filter="url(#filter1_f_2862_278)">
|
||||
<circle cx="20" cy="213" r="160" fill="#18E299" fill-opacity="0.33"/>
|
||||
</g>
|
||||
<g opacity="0.8" filter="url(#filter2_f_2862_278)">
|
||||
<circle cx="660" cy="480" r="160" fill="#18E299" fill-opacity="0.52"/>
|
||||
</g>
|
||||
<g opacity="0.8" filter="url(#filter3_f_2862_278)">
|
||||
<circle cx="20" cy="413" r="160" fill="#18E299" fill-opacity="0.22"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M0 50H700" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M0 82H700" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M239 0L239 320" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M271 0L271 320" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M461 0L461 320" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M350 0L350 320" stroke="url(#paint9_linear_2862_278)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M429 0L429 320" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M0 271H700" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M0 239H700" stroke="black" stroke-dasharray="4 4"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M0 160H700" stroke="url(#paint10_linear_2862_278)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M511 -1L189 321" stroke="url(#paint11_linear_2862_278)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.1">
|
||||
<path d="M511 321L189 -1" stroke="url(#paint12_linear_2862_278)"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.05">
|
||||
<circle cx="350" cy="160" r="111" stroke="black"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:overlay" opacity="0.05">
|
||||
<circle cx="350" cy="160" r="79" stroke="black"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_2862_278" x="260" y="-460" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_2862_278" x="-380" y="-187" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_2862_278" x="260" y="80" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_2862_278" x="-380" y="13" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_2862_278" x1="1.04308e-05" y1="320" x2="710.784" y2="26.0793" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#18E299" stop-opacity="0.09"/>
|
||||
<stop offset="0.729167" stop-color="#0D9373" stop-opacity="0.08"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(208.697 189.703) rotate(-10.029) scale(169.097 167.466)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint2_linear_2862_278" x1="306.587" y1="93.5598" x2="252.341" y2="224.228" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#18E299"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2862_278" x1="311.84" y1="123.717" x2="253.579" y2="224.761" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint4_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(313.407 243.64) rotate(-75.7542) scale(203.632 223.902)">
|
||||
<stop stop-color="#00BBBB"/>
|
||||
<stop offset="0.712616" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint5_linear_2862_278" x1="308.586" y1="102.284" x2="383.487" y2="201.169" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint6_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(311.447 249.925) rotate(-20.3524) scale(174.776 163.096)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint7_linear_2862_278" x1="395.843" y1="169.781" x2="332.121" y2="263.82" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00B1BC"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_2862_278" x1="395.843" y1="169.781" x2="370.991" y2="271.799" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_2862_278" x1="350" y1="0" x2="350" y2="320" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0"/>
|
||||
<stop offset="0.0001" stop-opacity="0.3"/>
|
||||
<stop offset="0.333333"/>
|
||||
<stop offset="0.666667"/>
|
||||
<stop offset="1" stop-opacity="0.3"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_2862_278" x1="0" y1="160" x2="700" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.1"/>
|
||||
<stop offset="0.5"/>
|
||||
<stop offset="1" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear_2862_278" x1="511" y1="-1" x2="189" y2="321" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.1"/>
|
||||
<stop offset="0.5"/>
|
||||
<stop offset="1" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint12_linear_2862_278" x1="511" y1="321" x2="189" y2="-0.999997" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.1"/>
|
||||
<stop offset="0.5"/>
|
||||
<stop offset="1" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2862_278">
|
||||
<rect width="700" height="320" rx="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,55 +0,0 @@
|
||||
<svg width="160" height="24" viewBox="0 0 160 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="white"/>
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint0_radial_115_109)"/>
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint1_linear_115_109)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M7.9354 21.1112C4.89702 21.0957 2.27411 19.4306 1.01347 16.279C-0.248375 13.1244 0.135612 9.18218 1.76165 5.64327C4.80004 5.65882 7.42295 7.32385 8.68359 10.4755C9.94543 13.63 9.56144 17.5723 7.9354 21.1112Z" stroke="url(#paint2_linear_115_109)" stroke-opacity="0.05" stroke-width="0.056338"/>
|
||||
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="white"/>
|
||||
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="url(#paint3_radial_115_109)"/>
|
||||
<path d="M16.6025 13.2251C14.8642 17.349 11.3512 20.1866 7.32411 21.2248C5.25257 17.624 4.82926 13.1324 6.56764 9.00855C8.30603 4.88472 11.819 2.04706 15.8461 1.00889C17.9176 4.60967 18.3409 9.10131 16.6025 13.2251Z" stroke="url(#paint4_linear_115_109)" stroke-opacity="0.05" stroke-width="0.056338"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="white"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint5_radial_115_109)"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint6_linear_115_109)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M16.5682 22.7874C13.2176 23.9184 9.81361 23.2124 7.2672 21.1975C8.49194 17.9068 11.0444 15.189 14.3959 14.0577C17.7465 12.9266 21.1504 13.6326 23.6968 15.6476C22.4721 18.9383 19.9196 21.656 16.5682 22.7874Z" stroke="url(#paint7_linear_115_109)" stroke-opacity="0.05" stroke-width="0.056338"/>
|
||||
<path d="M34.2124 19V5.4H39.4924L41.6924 12.2L42.3724 14.74L43.0524 12.2L45.2524 5.4H50.4124V19H46.3324L46.5924 9.98L45.5324 13.68L43.7924 19H40.8324L39.0524 13.6L38.0324 10.02L38.2924 19H34.2124ZM52.4155 7.3V4.6H56.2955V7.3H52.4155ZM52.4155 19V8.14H56.2955V19H52.4155ZM58.1038 19V8.14H61.9838V9.58C62.6638 8.34 63.7438 7.76 65.0038 7.76C66.9638 7.76 68.6238 8.98 68.6238 11.78V19H64.7438V12.56C64.7438 11.34 64.3038 10.86 63.4838 10.86C62.6038 10.86 61.9838 11.58 61.9838 12.88V19H58.1038ZM70.9327 15.22V11.06H69.7327V8.14H70.9327V5.62H74.8127V8.14H76.9327V11.06H74.8127V14.6C74.8127 15.5 75.0327 16.06 76.2127 16.06H76.9327V19C76.4927 19.2 75.6727 19.38 74.6527 19.38C72.1527 19.38 70.9327 17.88 70.9327 15.22Z" fill="url(#paint8_radial_115_109)"/>
|
||||
<path d="M87.232 10.519C87.232 13.687 94.1125 11.2285 94.1125 15.832C94.1125 17.9935 92.3635 19.198 89.971 19.198C87.562 19.198 85.912 18.0925 85.417 15.832H87.001C87.364 17.1685 88.3705 17.8945 89.9875 17.8945C91.6705 17.8945 92.5615 17.152 92.5615 16.03C92.5615 12.598 85.681 15.1555 85.681 10.618C85.681 9.001 87.034 7.582 89.509 7.582C91.6705 7.582 93.403 8.6215 93.8155 11.014H92.215C91.8685 9.529 90.9115 8.8855 89.476 8.8855C88.057 8.8855 87.232 9.529 87.232 10.519ZM96.2499 16.4755V11.3935H95.0289V10.2385H96.2499V8.2255H97.7019V10.2385H99.6324V11.3935H97.7019V16.4755C97.7019 17.5315 98.0154 18.0265 99.3024 18.0265H99.5994V19.066C99.4344 19.1485 99.0714 19.198 98.6589 19.198C97.0254 19.198 96.2499 18.3235 96.2499 16.4755ZM102.516 13.093H101.064C101.345 11.1625 102.615 10.024 104.76 10.024C107.103 10.024 108.242 11.3935 108.242 13.4395V16.888C108.242 17.8945 108.324 18.5215 108.555 19H107.021C106.856 18.6535 106.806 18.142 106.79 17.614C106.047 18.7195 104.859 19.198 103.803 19.198C101.988 19.198 100.767 18.3565 100.767 16.69C100.767 15.4855 101.427 14.611 102.714 14.182C103.902 13.786 105.107 13.687 106.79 13.6705V13.4725C106.79 12.0535 106.13 11.278 104.628 11.278C103.374 11.278 102.698 11.971 102.516 13.093ZM102.252 16.657C102.252 17.4655 102.929 17.944 103.952 17.944C105.569 17.944 106.79 16.6735 106.79 15.172V14.7595C103.061 14.7925 102.252 15.5845 102.252 16.657ZM110.787 19V10.2385H112.239V11.5915C112.833 10.519 113.774 10.024 114.83 10.024C115.176 10.024 115.49 10.1065 115.655 10.2385V11.542C115.407 11.4595 115.094 11.4265 114.747 11.4265C112.998 11.4265 112.239 12.5155 112.239 14.0995V19H110.787ZM117.305 16.4755V11.3935H116.084V10.2385H117.305V8.2255H118.757V10.2385H120.688V11.3935H118.757V16.4755C118.757 17.5315 119.071 18.0265 120.358 18.0265H120.655V19.066C120.49 19.1485 120.127 19.198 119.714 19.198C118.081 19.198 117.305 18.3235 117.305 16.4755ZM129.809 16.1455C129.33 18.1915 127.862 19.198 125.865 19.198C123.324 19.198 121.79 17.482 121.79 14.6275C121.79 11.6575 123.324 10.024 125.783 10.024C128.258 10.024 129.743 11.7235 129.743 14.512V14.875H123.275C123.357 16.8385 124.281 17.944 125.865 17.944C127.103 17.944 127.977 17.35 128.291 16.1455H129.809ZM125.783 11.278C124.38 11.278 123.539 12.1525 123.324 13.786H128.225C128.027 12.169 127.152 11.278 125.783 11.278ZM131.843 19V10.2385H133.295V11.5915C133.889 10.519 134.829 10.024 135.885 10.024C136.232 10.024 136.545 10.1065 136.71 10.2385V11.542C136.463 11.4595 136.149 11.4265 135.803 11.4265C134.054 11.4265 133.295 12.5155 133.295 14.0995V19H131.843ZM141.763 19V7.78H143.281V13.192L148.413 7.78H150.327L145.047 13.291L150.459 19H148.413L143.281 13.621V19H141.763ZM152.06 9.067V7.12H153.512V9.067H152.06ZM152.06 19V10.2385H153.512V19H152.06ZM156.178 16.4755V11.3935H154.957V10.2385H156.178V8.2255H157.63V10.2385H159.56V11.3935H157.63V16.4755C157.63 17.5315 157.943 18.0265 159.23 18.0265H159.527V19.066C159.362 19.1485 158.999 19.198 158.587 19.198C156.953 19.198 156.178 18.3235 156.178 16.4755Z" fill="white" fill-opacity="0.55"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-3.00503 15.023) rotate(-10.029) scale(17.9572 17.784)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_115_109" x1="7.39036" y1="4.81308" x2="1.62975" y2="18.6894" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#18E299"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_115_109" x1="7.94816" y1="8.01562" x2="1.7612" y2="18.746" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint3_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.11404 20.8822) rotate(-75.7542) scale(21.6246 23.7772)">
|
||||
<stop stop-color="#00BBBB"/>
|
||||
<stop offset="0.712616" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint4_linear_115_109" x1="7.60205" y1="5.8709" x2="15.5561" y2="16.3719" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint5_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.84537 21.5181) rotate(-20.3525) scale(18.5603 17.32)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint6_linear_115_109" x1="16.8078" y1="13.0071" x2="10.0409" y2="22.9937" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00B1BC"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_115_109" x1="16.8078" y1="13.0071" x2="14.1687" y2="23.841" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint8_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(47.2781 7) rotate(19.0047) scale(67.5582 85.7506)">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.5"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,51 +0,0 @@
|
||||
<svg width="160" height="24" viewBox="0 0 160 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="white"/>
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint0_radial_115_86)"/>
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint1_linear_115_86)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M7.9354 21.1112C4.89702 21.0957 2.27411 19.4306 1.01347 16.279C-0.248375 13.1244 0.135612 9.18218 1.76165 5.64327C4.80004 5.65882 7.42295 7.32385 8.68359 10.4755C9.94543 13.63 9.56144 17.5723 7.9354 21.1112Z" stroke="url(#paint2_linear_115_86)" stroke-opacity="0.05" stroke-width="0.056338"/>
|
||||
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="white"/>
|
||||
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="url(#paint3_radial_115_86)"/>
|
||||
<path d="M16.6025 13.2251C14.8642 17.349 11.3512 20.1866 7.32411 21.2248C5.25257 17.624 4.82926 13.1324 6.56764 9.00855C8.30603 4.88472 11.819 2.04706 15.8461 1.00889C17.9176 4.60967 18.3409 9.10131 16.6025 13.2251Z" stroke="url(#paint4_linear_115_86)" stroke-opacity="0.05" stroke-width="0.056338"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="white"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint5_radial_115_86)"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint6_linear_115_86)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
|
||||
<path d="M16.5682 22.7874C13.2176 23.9184 9.81361 23.2124 7.2672 21.1975C8.49194 17.9068 11.0444 15.189 14.3959 14.0577C17.7465 12.9266 21.1504 13.6326 23.6968 15.6476C22.4721 18.9383 19.9196 21.656 16.5682 22.7874Z" stroke="url(#paint7_linear_115_86)" stroke-opacity="0.05" stroke-width="0.056338"/>
|
||||
<path d="M34.2124 19V5.4H39.4924L41.6924 12.2L42.3724 14.74L43.0524 12.2L45.2524 5.4H50.4124V19H46.3324L46.5924 9.98L45.5324 13.68L43.7924 19H40.8324L39.0524 13.6L38.0324 10.02L38.2924 19H34.2124ZM52.4155 7.3V4.6H56.2955V7.3H52.4155ZM52.4155 19V8.14H56.2955V19H52.4155ZM58.1038 19V8.14H61.9838V9.58C62.6638 8.34 63.7438 7.76 65.0038 7.76C66.9638 7.76 68.6238 8.98 68.6238 11.78V19H64.7438V12.56C64.7438 11.34 64.3038 10.86 63.4838 10.86C62.6038 10.86 61.9838 11.58 61.9838 12.88V19H58.1038ZM70.9327 15.22V11.06H69.7327V8.14H70.9327V5.62H74.8127V8.14H76.9327V11.06H74.8127V14.6C74.8127 15.5 75.0327 16.06 76.2127 16.06H76.9327V19C76.4927 19.2 75.6727 19.38 74.6527 19.38C72.1527 19.38 70.9327 17.88 70.9327 15.22Z" fill="#001E13"/>
|
||||
<path d="M87.232 10.519C87.232 13.687 94.1125 11.2285 94.1125 15.832C94.1125 17.9935 92.3635 19.198 89.971 19.198C87.562 19.198 85.912 18.0925 85.417 15.832H87.001C87.364 17.1685 88.3705 17.8945 89.9875 17.8945C91.6705 17.8945 92.5615 17.152 92.5615 16.03C92.5615 12.598 85.681 15.1555 85.681 10.618C85.681 9.001 87.034 7.582 89.509 7.582C91.6705 7.582 93.403 8.6215 93.8155 11.014H92.215C91.8685 9.529 90.9115 8.8855 89.476 8.8855C88.057 8.8855 87.232 9.529 87.232 10.519ZM96.2499 16.4755V11.3935H95.0289V10.2385H96.2499V8.2255H97.7019V10.2385H99.6324V11.3935H97.7019V16.4755C97.7019 17.5315 98.0154 18.0265 99.3024 18.0265H99.5994V19.066C99.4344 19.1485 99.0714 19.198 98.6589 19.198C97.0254 19.198 96.2499 18.3235 96.2499 16.4755ZM102.516 13.093H101.064C101.345 11.1625 102.615 10.024 104.76 10.024C107.103 10.024 108.242 11.3935 108.242 13.4395V16.888C108.242 17.8945 108.324 18.5215 108.555 19H107.021C106.856 18.6535 106.806 18.142 106.79 17.614C106.047 18.7195 104.859 19.198 103.803 19.198C101.988 19.198 100.767 18.3565 100.767 16.69C100.767 15.4855 101.427 14.611 102.714 14.182C103.902 13.786 105.107 13.687 106.79 13.6705V13.4725C106.79 12.0535 106.13 11.278 104.628 11.278C103.374 11.278 102.698 11.971 102.516 13.093ZM102.252 16.657C102.252 17.4655 102.929 17.944 103.952 17.944C105.569 17.944 106.79 16.6735 106.79 15.172V14.7595C103.061 14.7925 102.252 15.5845 102.252 16.657ZM110.787 19V10.2385H112.239V11.5915C112.833 10.519 113.774 10.024 114.83 10.024C115.176 10.024 115.49 10.1065 115.655 10.2385V11.542C115.407 11.4595 115.094 11.4265 114.747 11.4265C112.998 11.4265 112.239 12.5155 112.239 14.0995V19H110.787ZM117.305 16.4755V11.3935H116.084V10.2385H117.305V8.2255H118.757V10.2385H120.688V11.3935H118.757V16.4755C118.757 17.5315 119.071 18.0265 120.358 18.0265H120.655V19.066C120.49 19.1485 120.127 19.198 119.714 19.198C118.081 19.198 117.305 18.3235 117.305 16.4755ZM129.809 16.1455C129.33 18.1915 127.862 19.198 125.865 19.198C123.324 19.198 121.79 17.482 121.79 14.6275C121.79 11.6575 123.324 10.024 125.783 10.024C128.258 10.024 129.743 11.7235 129.743 14.512V14.875H123.275C123.357 16.8385 124.281 17.944 125.865 17.944C127.103 17.944 127.977 17.35 128.291 16.1455H129.809ZM125.783 11.278C124.38 11.278 123.539 12.1525 123.324 13.786H128.225C128.027 12.169 127.152 11.278 125.783 11.278ZM131.843 19V10.2385H133.295V11.5915C133.889 10.519 134.829 10.024 135.885 10.024C136.232 10.024 136.545 10.1065 136.71 10.2385V11.542C136.463 11.4595 136.149 11.4265 135.803 11.4265C134.054 11.4265 133.295 12.5155 133.295 14.0995V19H131.843ZM141.763 19V7.78H143.281V13.192L148.413 7.78H150.327L145.047 13.291L150.459 19H148.413L143.281 13.621V19H141.763ZM152.06 9.067V7.12H153.512V9.067H152.06ZM152.06 19V10.2385H153.512V19H152.06ZM156.178 16.4755V11.3935H154.957V10.2385H156.178V8.2255H157.63V10.2385H159.56V11.3935H157.63V16.4755C157.63 17.5315 157.943 18.0265 159.23 18.0265H159.527V19.066C159.362 19.1485 158.999 19.198 158.587 19.198C156.953 19.198 156.178 18.3235 156.178 16.4755Z" fill="#002719" fill-opacity="0.6"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_115_86" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-3.00503 15.023) rotate(-10.029) scale(17.9572 17.784)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_115_86" x1="7.39036" y1="4.81308" x2="1.62975" y2="18.6894" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#18E299"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_115_86" x1="7.94816" y1="8.01562" x2="1.7612" y2="18.746" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint3_radial_115_86" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.11404 20.8822) rotate(-75.7542) scale(21.6246 23.7772)">
|
||||
<stop stop-color="#00BBBB"/>
|
||||
<stop offset="0.712616" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint4_linear_115_86" x1="7.60205" y1="5.8709" x2="15.5561" y2="16.3719" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint5_radial_115_86" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.84537 21.5181) rotate(-20.3525) scale(18.5603 17.32)">
|
||||
<stop stop-color="#00B0BB"/>
|
||||
<stop offset="1" stop-color="#00DB65"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint6_linear_115_86" x1="16.8078" y1="13.0071" x2="10.0409" y2="22.9937" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00B1BC"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_115_86" x1="16.8078" y1="13.0071" x2="14.1687" y2="23.841" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 163 KiB |
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Login (password)'
|
||||
openapi: 'POST /api/auth/password/login'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Me'
|
||||
openapi: 'GET /api/auth/me'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Register (password)'
|
||||
openapi: 'POST /api/auth/password/register'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Strategies'
|
||||
openapi: 'GET /api/auth/strategies'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Create Entity'
|
||||
openapi: 'POST /api/data/entity/{entity}'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Delete Entity'
|
||||
openapi: 'DELETE /api/data/entity/{entity}/{id}'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Get Entity'
|
||||
openapi: 'GET /api/data/entity/{entity}/{id}'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'List Entity'
|
||||
openapi: 'GET /api/data/entity/{entity}'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Update Entity'
|
||||
openapi: 'PATCH /api/data/entity/{entity}/{id}'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Create Plant'
|
||||
openapi: 'POST /plants'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Delete Plant'
|
||||
openapi: 'DELETE /plants/{id}'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Get Plants'
|
||||
openapi: 'GET /plants'
|
||||
---
|
||||
@@ -1,391 +0,0 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": { "title": "bknd API", "version": "0.0.0" },
|
||||
"paths": {
|
||||
"/api/system/ping": {
|
||||
"get": {
|
||||
"summary": "Ping",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Pong",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pong": { "default": true, "type": "boolean" }
|
||||
},
|
||||
"required": ["pong"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["system"]
|
||||
}
|
||||
},
|
||||
"/api/system/config": {
|
||||
"get": {
|
||||
"summary": "Get config",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": { "type": "number" },
|
||||
"server": { "type": "object", "properties": {} },
|
||||
"data": { "type": "object", "properties": {} },
|
||||
"auth": { "type": "object", "properties": {} },
|
||||
"flows": { "type": "object", "properties": {} },
|
||||
"media": { "type": "object", "properties": {} }
|
||||
},
|
||||
"required": [
|
||||
"version",
|
||||
"server",
|
||||
"data",
|
||||
"auth",
|
||||
"flows",
|
||||
"media"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["system"]
|
||||
}
|
||||
},
|
||||
"/api/system/schema": {
|
||||
"get": {
|
||||
"summary": "Get config",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Config",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": { "type": "number" },
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server": { "type": "object", "properties": {} },
|
||||
"data": { "type": "object", "properties": {} },
|
||||
"auth": { "type": "object", "properties": {} },
|
||||
"flows": { "type": "object", "properties": {} },
|
||||
"media": { "type": "object", "properties": {} }
|
||||
},
|
||||
"required": ["server", "data", "auth", "flows", "media"]
|
||||
}
|
||||
},
|
||||
"required": ["version", "schema"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["system"]
|
||||
}
|
||||
},
|
||||
"/api/data/entity/{entity}": {
|
||||
"get": {
|
||||
"summary": "List entities",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of entities",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": { "id": { "type": "number" } },
|
||||
"required": ["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["data"]
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create entity",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "type": "object", "properties": {} }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Entity",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": { "id": { "type": "number" } },
|
||||
"required": ["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["data"]
|
||||
}
|
||||
},
|
||||
"/api/data/entity/{entity}/{id}": {
|
||||
"get": {
|
||||
"summary": "Get entity",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" }
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Entity",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": { "id": { "type": "number" } },
|
||||
"required": ["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["data"]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "Update entity",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" }
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "type": "object", "properties": {} }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Entity",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": { "id": { "type": "number" } },
|
||||
"required": ["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["data"]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete entity",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" }
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": { "200": { "description": "Entity deleted" } },
|
||||
"tags": ["data"]
|
||||
}
|
||||
},
|
||||
"/api/auth/password/login": {
|
||||
"post": {
|
||||
"summary": "Login",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": { "type": "string" },
|
||||
"password": { "type": "string" }
|
||||
},
|
||||
"required": ["email", "password"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"email": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["id", "email", "name"]
|
||||
}
|
||||
},
|
||||
"required": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["auth"]
|
||||
}
|
||||
},
|
||||
"/api/auth/password/register": {
|
||||
"post": {
|
||||
"summary": "Register",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": { "type": "string" },
|
||||
"password": { "type": "string" }
|
||||
},
|
||||
"required": ["email", "password"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"email": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["id", "email", "name"]
|
||||
}
|
||||
},
|
||||
"required": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["auth"]
|
||||
}
|
||||
},
|
||||
"/api/auth/me": {
|
||||
"get": {
|
||||
"summary": "Get me",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"email": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["id", "email", "name"]
|
||||
}
|
||||
},
|
||||
"required": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["auth"]
|
||||
}
|
||||
},
|
||||
"/api/auth/strategies": {
|
||||
"get": {
|
||||
"summary": "Get auth strategies",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Strategies",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"strategies": { "type": "object", "properties": {} }
|
||||
},
|
||||
"required": ["strategies"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["auth"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Config'
|
||||
openapi: 'GET /api/system/config'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Ping'
|
||||
openapi: 'GET /api/system/ping'
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: 'Schema'
|
||||
openapi: 'GET /api/system/schema'
|
||||
---
|
||||
64
docs/app/[[...slug]]/page.tsx
Normal file
64
docs/app/[[...slug]]/page.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { source } from "@/lib/source";
|
||||
import { DocsPage, DocsBody, DocsDescription, DocsTitle } from "fumadocs-ui/page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { createRelativeLink } from "fumadocs-ui/mdx";
|
||||
import { getMDXComponents } from "@/mdx-components";
|
||||
import { LLMCopyButton, ViewOptions } from "@/components/ai/page-actions";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const MDXContent = page.data.body;
|
||||
|
||||
return (
|
||||
<DocsPage
|
||||
toc={page.data.toc}
|
||||
full={page.data.full}
|
||||
tableOfContent={{
|
||||
style: "clerk",
|
||||
}}
|
||||
>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription className="mb-0">{page.data.description}</DocsDescription>
|
||||
<div className="flex flex-row gap-2 items-center border-b pt-2 pb-6">
|
||||
<LLMCopyButton markdownUrl={`${page.url}.mdx`} />
|
||||
<ViewOptions
|
||||
markdownUrl={`${page.url}.mdx`}
|
||||
githubUrl={`https://github.com/bknd-io/bknd/blob/dev/apps/docs/content/docs/${page.path}`}
|
||||
/>
|
||||
</div>
|
||||
<DocsBody>
|
||||
<MDXContent
|
||||
components={getMDXComponents({
|
||||
// this allows you to link to other pages with relative file paths
|
||||
a: createRelativeLink(source, page),
|
||||
})}
|
||||
/>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return source.generateParams();
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
icons: {
|
||||
icon: "/favicon.svg",
|
||||
},
|
||||
};
|
||||
}
|
||||
156
docs/app/_components/AnimatedGridPattern.tsx
Normal file
156
docs/app/_components/AnimatedGridPattern.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import {
|
||||
ComponentPropsWithoutRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { twMerge as cn } from "tailwind-merge";
|
||||
|
||||
export interface AnimatedGridPatternProps
|
||||
extends ComponentPropsWithoutRef<"svg"> {
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
strokeDasharray?: string | number;
|
||||
numSquares?: number;
|
||||
maxOpacity?: number;
|
||||
duration?: number;
|
||||
repeatDelay?: number;
|
||||
}
|
||||
|
||||
export function AnimatedGridPattern({
|
||||
width = 40,
|
||||
height = 40,
|
||||
x = -1,
|
||||
y = -1,
|
||||
strokeDasharray = 0,
|
||||
numSquares = 50,
|
||||
className,
|
||||
maxOpacity = 0.5,
|
||||
duration = 4,
|
||||
repeatDelay = 0.5,
|
||||
...props
|
||||
}: AnimatedGridPatternProps) {
|
||||
const id = useId();
|
||||
const containerRef = useRef<SVGSVGElement>(null);
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||
|
||||
const getPos = useCallback(() => {
|
||||
return [
|
||||
Math.floor((Math.random() * dimensions.width) / width),
|
||||
Math.floor((Math.random() * dimensions.height) / height),
|
||||
];
|
||||
}, [dimensions.width, dimensions.height, width, height]);
|
||||
|
||||
const generateSquares = useCallback(
|
||||
(count: number) => {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
id: i,
|
||||
pos: getPos(),
|
||||
}));
|
||||
},
|
||||
[getPos],
|
||||
);
|
||||
|
||||
const [squares, setSquares] = useState(() => generateSquares(numSquares));
|
||||
|
||||
const updateSquarePosition = (id: number) => {
|
||||
setSquares((currentSquares) =>
|
||||
currentSquares.map((sq) =>
|
||||
sq.id === id
|
||||
? {
|
||||
...sq,
|
||||
pos: getPos(),
|
||||
}
|
||||
: sq,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (dimensions.width && dimensions.height) {
|
||||
setSquares(generateSquares(numSquares));
|
||||
}
|
||||
}, [dimensions, numSquares, generateSquares]);
|
||||
|
||||
useEffect(() => {
|
||||
const current = containerRef.current;
|
||||
if (!current) return;
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
setDimensions({
|
||||
width: entry.contentRect.width,
|
||||
height: entry.contentRect.height,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(current);
|
||||
|
||||
return () => {
|
||||
resizeObserver.unobserve(current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={containerRef}
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 h-full w-full",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
id={id}
|
||||
width={width}
|
||||
height={height}
|
||||
patternUnits="userSpaceOnUse"
|
||||
x={x}
|
||||
y={y}
|
||||
>
|
||||
<path
|
||||
d={`M.5 ${height}V.5H${width}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeDasharray={strokeDasharray}
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill={`url(#${id})`} />
|
||||
<svg x={x} y={y} className="overflow-visible">
|
||||
{squares.map(({ pos: [x, y], id }, index) => (
|
||||
<motion.rect
|
||||
key={`${x}-${y}-${index}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: maxOpacity }}
|
||||
transition={{
|
||||
duration,
|
||||
repeat: 1,
|
||||
delay: index * 0.1,
|
||||
repeatDelay,
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
onAnimationComplete={() => updateSquarePosition(id)}
|
||||
width={width - 1}
|
||||
height={height - 1}
|
||||
x={x * width + 1}
|
||||
y={y * height + 1}
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
29
docs/app/_components/Callout/CalloutCaution.tsx
Normal file
29
docs/app/_components/Callout/CalloutCaution.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function CalloutCaution({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl my-4 px-4 py-3 flex gap-3 border bg-yellow-50 border-yellow-200 text-yellow-900 dark:bg-yellow-900/15 dark:border-yellow-900 dark:text-yellow-100 [&>div>p]:m-0">
|
||||
<div className="pt-1.5 text-yellow-500 dark:text-yellow-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="-2 -2 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16m0-13a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V6a1 1 0 0 1 1-1m0 10a1 1 0 1 1 0-2a1 1 0 0 1 0 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
{title && <span className="font-semibold">{title}</span>}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
docs/app/_components/Callout/CalloutDanger.tsx
Normal file
36
docs/app/_components/Callout/CalloutDanger.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function CalloutDanger({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl my-4 px-4 py-3 flex gap-3 border bg-red-50 border-red-200 text-red-900 dark:bg-red-900/15 dark:border-red-900 dark:text-red-100 [&>div>p]:m-0">
|
||||
<div className="pt-1.5 text-red-500 dark:text-red-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g>
|
||||
<path d="M16.34 9.322a1 1 0 1 0-1.364-1.463l-2.926 2.728L9.322 7.66A1 1 0 0 0 7.86 9.024l2.728 2.926l-2.927 2.728a1 1 0 1 0 1.364 1.462l2.926-2.727l2.728 2.926a1 1 0 1 0 1.462-1.363l-2.727-2.926z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12m11 9a9 9 0 1 1 0-18a9 9 0 0 1 0 18"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
{title && <span className="font-semibold">{title}</span>}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
docs/app/_components/Callout/CalloutInfo.tsx
Normal file
29
docs/app/_components/Callout/CalloutInfo.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function CalloutInfo({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl my-4 px-4 py-3 flex gap-3 border bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-900/15 dark:border-blue-900 dark:text-blue-100 [&>div>p]:m-0">
|
||||
<div className="pt-1.5 text-blue-500 dark:text-blue-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={18}
|
||||
height={18}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M11 9h2V7h-2m1 13c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m-1 15h2v-6h-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
{title && <span className="font-semibold">{title}</span>}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
docs/app/_components/Callout/CalloutPositive.tsx
Normal file
35
docs/app/_components/Callout/CalloutPositive.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function CalloutPositive({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl my-4 px-4 py-3 flex gap-3 border bg-green-50 border-green-200 text-green-900 dark:bg-green-900/15 dark:border-green-900 dark:text-green-100 [&>div>p]:m-0">
|
||||
<div className="pt-1.5 text-green-500 dark:text-green-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M21.546 5.111a1.5 1.5 0 0 1 0 2.121L10.303 18.475a1.6 1.6 0 0 1-2.263 0L2.454 12.89a1.5 1.5 0 1 1 2.121-2.121l4.596 4.596L19.424 5.111a1.5 1.5 0 0 1 2.122 0"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
{title && <span className="font-semibold">{title}</span>}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
docs/app/_components/Callout/index.ts
Normal file
4
docs/app/_components/Callout/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { CalloutPositive } from "./CalloutPositive";
|
||||
export { CalloutCaution } from "./CalloutCaution";
|
||||
export { CalloutDanger } from "./CalloutDanger";
|
||||
export { CalloutInfo } from "./CalloutInfo";
|
||||
48
docs/app/_components/FooterIcons.tsx
Normal file
48
docs/app/_components/FooterIcons.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ThemeToggle } from "fumadocs-ui/components/layout/theme-toggle";
|
||||
|
||||
export function FooterIcons() {
|
||||
return (
|
||||
<div className="flex justify-between items-center w-full px-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<a
|
||||
href="https://github.com/bknd-io/bknd"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub"
|
||||
className="text-fd-muted-foreground hover:text-fd-accent-foreground"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/952SFk8Tb8"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Discord"
|
||||
className="text-fd-muted-foreground hover:text-fd-accent-foreground"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-6.5 h-6.5"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18.942 5.556a16.3 16.3 0 0 0-4.126-1.3a12 12 0 0 0-.529 1.1a15.2 15.2 0 0 0-4.573 0a12 12 0 0 0-.535-1.1a16.3 16.3 0 0 0-4.129 1.3a17.4 17.4 0 0 0-2.868 11.662a15.8 15.8 0 0 0 4.963 2.521q.616-.847 1.084-1.785a10.6 10.6 0 0 1-1.706-.83q.215-.16.418-.331a11.66 11.66 0 0 0 10.118 0q.206.172.418.331q-.817.492-1.71.832a12.6 12.6 0 0 0 1.084 1.785a16.5 16.5 0 0 0 5.064-2.595a17.3 17.3 0 0 0-2.973-11.59M8.678 14.813a1.94 1.94 0 0 1-1.8-2.045a1.93 1.93 0 0 1 1.8-2.047a1.92 1.92 0 0 1 1.8 2.047a1.93 1.93 0 0 1-1.8 2.045m6.644 0a1.94 1.94 0 0 1-1.8-2.045a1.93 1.93 0 0 1 1.8-2.047a1.92 1.92 0 0 1 1.8 2.047a1.93 1.93 0 0 1-1.8 2.045"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ThemeToggle className="p-0" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
docs/app/_components/Logo.tsx
Normal file
24
docs/app/_components/Logo.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export function Logo() {
|
||||
return (
|
||||
<>
|
||||
<Image
|
||||
src="/logo/bknd_logo_white.svg"
|
||||
alt="bknd logo"
|
||||
width={110}
|
||||
height={24}
|
||||
className="hidden dark:block pl-1.5"
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
src="/logo/bknd_logo_black.svg"
|
||||
alt="bknd logo"
|
||||
width={110}
|
||||
height={24}
|
||||
className="block dark:hidden pl-1.5"
|
||||
priority
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
67
docs/app/_components/Search.tsx
Normal file
67
docs/app/_components/Search.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import { create } from "@orama/orama";
|
||||
import { useDocsSearch } from "fumadocs-core/search/client";
|
||||
import {
|
||||
SearchDialog,
|
||||
SearchDialogClose,
|
||||
SearchDialogContent,
|
||||
SearchDialogFooter,
|
||||
SearchDialogHeader,
|
||||
SearchDialogIcon,
|
||||
SearchDialogInput,
|
||||
SearchDialogList,
|
||||
SearchDialogOverlay,
|
||||
type SharedProps,
|
||||
} from "fumadocs-ui/components/dialog/search";
|
||||
import { useI18n } from "fumadocs-ui/contexts/i18n";
|
||||
|
||||
function initOrama() {
|
||||
return create({
|
||||
schema: { _: "string" },
|
||||
// https://docs.orama.com/open-source/supported-languages
|
||||
language: "english",
|
||||
});
|
||||
}
|
||||
|
||||
export default function DefaultSearchDialog(props: SharedProps) {
|
||||
const { locale } = useI18n(); // (optional) for i18n
|
||||
const { search, setSearch, query } = useDocsSearch({
|
||||
type: "static",
|
||||
initOrama,
|
||||
locale,
|
||||
from: "/api/search",
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchDialog
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
isLoading={query.isLoading}
|
||||
{...props}
|
||||
>
|
||||
<SearchDialogOverlay />
|
||||
<SearchDialogContent>
|
||||
<SearchDialogHeader>
|
||||
<SearchDialogIcon />
|
||||
<SearchDialogInput />
|
||||
<SearchDialogClose />
|
||||
</SearchDialogHeader>
|
||||
|
||||
<SearchDialogList items={query.data !== "empty" ? query.data : null} />
|
||||
|
||||
<SearchDialogFooter>
|
||||
<div className="flex w-full">
|
||||
<a
|
||||
href="https://orama.com"
|
||||
rel="noreferrer noopener"
|
||||
className="ml-auto text-xs text-nowrap text-fd-muted-foreground"
|
||||
>
|
||||
Powered by Orama
|
||||
</a>
|
||||
</div>
|
||||
</SearchDialogFooter>
|
||||
</SearchDialogContent>
|
||||
</SearchDialog>
|
||||
);
|
||||
}
|
||||
138
docs/app/_components/SearchByTags.tsx
Normal file
138
docs/app/_components/SearchByTags.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
"use client";
|
||||
|
||||
import { create } from "@orama/orama";
|
||||
import { clsx } from "clsx";
|
||||
import { useDocsSearch } from "fumadocs-core/search/client";
|
||||
import {
|
||||
SearchDialog,
|
||||
SearchDialogClose,
|
||||
SearchDialogContent,
|
||||
SearchDialogFooter,
|
||||
SearchDialogHeader,
|
||||
SearchDialogIcon,
|
||||
SearchDialogInput,
|
||||
SearchDialogList,
|
||||
SearchDialogOverlay,
|
||||
type SharedProps,
|
||||
} from "fumadocs-ui/components/dialog/search";
|
||||
import { buttonVariants } from "fumadocs-ui/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "fumadocs-ui/components/ui/popover";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
function initOrama() {
|
||||
return create({
|
||||
schema: {
|
||||
_: "string",
|
||||
title: "string",
|
||||
description: "string",
|
||||
url: "string",
|
||||
tags: "string[]",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const tagItems = [
|
||||
{
|
||||
name: "All",
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
name: "Documentation",
|
||||
description: "Developer documentation",
|
||||
value: "documentation",
|
||||
},
|
||||
{
|
||||
name: "Guide",
|
||||
description: "User Guide",
|
||||
value: "guide",
|
||||
},
|
||||
{
|
||||
name: "OpenAPI",
|
||||
description: "OpenAPI Reference",
|
||||
value: "openapi",
|
||||
},
|
||||
];
|
||||
|
||||
export default function DefaultSearchDialog(props: SharedProps) {
|
||||
const [tag, setTag] = useState<string>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { search, setSearch, query } = useDocsSearch({
|
||||
type: "static",
|
||||
initOrama,
|
||||
tag,
|
||||
from: "/api/search",
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchDialog
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
isLoading={query.isLoading}
|
||||
{...props}
|
||||
>
|
||||
<SearchDialogOverlay />
|
||||
<SearchDialogContent>
|
||||
<SearchDialogHeader>
|
||||
<SearchDialogIcon />
|
||||
<SearchDialogInput />
|
||||
<SearchDialogClose />
|
||||
</SearchDialogHeader>
|
||||
|
||||
<SearchDialogList items={query.data !== "empty" ? query.data : null} />
|
||||
|
||||
<SearchDialogFooter className="flex flex-row flex-wrap gap-2 items-center">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger
|
||||
className={buttonVariants({
|
||||
size: "sm",
|
||||
color: "ghost",
|
||||
className: "-m-1.5 me-auto",
|
||||
})}
|
||||
>
|
||||
<span className="text-fd-muted-foreground/80 me-2">Filter</span>
|
||||
{tagItems.find((item) => item.value === tag)?.name ?? "All"}
|
||||
<ChevronDown className="size-3.5 text-fd-muted-foreground" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex flex-col p-1 gap-1" align="start">
|
||||
{tagItems.map((item, i) => {
|
||||
const isSelected = item.value === tag;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => {
|
||||
setTag(item.value);
|
||||
setOpen(false);
|
||||
}}
|
||||
className={clsx(
|
||||
"rounded-lg text-start px-2 py-1.5",
|
||||
isSelected
|
||||
? "text-fd-primary bg-fd-primary/10"
|
||||
: "hover:text-fd-accent-foreground hover:bg-fd-accent",
|
||||
)}
|
||||
>
|
||||
<p className="font-medium mb-0.5">{item.name}</p>
|
||||
<p className="text-xs opacity-70">{item.description}</p>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<a
|
||||
href="https://orama.com"
|
||||
rel="noreferrer noopener"
|
||||
className="text-xs text-nowrap text-fd-muted-foreground"
|
||||
>
|
||||
Powered by Orama
|
||||
</a>
|
||||
</SearchDialogFooter>
|
||||
</SearchDialogContent>
|
||||
</SearchDialog>
|
||||
);
|
||||
}
|
||||
81
docs/app/_components/StackBlitz.tsx
Normal file
81
docs/app/_components/StackBlitz.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
export const examples = {
|
||||
adminRich: {
|
||||
path: "github/bknd-io/bknd-examples",
|
||||
startScript: "example-admin-rich",
|
||||
initialPath: "/data/schema"
|
||||
}
|
||||
};
|
||||
|
||||
export const StackBlitz = ({
|
||||
path,
|
||||
ratio = 9 / 16,
|
||||
example,
|
||||
...props
|
||||
}: {
|
||||
path?: string;
|
||||
ratio?: number;
|
||||
example?: keyof typeof examples;
|
||||
[key: string]: unknown;
|
||||
}) => {
|
||||
const selected = example ? examples[example] : undefined;
|
||||
const finalPath = path || selected?.path || "github/bknd-io/bknd-examples";
|
||||
|
||||
const params = new URLSearchParams({
|
||||
ctl: "1",
|
||||
hideExplorer: "1",
|
||||
embed: "1",
|
||||
view: "preview",
|
||||
...(selected || {}),
|
||||
...props
|
||||
});
|
||||
|
||||
const url = new URL(
|
||||
`https://stackblitz.com/${finalPath}?${params.toString()}`
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
paddingTop: `${ratio * 100}%`
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
width="100%"
|
||||
height="100%"
|
||||
src={url.toString()}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
border: "none"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "80%",
|
||||
opacity: 0.7,
|
||||
marginTop: "0.2rem",
|
||||
marginBottom: "1rem",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
If you’re having issues viewing it inline,{" "}
|
||||
<a href={url.toString()} target="_blank" rel="noreferrer">
|
||||
try in a new tab
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
5
docs/app/api/search/route.ts
Normal file
5
docs/app/api/search/route.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { source } from "@/lib/source";
|
||||
import { createFromSource } from "fumadocs-core/search/server";
|
||||
|
||||
export const revalidate = false;
|
||||
export const { staticGET: GET } = createFromSource(source);
|
||||
19
docs/app/global.css
Normal file
19
docs/app/global.css
Normal file
@@ -0,0 +1,19 @@
|
||||
@import "tailwindcss";
|
||||
@import "fumadocs-ui/css/neutral.css";
|
||||
@import "fumadocs-ui/css/preset.css";
|
||||
@import "fumadocs-openapi/css/preset.css";
|
||||
@import "fumadocs-twoslash/twoslash.css";
|
||||
|
||||
:root {
|
||||
--color-fd-primary: #008001;
|
||||
--color-fd-primary-foreground: #000000;
|
||||
--tw-prose-links: #008001;
|
||||
--tw-prose-code: #008001;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-fd-primary: #4ec9b0;
|
||||
--color-fd-primary-foreground: #000000;
|
||||
--tw-prose-links: #4ec9b0;
|
||||
--tw-prose-code: #4ec9b0;
|
||||
}
|
||||
17
docs/app/layout.config.tsx
Normal file
17
docs/app/layout.config.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
|
||||
import { Logo } from "@/app/_components/Logo";
|
||||
|
||||
/**
|
||||
* Shared layout configurations
|
||||
*
|
||||
* you can customise layouts individually from:
|
||||
* Home Layout: app/(home)/layout.tsx
|
||||
* Docs Layout: app/docs/layout.tsx
|
||||
*/
|
||||
export const baseOptions: BaseLayoutProps = {
|
||||
nav: {
|
||||
title: <Logo />,
|
||||
},
|
||||
// see https://fumadocs.dev/docs/ui/navigation/links
|
||||
links: [],
|
||||
};
|
||||
34
docs/app/layout.tsx
Normal file
34
docs/app/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import "./global.css";
|
||||
import { DocsLayout } from "fumadocs-ui/layouts/docs";
|
||||
import { baseOptions } from "./layout.config";
|
||||
import { source } from "@/lib/source";
|
||||
import type { ReactNode } from "react";
|
||||
import { Provider } from "./provider";
|
||||
import { AnimatedGridPattern } from "./_components/AnimatedGridPattern";
|
||||
import { FooterIcons } from "./_components/FooterIcons";
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className="relative bg-background">
|
||||
<div className="absolute top-0 inset-x-0 h-[300px] -z-10 pointer-events-none [mask-image:linear-gradient(to_bottom,black,transparent)]">
|
||||
<AnimatedGridPattern className="h-full w-full opacity-15 dark:opacity-10 text-[var(--color-fd-primary)] dark:text-[var(--color-fd-primary)]" />
|
||||
</div>
|
||||
|
||||
<Provider>
|
||||
<DocsLayout
|
||||
tree={source.pageTree}
|
||||
nav={{ ...baseOptions.nav }}
|
||||
themeSwitch={{
|
||||
enabled: true,
|
||||
component: <FooterIcons />,
|
||||
mode: "light-dark",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
</Provider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
12
docs/app/llms-full.txt/route.ts
Normal file
12
docs/app/llms-full.txt/route.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { source } from "@/lib/source";
|
||||
import { getLLMText } from "@/lib/get-llm-text";
|
||||
|
||||
// cached forever
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET() {
|
||||
const scan = source.getPages().map(getLLMText);
|
||||
const scanned = await Promise.all(scan);
|
||||
|
||||
return new Response(scanned.join("\n\n"));
|
||||
}
|
||||
18
docs/app/llms.mdx/[[...slug]]/route.ts
Normal file
18
docs/app/llms.mdx/[[...slug]]/route.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { getLLMText } from "@/lib/get-llm-text";
|
||||
import { source } from "@/lib/source";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET(_req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }) {
|
||||
const { slug } = await params;
|
||||
const page = source.getPage(slug);
|
||||
if (!page) notFound();
|
||||
|
||||
return new NextResponse(await getLLMText(page));
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return source.generateParams();
|
||||
}
|
||||
10
docs/app/provider.tsx
Normal file
10
docs/app/provider.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
"use client";
|
||||
import { RootProvider } from "fumadocs-ui/provider";
|
||||
import Search from "./_components/Search";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function Provider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<RootProvider search={{ SearchDialog: Search }}>{children}</RootProvider>
|
||||
);
|
||||
}
|
||||
164
docs/components/ai/page-actions.tsx
Normal file
164
docs/components/ai/page-actions.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Check, ChevronDown, Copy, ExternalLinkIcon } from "lucide-react";
|
||||
import { cn } from "../../lib/cn";
|
||||
import { useCopyButton } from "fumadocs-ui/utils/use-copy-button";
|
||||
import { buttonVariants } from "fumadocs-ui/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "fumadocs-ui/components/ui/popover";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
const cache = new Map<string, string>();
|
||||
|
||||
export function LLMCopyButton({
|
||||
/**
|
||||
* A URL to fetch the raw Markdown/MDX content of page
|
||||
*/
|
||||
markdownUrl,
|
||||
}: {
|
||||
markdownUrl: string;
|
||||
}) {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [checked, onClick] = useCopyButton(async () => {
|
||||
const cached = cache.get(markdownUrl);
|
||||
if (cached) return navigator.clipboard.writeText(cached);
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
"text/plain": fetch(markdownUrl).then(async (res) => {
|
||||
const content = await res.text();
|
||||
cache.set(markdownUrl, content);
|
||||
|
||||
return content;
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isLoading}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
color: "secondary",
|
||||
size: "sm",
|
||||
className: "gap-2 [&_svg]:size-3.5 [&_svg]:text-fd-muted-foreground",
|
||||
}),
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{checked ? <Check /> : <Copy />}
|
||||
Copy Markdown
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const optionVariants = cva(
|
||||
"text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4",
|
||||
);
|
||||
|
||||
export function ViewOptions({
|
||||
markdownUrl,
|
||||
githubUrl,
|
||||
}: {
|
||||
/**
|
||||
* A URL to the raw Markdown/MDX content of page
|
||||
*/
|
||||
markdownUrl: string;
|
||||
|
||||
/**
|
||||
* Source file URL on GitHub
|
||||
*/
|
||||
githubUrl: string;
|
||||
}) {
|
||||
const items = useMemo(() => {
|
||||
const fullMarkdownUrl =
|
||||
typeof window !== "undefined" ? new URL(markdownUrl, window.location.origin) : "loading";
|
||||
const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`;
|
||||
|
||||
return [
|
||||
/* {
|
||||
title: "Open in GitHub",
|
||||
href: githubUrl,
|
||||
icon: (
|
||||
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
),
|
||||
}, */
|
||||
{
|
||||
title: "Open in ChatGPT",
|
||||
href: `https://chatgpt.com/?${new URLSearchParams({
|
||||
hints: "search",
|
||||
q,
|
||||
})}`,
|
||||
icon: (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>OpenAI</title>
|
||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Open in Claude",
|
||||
href: `https://claude.ai/new?${new URLSearchParams({
|
||||
q,
|
||||
})}`,
|
||||
icon: (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Anthropic</title>
|
||||
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [githubUrl, markdownUrl]);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
color: "secondary",
|
||||
size: "sm",
|
||||
className: "gap-2",
|
||||
}),
|
||||
)}
|
||||
>
|
||||
Open
|
||||
<ChevronDown className="size-3.5 text-fd-muted-foreground" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex flex-col overflow-auto">
|
||||
{items.map((item) => (
|
||||
<a
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className={cn(optionVariants())}
|
||||
>
|
||||
{item.icon}
|
||||
{item.title}
|
||||
<ExternalLinkIcon className="text-fd-muted-foreground size-3.5 ms-auto" />
|
||||
</a>
|
||||
))}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
title: "Concepts"
|
||||
---
|
||||
|
||||
There are a few important concepts around **bknd** you should be aware of in order to understand
|
||||
it better.
|
||||
|
||||
## Primitive
|
||||
Instead of going to deep into specific use-cases, **bknd** is designed to implement the lower
|
||||
level functionalities in order to provide a solid foundation for building more complex logic.
|
||||
|
||||
## Minimal database
|
||||
Although we focus on SQL-databases, we follow the priciple of moving as much logic as possible to
|
||||
the application layer. E.g. default values are computed application side instead
|
||||
of instruction the database to do so. This enables us to support a wide range of databases in
|
||||
the future.
|
||||
|
||||
## Lowest runtime first
|
||||
The main development target is Cloudflare Workers, since it's the most limited JavaScript
|
||||
runtime built on Web Standards. If it runs there, it will run (probably) anywhere.
|
||||
|
||||
## Web Standards
|
||||
...
|
||||
@@ -1,24 +1,25 @@
|
||||
---
|
||||
title: bknd.config.ts
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
The central configuration file to extend bknd should be placed in the root of your project, so that the [CLI](/usage/cli#using-configuration-file-bknd-config) can automatically pick it up. It allows to:
|
||||
* define your database connection centrally
|
||||
* pass in [initial configuration](/usage/database#initial-structure) or [data seeds](/usage/database#seeding-the-database) when booting the first time
|
||||
* add plugins to the app
|
||||
* hook into system events
|
||||
* define custom routes and endpoints
|
||||
|
||||
- define your database connection centrally
|
||||
- pass in [initial configuration](/usage/database#initial-structure) or [data seeds](/usage/database#seeding-the-database) when booting the first time
|
||||
- add plugins to the app
|
||||
- hook into system events
|
||||
- define custom routes and endpoints
|
||||
|
||||
A simple example of a `bknd.config.ts` file:
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
}
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
@@ -26,57 +27,62 @@ export default {
|
||||
|
||||
The `BkndConfig` extends the [`CreateAppConfig`](/usage/introduction#configuration-createappconfig) type with the following properties:
|
||||
|
||||
{/* <AutoTypeTable path="../app/src/adapter/index.ts" name="BkndConfig" /> */}
|
||||
|
||||
|
||||
```typescript
|
||||
export type BkndConfig = CreateAppConfig & {
|
||||
// return the app configuration as object or from a function
|
||||
app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
|
||||
// called before the app is built
|
||||
beforeBuild?: (app: App) => Promise<void>;
|
||||
// called after the app has been built
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
// passed as the first argument to the `App.build` method
|
||||
buildConfig?: Parameters<App["build"]>[0];
|
||||
// force the app to be recreated
|
||||
force?: boolean;
|
||||
// the id of the app, defaults to `app`
|
||||
id?: string;
|
||||
}
|
||||
// return the app configuration as object or from a function
|
||||
app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
|
||||
// called before the app is built
|
||||
beforeBuild?: (app: App) => Promise<void>;
|
||||
// called after the app has been built
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
// passed as the first argument to the `App.build` method
|
||||
buildConfig?: Parameters<App["build"]>[0];
|
||||
// force the app to be recreated
|
||||
force?: boolean;
|
||||
// the id of the app, defaults to `app`
|
||||
id?: string;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
The supported configuration file extensions are `js`, `ts`, `mjs`, `cjs` and `json`. Throughout the documentation, we'll use `ts` for the file extension.
|
||||
|
||||
### `app` (CreateAppConfig)
|
||||
|
||||
The `app` property is a function that returns a `CreateAppConfig` object. It allows to pass in the environment variables to the configuration object.
|
||||
|
||||
```typescript
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
app: ({ env }) => ({
|
||||
connection: {
|
||||
url: env.DB_URL,
|
||||
}
|
||||
})
|
||||
app: ({ env }) => ({
|
||||
connection: {
|
||||
url: env.DB_URL,
|
||||
},
|
||||
}),
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
See [Database](/usage/database) for more information on how to configure the database connection.
|
||||
|
||||
### `beforeBuild`
|
||||
|
||||
The `beforeBuild` property is an async function that is called before the app is built. It allows to modify the app instance that may influence the build process.
|
||||
|
||||
```typescript
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
beforeBuild: async (app: App) => {
|
||||
// do something that has to happen before the app is built
|
||||
}
|
||||
}
|
||||
beforeBuild: async (app: App) => {
|
||||
// do something that has to happen before the app is built
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `onBuilt`
|
||||
|
||||
The `onBuilt` property is an async function that is called after the app has been built. It allows to hook into the app after it has been built. This is useful for defining event listeners or register custom routes, as both the event manager and the server are recreated during the build process.
|
||||
|
||||
```typescript
|
||||
@@ -84,21 +90,22 @@ import { type App, AppEvents } from "bknd";
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
onBuilt: async (app: App) => {
|
||||
console.log("App built", app.version());
|
||||
|
||||
// registering an event listener
|
||||
app.emgr.onEvent(AppEvents.AppRequest, (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
})
|
||||
|
||||
// registering a custom route
|
||||
app.server.get("/hello", (c) => c.text("Hello World"));
|
||||
}
|
||||
}
|
||||
onBuilt: async (app: App) => {
|
||||
console.log("App built", app.version());
|
||||
|
||||
// registering an event listener
|
||||
app.emgr.onEvent(AppEvents.AppRequest, (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
|
||||
// registering a custom route
|
||||
app.server.get("/hello", (c) => c.text("Hello World"));
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `force` & `id`
|
||||
|
||||
The `force` property is a boolean that forces the app to be recreated. This is mainly useful for serverless environments where the execution environment is re-used, and you may or may not want to recreate the app on every request.
|
||||
|
||||
The `id` property is the reference in a cache map. You may create multiple instances of apps in the same process by using different ids (e.g. multi tenant applications).
|
||||
@@ -113,12 +120,12 @@ Depending on which framework or runtime you're using to run bknd, the configurat
|
||||
|
||||
```typescript
|
||||
export type RuntimeBkndConfig<Args = any> = BkndConfig<Args> & {
|
||||
// the path to the dist folder to serve static assets for the admin UI
|
||||
distPath?: string;
|
||||
// custom middleware to serve static assets for the admin UI
|
||||
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
|
||||
// options for the admin UI
|
||||
adminOptions?: AdminControllerOptions | false;
|
||||
// the path to the dist folder to serve static assets for the admin UI
|
||||
distPath?: string;
|
||||
// custom middleware to serve static assets for the admin UI
|
||||
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
|
||||
// options for the admin UI
|
||||
adminOptions?: AdminControllerOptions | false;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -129,13 +136,12 @@ export type RuntimeBkndConfig<Args = any> = BkndConfig<Args> & {
|
||||
```typescript
|
||||
type NextjsEnv = NextApiRequest["env"];
|
||||
export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
|
||||
cleanRequest?: { searchParams?: string[] };
|
||||
cleanRequest?: { searchParams?: string[] };
|
||||
};
|
||||
```
|
||||
|
||||
Next.js adds the mounted path to the request object, so that the `cleanRequest` property can be used to remove the mounted path from the request URL. See other frameworks for more information on how to configure them.
|
||||
|
||||
|
||||
## Using the configuration file
|
||||
|
||||
The configuration file is automatically picked up if you're using the [CLI](/usage/cli). This allows interacting with your application using the `bknd` command. For example, you can run the following command in the root of your project to start an instance:
|
||||
@@ -150,4 +156,4 @@ When serving your application, you need to make sure to import the contents of y
|
||||
2. create a `bknd.ts` file inside your app folder which exports helper functions to instantiate the bknd instance and retrieve the API.
|
||||
3. create a catch-all route file at `src/api/[[...bknd]]/route.ts` which serves the bknd API.
|
||||
|
||||
This way, your application and the CLI are using the same configuration.
|
||||
This way, your application and the CLI are using the same configuration.
|
||||
141
docs/content/docs/(documentation)/extending/events.mdx
Normal file
141
docs/content/docs/(documentation)/extending/events.mdx
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Events & Hooks
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
bknd comes with a powerful built-in event system that allows you to hook into the app lifecycle and extend its functionality. You can hook into these events in two ways:
|
||||
|
||||
- `async`: Your listener is not blocking the main execution flow. E.g. on Cloudflare Workers, by default, the `ExecutionContext`'s `waitUntil` method is used so that the listeners runs after the response is sent.
|
||||
- `sync`: Your listener is blocking the main execution flow. This allows to abort the request in your custom conditions. Some events also allow to return a modified event payload.
|
||||
|
||||
<Callout type="info">
|
||||
Don't mistake `async` with JavaScript's `async` keyword. The `async` keyword
|
||||
is used to indicate that the listener is not blocking the main execution flow.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
### Listening to events
|
||||
|
||||
You can listen to events by using the `EventManager` exposed at `app.emgr`. To register a listener, you can use either of these methods:
|
||||
|
||||
- `onEvent`: Register a listener for a specific event with a typed event.
|
||||
- `on`: Register a listener for an event by its slug.
|
||||
- `onAny`: Register a listener for all events.
|
||||
|
||||
```typescript
|
||||
import { createApp, AppEvents } from "bknd";
|
||||
const app = createApp();
|
||||
|
||||
app.emgr.onEvent(AppEvents.AppRequest, async (event) => {
|
||||
// ^? AppRequest
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
|
||||
app.emgr.on("app-request", async (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
|
||||
app.emgr.onAny(async (event) => {
|
||||
console.log("Event received", event.slug);
|
||||
});
|
||||
```
|
||||
|
||||
You may want to register your listeners inside [`bknd.config.ts`](/extending/config) to make sure they are registered before the app is built:
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import { AppEvents } from "bknd";
|
||||
|
||||
export default {
|
||||
onBuilt: (app) => {
|
||||
app.emgr.onEvent(AppEvents.AppRequest, async (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Setting a mode
|
||||
|
||||
By default, listeners are registered as `async` listeners, meaning that the listener is not blocking the main execution flow. You can change this by passing the mode as the third argument:
|
||||
|
||||
```typescript
|
||||
app.emgr.onEvent(
|
||||
AppEvents.AppRequest,
|
||||
async (event) => {
|
||||
console.log("Request received", event.request.url);
|
||||
},
|
||||
{ mode: "sync" },
|
||||
);
|
||||
```
|
||||
|
||||
This works for all three methods.
|
||||
|
||||
## App Events
|
||||
|
||||
These events are emitted by the `App` class and are available on the `AppEvents` object.
|
||||
|
||||
```typescript
|
||||
import { AppEvents } from "bknd";
|
||||
```
|
||||
|
||||
Available events:
|
||||
|
||||
{/* <AutoTypeTable path="../app/src/App.ts" name="AppEvents" /> */}
|
||||
| Event | Params | Description |
|
||||
|:------|:--------|:------------|
|
||||
| `AppConfigUpdatedEvent` | `{ app: App }` | Emitted when the app configuration is updated |
|
||||
| `AppBuiltEvent` | `{ app: App }` | Emitted when the app is built |
|
||||
| `AppFirstBoot` | `{ app: App }` | Emitted when the app is first booted |
|
||||
| `AppRequest` | `{ app: App, request: Request }` | Emitted when a request is received |
|
||||
| `AppBeforeResponse` | `{ app: App, request: Request, response: Response }` | Emitted before a response is sent |
|
||||
|
||||
## Database Events
|
||||
|
||||
These events are emitted by the `Database` class and are available on the `DatabaseEvents` object. These are divided by events triggered by the `Mutator` and `Repository` classes.
|
||||
|
||||
```typescript
|
||||
import { DatabaseEvents } from "bknd";
|
||||
```
|
||||
|
||||
### Mutator Events
|
||||
|
||||
These events are emitted during database mutations (insert, update, delete operations).
|
||||
|
||||
| Event | Params | Description |
|
||||
| :-------------------- | :-------------------------------------------------------------------------------------- | :---------------------------------------------------------- |
|
||||
| `MutatorInsertBefore` | `{ entity: Entity, data: EntityData }` | Emitted before inserting a new record. Can modify the data. |
|
||||
| `MutatorInsertAfter` | `{ entity: Entity, data: EntityData, changed: EntityData }` | Emitted after inserting a new record. |
|
||||
| `MutatorUpdateBefore` | `{ entity: Entity, entityId: PrimaryFieldType, data: EntityData }` | Emitted before updating a record. Can modify the data. |
|
||||
| `MutatorUpdateAfter` | `{ entity: Entity, entityId: PrimaryFieldType, data: EntityData, changed: EntityData }` | Emitted after updating a record. |
|
||||
| `MutatorDeleteBefore` | `{ entity: Entity, entityId: PrimaryFieldType }` | Emitted before deleting a record. |
|
||||
| `MutatorDeleteAfter` | `{ entity: Entity, entityId: PrimaryFieldType, data: EntityData }` | Emitted after deleting a record. |
|
||||
|
||||
### Repository Events
|
||||
|
||||
These events are emitted during database queries (find operations).
|
||||
|
||||
| Event | Params | Description |
|
||||
| :------------------------- | :--------------------------------------------------------- | :--------------------------------------- |
|
||||
| `RepositoryFindOneBefore` | `{ entity: Entity, options: RepoQuery }` | Emitted before finding a single record. |
|
||||
| `RepositoryFindOneAfter` | `{ entity: Entity, options: RepoQuery, data: EntityData }` | Emitted after finding a single record. |
|
||||
| `RepositoryFindManyBefore` | `{ entity: Entity, options: RepoQuery }` | Emitted before finding multiple records. |
|
||||
| `RepositoryFindManyAfter` | `{ entity: Entity, options: RepoQuery, data: EntityData }` | Emitted after finding multiple records. |
|
||||
|
||||
## Media Events
|
||||
|
||||
These events are emitted by the `Storage` class and are available on the `MediaEvents` object.
|
||||
|
||||
```typescript
|
||||
import { MediaEvents } from "bknd";
|
||||
```
|
||||
|
||||
| Event | Params | Description |
|
||||
| :------------------ | :--------------------------------------- | :----------------------------------------------------------------------- |
|
||||
| `FileUploadedEvent` | `{ file: FileBody } & FileUploadPayload` | Emitted when a file is successfully uploaded. Can modify the event data. |
|
||||
| `FileDeletedEvent` | `{ name: string }` | Emitted when a file is deleted. |
|
||||
| `FileAccessEvent` | `{ name: string }` | Emitted when a file is accessed. |
|
||||
|
||||
## Auth Events
|
||||
|
||||
Coming soon.
|
||||
@@ -1,69 +1,95 @@
|
||||
---
|
||||
title: 'Astro'
|
||||
description: 'Run bknd inside Astro'
|
||||
title: "Astro"
|
||||
description: "Run bknd inside Astro"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
import InstallBknd from '/snippets/install-bknd.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Astro and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI Starter">
|
||||
Create a new Astro CLI starter project by running the following command:
|
||||
### CLI Starter
|
||||
|
||||
```sh
|
||||
npx bknd create -i astro
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Manual">
|
||||
Create a new Astro project by following the [official guide](https://docs.astro.build/en/install-and-setup/), and then install bknd as a dependency:
|
||||
Create a new Astro CLI starter project by running the following command:
|
||||
|
||||
<InstallBknd />
|
||||
```sh
|
||||
npx bknd create -i astro
|
||||
```
|
||||
|
||||
<Note>The guide below assumes you're using Astro v4. We've experienced issues with Astro DB
|
||||
using v5, see [this issue](https://github.com/withastro/astro/issues/12474).</Note>
|
||||
### Manual
|
||||
|
||||
For the Astro integration to work, you also need to [add the react integration](https://docs.astro.build/en/guides/integrations-guide/react/):
|
||||
```bash
|
||||
npx astro add react
|
||||
```
|
||||
Create a new Astro project by following the [official guide](https://docs.astro.build/en/install-and-setup/), and then install bknd as a dependency:
|
||||
|
||||
You also need to make sure to set the output to `server` in your Astro config:
|
||||
```js {6}
|
||||
// astro.config.mjs
|
||||
import { defineConfig } from "astro/config";
|
||||
import react from "@astrojs/react";
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
export default defineConfig({
|
||||
output: "server",
|
||||
integrations: [react()]
|
||||
});
|
||||
```
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you don't want to use React with Astro, there is also an option to serve the bknd Admin UI
|
||||
statically using Astro's middleware. In case you're interested in this, feel free to reach
|
||||
out in [Discord](https://discord.gg/952SFk8Tb8) or open an [issue on GitHub](https://github.com/bknd-io/bknd/issues/new).
|
||||
</Note>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Callout type="info">
|
||||
The guide below assumes you're using Astro v4. We've experienced issues with
|
||||
Astro DB using v5, see [this
|
||||
issue](https://github.com/withastro/astro/issues/12474).
|
||||
</Callout>
|
||||
|
||||
For the Astro integration to work, you also need to [add the react integration](https://docs.astro.build/en/guides/integrations-guide/react/):
|
||||
|
||||
```bash
|
||||
npx astro add react
|
||||
```
|
||||
|
||||
You also need to make sure to set the output to `server` in your Astro config:
|
||||
|
||||
```js {6}
|
||||
// astro.config.mjs
|
||||
import { defineConfig } from "astro/config";
|
||||
import react from "@astrojs/react";
|
||||
|
||||
export default defineConfig({
|
||||
output: "server", // [!code highlight]
|
||||
integrations: [react()],
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
If you don't want to use React with Astro, there is also an option to serve
|
||||
the bknd Admin UI statically using Astro's middleware. In case you're
|
||||
interested in this, feel free to reach out in
|
||||
[Discord](https://discord.gg/952SFk8Tb8) or open an [issue on
|
||||
GitHub](https://github.com/bknd-io/bknd/issues/new).
|
||||
</Callout>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Warning>
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please verify your version by running `node -v`, and [upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Warning>
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { AstroBkndConfig } from "bknd/adapter/astro";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db"
|
||||
},
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies AstroBkndConfig;
|
||||
```
|
||||
|
||||
@@ -75,9 +101,10 @@ export type AstroBkndConfig<Env = AstroEnv> = FrameworkBkndConfig<Env>;
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the `bknd.config.ts` file:
|
||||
|
||||
```ts src/bknd.ts
|
||||
```ts title="src/bknd.ts"
|
||||
import type { AstroGlobal } from "astro";
|
||||
import { getApp as getBkndApp } from "bknd/adapter/astro";
|
||||
import config from "../bknd.config";
|
||||
@@ -85,37 +112,37 @@ import config from "../bknd.config";
|
||||
export { config };
|
||||
|
||||
export async function getApp() {
|
||||
return await getBkndApp(config);
|
||||
return await getBkndApp(config);
|
||||
}
|
||||
|
||||
export async function getApi(
|
||||
astro: AstroGlobal,
|
||||
opts?: { mode: "static" } | { mode?: "dynamic"; verify?: boolean },
|
||||
astro: AstroGlobal,
|
||||
opts?: { mode: "static" } | { mode?: "dynamic"; verify?: boolean },
|
||||
) {
|
||||
const app = await getApp();
|
||||
if (opts?.mode !== "static" && opts?.verify) {
|
||||
const api = app.getApi({ headers: astro.request.headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
const app = await getApp();
|
||||
if (opts?.mode !== "static" && opts?.verify) {
|
||||
const api = app.getApi({ headers: astro.request.headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
return app.getApi();
|
||||
}
|
||||
```
|
||||
|
||||
Create a new catch-all route at `src/pages/api/[...api].ts`.
|
||||
|
||||
```ts src/pages/api/[...api].ts
|
||||
```ts title="src/pages/api/[...api].ts"
|
||||
import { serve } from "bknd/adapter/astro";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const ALL = serve({
|
||||
connection: {
|
||||
// location of your local Astro DB
|
||||
// make sure to use a remote URL in production
|
||||
url: "file:.astro/content.db"
|
||||
}
|
||||
connection: {
|
||||
// location of your local Astro DB
|
||||
// make sure to use a remote URL in production
|
||||
url: "file:.astro/content.db",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -124,8 +151,10 @@ special case of astro, you may also use your Astro DB credentials since it's als
|
||||
under the hood. Refer to the [Astro DB documentation](https://docs.astro.build/en/guides/astro-db/) for more information.
|
||||
|
||||
## Enabling the Admin UI
|
||||
|
||||
Create a new catch-all route at `src/pages/admin/[...admin].astro`:
|
||||
```jsx src/pages/admin/[...admin].astro
|
||||
|
||||
```jsx title="src/pages/admin/[...admin].astro"
|
||||
---
|
||||
import { Admin } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
@@ -154,10 +183,12 @@ export const prerender = false;
|
||||
```
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You use the API in both static and SSR pages. Just note that on static pages, authentication
|
||||
might not work as expected, because Cookies are not available in the static context.
|
||||
|
||||
Here is an example of using the API in static context:
|
||||
|
||||
```jsx
|
||||
---
|
||||
import { getApi } from "bknd/adapter/astro";
|
||||
@@ -173,6 +204,7 @@ const { data } = await api.data.readMany("todos");
|
||||
```
|
||||
|
||||
On SSR pages, you can also access the authenticated user:
|
||||
|
||||
```jsx
|
||||
---
|
||||
import { getApi } from "bknd/adapter/astro";
|
||||
@@ -194,4 +226,4 @@ export const prerender = false;
|
||||
```
|
||||
|
||||
Check the [astro repository example](https://github.com/bknd-io/bknd/tree/main/examples/astro)
|
||||
for more implementation details or a [fully working example using Astro DB](https://github.com/dswbx/bknd-astro-example).
|
||||
for more implementation details or a [fully working example using Astro DB](https://github.com/dswbx/bknd-astro-example).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["nextjs", "react-router", "astro", "vite"]
|
||||
}
|
||||
@@ -1,42 +1,62 @@
|
||||
---
|
||||
title: 'Next.js'
|
||||
description: 'Run bknd inside Next.js'
|
||||
title: "Next.js"
|
||||
description: "Run bknd inside Next.js"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
import InstallBknd from '/snippets/install-bknd.mdx';
|
||||
|
||||
|
||||
## Installation
|
||||
To get started with Next.js and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI Starter">
|
||||
Create a new Next.js CLI starter project by running the following command:
|
||||
To get started with Next.js and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter.
|
||||
|
||||
```sh
|
||||
npx bknd create -i nextjs
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Manual">
|
||||
Create a new Next.js project by following the [official guide](https://nextjs.org/docs/pages/api-reference/cli/create-next-app), and then install bknd as a dependency:
|
||||
### CLI Starter
|
||||
|
||||
Create a new Next.js CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i nextjs
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Next.js project by following the [official guide](https://nextjs.org/docs/pages/api-reference/cli/create-next-app), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
<InstallBknd />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
<Warning>
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please verify your version by running `node -v`, and [upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Warning>
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { NextjsBkndConfig } from "bknd/adapter/nextjs";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db"
|
||||
},
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies NextjsBkndConfig;
|
||||
```
|
||||
|
||||
@@ -45,39 +65,45 @@ See [bknd.config.ts](/extending/config) for more information on how to configure
|
||||
```typescript
|
||||
type NextjsEnv = NextApiRequest["env"];
|
||||
export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
|
||||
cleanRequest?: { searchParams?: string[] };
|
||||
cleanRequest?: { searchParams?: string[] };
|
||||
};
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the `bknd.config.ts` file:
|
||||
|
||||
```ts src/bknd.ts
|
||||
import { type NextjsBkndConfig, getApp as getBkndApp } from "bknd/adapter/nextjs";
|
||||
```ts title="src/bknd.ts"
|
||||
import {
|
||||
type NextjsBkndConfig,
|
||||
getApp as getBkndApp,
|
||||
} from "bknd/adapter/nextjs";
|
||||
import { headers } from "next/headers";
|
||||
import config from "../bknd.config";
|
||||
|
||||
export { config };
|
||||
|
||||
export async function getApp() {
|
||||
return await getBkndApp(config, process.env);
|
||||
return await getBkndApp(config, process.env);
|
||||
}
|
||||
|
||||
export async function getApi(opts?: { verify?: boolean }) {
|
||||
const app = await getApp();
|
||||
if (opts?.verify) {
|
||||
const api = app.getApi({ headers: await headers() });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
const app = await getApp();
|
||||
if (opts?.verify) {
|
||||
const api = app.getApi({ headers: await headers() });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
return app.getApi();
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Now to expose the API, create a catch-all route file at `src/api/[[...bknd]]/route.ts`:
|
||||
```ts src/api/[[...bknd]]/route.ts
|
||||
|
||||
```ts title="src/api/[[...bknd]]/route.ts"
|
||||
import { config } from "@/bknd";
|
||||
import { serve } from "bknd/adapter/nextjs";
|
||||
|
||||
@@ -85,12 +111,12 @@ import { serve } from "bknd/adapter/nextjs";
|
||||
export const runtime = "edge";
|
||||
|
||||
const handler = serve({
|
||||
...config,
|
||||
cleanRequest: {
|
||||
// depending on what name you used for the catch-all route,
|
||||
// you need to change this to clean it from the request.
|
||||
searchParams: ["bknd"],
|
||||
},
|
||||
...config,
|
||||
cleanRequest: {
|
||||
// depending on what name you used for the catch-all route,
|
||||
// you need to change this to clean it from the request.
|
||||
searchParams: ["bknd"],
|
||||
},
|
||||
});
|
||||
|
||||
export const GET = handler;
|
||||
@@ -101,43 +127,48 @@ export const DELETE = handler;
|
||||
```
|
||||
|
||||
## Enabling the Admin UI
|
||||
|
||||
Create a page at `admin/[[...admin]]/page.tsx`:
|
||||
```tsx admin/[[...admin]]/page.tsx
|
||||
|
||||
```tsx title="admin/[[...admin]]/page.tsx"
|
||||
import { Admin } from "bknd/ui";
|
||||
import { getApi } from "@/bknd";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default async function AdminPage() {
|
||||
// make sure to verify auth using headers
|
||||
const api = await getApi({ verify: true });
|
||||
// make sure to verify auth using headers
|
||||
const api = await getApi({ verify: true });
|
||||
|
||||
return (
|
||||
<Admin
|
||||
withProvider={{ user: api.getUser() }}
|
||||
config={{
|
||||
basepath: "/admin",
|
||||
logo_return_path: "/../",
|
||||
color_scheme: "system",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Admin
|
||||
withProvider={{ user: api.getUser() }}
|
||||
config={{
|
||||
basepath: "/admin",
|
||||
logo_return_path: "/../",
|
||||
color_scheme: "system",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You can use the `getApi` helper function we've already set up to fetch and mutate in static pages and server components:
|
||||
|
||||
```tsx app/page.tsx
|
||||
```tsx title="app/page.tsx"
|
||||
import { getApi } from "@/bknd";
|
||||
|
||||
export default async function Home() {
|
||||
const api = await getApi();
|
||||
const { data: todos } = await api.data.readMany("todos", { limit: 5 });
|
||||
const api = await getApi();
|
||||
const { data: todos } = await api.data.readMany("todos", { limit: 5 });
|
||||
|
||||
return <ul>
|
||||
return (
|
||||
<ul>
|
||||
{todos.map((todo) => (
|
||||
<li key={String(todo.id)}>{todo.title}</li>
|
||||
<li key={String(todo.id)}>{todo.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: "React Router"
|
||||
description: "Run bknd inside React Router"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with React Router and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new React Router CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i react-router
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new React Router project by following the [official guide](https://reactrouter.com/start/framework/installation), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { ReactRouterBkndConfig } from "bknd/adapter/react-router";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies ReactRouterBkndConfig;
|
||||
```
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd. The `ReactRouterBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
type ReactRouterEnv = NodeJS.ProcessEnv;
|
||||
type ReactRouterFunctionArgs = {
|
||||
request: Request;
|
||||
};
|
||||
export type ReactRouterBkndConfig<Env = ReactRouterEnv> =
|
||||
FrameworkBkndConfig<Env>;
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the `bknd.config.ts` file:
|
||||
|
||||
```ts title="app/bknd.ts"
|
||||
import {
|
||||
type ReactRouterBkndConfig,
|
||||
getApp as getBkndApp,
|
||||
} from "bknd/adapter/react-router";
|
||||
import config from "../bknd.config";
|
||||
|
||||
export { config };
|
||||
|
||||
// you may adjust this function depending on your runtime environment.
|
||||
// e.g. when deploying to cloudflare workers, you'd want the FunctionArgs to be passed in
|
||||
// to resolve environment variables
|
||||
export async function getApp() {
|
||||
return await getBkndApp(config, process.env as any);
|
||||
}
|
||||
|
||||
export async function getApi(
|
||||
args?: { request: Request },
|
||||
opts?: { verify?: boolean },
|
||||
) {
|
||||
const app = await getApp();
|
||||
if (opts?.verify) {
|
||||
const api = app.getApi({ headers: args?.request.headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Create a new api splat route file at `app/routes/api.$.ts`:
|
||||
|
||||
```ts title="app/routes/api.$.ts"
|
||||
import { getApp } from "~/bknd";
|
||||
|
||||
const handler = async (args: { request: Request }) => {
|
||||
const app = await getApp();
|
||||
return app.fetch(args.request);
|
||||
};
|
||||
|
||||
export const loader = handler;
|
||||
export const action = handler;
|
||||
```
|
||||
|
||||
## Enabling the Admin UI
|
||||
|
||||
Create a new splat route file at `app/routes/admin.$.tsx`:
|
||||
|
||||
```tsx title="app/routes/admin.$.tsx"
|
||||
import { lazy, Suspense, useSyncExternalStore } from "react";
|
||||
import { type LoaderFunctionArgs, useLoaderData } from "react-router";
|
||||
import { getApi } from "~/bknd";
|
||||
|
||||
const Admin = lazy(() =>
|
||||
import("bknd/ui").then((mod) => ({ default: mod.Admin })),
|
||||
);
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export const loader = async (args: LoaderFunctionArgs) => {
|
||||
const api = await getApi(args, { verify: true });
|
||||
return {
|
||||
user: api.getUser(),
|
||||
};
|
||||
};
|
||||
|
||||
export default function AdminPage() {
|
||||
const { user } = useLoaderData<typeof loader>();
|
||||
// derived from https://github.com/sergiodxa/remix-utils
|
||||
// @ts-ignore
|
||||
const hydrated = useSyncExternalStore(
|
||||
() => {},
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
if (!hydrated) return null;
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<Admin
|
||||
withProvider={{ user }}
|
||||
config={{ basepath: "/admin", logo_return_path: "/../" }}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You can use the `getApi` helper function we've already set up to fetch and mutate:
|
||||
|
||||
```tsx title="app/routes/_index.tsx"
|
||||
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
|
||||
import { getApi } from "~/bknd";
|
||||
|
||||
export const loader = async (args: LoaderFunctionArgs) => {
|
||||
// use authentication from request
|
||||
const api = await getApi(args, { verify: true });
|
||||
const { data } = await api.data.readMany("todos");
|
||||
return { data, user: api.getUser() };
|
||||
};
|
||||
|
||||
export default function Index() {
|
||||
const { data, user } = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Data</h1>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<h1>User</h1>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,36 +1,59 @@
|
||||
---
|
||||
title: 'Vite'
|
||||
description: 'Run bknd inside Vite'
|
||||
title: "Vite"
|
||||
description: "Run bknd inside Vite"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
import InstallBknd from '/snippets/install-bknd.mdx';
|
||||
|
||||
Vite is a powerful toolkit to accelerate your local development.
|
||||
|
||||
## Installation
|
||||
|
||||
Create a new vite project by following the [official guide](https://vite.dev/guide/#scaffolding-your-first-vite-project)
|
||||
and then install bknd as a dependency:
|
||||
<InstallBknd />
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
Additionally, install required dependencies:
|
||||
|
||||
```bash
|
||||
npm install @hono/vite-dev-server
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
<Warning>
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please verify your version by running `node -v`, and [upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Warning>
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { ViteBkndConfig } from "bknd/adapter/vite";
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db"
|
||||
}
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
} satisfies ViteBkndConfig;
|
||||
```
|
||||
|
||||
@@ -43,22 +66,25 @@ export type ViteBkndConfig<Env = ViteEnv> = RuntimeBkndConfig<Env> & {};
|
||||
```
|
||||
|
||||
## Serve the API
|
||||
|
||||
To serve the **bknd** API, you first have to create a local server file for you vite environment.
|
||||
Create a `server.ts` file:
|
||||
|
||||
```typescript
|
||||
```typescript title="server.ts"
|
||||
import { serve } from "bknd/adapter/vite";
|
||||
import config from "./bknd.config";
|
||||
|
||||
export default serve(config)
|
||||
export default serve(config);
|
||||
```
|
||||
|
||||
You can also run your vite server in `mode: "fresh"`, this will re-create the app on every fetch.
|
||||
This is only useful for when working on the `bknd` repository directly.
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Next, adjust your `vite.config.ts` to look like the following:
|
||||
```ts
|
||||
|
||||
```ts title="vite.config.ts"
|
||||
import { devServer } from "bknd/adapter/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
@@ -67,13 +93,13 @@ import tsconfigPaths from "vite-tsconfig-paths";
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
tsconfigPaths(),
|
||||
devServer({
|
||||
// point to your previously created server file
|
||||
entry: "./server.ts"
|
||||
})
|
||||
]
|
||||
react(),
|
||||
tsconfigPaths(),
|
||||
devServer({
|
||||
// point to your previously created server file
|
||||
entry: "./server.ts",
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -82,64 +108,68 @@ looks like an empty project. That's because we only registered the API, head ove
|
||||
http://localhost:5174/api/system/config to see **bknd** respond.
|
||||
|
||||
## Serve the Admin UI
|
||||
|
||||
After adding the API, you can easily add the Admin UI by simply returning it in your `App.tsx`.
|
||||
Replace all of its content with the following:
|
||||
|
||||
```tsx
|
||||
```tsx title="App.tsx"
|
||||
import { Admin } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default function App() {
|
||||
return <Admin withProvider />
|
||||
return <Admin withProvider />;
|
||||
}
|
||||
```
|
||||
|
||||
Now http://localhost:5174/ should give you the Admin UI.
|
||||
|
||||
## Customizations
|
||||
|
||||
This is just the bare minimum and may not always fulfill your requirements. There are a few
|
||||
options you can make use of to adjust it according to your setup.
|
||||
|
||||
### Use custom HTML to serve the Admin UI
|
||||
|
||||
There might be cases you want to be sure to be in control over the HTML that is being used.
|
||||
`bknd` generates it automatically, but you use your own one as follows:
|
||||
|
||||
```typescript server.ts
|
||||
```typescript title="server.ts"
|
||||
import { serve, addViteScript } from "bknd/adapter/vite";
|
||||
import { readFile } from "node:fs/promises"
|
||||
import { readFile } from "node:fs/promises";
|
||||
import config from "./bknd.config";
|
||||
|
||||
let html = await readFile("./index.html", "utf-8");
|
||||
|
||||
// then add it as an option
|
||||
export default serve({
|
||||
...config,
|
||||
adminOptions: {
|
||||
html: addViteScript(html),
|
||||
// optionally, you can change the base path for the admin UI
|
||||
adminBasePath: "/admin"
|
||||
}
|
||||
})
|
||||
export default serve({
|
||||
...config,
|
||||
adminOptions: {
|
||||
html: addViteScript(html),
|
||||
// optionally, you can change the base path for the admin UI
|
||||
adminBasePath: "/admin",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The vite scripts has to be added manually currently, as adding them automatically with
|
||||
`@hono/vite-dev-server` is buggy. This may change in the future.
|
||||
|
||||
### Use a custom entry point
|
||||
|
||||
By default, the entry point `/src/main.tsx` is used and should fit most cases. If that's not you,
|
||||
you can supply a different one like so:
|
||||
|
||||
```typescript server.ts
|
||||
```typescript title="server.ts"
|
||||
import { serve } from "bknd/adapter/vite";
|
||||
import config from "./bknd.config";
|
||||
|
||||
// the configuration given is optional
|
||||
export default serve({
|
||||
...config,
|
||||
adminOptions: {
|
||||
forceDev: {
|
||||
mainPath: "/src/special.tsx"
|
||||
}
|
||||
}
|
||||
...config,
|
||||
adminOptions: {
|
||||
forceDev: {
|
||||
mainPath: "/src/special.tsx",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
```
|
||||
@@ -1,44 +1,65 @@
|
||||
---
|
||||
title: 'AWS Lambda'
|
||||
description: 'Run bknd inside AWS Lambda'
|
||||
title: "AWS Lambda"
|
||||
description: "Run bknd inside AWS Lambda"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
import InstallBknd from '/snippets/install-bknd.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with AWS Lambda and bknd you can either install the package manually and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI Starter">
|
||||
Create a new Bun CLI starter project by running the following command:
|
||||
### CLI Starter
|
||||
|
||||
```sh
|
||||
npx bknd create -i aws
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Manual">
|
||||
Create a new AWS Lambda project and then install bknd as a dependency:
|
||||
Create a new Bun CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i aws
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new AWS Lambda project and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
<InstallBknd />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Serve the API
|
||||
|
||||
To serve the API, you can use the `serveLambda` function of the AWS Lambda adapter.
|
||||
|
||||
```tsx index.mjs
|
||||
```tsx title="index.mjs"
|
||||
import { serveLambda } from "bknd/adapter/aws";
|
||||
import { libsql } from "bknd/data";
|
||||
import { libsql } from "bknd";
|
||||
|
||||
export const handler = serveLambda({
|
||||
connection: libsql({
|
||||
url: "libsql://your-database-url.turso.io",
|
||||
authToken: "your-auth-token",
|
||||
}),
|
||||
connection: libsql({
|
||||
url: "libsql://your-database-url.turso.io",
|
||||
authToken: "your-auth-token",
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Although the runtime would support database as a file, we don't recommend it. You'd need to also bundle the native dependencies which increases the deployment size and cold start time. Instead, we recommend you to use [LibSQL on Turso](/usage/database#sqlite-using-libsql-on-turso).
|
||||
|
||||
## Serve the Admin UI
|
||||
|
||||
Lambda functions should be as small as possible. Therefore, the static files for the admin panel should not be served from node_modules like with the Node adapter.
|
||||
|
||||
Instead, we recommend to copy the static files and bundle them with the lambda function. To copy the static files, you can use the `copy-assets` command:
|
||||
@@ -49,35 +70,37 @@ npx bknd copy-assets --out static
|
||||
|
||||
This will copy the static files to the `static` directory and then serve them from there:
|
||||
|
||||
```tsx index.mjs {8-11}
|
||||
```tsx title="index.mjs"
|
||||
import { serveLambda } from "bknd/adapter/aws";
|
||||
|
||||
export const handler = serveLambda({
|
||||
connection: {
|
||||
url: process.env.DB_URL!,
|
||||
authToken: process.env.DB_AUTH_TOKEN!
|
||||
},
|
||||
assets: {
|
||||
mode: "local",
|
||||
root: "./static"
|
||||
}
|
||||
connection: {
|
||||
url: process.env.DB_URL!,
|
||||
authToken: process.env.DB_AUTH_TOKEN!,
|
||||
},
|
||||
assets: {
|
||||
// [!code highlight]
|
||||
mode: "local", // [!code highlight]
|
||||
root: "./static", // [!code highlight]
|
||||
}, // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
To deploy a lambda function, you could follow these steps:
|
||||
|
||||
1. Create an IAM role with a trust policy that allows lambda to assume the role.
|
||||
2. Attach the `AWSLambdaBasicExecutionRole` policy to the role.
|
||||
3. Bundle the lambda function with the static files (e.g. using esbuild)
|
||||
4. Create a zip file with the bundled lambda function
|
||||
5. Create a lambda function
|
||||
5. Create a lambda function
|
||||
6. Create a function URL for the lambda function & make it publicly accessible (optional)
|
||||
|
||||
Depending on your use case, you may want to skip step 6 and use the AWS API Gateway to serve the lambda function. Here is an [example deployment script](https://github.com/bknd-io/bknd/blob/main/examples/aws-lambda/deploy.sh) which creates the AWS resources described above, bundles the lambda function and uploads it.
|
||||
|
||||
|
||||
### Using the CLI starter
|
||||
|
||||
The CLI starter example includes a basic build script that creates the required AWS resources, copies the static files, bundles the lambda function and uploads it. To deploy the lambda function, you can run:
|
||||
|
||||
```bash
|
||||
@@ -86,7 +109,7 @@ npm run deploy
|
||||
|
||||
To make adjustments to the lambda function created (e.g. architecture, memory, timeout, etc.) you can edit the head section of the `deploy.sh` script.
|
||||
|
||||
```sh deploy.sh
|
||||
```sh title="deploy.sh"
|
||||
# cat deploy.sh | head -12
|
||||
FUNCTION_NAME="bknd-lambda"
|
||||
ROLE_NAME="bknd-lambda-execution-role"
|
||||
@@ -105,4 +128,3 @@ To clean up AWS resources created by the deployment script, you can run:
|
||||
```bash
|
||||
npm run clean
|
||||
```
|
||||
|
||||
@@ -1,46 +1,65 @@
|
||||
---
|
||||
title: 'Bun'
|
||||
description: 'Run bknd inside Bun'
|
||||
title: "Bun"
|
||||
description: "Run bknd inside Bun"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
import InstallBknd from '/snippets/install-bknd.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Bun and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI Starter">
|
||||
Create a new Bun CLI starter project by running the following command:
|
||||
## CLI Starter
|
||||
|
||||
```sh
|
||||
npx bknd create -i bun
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Manual">
|
||||
Create a new Bun project and then install bknd as a dependency:
|
||||
Create a new Bun CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i bun
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Bun project and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
<InstallBknd />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Serve the API & static files
|
||||
|
||||
The `serve` function of the Bun adapter makes sure to also serve the static files required for
|
||||
the admin panel.
|
||||
|
||||
``` tsx
|
||||
// index.ts
|
||||
```tsx title="index.ts"
|
||||
import { serve } from "bknd/adapter/bun";
|
||||
|
||||
// if the configuration is omitted, it uses an in-memory database
|
||||
serve({
|
||||
connection: {
|
||||
url: "file:data.db"
|
||||
}
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Run the application using Bun by executing:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
```
|
||||
@@ -1,33 +1,51 @@
|
||||
---
|
||||
title: 'Cloudflare'
|
||||
description: 'Run bknd inside Cloudflare Worker'
|
||||
title: "Cloudflare"
|
||||
description: "Run bknd inside Cloudflare Worker"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
import InstallBknd from '/snippets/install-bknd.mdx';
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Cloudflare Workers and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI Starter">
|
||||
Create a new Cloudflare CLI starter project by running the following command:
|
||||
### CLI Starter
|
||||
|
||||
```sh
|
||||
npx bknd create -i cloudflare
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Manual">
|
||||
Create a new cloudflare worker project by following the [official guide](https://developers.cloudflare.com/workers/get-started/guide/), and then install bknd as a dependency:
|
||||
Create a new Cloudflare CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i cloudflare
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new cloudflare worker project by following the [official guide](https://developers.cloudflare.com/workers/get-started/guide/), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
<InstallBknd />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Serve the API
|
||||
|
||||
If you don't choose anything specific, the following code will use the `warm` mode and uses the first D1 binding it finds. See the
|
||||
chapter [Using a different mode](#using-a-different-mode) for available modes.
|
||||
|
||||
```ts src/index.ts
|
||||
```ts title="src/index.ts"
|
||||
import { serve, d1 } from "bknd/adapter/cloudflare";
|
||||
|
||||
// scans your environment for the first D1 binding it finds
|
||||
@@ -35,23 +53,24 @@ export default serve();
|
||||
|
||||
// manually specifying a D1 binding:
|
||||
export default serve<Env>({
|
||||
app: ({ env }) => d1({ binding: env.D1_BINDING })
|
||||
app: ({ env }) => d1({ binding: env.D1_BINDING }),
|
||||
});
|
||||
|
||||
// or specify binding using `bindings`
|
||||
export default serve<Env>({
|
||||
bindings: ({ env }) => ({ db: env.D1_BINDING })
|
||||
bindings: ({ env }) => ({ db: env.D1_BINDING }),
|
||||
});
|
||||
|
||||
// or use LibSQL
|
||||
export default serve<Env>({
|
||||
app: ({ env }) => ({ url: env.DB_URL })
|
||||
app: ({ env }) => ({ url: env.DB_URL }),
|
||||
});
|
||||
```
|
||||
|
||||
For more information about the connection object when using LibSQL, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Now run the worker:
|
||||
|
||||
```bash
|
||||
wrangler dev
|
||||
```
|
||||
@@ -60,76 +79,86 @@ And confirm it works by opening [http://localhost:8787](http://localhost:8787) i
|
||||
your browser.
|
||||
|
||||
## Serve the Admin UI
|
||||
|
||||
Now in order to also server the static admin files, you have to modify the `wrangler.toml` to include the static assets. You can do so by either serving the static using the new [Assets feature](https://developers.cloudflare.com/workers/static-assets/), or the deprecated [Workers Site](https://developers.cloudflare.com/workers/configuration/sites/configuration/).
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Assets">
|
||||
Make sure your assets point to the static assets included in the bknd package:
|
||||
### Assets
|
||||
|
||||
```toml wrangler.toml
|
||||
assets = { directory = "node_modules/bknd/dist/static" }
|
||||
```
|
||||
Make sure your assets point to the static assets included in the bknd package:
|
||||
|
||||
</Tab>
|
||||
<Tab title="Workers Sites">
|
||||
Make sure your site points to the static assets included in the bknd package:
|
||||
```toml title="wrangler.toml"
|
||||
assets = { directory = "node_modules/bknd/dist/static" }
|
||||
```
|
||||
|
||||
```toml wrangler.toml
|
||||
[site]
|
||||
bucket = "node_modules/bknd/dist/static"
|
||||
```
|
||||
### Workers Sites
|
||||
|
||||
And then modify the worker entry as follows:
|
||||
```ts {2, 6} src/index.ts
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
import manifest from "__STATIC_CONTENT_MANIFEST";
|
||||
Make sure your site points to the static assets included in the bknd package:
|
||||
|
||||
export default serve<Env>({
|
||||
app: () => ({/* ... */}),
|
||||
manifest
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
```toml title="wrangler.toml"
|
||||
[site]
|
||||
bucket = "node_modules/bknd/dist/static"
|
||||
```
|
||||
|
||||
And then modify the worker entry as follows:
|
||||
|
||||
```ts title="src/index.ts"
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
import manifest from "__STATIC_CONTENT_MANIFEST"; // [!code highlight]
|
||||
|
||||
export default serve<Env>({
|
||||
app: () => ({
|
||||
/* ... */
|
||||
}),
|
||||
manifest, // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
## Adding custom routes
|
||||
|
||||
You can also add custom routes by defining them after the app has been built, like so:
|
||||
```ts {5-7}
|
||||
|
||||
```ts
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve<Env>({
|
||||
// ...
|
||||
onBuilt: async (app) => {
|
||||
app.server.get("/hello", (c) => c.json({ hello: "world" }));
|
||||
}
|
||||
// ...
|
||||
onBuilt: async (app) => {
|
||||
// [!code highlight]
|
||||
app.server.get("/hello", (c) => c.json({ hello: "world" })); // [!code highlight]
|
||||
}, // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
The property `app.server` is a [Hono](https://hono.dev/) instance, you can literally anything you can do with Hono.
|
||||
|
||||
## Using a different mode
|
||||
|
||||
With the Cloudflare Workers adapter, you're being offered to 4 modes to choose from (default:
|
||||
`warm`):
|
||||
|
||||
| Mode | Description | Use Case |
|
||||
|:----------|:-------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| :-------- | :----------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fresh` | On every request, the configuration gets refetched, app built and then served. | Ideal if you don't want to deal with eviction, KV or Durable Objects. |
|
||||
| `warm` | It tries to keep the built app in memory for as long as possible, and rebuilds if evicted. | Better response times, should be the default choice. |
|
||||
| `cache` | The configuration is fetched from KV to reduce the initial roundtrip to the database. | Generally faster response times with irregular access patterns. |
|
||||
| `durable` | The bknd app is ran inside a Durable Object and can be configured to stay alive. | Slowest boot time, but fastest responses. Can be kept alive for as long as you want, giving similar response times as server instances. |
|
||||
|
||||
### Modes: `fresh` and `warm`
|
||||
|
||||
To use either `fresh` or `warm`, all you have to do is adding the desired mode to `cloudflare.
|
||||
mode`, like so:
|
||||
|
||||
```ts
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve({
|
||||
// ...
|
||||
mode: "fresh" // mode: "fresh" | "warm" | "cache" | "durable"
|
||||
// ...
|
||||
mode: "fresh", // mode: "fresh" | "warm" | "cache" | "durable"
|
||||
});
|
||||
```
|
||||
|
||||
### Mode: `cache`
|
||||
|
||||
For the cache mode to work, you also need to specify the KV to be used. For this, use the
|
||||
`bindings` property:
|
||||
|
||||
@@ -137,29 +166,32 @@ For the cache mode to work, you also need to specify the KV to be used. For this
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve<Env>({
|
||||
// ...
|
||||
mode: "cache",
|
||||
bindings: ({ env }) => ({ kv: env.KV })
|
||||
// ...
|
||||
mode: "cache",
|
||||
bindings: ({ env }) => ({ kv: env.KV }),
|
||||
});
|
||||
```
|
||||
|
||||
### Mode: `durable` (advanced)
|
||||
|
||||
To use the `durable` mode, you have to specify the Durable Object to extract from your
|
||||
environment, and additionally export the `DurableBkndApp` class:
|
||||
|
||||
```ts
|
||||
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";
|
||||
|
||||
export { DurableBkndApp };
|
||||
export default serve<Env>({
|
||||
// ...
|
||||
mode: "durable",
|
||||
bindings: ({ env }) => ({ dobj: env.DOBJ }),
|
||||
keepAliveSeconds: 60 // optional
|
||||
// ...
|
||||
mode: "durable",
|
||||
bindings: ({ env }) => ({ dobj: env.DOBJ }),
|
||||
keepAliveSeconds: 60, // optional
|
||||
});
|
||||
```
|
||||
|
||||
Next, you need to define the Durable Object in your `wrangler.toml` file (refer to the [Durable
|
||||
Objects](https://developers.cloudflare.com/durable-objects/) documentation):
|
||||
|
||||
```toml
|
||||
[[durable_objects.bindings]]
|
||||
name = "DOBJ"
|
||||
@@ -173,25 +205,28 @@ new_classes = ["DurableBkndApp"]
|
||||
Since the communication between the Worker and Durable Object is serialized, the `onBuilt`
|
||||
property won't work. To use it (e.g. to specify special routes), you need to extend from the
|
||||
`DurableBkndApp`:
|
||||
|
||||
```ts
|
||||
import type { App } from "bknd";
|
||||
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve({
|
||||
// ...
|
||||
mode: "durable",
|
||||
bindings: ({ env }) => ({ dobj: env.DOBJ }),
|
||||
keepAliveSeconds: 60 // optional
|
||||
// ...
|
||||
mode: "durable",
|
||||
bindings: ({ env }) => ({ dobj: env.DOBJ }),
|
||||
keepAliveSeconds: 60, // optional
|
||||
});
|
||||
|
||||
export class CustomDurableBkndApp extends DurableBkndApp {
|
||||
async onBuilt(app: App) {
|
||||
app.modules.server.get("/custom/endpoint", (c) => c.text("Custom"));
|
||||
}
|
||||
async onBuilt(app: App) {
|
||||
app.modules.server.get("/custom/endpoint", (c) => c.text("Custom"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In case you've already deployed your Worker, the deploy command may complain about a new class
|
||||
being used. To fix this issue, you need to add a "rename migration":
|
||||
|
||||
```toml
|
||||
[[durable_objects.bindings]]
|
||||
name = "DOBJ"
|
||||
@@ -208,26 +243,27 @@ deleted_classes = ["DurableBkndApp"]
|
||||
```
|
||||
|
||||
## D1 Sessions (experimental)
|
||||
|
||||
D1 now supports to enable [global read replication](https://developers.cloudflare.com/d1/best-practices/read-replication/). This allows to reduce latency by reading from the closest region. In order for this to work, D1 has to be started from a bookmark. You can enable this behavior on bknd by setting the `d1.session` property:
|
||||
|
||||
```typescript src/index.ts
|
||||
```typescript title="src/index.ts"
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve({
|
||||
// currently recommended to use "fresh" mode
|
||||
// otherwise consecutive requests will use the same bookmark
|
||||
mode: "fresh",
|
||||
// ...
|
||||
d1: {
|
||||
// enables D1 sessions
|
||||
session: true,
|
||||
// (optional) restrict the transport, options: "header" | "cookie"
|
||||
// if not specified, it supports both
|
||||
transport: "cookie",
|
||||
// (optional) choose session constraint if not bookmark present
|
||||
// options: "first-primary" | "first-unconstrained"
|
||||
first: "first-primary"
|
||||
}
|
||||
// currently recommended to use "fresh" mode
|
||||
// otherwise consecutive requests will use the same bookmark
|
||||
mode: "fresh",
|
||||
// ...
|
||||
d1: {
|
||||
// enables D1 sessions
|
||||
session: true,
|
||||
// (optional) restrict the transport, options: "header" | "cookie"
|
||||
// if not specified, it supports both
|
||||
transport: "cookie",
|
||||
// (optional) choose session constraint if not bookmark present
|
||||
// options: "first-primary" | "first-unconstrained"
|
||||
first: "first-primary",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -235,4 +271,4 @@ If bknd is used in a stateful user context (like in a browser), it'll automatica
|
||||
|
||||
```bash
|
||||
curl -H "x-cf-d1-session: <bookmark>" ...
|
||||
```
|
||||
```
|
||||
@@ -1,14 +1,17 @@
|
||||
---
|
||||
title: 'Docker'
|
||||
description: 'Official docker image for bknd'
|
||||
title: "Docker"
|
||||
description: "Official docker image for bknd"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
# Official `bknd` Docker image
|
||||
|
||||
The docker image intentially doesn't copy any data into the image for now, so you can copy the Dockerfile and build the image anywhere.
|
||||
|
||||
Locate the Dockerfile either by pulling the [repository](https://github.com/bknd-io/bknd) and navigating to the `docker` directory, or download from [here](https://github.com/bknd-io/bknd/blob/main/docker/Dockerfile).
|
||||
|
||||
## Building the Docker image
|
||||
|
||||
To build the Docker image, run the following command:
|
||||
|
||||
```bash
|
||||
@@ -16,11 +19,13 @@ docker build -t bknd .
|
||||
```
|
||||
|
||||
If you want to override the bknd version used, you can pass a `VERSION` build argument:
|
||||
|
||||
```bash
|
||||
docker build --build-arg VERSION=<version> -t bknd .
|
||||
```
|
||||
|
||||
## Running the Docker container
|
||||
|
||||
To run the Docker container, run the following command:
|
||||
|
||||
```bash
|
||||
@@ -34,6 +39,7 @@ docker run -p 1337:1337 -e ARGS="--db-url file:/data/data.db" bknd
|
||||
```
|
||||
|
||||
To mount the data directory to the host, you can use the `-v` flag:
|
||||
|
||||
```bash
|
||||
docker run -p 1337:1337 -v /path/to/data:/data bknd
|
||||
```
|
||||
@@ -42,7 +48,7 @@ docker run -p 1337:1337 -v /path/to/data:/data bknd
|
||||
|
||||
If you want to use docker compose and build the image directly from the git repository.
|
||||
|
||||
```yaml compose.yml
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
bknd:
|
||||
pull_policy: build
|
||||
@@ -55,12 +61,11 @@ services:
|
||||
- ${DATA_DIR:-.}/data:/data
|
||||
```
|
||||
|
||||
|
||||
The docker compose file can be extended to build a specific version of bknd.
|
||||
Extend the `build` section with `args` and `labels`.
|
||||
Inside `args`, you can pass a `VERSION` build argument, and use `labels` so the built image receives a unique identifier.
|
||||
|
||||
```yaml compose.yml
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
bknd:
|
||||
pull_policy: build
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pages": ["node", "bun", "cloudflare", "aws", "docker"]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "Node"
|
||||
description: "Run bknd inside Node"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Node and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
|
||||
|
||||
### CLI Starter
|
||||
|
||||
Create a new Node CLI starter project by running the following command:
|
||||
|
||||
```sh
|
||||
npx bknd create -i node
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
Create a new Node project and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Serve the API & static files
|
||||
|
||||
The `serve` function of the Node adapter makes sure to also serve the static files required for
|
||||
the admin panel.
|
||||
|
||||
```tsx title="server.ts"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
|
||||
// if the configuration is omitted, it uses an in-memory database
|
||||
/** @type {import("bknd/adapter/node").NodeAdapterOptions} */
|
||||
const config = {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
};
|
||||
|
||||
serve(config);
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
Run the application using node by executing:
|
||||
|
||||
```bash
|
||||
node server.js
|
||||
```
|
||||
116
docs/content/docs/(documentation)/integration/introduction.mdx
Normal file
116
docs/content/docs/(documentation)/integration/introduction.mdx
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
description: "Integrate bknd into your runtime/framework of choice"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
## Start with a Framework
|
||||
|
||||
bknd seamlessly integrates with popular frameworks, allowing you to use what you're already familar with. The following guides will help you get started with your framework of choice.
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="simple-icons:reactrouter" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="React Router"
|
||||
href="/integration/react-router"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:astro" className="text-fd-primary !size-6" />}
|
||||
title="Astro"
|
||||
href="/integration/astro"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your framework.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Start with a Runtime
|
||||
|
||||
If you prefer to use a runtime instead of a framework, you can choose from the following options. These runtimes allow you to serve the API and UI in the runtime's native server and serve the UI assets statically from `node_modules`.
|
||||
|
||||
<Cards>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:brand-nodejs" className="text-fd-primary !size-6" />}
|
||||
title="NodeJS"
|
||||
href="/integration/node"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:bun" className="text-fd-primary !size-6" />}
|
||||
title="Bun"
|
||||
href="/integration/bun"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="devicon-plain:cloudflareworkers"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Cloudflare"
|
||||
href="/integration/cloudflare"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
|
||||
title="AWS Lambda"
|
||||
href="/integration/aws"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:vitest" className="text-fd-primary !size-6" />}
|
||||
title="Vite"
|
||||
href="/integration/vite"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="streamline-logos:docker-logo-solid"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Docker"
|
||||
href="/integration/docker"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your runtime.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Overview
|
||||
|
||||
### Serving the backend (API)
|
||||
|
||||
Serve the backend as an API for any JS runtime or framework. The latter is especially handy, as it allows you to deploy your frontend and backend bundled together. Furthermore it allows adding additional logic in a way you're already familar with. Just add another route and you're good to go.
|
||||
|
||||
Here is an example of serving the API using node:
|
||||
|
||||
```js title="index.js"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
serve();
|
||||
```
|
||||
|
||||
### Serving the Admin UI
|
||||
|
||||
The admin UI allows to manage your data including full configuration of your backend using a graphical user interface. Using `vite`, your admin route looks like this:
|
||||
|
||||
```tsx title="admin.tsx"
|
||||
import { Admin } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
export default function AdminPage() {
|
||||
return <Admin withProvider />;
|
||||
}
|
||||
```
|
||||
33
docs/content/docs/(documentation)/meta.json
Normal file
33
docs/content/docs/(documentation)/meta.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"title": "Documentation",
|
||||
"root": true,
|
||||
"description": "Technical reference and integration",
|
||||
"icon": "Book",
|
||||
"slug": "",
|
||||
"pages": [
|
||||
"---Getting Started---",
|
||||
"start",
|
||||
"motivation",
|
||||
"---Usage---",
|
||||
"./usage/introduction",
|
||||
"./usage/database",
|
||||
"./usage/cli",
|
||||
"./usage/sdk",
|
||||
"./usage/react",
|
||||
"./usage/elements",
|
||||
"---Extending---",
|
||||
"./extending/config",
|
||||
"./extending/events",
|
||||
"---Integration---",
|
||||
"./integration/introduction",
|
||||
"./integration/(frameworks)/",
|
||||
"./integration/(runtimes)/",
|
||||
"---Modules---",
|
||||
"./modules/overview",
|
||||
"./modules/server",
|
||||
"./modules/data",
|
||||
"./modules/auth",
|
||||
"./modules/media",
|
||||
"./modules/flows"
|
||||
]
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
---
|
||||
title: 'Auth'
|
||||
description: 'Easily implement reliable authentication strategies.'
|
||||
title: "Auth"
|
||||
description: "Easily implement reliable authentication strategies."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Authentication is essential for securing applications, and **bknd** provides a straightforward approach to implementing robust strategies.
|
||||
|
||||
### **Core Features**
|
||||
|
||||
- Automatically creates a `user` entity, with support for customizable fields.
|
||||
- Authenticates users based on configurable strategies.
|
||||
- Generates JWTs according to specified configurations.
|
||||
- Provides session management for maintaining user authentication state.
|
||||
|
||||
### **Supported Authentication Strategies**
|
||||
|
||||
- **Email/Password**: Supports plain and SHA-256 password hashing (bcrypt planned for future
|
||||
releases).
|
||||
releases).
|
||||
- **OAuth/OIDC**: Works with providers like Google and GitHub.
|
||||
- Compatible with any specification-compliant provider.
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
---
|
||||
title: 'Data'
|
||||
description: 'Define, query, and control your data with ease.'
|
||||
title: "Data"
|
||||
description: "Define, query, and control your data with ease."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
|
||||
Data is the lifeblood of any application, and **bknd** provides the tools to handle it effortlessly. From defining entities to executing complex queries, bknd offers a developer-friendly, intuitive, and powerful data management experience.
|
||||
|
||||
### **Define Your Data**
|
||||
|
||||
Model entities with **fields** and **relationships**, automatically synced to your database.
|
||||
|
||||
- Supported field types: `primary`, `text`, `number`, `date`, `boolean`, `enum`, `json`, and `jsonschema`.
|
||||
- Relationship types: `many-to-one`, `one-to-one`, `many-to-many`, and `polymorphic`.
|
||||
- Add **indices** to enhance database performance: single, compound, and unique indices.
|
||||
|
||||
### **Entity Management**
|
||||
|
||||
Use the **EntityManager** to simplify handling your entities. Access data efficiently with the **Repository**:
|
||||
|
||||
- Select specific properties to return.
|
||||
- Define sort directions, limits, and offsets.
|
||||
- Include or join relational data seamlessly within a single query using `with` and joins.
|
||||
- Apply robust filtering with an extensive `where` object: `$eq`, `$ne`, `$isnull`, `$notnull`, `$in`, `$notin`, `$gt`, `$gte`, `$lt`, `$lte`, `$between`.
|
||||
|
||||
### **Seamless Data Manipulation with Mutators**
|
||||
|
||||
Create, update, and delete entity data with confidence.
|
||||
|
||||
- Validates data against the entity schema.
|
||||
- (coming soon) Supports relational data with flexible syntax: `$create`, `$set`, `$attach`,
|
||||
`$detach`.
|
||||
`$detach`.
|
||||
|
||||
### **Powerful Event System**
|
||||
|
||||
Hook into critical lifecycle events for fine-grained control:
|
||||
|
||||
- Repository events: `find-one-before`, `find-one-after`, `find-many-before`, `find-many-after`.
|
||||
- Mutator events: `insert-before`, `insert-after`, `update-before`, `update-after`, `delete-before`, `delete-after`.
|
||||
|
||||
### **Enhanced Database Communication**
|
||||
|
||||
The **Connection** class communicates with the database. It's based on kysely, so that it
|
||||
generally supports multiple database dialects (but currently only SQLite/LibSQL is supported).
|
||||
|
||||
Whether you're modeling simple data structures or managing complex relationships, bknd's data tools empower you to build applications with confidence and scalability.
|
||||
Whether you're modeling simple data structures or managing complex relationships, bknd's data tools empower you to build applications with confidence and scalability.
|
||||
|
||||
40
docs/content/docs/(documentation)/modules/flows.mdx
Normal file
40
docs/content/docs/(documentation)/modules/flows.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "Flows"
|
||||
description: "Design and run workflows with seamless automation."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
<Callout type="info">
|
||||
Flows is the youngest module and its UI is still in development. For the sake
|
||||
of tech preview, we decided to exclude it from the Admin UI for now. It will
|
||||
be available very soon.
|
||||
</Callout>
|
||||
|
||||
**bknd** enables you to automate tasks and processes through flexible and powerful workflows.
|
||||
|
||||
## **Core Features**
|
||||
|
||||
### Trigger-Based Executions
|
||||
|
||||
- **Manual**: Trigger workflows manually.
|
||||
- **System Events**: Automate workflows based on system events, such as:
|
||||
- **Data Events**: Entity create, update, or delete.
|
||||
- **Auth Events**: User sign-in, sign-up, sign-out, or deletion.
|
||||
- **Media Events**: File uploads or downloads.
|
||||
- **Server Events**: Before and after request/response handling.
|
||||
- **HTTP Trigger**: Map workflows to any URL path for external triggers.
|
||||
|
||||
- **Custom Event Hooks**: Define and trigger custom events within workflows for tailored automation.
|
||||
|
||||
### Task Management
|
||||
|
||||
- Define tasks to run in sequence, parallel, or loops.
|
||||
- Enable conditional task execution based on output results.
|
||||
|
||||
### Execution Modes
|
||||
|
||||
- **Asynchronous**: Run workflows in the background.
|
||||
- **Synchronous**: Block further execution until the workflow completes, similar to traditional code execution.
|
||||
|
||||
- **Reusable Components**: Create reusable task sequences (sub-workflows) for better organization and efficiency.
|
||||
- **OpenAPI Specification (OAS) Tasks**: Upload an OpenAPI spec to execute API requests as part of a workflow.
|
||||
@@ -1,16 +1,18 @@
|
||||
---
|
||||
title: 'Media'
|
||||
description: 'Effortlessly manage and serve all your media files.'
|
||||
title: "Media"
|
||||
description: "Effortlessly manage and serve all your media files."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
**bknd** provides a flexible and efficient way to handle media files, making it easy to upload, manage, and deliver content across various platforms.
|
||||
|
||||
### **Core Features**
|
||||
|
||||
- **File Uploads**: Supports direct (chunked coming soon) uploads of files up to 5GB (depending
|
||||
on your environment).
|
||||
on your environment).
|
||||
- **Adapter-Based Design**:
|
||||
- Pre-built support for S3, S3-compatible services (e.g., R2, Tigris), and Cloudinary.
|
||||
- Extend functionality by implementing custom adapters.
|
||||
- Pre-built support for S3, S3-compatible services (e.g., R2, Tigris), and Cloudinary.
|
||||
- Extend functionality by implementing custom adapters.
|
||||
- **Entity Integration**: Attach media files directly to entities for seamless data association.
|
||||
- **Content Delivery**: Serve media over an API endpoint or directly from your provider.
|
||||
|
||||
@@ -19,7 +21,7 @@ and control without added complexity.
|
||||
|
||||
### Pre-built support for S3
|
||||
|
||||
There are two ways to enable S3 or S3 compatible storage in bknd.
|
||||
There are two ways to enable S3 or S3 compatible storage in bknd.
|
||||
|
||||
1. Enable via the admin UI.
|
||||
|
||||
@@ -28,6 +30,7 @@ There are two ways to enable S3 or S3 compatible storage in bknd.
|
||||
This will automatically configure the S3 adapter for you.
|
||||
|
||||
**URL Format Examples:**
|
||||
|
||||
- **S3**: `https://{bucket}.s3.{region}.amazonaws.com`
|
||||
- **R2 (Cloudflare)**: `https://{account_id}.r2.cloudflarestorage.com/{bucket}`
|
||||
|
||||
@@ -35,23 +38,23 @@ There are two ways to enable S3 or S3 compatible storage in bknd.
|
||||
|
||||
To enable using a code-first approach, create an [Initial Structure](/usage/database#initial-structure) using the `initialConfig` option in your `bknd.config.ts` file.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
initialConfig: {
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "s3",
|
||||
config: {
|
||||
access_key: "<key>",
|
||||
secret_access_key: "<secret key>",
|
||||
url: "<bucket url>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
initialConfig: {
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "s3",
|
||||
config: {
|
||||
access_key: "<key>",
|
||||
secret_access_key: "<secret key>",
|
||||
url: "<bucket url>",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
@@ -59,7 +62,7 @@ export default {
|
||||
|
||||
For local development and testing, you can use the local file system adapter. This is particularly useful when working with Node.js environments.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
@@ -67,17 +70,17 @@ import type { BkndConfig } from "bknd/adapter";
|
||||
const local = registerLocalMediaAdapter();
|
||||
|
||||
export default {
|
||||
initialConfig: {
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: local({
|
||||
path: "./public/uploads", // Files will be stored in this directory
|
||||
}),
|
||||
}
|
||||
}
|
||||
initialConfig: {
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: local({
|
||||
path: "./public/uploads", // Files will be stored in this directory
|
||||
}),
|
||||
},
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
This configuration will store uploaded files in the specified directory,
|
||||
This configuration will store uploaded files in the specified directory,
|
||||
making them accessible through your application's public path (in this case)
|
||||
or at `/api/media/file/{filename}`.
|
||||
53
docs/content/docs/(documentation)/modules/overview.mdx
Normal file
53
docs/content/docs/(documentation)/modules/overview.mdx
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "Overview"
|
||||
description: "General overview of the system"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
This backend system focuses on 4 essential building blocks which can be tightly connected:
|
||||
Data, Auth, Media and Flows.
|
||||
The main idea is to supply all baseline functionality required in order to accomplish complex
|
||||
logic and behaviors – instead of hard coding it into the system code. This way, almost any part
|
||||
of the application is customizable and additionally won't limit you to what it can do.
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="majesticons:data-line" className="text-fd-primary !size-6" />} title="Data" href="/modules/data">
|
||||
Define, query, and control your data with ease.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="meteor-icons:fingerprint" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="Auth"
|
||||
href="/modules/auth"
|
||||
>
|
||||
Easily implement reliable authentication strategies.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="flowbite:image-outline" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="Media"
|
||||
href="/modules/media"
|
||||
>
|
||||
Effortlessly manage and serve all your media files.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="hugeicons:workflow-square-03"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Flows"
|
||||
href="/modules/flows"
|
||||
>
|
||||
Design and run workflows with seamless automation.
|
||||
</Card>
|
||||
|
||||
</Cards>
|
||||
11
docs/content/docs/(documentation)/modules/server.mdx
Normal file
11
docs/content/docs/(documentation)/modules/server.mdx
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Server"
|
||||
description: "Effortlessly manage and serve all your files."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
<Callout type="info" title="The documentation is currently a work in progress">
|
||||
Check back soon — or stay updated on our progress on
|
||||
[GitHub](https://github.com/bknd-io/bknd) and join the conversation in
|
||||
[Discord](https://discord.gg/952SFk8Tb8).
|
||||
</Callout>
|
||||
@@ -1,23 +1,27 @@
|
||||
---
|
||||
title: "Motivation"
|
||||
description: "Why another backend system?"
|
||||
icon: Rocket
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Creating digital products always requires developing both the backend (the logic) and the frontend (the appearance). Building a backend from scratch demands deep knowledge in areas such as authentication and database management. Using a backend framework can speed up initial development, but it still requires ongoing effort to work within its constraints (e.g., *"how to do X with Y?"*), which can quickly slow you down. Choosing a backend system is a tough decision, as you might not be aware of its limitations until you encounter them.
|
||||
Creating digital products always requires developing both the backend (the logic) and the frontend (the appearance). Building a backend from scratch demands deep knowledge in areas such as authentication and database management. Using a backend framework can speed up initial development, but it still requires ongoing effort to work within its constraints (e.g., _"how to do X with Y?"_), which can quickly slow you down. Choosing a backend system is a tough decision, as you might not be aware of its limitations until you encounter them.
|
||||
|
||||
<Check>
|
||||
**The solution:** A backend system that only assumes and implements primitive details, integrates into multiple environments, and adheres to industry standards.
|
||||
</Check>
|
||||
<CalloutPositive title="The solution">
|
||||
A backend system that only assumes and implements primitive details,
|
||||
integrates into multiple environments, and adheres to industry standards.
|
||||
</CalloutPositive>
|
||||
|
||||
For the sake of brevity, let's assume you are looking for a "backend system" rather than dealing with custom implementations yourself. Let's identify the most common challenges:
|
||||
|
||||
1. Database lock-in
|
||||
2. Environment and framework lock-in
|
||||
3. Deviation from standards (such as `X-Auth` headers for authentication)
|
||||
4. *Wrong-for-your-use-case* implementations
|
||||
4. _Wrong-for-your-use-case_ implementations
|
||||
5. Complex self-hosting
|
||||
|
||||
## Database lock-in
|
||||
|
||||
As the developer of a backend system, you must make tough decisions, one of which is choosing which database(s) to support. To simplify development, many systems lock you into a single database, leveraging its advanced features.
|
||||
|
||||
But isn't the database known to be the hardest part to scale? Isn't more logic moving to the application layer? Haven't NoSQL databases proven this? If you're like me, you may have dipped your toes into the NoSQL world only to quickly return to SQL. SQL is known, predictable, and safe. But what if we could have both? NoSQL offers flexibility and scalability, yet querying it is tedious due to vendor-specific implementations.
|
||||
@@ -25,21 +29,23 @@ But isn't the database known to be the hardest part to scale? Isn't more logic m
|
||||
To get the best of both worlds, bknd focuses on the weakest SQL database (SQLite), treating it as a data store and query interface. Schema details and enforcement are moved to the application layer, making it easy to adjust a default value or property length. The added benefit is that any SQL database could theoretically work the same way, and since it's all TypeScript, the same validation logic can be used on both the client and server sides–you can validate your data before it even reaches your server. It even works without database-enforced referential integrity, as the integrity checks occur on the application layer. This opens the door to NewSQL systems like PlanetScale.
|
||||
|
||||
## Environment and framework lock-in
|
||||
|
||||
There are backend systems that embed themselves into a specific React framework. This works well until you realize it doesn't support your preferred framework or the new hyped one you're considering switching to. Just like database choices, decisions must be made. The easiest path is to select a single option and let people live with it.
|
||||
|
||||
Alternatively, you could develop for the weakest environment (workerd) by strictly using Web APIs, avoiding shortcuts, and implementing certain logic manually because the go-to package is using Node APIs. This isn't always fun, but it's essential. The benefit? It works anywhere JavaScript does.
|
||||
|
||||
bknd is the only backend system that not only works with any JavaScript framework but also integrates directly into it. It runs within the framework, enabling a single deployment for your entire app.
|
||||
|
||||
*"But isn't it ironic that it forces a JavaScript environment?"* you might ask. And you're right, but it also allows running standalone via CLI or Docker.
|
||||
|
||||
_"But isn't it ironic that it forces a JavaScript environment?"_ you might ask. And you're right, but it also allows running standalone via CLI or Docker.
|
||||
|
||||
## Deviation from standards
|
||||
|
||||
One of the biggest frustrations I've encountered is when software vendors choose custom headers for authentication or implement query parameters in a format they find more suitable—such as unencoded JSON for simplicity. When you are in full control, it's tempting to use a more suitable format, or just use an auth-ish name for the header property–after all, it's just a header, right?
|
||||
|
||||
The issue is that users may rely on HTTP clients that offer built-in authentication methods, which won't include your custom solution. Custom `SearchParams` implementations might be convenient, but translating them across different environments and languages can be challenging without trial and error.
|
||||
|
||||
bknd strives to adhere to web standards as much as possible while offering handy alternatives. Here's an example of the `select` search parameter for retrieving a list of entities:
|
||||
|
||||
```bash
|
||||
/api/data/todos?select=id&select=name # web standard
|
||||
/api/data/todos?select=id,name # handy alternative
|
||||
@@ -47,13 +53,14 @@ bknd strives to adhere to web standards as much as possible while offering handy
|
||||
|
||||
If you ever find an instance where bknd isn't adhering to standards or could be improved, please feel free to [file an issue](https://github.com/bknd-io/bknd/issues/new). Your feedback is greatly appreciated!
|
||||
|
||||
|
||||
## Wrong-for-your-use-case implementations
|
||||
|
||||
If you've ever developed a social chat application, you likely discovered the extensive feature depth required—features we often take for granted. Things like socket connections for single and group chats, partial loading, asset attachments, and emoji reactions. Even more frustrating, these features make the app being considered incomplete until delivered.
|
||||
|
||||
The same applies to backend systems. Features such as email sending, password resets, and image transformations are expected. Worse still, you'll receive feedback requesting different email verification methods—PIN codes instead of links, 4-digit codes versus 6-digit ones, or UUIDs like Axiom uses.
|
||||
|
||||
Since it's impossible to satisfy all requirements, why implement them at all? *"Because people expect it."* That's fair. But technically, email verification is not a core backend feature—it's business logic. Setting it up involves:
|
||||
Since it's impossible to satisfy all requirements, why implement them at all? _"Because people expect it."_ That's fair. But technically, email verification is not a core backend feature—it's business logic. Setting it up involves:
|
||||
|
||||
1. Adding a `code` and `verified` field to the users' entity and generating a random code on creation.
|
||||
2. Creating an endpoint to accept the code, retrieve the authenticated user, check the code, clear it, and mark the user as verified.
|
||||
|
||||
@@ -62,10 +69,7 @@ Additional security measures, such as short-lived tokens, can be added, but the
|
||||
Instead of hardcoding such features, bknd offers a powerful event system that supports asynchronous (like webhooks) and synchronous execution, blocking further actions if needed. With integrated workflows (UI coming soon), you can listen to and react to system events, and even map them to endpoints. Since workflows, like everything else in bknd, are JSON-serializable, they're easy to export and import.
|
||||
|
||||
## Complex self-hosting
|
||||
|
||||
Finally, hosting. It's a business advantage if your system is highly sought after but difficult to self-host, forcing users to opt for your cloud service. The truth is, if it's hard for users, it's also hard for the vendor, which drives up costs.
|
||||
|
||||
If you know how to deploy your Next.js, Remix, or Astro application, you can deploy bknd. It's straightforward to deploy using Cloudflare Workers/Pages or with just 28 lines of a Dockerfile. No PhD required.
|
||||
|
||||
|
||||
|
||||
|
||||
158
docs/content/docs/(documentation)/start.mdx
Normal file
158
docs/content/docs/(documentation)/start.mdx
Normal file
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: Introduction
|
||||
icon: Album
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { examples } from "@/app/_components/StackBlitz";
|
||||
|
||||
Glad you're here! **bknd** is a lightweight, infrastructure agnostic and feature-rich backend that runs in any JavaScript environment.
|
||||
|
||||
- Instant backend with full REST API
|
||||
- Built on Web Standards for maximum compatibility
|
||||
- Multiple run modes (standalone, runtime, framework)
|
||||
- Official API and React SDK with type-safety
|
||||
- React elements for auto-configured authentication and media components
|
||||
|
||||
## Preview
|
||||
|
||||
Here is a preview of **bknd** in StackBlitz:
|
||||
|
||||
<StackBlitz {...examples.adminRich} />
|
||||
|
||||
<Accordions>
|
||||
<Accordion title="What's going on?">
|
||||
The example shown is starting a [node server](/integration/node) using an [in-memory database](/usage/database#sqlite-in-memory). To ensure there are a few entities defined, it is using an [initial structure](/usage/database#initial-structure) using the prototype methods. Furthermore it uses the [seed option](/usage/database#seeding-the-database) to seed some data in the structure created.
|
||||
|
||||
To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending).
|
||||
|
||||
</Accordion>
|
||||
</Accordions>
|
||||
|
||||
## Quickstart
|
||||
|
||||
Enter the following command to spin up an instance:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm','bun' ]}>
|
||||
|
||||
```bash tab="npm"
|
||||
npx bknd run
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bunx bknd run
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
To learn more about the CLI, check out the [Using the CLI](/usage/cli) guide.
|
||||
|
||||
## Start with a Framework/Runtime
|
||||
|
||||
Start by using the integration guide for these popular frameworks/runtimes. There will be more in the future, so stay tuned!
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="simple-icons:reactrouter" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="React Router"
|
||||
href="/integration/react-router"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:astro" className="text-fd-primary !size-6" />}
|
||||
title="Astro"
|
||||
href="/integration/astro"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:brand-nodejs" className="text-fd-primary !size-6" />}
|
||||
title="NodeJS"
|
||||
href="/integration/node"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="devicon-plain:cloudflareworkers"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Cloudflare"
|
||||
href="/integration/cloudflare"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:bun" className="text-fd-primary !size-6" />}
|
||||
title="Bun"
|
||||
href="/integration/bun"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
|
||||
title="AWS Lambda"
|
||||
href="/integration/aws"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:vitest" className="text-fd-primary !size-6" />}
|
||||
title="Vite"
|
||||
href="/integration/vite"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="streamline-logos:docker-logo-solid"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Docker"
|
||||
href="/integration/docker"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your runtime or framework.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Use your favorite SQL Database
|
||||
|
||||
The following databases are currently supported. Request a new integration if your favorite is missing.
|
||||
|
||||
<Cards>
|
||||
<Card icon={<Icon icon="simple-icons:sqlite" className="text-fd-primary !size-6" />} title="SQLite" href="/usage/database#database" />
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:turso" className="text-fd-primary !size-6" />}
|
||||
title="Turso/LibSQL"
|
||||
href="/usage/database#sqlite-using-libsql-on-turso"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon icon="lineicons:postgresql" className="text-fd-primary !size-6" />
|
||||
}
|
||||
title="PostgreSQL"
|
||||
href="/usage/database#postgresql"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={
|
||||
<Icon
|
||||
icon="streamline-plump:database"
|
||||
className="text-fd-primary !size-6"
|
||||
/>
|
||||
}
|
||||
title="Cloudflare D1"
|
||||
href="/usage/database#cloudflare-d1"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a new database integration.
|
||||
</Card>
|
||||
</Cards>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: 'Using the CLI'
|
||||
description: 'How to start a bknd instance using the CLI.'
|
||||
title: "Using the CLI"
|
||||
description: "How to start a bknd instance using the CLI."
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
The bknd package includes a command-line interface (CLI) that allows you to run a bknd instance and perform various tasks.
|
||||
@@ -10,11 +11,12 @@ npx bknd
|
||||
```
|
||||
|
||||
Here is the output:
|
||||
|
||||
```
|
||||
$ npx bknd
|
||||
Usage: bknd [options] [command]
|
||||
|
||||
⚡ bknd cli v0.13.0
|
||||
⚡ bknd cli v0.16.0
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
@@ -33,6 +35,7 @@ Commands:
|
||||
```
|
||||
|
||||
## Starting an instance (`run`)
|
||||
|
||||
To see all available `run` options, execute `npx bknd run --help`.
|
||||
|
||||
```
|
||||
@@ -51,6 +54,7 @@ Options:
|
||||
```
|
||||
|
||||
To order in which the connection is determined is as follows:
|
||||
|
||||
1. `--db-url`
|
||||
2. `--config` or reading the filesystem looking for `bknd.config.[js|ts|mjs|cjs|json]`
|
||||
3. `--memory`
|
||||
@@ -58,6 +62,7 @@ To order in which the connection is determined is as follows:
|
||||
5. Fallback to file-based database `data.db`
|
||||
|
||||
### File-based database
|
||||
|
||||
By default, a file-based database `data.db` is used when running without any arguments. You can specify a different file name or path using the `--db-url` option. The database file will be created in the current working directory if it does not exist.
|
||||
|
||||
```
|
||||
@@ -65,6 +70,7 @@ npx bknd run --db-url file:data.db
|
||||
```
|
||||
|
||||
### Using configuration file (`bknd.config.*`)
|
||||
|
||||
You can create a configuration file on the working directory that automatically gets picked up: `bknd.config.[js|ts|mjs|cjs|json]`
|
||||
|
||||
Here is an example of a `bknd.config.ts` file:
|
||||
@@ -73,20 +79,20 @@ Here is an example of a `bknd.config.ts` file:
|
||||
import type { BkndConfig } from "bknd/adapter";
|
||||
|
||||
export default {
|
||||
// you can either specify the connection directly
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
// or use the `app` function which passes the environment variables
|
||||
app: ({ env }) => ({
|
||||
connection: {
|
||||
url: env.DB_URL,
|
||||
}
|
||||
})
|
||||
// you can either specify the connection directly
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
// or use the `app` function which passes the environment variables
|
||||
app: ({ env }) => ({
|
||||
connection: {
|
||||
url: env.DB_URL,
|
||||
},
|
||||
}),
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
The `app` function is useful if you need a cross-platform way to access the environment variables. For example, on Cloudflare Workers, you can only access environment variables inside a request handler. If you're exclusively using a node-like environment, it's safe to access the environment variables directly from `process.env`.
|
||||
|
||||
The `app` function is useful if you need a cross-platform way to access the environment variables. For example, on Cloudflare Workers, you can only access environment variables inside a request handler. If you're exclusively using a node-like environment, it's safe to access the environment variables directly from `process.env`.
|
||||
|
||||
If you're using `npx bknd run`, make sure to create a file in a file format that `node` can load, otherwise you may run into an error that the file couldn't be found:
|
||||
|
||||
@@ -112,21 +118,27 @@ npx tsx node_modules/.bin/bknd run
|
||||
```
|
||||
|
||||
### Turso/LibSQL database
|
||||
|
||||
To start an instance with a Turso/LibSQL database, run the following:
|
||||
|
||||
```
|
||||
npx bknd run --db-url libsql://your-db.turso.io --db-token <your-token>
|
||||
```
|
||||
|
||||
The `--db-token` option is optional and only required if the database is protected.
|
||||
|
||||
|
||||
### In-memory database
|
||||
|
||||
To start an instance with an ephemeral in-memory database, run the following:
|
||||
|
||||
```
|
||||
npx bknd run --memory
|
||||
```
|
||||
|
||||
Keep in mind that the database is not persisted and will be lost when the process is terminated.
|
||||
|
||||
## Generating types (`types`)
|
||||
|
||||
To see all available `types` options, execute `npx bknd types --help`.
|
||||
|
||||
```
|
||||
@@ -142,6 +154,7 @@ Options:
|
||||
```
|
||||
|
||||
To generate types for the database, run the following:
|
||||
|
||||
```
|
||||
npx bknd types
|
||||
```
|
||||
@@ -149,7 +162,7 @@ npx bknd types
|
||||
This will generate types for your database schema in `bknd-types.d.ts`. The generated file could look like this:
|
||||
|
||||
```typescript bknd-types.d.ts
|
||||
import type { DB } from "bknd/core";
|
||||
import type { DB } from "bknd";
|
||||
import type { Insertable, Selectable, Updateable, Generated } from "kysely";
|
||||
|
||||
declare global {
|
||||
@@ -168,7 +181,7 @@ interface Database {
|
||||
todos: Todos;
|
||||
}
|
||||
|
||||
declare module "bknd/core" {
|
||||
declare module "bknd" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
```
|
||||
@@ -177,18 +190,17 @@ Make sure to add the generated file in your `tsconfig.json` file:
|
||||
|
||||
```json tsconfig.json
|
||||
{
|
||||
"include": [
|
||||
"bknd-types.d.ts"
|
||||
]
|
||||
"include": ["bknd-types.d.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
You can then use the types by importing them from `bknd/core`:
|
||||
You can then use the types by importing them from `bknd`:
|
||||
|
||||
```typescript
|
||||
import type { DB } from "bknd/core";
|
||||
import type { DB } from "bknd";
|
||||
|
||||
type Todo = DB["todos"];
|
||||
```
|
||||
|
||||
All bknd methods that involve your database schema will be automatically typed.
|
||||
All bknd methods that involve your database schema will be automatically typed.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
---
|
||||
title: 'Database'
|
||||
description: 'Choosing the right database configuration'
|
||||
title: "Database"
|
||||
description: "Choosing the right database configuration"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
In order to use **bknd**, you need to prepare access information to your database and potentially install additional dependencies. Connections to the database are managed using Kysely. Therefore, all [its dialects](https://kysely.dev/docs/dialects) are theoretically supported.
|
||||
In order to use **bknd**, you need to prepare access information to your database and potentially install additional dependencies. Connections to the database are managed using Kysely. Therefore, all [its dialects](https://kysely.dev/docs/dialects) are theoretically supported.
|
||||
|
||||
Currently supported and tested databases are:
|
||||
|
||||
- SQLite (embedded): Node.js SQLite, Bun SQLite, LibSQL, SQLocal
|
||||
- SQLite (remote): Turso, Cloudflare D1
|
||||
- Postgres: Vanilla Postgres, Supabase, Neon, Xata
|
||||
@@ -13,78 +15,80 @@ Currently supported and tested databases are:
|
||||
By default, bknd will try to use a SQLite database in-memory. Depending on your runtime, a different SQLite implementation will be used.
|
||||
|
||||
## Defining the connection
|
||||
|
||||
There are mainly 3 ways to define the connection to your database, when
|
||||
|
||||
1. creating an app using `App.create()` or `createApp()`
|
||||
2. creating an app using a [Framework or Runtime adapter](/integration/introduction)
|
||||
3. starting a quick instance using the [CLI](/usage/cli#using-configuration-file-bknd-config)
|
||||
|
||||
When creating an app using `App.create()` or `createApp()`, you can pass a connection object in the configuration object.
|
||||
|
||||
```typescript app.ts
|
||||
```typescript title="app.ts"
|
||||
import { createApp } from "bknd";
|
||||
import { sqlite } from "bknd/adapter/sqlite";
|
||||
|
||||
|
||||
// a connection is required when creating an app like this
|
||||
const app = createApp({
|
||||
connection: sqlite({ url: ":memory:" }),
|
||||
connection: sqlite({ url: ":memory:" }),
|
||||
});
|
||||
```
|
||||
|
||||
When using an adapter, or using the CLI, bknd will automatically try to use a SQLite implementation depending on the runtime:
|
||||
|
||||
|
||||
```javascript app.js
|
||||
```javascript title="app.js"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
|
||||
serve({
|
||||
// connection is optional, but recommended
|
||||
connection: { url: "file:data.db" },
|
||||
// connection is optional, but recommended
|
||||
connection: { url: "file:data.db" },
|
||||
});
|
||||
```
|
||||
|
||||
You can also pass a connection instance to the `connection` property to explictly use a specific connection.
|
||||
|
||||
```javascript app.js
|
||||
```javascript title="app.js"
|
||||
import { serve } from "bknd/adapter/node";
|
||||
import { sqlite } from "bknd/adapter/sqlite";
|
||||
|
||||
serve({
|
||||
connection: sqlite({ url: "file:data.db" }),
|
||||
connection: sqlite({ url: "file:data.db" }),
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
If you're using [`bknd.config.*`](/extending/config), you can specify the connection on the exported object.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd";
|
||||
|
||||
export default {
|
||||
connection: { url: "file:data.db" },
|
||||
connection: { url: "file:data.db" },
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
Throughout the documentation, it is assumed you use `bknd.config.ts` to define your connection.
|
||||
|
||||
## SQLite
|
||||
|
||||
### Using config object
|
||||
|
||||
<Callout type="warn">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
<Warning>
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please verify your version by running `node -v`, and [upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Warning>
|
||||
The `sqlite` adapter is automatically resolved based on the runtime.
|
||||
|
||||
The `sqlite` adapter is automatically resolved based on the runtime.
|
||||
|
||||
| Runtime | Adapter | In-Memory | File | Remote |
|
||||
| ------- | ------- | --------- | ---- | ------ |
|
||||
| Node.js | `node:sqlite` | ✅ | ✅ | ❌ |
|
||||
| Bun | `bun:sqlite` | ✅ | ✅ | ❌ |
|
||||
| Cloudflare Worker/Browser/Edge | `libsql` | 🟠 | 🟠 | ✅ |
|
||||
| Runtime | Adapter | In-Memory | File | Remote |
|
||||
| ------------------------------ | ------------- | --------- | ---- | ------ |
|
||||
| Node.js | `node:sqlite` | ✅ | ✅ | ❌ |
|
||||
| Bun | `bun:sqlite` | ✅ | ✅ | ❌ |
|
||||
| Cloudflare Worker/Browser/Edge | `libsql` | 🟠 | 🟠 | ✅ |
|
||||
|
||||
The bundled version of the `libsql` connection only works with remote databases. However, you can pass in a `Client` from `@libsql/client`, see [LibSQL](#libsql) for more details.
|
||||
|
||||
```typescript bknd.config.ts
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { BkndConfig } from "bknd";
|
||||
|
||||
// no connection is required, bknd will use a SQLite database in-memory
|
||||
@@ -93,51 +97,49 @@ export default {} as const satisfies BkndConfig;
|
||||
|
||||
// or explicitly in-memory
|
||||
export default {
|
||||
connection: { url: ":memory:" },
|
||||
connection: { url: ":memory:" },
|
||||
} as const satisfies BkndConfig;
|
||||
|
||||
// or explicitly as a file
|
||||
export default {
|
||||
connection: { url: "file:<path/to/your/database.db>" },
|
||||
connection: { url: "file:<path/to/your/database.db>" },
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
|
||||
### LibSQL
|
||||
|
||||
Turso offers a SQLite-fork called LibSQL that runs a server around your SQLite database. The edge-version of the adapter is included in the bundle (remote only):
|
||||
|
||||
```typescript bknd.config.ts
|
||||
import type { BkndConfig } from "bknd";
|
||||
import { libsql } from "bknd/data";
|
||||
```typescript title="bknd.config.ts"
|
||||
import { libsql, type BkndConfig } from "bknd";
|
||||
|
||||
export default {
|
||||
connection: libsql({
|
||||
url: "libsql://<database>.turso.io",
|
||||
authToken: "<auth-token>",
|
||||
}),
|
||||
connection: libsql({
|
||||
url: "libsql://<database>.turso.io",
|
||||
authToken: "<auth-token>",
|
||||
}),
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
If you wish to use LibSQL as file, in-memory or make use of [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction), you have to pass in the `Client` from `@libsql/client`:
|
||||
If you wish to use LibSQL as file, in-memory or make use of [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction), you have to pass in the `Client` from `@libsql/client`:
|
||||
|
||||
```typescript bknd.config.ts
|
||||
import type { BkndConfig } from "bknd";
|
||||
import { libsql } from "bknd/data";
|
||||
```typescript title="bknd.config.ts"
|
||||
import { libsql, type BkndConfig } from "bknd";
|
||||
import { createClient } from "@libsql/client";
|
||||
|
||||
const client = createClient({
|
||||
url: "libsql://<database>.turso.io",
|
||||
authToken: "<auth-token>",
|
||||
})
|
||||
const client = createClient({
|
||||
url: "libsql://<database>.turso.io",
|
||||
authToken: "<auth-token>",
|
||||
});
|
||||
|
||||
export default {
|
||||
connection: libsql(client),
|
||||
connection: libsql(client),
|
||||
} as const satisfies BkndConfig;
|
||||
```
|
||||
|
||||
|
||||
### Cloudflare D1
|
||||
Using the [Cloudflare Adapter](/integration/cloudflare), you can choose to use a D1 database binding. To do so, you only need to add a D1 database to your `wrangler.toml` and it'll pick up automatically.
|
||||
|
||||
Using the [Cloudflare Adapter](/integration/cloudflare), you can choose to use a D1 database binding. To do so, you only need to add a D1 database to your `wrangler.toml` and it'll pick up automatically.
|
||||
|
||||
To manually specify which D1 database to take, you can specify it explicitly:
|
||||
|
||||
@@ -145,12 +147,12 @@ To manually specify which D1 database to take, you can specify it explicitly:
|
||||
import { serve, d1 } from "bknd/adapter/cloudflare";
|
||||
|
||||
export default serve<Env>({
|
||||
app: ({ env }) => d1({ binding: env.D1_BINDING })
|
||||
app: ({ env }) => d1({ binding: env.D1_BINDING }),
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### SQLocal
|
||||
|
||||
To use bknd with `sqlocal` for a offline expierence, you need to install the `@bknd/sqlocal` package. You can do so by running the following command:
|
||||
|
||||
```bash
|
||||
@@ -164,14 +166,15 @@ import { createApp } from "bknd";
|
||||
import { SQLocalConnection } from "@bknd/sqlocal";
|
||||
|
||||
const app = createApp({
|
||||
connection: new SQLocalConnection({
|
||||
databasePath: ":localStorage:",
|
||||
verbose: true,
|
||||
})
|
||||
connection: new SQLocalConnection({
|
||||
databasePath: ":localStorage:",
|
||||
verbose: true,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
To use bknd with Postgres, you need to install the `@bknd/postgres` package. You can do so by running the following command:
|
||||
|
||||
```bash
|
||||
@@ -182,7 +185,7 @@ You can connect to your Postgres database using `pg` or `postgres` dialects. Add
|
||||
|
||||
### Using `pg`
|
||||
|
||||
To establish a connection to your database, you can use any connection options available on the [`pg`](https://node-postgres.com/apis/client) package.
|
||||
To establish a connection to your database, you can use any connection options available on the [`pg`](https://node-postgres.com/apis/client) package.
|
||||
|
||||
```js
|
||||
import { serve } from "bknd/adapter/node";
|
||||
@@ -190,10 +193,9 @@ import { pg } from "@bknd/postgres";
|
||||
|
||||
/** @type {import("bknd/adapter/node").NodeBkndConfig} */
|
||||
const config = {
|
||||
connection: pg({
|
||||
connectionString:
|
||||
"postgresql://user:password@localhost:5432/database",
|
||||
}),
|
||||
connection: pg({
|
||||
connectionString: "postgresql://user:password@localhost:5432/database",
|
||||
}),
|
||||
};
|
||||
|
||||
serve(config);
|
||||
@@ -201,14 +203,14 @@ serve(config);
|
||||
|
||||
### Using `postgres`
|
||||
|
||||
To establish a connection to your database, you can use any connection options available on the [`postgres`](https://github.com/porsager/postgres) package.
|
||||
To establish a connection to your database, you can use any connection options available on the [`postgres`](https://github.com/porsager/postgres) package.
|
||||
|
||||
```js
|
||||
import { serve } from "bknd/adapter/node";
|
||||
import { postgresJs } from "@bknd/postgres";
|
||||
|
||||
serve({
|
||||
connection: postgresJs("postgresql://user:password@localhost:5432/database"),
|
||||
connection: postgresJs("postgresql://user:password@localhost:5432/database"),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -225,9 +227,9 @@ import { NeonDialect } from "kysely-neon";
|
||||
const neon = createCustomPostgresConnection(NeonDialect);
|
||||
|
||||
serve({
|
||||
connection: neon({
|
||||
connectionString: process.env.NEON,
|
||||
}),
|
||||
connection: neon({
|
||||
connectionString: process.env.NEON,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
@@ -240,73 +242,78 @@ import { buildClient } from "@xata.io/client";
|
||||
|
||||
const client = buildClient();
|
||||
const xata = new client({
|
||||
databaseURL: process.env.XATA_URL,
|
||||
apiKey: process.env.XATA_API_KEY,
|
||||
branch: process.env.XATA_BRANCH,
|
||||
databaseURL: process.env.XATA_URL,
|
||||
apiKey: process.env.XATA_API_KEY,
|
||||
branch: process.env.XATA_BRANCH,
|
||||
});
|
||||
|
||||
const xataConnection = createCustomPostgresConnection(XataDialect, {
|
||||
supports: {
|
||||
batching: false,
|
||||
},
|
||||
supports: {
|
||||
batching: false,
|
||||
},
|
||||
});
|
||||
|
||||
serve({
|
||||
connection: xataConnection({ xata }),
|
||||
connection: xataConnection({ xata }),
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Connection
|
||||
|
||||
Creating a custom connection is as easy as extending the `Connection` class and passing constructing a Kysely instance.
|
||||
|
||||
```ts
|
||||
import { createApp } from "bknd";
|
||||
import { Connection } from "bknd/data";
|
||||
import { createApp, Connection } from "bknd";
|
||||
import { Kysely } from "kysely";
|
||||
|
||||
class CustomConnection extends Connection {
|
||||
constructor() {
|
||||
const kysely = new Kysely(/* ... */);
|
||||
super(kysely);
|
||||
}
|
||||
constructor() {
|
||||
const kysely = new Kysely(/* ... */);
|
||||
super(kysely);
|
||||
}
|
||||
}
|
||||
|
||||
const connection = new CustomConnection();
|
||||
|
||||
// e.g. and then, create an instance
|
||||
const app = createApp({ connection })
|
||||
const app = createApp({ connection });
|
||||
```
|
||||
|
||||
## Initial Structure
|
||||
|
||||
To provide an initial database structure, you can pass `initialConfig` to the creation of an app. This will only be used if there isn't an existing configuration found in the database given. Here is a quick example:
|
||||
|
||||
<Note>
|
||||
The initial structure is only respected if the database is empty! If you made updates, ensure to delete the database first, or perform updates through the Admin UI.
|
||||
</Note>
|
||||
<Callout type="info">
|
||||
The initial structure is only respected if the database is empty! If you made
|
||||
updates, ensure to delete the database first, or perform updates through the
|
||||
Admin UI.
|
||||
</Callout>
|
||||
|
||||
```typescript
|
||||
import { createApp } from "bknd";
|
||||
import { em, entity, text, number } from "bknd/data";
|
||||
import { createApp, em, entity, text, number } from "bknd";
|
||||
|
||||
const schema = em({
|
||||
posts: entity("posts", {
|
||||
const schema = em(
|
||||
{
|
||||
posts: entity("posts", {
|
||||
// "id" is automatically added
|
||||
title: text().required(),
|
||||
slug: text().required(),
|
||||
content: text(),
|
||||
views: number()
|
||||
}),
|
||||
comments: entity("comments", {
|
||||
content: text()
|
||||
})
|
||||
|
||||
// relations and indices are defined separately.
|
||||
// the first argument are the helper functions, the second the entities.
|
||||
}, ({ relation, index }, { posts, comments }) => {
|
||||
relation(comments).manyToOne(posts);
|
||||
// relation as well as index can be chained!
|
||||
index(posts).on(["title"]).on(["slug"], true);
|
||||
});
|
||||
views: number(),
|
||||
}),
|
||||
comments: entity("comments", {
|
||||
content: text(),
|
||||
}),
|
||||
|
||||
// relations and indices are defined separately.
|
||||
// the first argument are the helper functions, the second the entities.
|
||||
},
|
||||
({ relation, index }, { posts, comments }) => {
|
||||
relation(comments).manyToOne(posts);
|
||||
// relation as well as index can be chained!
|
||||
index(posts).on(["title"]).on(["slug"], true);
|
||||
},
|
||||
);
|
||||
|
||||
// to get a type from your schema, use:
|
||||
type Database = (typeof schema)["DB"];
|
||||
@@ -325,66 +332,81 @@ type Database = (typeof schema)["DB"];
|
||||
|
||||
// pass the schema to the app
|
||||
const app = createApp({
|
||||
connection: { /* ... */ },
|
||||
initialConfig: {
|
||||
data: schema.toJSON()
|
||||
}
|
||||
connection: {
|
||||
/* ... */
|
||||
},
|
||||
initialConfig: {
|
||||
data: schema.toJSON(),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Note that we didn't add relational fields directly to the entity, but instead defined them afterwards. That is because the relations are managed outside the entity scope to have an unified expierence for all kinds of relations (e.g. many-to-many).
|
||||
|
||||
<Note>
|
||||
Defined relations are currently not part of the produced types for the structure. We're working on that, but in the meantime, you can define them manually.
|
||||
</Note>
|
||||
<Callout type="info">
|
||||
Defined relations are currently not part of the produced types for the
|
||||
structure. We're working on that, but in the meantime, you can define them
|
||||
manually.
|
||||
</Callout>
|
||||
|
||||
### Type completion
|
||||
|
||||
To get type completion, there are two options:
|
||||
|
||||
1. Use the CLI to [generate the types](/usage/cli#generating-types-types)
|
||||
2. If you have an initial structure created with the prototype functions, you can extend the `DB` interface with your own schema.
|
||||
|
||||
All entity related functions use the types defined in `DB` from `bknd/core`. To get type completion, you can extend that interface with your own schema:
|
||||
All entity related functions use the types defined in `DB` from `bknd`. To get type completion, you can extend that interface with your own schema:
|
||||
|
||||
```typescript
|
||||
import { em } from "bknd/data";
|
||||
import { em } from "bknd";
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
const schema = em({ /* ... */ });
|
||||
const schema = em({
|
||||
/* ... */
|
||||
});
|
||||
|
||||
type Database = (typeof schema)["DB"];
|
||||
declare module "bknd/core" {
|
||||
interface DB extends Database {}
|
||||
declare module "bknd" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
|
||||
const api = new Api({ /* ... */ });
|
||||
const { data: posts } = await api.data.readMany("posts", {})
|
||||
const api = new Api({
|
||||
/* ... */
|
||||
});
|
||||
const { data: posts } = await api.data.readMany("posts", {});
|
||||
// `posts` is now typed as Database["posts"]
|
||||
```
|
||||
|
||||
The type completion is available for the API as well as all provided [React hooks](/usage/react).
|
||||
|
||||
### Seeding the database
|
||||
|
||||
To seed your database with initial data, you can pass a `seed` function to the configuration. It
|
||||
provides the `ModuleBuildContext` as the first argument.
|
||||
|
||||
<Note>
|
||||
Note that the seed function will only be executed on app's first boot. If a configuration
|
||||
already exists in the database, it will not be executed.
|
||||
</Note>
|
||||
<Callout type="info">
|
||||
Note that the seed function will only be executed on app's first boot. If a
|
||||
configuration already exists in the database, it will not be executed.
|
||||
</Callout>
|
||||
|
||||
```typescript
|
||||
import { createApp, type ModuleBuildContext } from "bknd";
|
||||
|
||||
const app = createApp({
|
||||
connection: { /* ... */ },
|
||||
initialConfig: { /* ... */ },
|
||||
options: {
|
||||
seed: async (ctx: ModuleBuildContext) => {
|
||||
await ctx.em.mutator("posts").insertMany([
|
||||
{ title: "First post", slug: "first-post", content: "..." },
|
||||
{ title: "Second post", slug: "second-post" }
|
||||
]);
|
||||
}
|
||||
}
|
||||
connection: {
|
||||
/* ... */
|
||||
},
|
||||
initialConfig: {
|
||||
/* ... */
|
||||
},
|
||||
options: {
|
||||
seed: async (ctx: ModuleBuildContext) => {
|
||||
await ctx.em.mutator("posts").insertMany([
|
||||
{ title: "First post", slug: "first-post", content: "..." },
|
||||
{ title: "Second post", slug: "second-post" },
|
||||
]);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
```
|
||||
@@ -1,24 +1,28 @@
|
||||
---
|
||||
title: "React Elements"
|
||||
description: "Speed up your frontend development"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
Not only creating and maintaing a backend is time-consuming, but also integrating it into your frontend can be a hassle. With `bknd/elements`, you can easily add media uploads and authentication forms to your app without having to figure out API details.
|
||||
|
||||
<Note>
|
||||
In order to use these exported elements, make sure to wrap your app inside `ClientProvider`. See the [React Setup](/usage/react#setup) for more information.
|
||||
</Note>
|
||||
<Callout type="info">
|
||||
In order to use these exported elements, make sure to wrap your app inside
|
||||
`ClientProvider`. See the [React Setup](/usage/react#setup) for more
|
||||
information.
|
||||
</Callout>
|
||||
|
||||
# Media
|
||||
## Media
|
||||
|
||||
### Media.Dropzone
|
||||
|
||||
## Media.Dropzone
|
||||
The `Media.Dropzone` element allows retrieving from and uploading media items to your bknd instance. Without any properties specified, it will behave similar to your media library inside the bknd Admin UI. Here is how to get the last 10 items:
|
||||
|
||||
```tsx
|
||||
import { Media } from "bknd/elements"
|
||||
import { Media } from "bknd/elements";
|
||||
|
||||
export default function MediaGallery() {
|
||||
return <Media.Dropzone query={{ limit: 10, sort: "-id" }} />
|
||||
return <Media.Dropzone query={{ limit: 10, sort: "-id" }} />;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -28,15 +32,18 @@ Since you can also upload media to a specific entity, you can also point that `D
|
||||
import { Media } from "bknd/elements";
|
||||
|
||||
export default function UserAvatar() {
|
||||
return <Media.Dropzone
|
||||
return (
|
||||
<Media.Dropzone
|
||||
entity={{ name: "users", id: 1, field: "avatar" }}
|
||||
maxItems={1}
|
||||
overwrite
|
||||
/>
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
#### Props
|
||||
|
||||
- `initialItems?: xMediaFieldSchema[]`: Initial items to display, must be an array of media objects.
|
||||
- `entity?: { name: string; id: number; field: string }`: If given, the initial media items fetched will be from this entity.
|
||||
- `query?: RepoQueryIn`: Query to filter the media items.
|
||||
@@ -48,88 +55,97 @@ export default function UserAvatar() {
|
||||
- `onUploaded?: (file: FileState) => void`: Callback when a file is uploaded.
|
||||
- `placeholder?: { show?: boolean; text?: string }`: Placeholder text to show when no media items are present.
|
||||
|
||||
### Customize Rendering
|
||||
#### Customize Rendering
|
||||
|
||||
You can also customize the rendering of the media items and its uploading by passing a react element as a child. Here is an example of a custom `Media.Dropzone` that renders an user avatar (styled using tailwind):
|
||||
|
||||
```tsx
|
||||
import { Media, useMediaDropzone, useMediaDropzoneState } from "bknd/elements";
|
||||
|
||||
export default function CustomUserAvatar() {
|
||||
return <Media.Dropzone
|
||||
return (
|
||||
<Media.Dropzone
|
||||
entity={{ name: "users", id: 1, field: "avatar" }}
|
||||
maxItems={1}
|
||||
overwrite
|
||||
>
|
||||
>
|
||||
<CustomUserAvatar />
|
||||
</Media.Dropzone>
|
||||
</Media.Dropzone>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomUserAvatar() {
|
||||
const {
|
||||
wrapperRef,
|
||||
inputProps,
|
||||
showPlaceholder,
|
||||
actions: { openFileInput }
|
||||
} = useMediaDropzone();
|
||||
const { files: [ file ], isOver, isOverAccepted } = useMediaDropzoneState();
|
||||
const {
|
||||
wrapperRef,
|
||||
inputProps,
|
||||
showPlaceholder,
|
||||
actions: { openFileInput },
|
||||
} = useMediaDropzone();
|
||||
const {
|
||||
files: [file],
|
||||
isOver,
|
||||
isOverAccepted,
|
||||
} = useMediaDropzoneState();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="size-32 rounded-full border border-gray-200 flex justify-center items-center leading-none overflow-hidden"
|
||||
>
|
||||
<div className="hidden">
|
||||
<input {...inputProps} />
|
||||
</div>
|
||||
{showPlaceholder && <>{isOver && isOverAccepted ? "let it drop" : "drop here"}</>}
|
||||
{file && (
|
||||
<Media.Preview
|
||||
file={file}
|
||||
className="object-cover w-full h-full"
|
||||
onClick={openFileInput}
|
||||
/>
|
||||
)}
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="size-32 rounded-full border border-gray-200 flex justify-center items-center leading-none overflow-hidden"
|
||||
>
|
||||
<div className="hidden">
|
||||
<input {...inputProps} />
|
||||
</div>
|
||||
);
|
||||
{showPlaceholder && (
|
||||
<>{isOver && isOverAccepted ? "let it drop" : "drop here"}</>
|
||||
)}
|
||||
{file && (
|
||||
<Media.Preview
|
||||
file={file}
|
||||
className="object-cover w-full h-full"
|
||||
onClick={openFileInput}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
# Auth
|
||||
## Auth
|
||||
|
||||
Adding authentication to your app with bknd is as easy as adding a `<form method="POST" />` with an action pointing to the action (`login` or `register`) to the strategy you want to use, e.g. for the password strategy, use `/api/auth/password/login`. But to make it even easier, you can use the `Auth.*` elements.
|
||||
|
||||
## `Auth.Screen`
|
||||
### `Auth.Screen`
|
||||
|
||||
The `Auth.Screen` element is a wrapper around the `Auth.Form` element that provides a full page screen. The current layout is admittedly very basic, but there will be more customization options in the future.
|
||||
|
||||
```tsx
|
||||
import { Auth } from "bknd/elements"
|
||||
import { Auth } from "bknd/elements";
|
||||
|
||||
export default function LoginScreen() {
|
||||
return <Auth.Screen action="login" />
|
||||
return <Auth.Screen action="login" />;
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
Note that this component doesn't require any strategy-specific information, as it gathers it itself.
|
||||
#### Props
|
||||
|
||||
Note that this component doesn't require any strategy-specific information, as it gathers it itself.
|
||||
|
||||
- `action: "login" | "register"`: The action to perform.
|
||||
- `method?: "POST" | "GET"`: The method to use for the form.
|
||||
|
||||
### `Auth.Form`
|
||||
|
||||
|
||||
## `Auth.Form`
|
||||
If you only wish to render the form itself without the screen, you can use the `Auth.Form` element. Unlike the `Auth.Screen`, this element requires the `strategy` prop to be set to the strategy you want to use. You can either specify it manually, use use the exported hook `useAuthStrategies()` for fetch them from your bknd instance.
|
||||
|
||||
```tsx
|
||||
import { Auth, useAuthStrategies } from "bknd/elements"
|
||||
import { Auth, useAuthStrategies } from "bknd/elements";
|
||||
|
||||
export default function LoginForm() {
|
||||
const { strategies, basepath, loading } = useAuthStrategies();
|
||||
if (loading) return null;
|
||||
|
||||
return <Auth.Form
|
||||
action="login"
|
||||
strategies={strategies}
|
||||
basepath={basepath}
|
||||
/>
|
||||
const { strategies, basepath, loading } = useAuthStrategies();
|
||||
if (loading) return null;
|
||||
|
||||
return (
|
||||
<Auth.Form action="login" strategies={strategies} basepath={basepath} />
|
||||
);
|
||||
}
|
||||
```
|
||||
```
|
||||
@@ -1,20 +1,23 @@
|
||||
---
|
||||
title: 'Introduction'
|
||||
description: 'Setting up bknd'
|
||||
title: "Introduction"
|
||||
description: "Setting up bknd"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
There are several methods to get **bknd** up and running. You can choose between these options:
|
||||
|
||||
1. [Run it using the CLI](/usage/cli): That's the easiest and fastest way to get started.
|
||||
2. Use a runtime like [Node](/integration/node), [Bun](/integration/bun) or
|
||||
[Cloudflare](/integration/cloudflare) (workerd). This will run the API and UI in the runtime's
|
||||
native server and serves the UI assets statically from `node_modules`.
|
||||
[Cloudflare](/integration/cloudflare) (workerd). This will run the API and UI in the runtime's
|
||||
native server and serves the UI assets statically from `node_modules`.
|
||||
3. Run it inside your React framework of choice like [Next.js](/integration/nextjs),
|
||||
[Astro](/integration/astro) or [Remix](/integration/remix).
|
||||
[Astro](/integration/astro) or [Remix](/integration/remix).
|
||||
|
||||
There is also a fourth option, which is running it inside a
|
||||
[Docker container](/integration/docker). This is essentially a wrapper around the CLI.
|
||||
|
||||
## Basic setup
|
||||
|
||||
Regardless of the method you choose, at the end all adapters come down to the actual
|
||||
instantiation of the `App`, which in raw looks like this:
|
||||
|
||||
@@ -22,7 +25,9 @@ instantiation of the `App`, which in raw looks like this:
|
||||
import { createApp, type CreateAppConfig } from "bknd";
|
||||
|
||||
// create the app
|
||||
const config = { /* ... */ } satisfies CreateAppConfig;
|
||||
const config = {
|
||||
/* ... */
|
||||
} satisfies CreateAppConfig;
|
||||
const app = createApp(config);
|
||||
|
||||
// build the app
|
||||
@@ -36,40 +41,41 @@ In Web API compliant environments, all you have to do is to default exporting th
|
||||
implements the `Fetch` API.
|
||||
|
||||
## Configuration (`CreateAppConfig`)
|
||||
|
||||
The `CreateAppConfig` type is the main configuration object for the `createApp` function. It has
|
||||
the following properties:
|
||||
|
||||
```typescript
|
||||
import type { App, InitialModuleConfigs, ModuleBuildContext } from "bknd";
|
||||
import type { Connection } from "bknd/data";
|
||||
import type { App, InitialModuleConfigs, ModuleBuildContext, Connection } from "bknd";
|
||||
import type { Config } from "@libsql/client";
|
||||
|
||||
type AppPlugin = (app: App) => Promise<void> | void;
|
||||
type ManagerOptions = {
|
||||
basePath?: string;
|
||||
trustFetched?: boolean;
|
||||
onFirstBoot?: () => Promise<void>;
|
||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||
basePath?: string;
|
||||
trustFetched?: boolean;
|
||||
onFirstBoot?: () => Promise<void>;
|
||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||
};
|
||||
|
||||
type CreateAppConfig = {
|
||||
connection?:
|
||||
| Connection
|
||||
| Config;
|
||||
initialConfig?: InitialModuleConfigs;
|
||||
options?: {
|
||||
plugins?: AppPlugin[];
|
||||
manager?: ManagerOptions
|
||||
};
|
||||
connection?: Connection | Config;
|
||||
initialConfig?: InitialModuleConfigs;
|
||||
options?: {
|
||||
plugins?: AppPlugin[];
|
||||
manager?: ManagerOptions;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### `connection`
|
||||
|
||||
The `connection` property is the main connection object to the database. It can be either an object with libsql config or the actual `Connection` class.
|
||||
|
||||
```ts
|
||||
const connection = {
|
||||
url: "<url>",
|
||||
authToken: "<token>"
|
||||
}
|
||||
url: "<url>",
|
||||
authToken: "<token>",
|
||||
};
|
||||
```
|
||||
|
||||
Alternatively, you can pass an instance of a `Connection` class directly,
|
||||
@@ -78,6 +84,7 @@ see [Custom Connection](/usage/database#custom-connection) as a reference.
|
||||
If the connection object is omitted, the app will try to use an in-memory database.
|
||||
|
||||
### `initialConfig`
|
||||
|
||||
As [initial configuration](/usage/database#initial-structure), you can either pass a partial configuration object or a complete one
|
||||
with a version number. The version number is used to automatically migrate the configuration up
|
||||
to the latest version upon boot. The default configuration looks like this:
|
||||
@@ -92,8 +99,13 @@ to the latest version upon boot. The default configuration looks like this:
|
||||
},
|
||||
"cors": {
|
||||
"origin": "*",
|
||||
"allow_methods": ["GET", "POST", "PATCH", "PUT", "DELETE" ],
|
||||
"allow_headers": ["Content-Type", "Content-Length", "Authorization", "Accept"]
|
||||
"allow_methods": ["GET", "POST", "PATCH", "PUT", "DELETE"],
|
||||
"allow_headers": [
|
||||
"Content-Type",
|
||||
"Content-Length",
|
||||
"Authorization",
|
||||
"Accept"
|
||||
]
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
@@ -146,11 +158,13 @@ to the latest version upon boot. The default configuration looks like this:
|
||||
```
|
||||
|
||||
You can use the CLI to get the default configuration:
|
||||
|
||||
```sh
|
||||
npx bknd config --pretty
|
||||
```
|
||||
|
||||
To validate your configuration against a JSON schema, you can also dump the schema using the CLI:
|
||||
|
||||
```sh
|
||||
npx bknd schema
|
||||
```
|
||||
@@ -158,6 +172,7 @@ npx bknd schema
|
||||
To create an initial data structure, you can use helpers [described here](/usage/database#initial-structure).
|
||||
|
||||
### `options.plugins`
|
||||
|
||||
The `plugins` property is an array of functions that are called after the app has been built,
|
||||
but before its event is emitted. This is useful for adding custom routes or other functionality.
|
||||
A simple plugin that adds a custom route looks like this:
|
||||
@@ -166,7 +181,7 @@ A simple plugin that adds a custom route looks like this:
|
||||
import type { AppPlugin } from "bknd";
|
||||
|
||||
export const myPlugin: AppPlugin = (app) => {
|
||||
app.server.get("/hello", (c) => c.json({ hello: "world" }));
|
||||
app.server.get("/hello", (c) => c.json({ hello: "world" }));
|
||||
};
|
||||
```
|
||||
|
||||
@@ -175,35 +190,39 @@ structure, add custom middlewares, respond to or add events, etc. Plugins are ve
|
||||
make sure to only run trusted ones.
|
||||
|
||||
### `options.seed`
|
||||
|
||||
The `seed` property is a function that is called when the app is booted for the first time. It is used to seed the database with initial data. The function is passed a `ModuleBuildContext` object:
|
||||
|
||||
```ts
|
||||
type ModuleBuildContext = {
|
||||
connection: Connection;
|
||||
server: Hono;
|
||||
em: EntityManager;
|
||||
emgr: EventManager;
|
||||
guard: Guard;
|
||||
connection: Connection;
|
||||
server: Hono;
|
||||
em: EntityManager;
|
||||
emgr: EventManager;
|
||||
guard: Guard;
|
||||
};
|
||||
|
||||
const seed = async (ctx: ModuleBuildContext) => {
|
||||
// seed the database
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false }
|
||||
]);
|
||||
// seed the database
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false },
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
### `options.manager`
|
||||
|
||||
This object is passed to the `ModuleManager` which is responsible for:
|
||||
|
||||
- validating and maintaining configuration of all modules
|
||||
- building all modules (data, auth, media, flows)
|
||||
- maintaining the `ModuleBuildContext` used by the modules
|
||||
|
||||
The `options.manager` object has the following properties:
|
||||
|
||||
- `basePath` (`string`): The base path for the Hono instance. This is used to prefix all routes.
|
||||
- `trustFetched` (`boolean`): If set to `true`, the app will not perform any validity checks for
|
||||
the given or fetched configuration.
|
||||
the given or fetched configuration.
|
||||
- `onFirstBoot` (`() => Promise<void>`): A function that is called when the app is booted for
|
||||
the first time.
|
||||
the first time.
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
title: 'SDK (React)'
|
||||
description: 'Use the bknd SDK for React'
|
||||
title: "SDK (React)"
|
||||
description: "Use the bknd SDK for React"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
There are 4 useful hooks to work with your backend:
|
||||
|
||||
1. simple hooks which are solely based on the [API](/usage/sdk):
|
||||
- [`useApi`](#useapi)
|
||||
- [`useEntity`](#useentity)
|
||||
@@ -11,69 +13,74 @@ There are 4 useful hooks to work with your backend:
|
||||
- [`useApiQuery`](#useapiquery)
|
||||
- [`useEntityQuery`](#useentityquery)
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
In order to use them, make sure you wrap your `<App />` inside `<ClientProvider />`, so that these hooks point to your bknd instance:
|
||||
|
||||
```tsx
|
||||
import { ClientProvider } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
return <ClientProvider>
|
||||
{/* your app */}
|
||||
</ClientProvider>
|
||||
return <ClientProvider>{/* your app */}</ClientProvider>;
|
||||
}
|
||||
```
|
||||
|
||||
For all other examples below, we'll assume that your app is wrapped inside the `ClientProvider`.
|
||||
|
||||
## `useApi()`
|
||||
|
||||
To use the simple hook that returns the Api, you can use:
|
||||
|
||||
```tsx
|
||||
import { useApi } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const api = useApi();
|
||||
// ...
|
||||
const api = useApi();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## `useApiQuery()`
|
||||
|
||||
This hook wraps the API class in an SWR hook for convenience. You can use any API endpoint
|
||||
supported, like so:
|
||||
|
||||
```tsx
|
||||
import { useApiQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const { data, ...swr } = useApiQuery((api) => api.data.readMany("comments"));
|
||||
const { data, ...swr } = useApiQuery((api) => api.data.readMany("comments"));
|
||||
|
||||
if (swr.error) return <div>Error</div>
|
||||
if (swr.isLoading) return <div>Loading...</div>
|
||||
if (swr.error) return <div>Error</div>;
|
||||
if (swr.isLoading) return <div>Loading...</div>;
|
||||
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
* `selector: (api: Api) => FetchPromise`
|
||||
|
||||
The first parameter is a selector function that provides an Api instance and expects an
|
||||
endpoint function to be returned.
|
||||
- `selector: (api: Api) => FetchPromise`
|
||||
|
||||
* `options`: optional object that inherits from `SWRConfiguration`
|
||||
The first parameter is a selector function that provides an Api instance and expects an
|
||||
endpoint function to be returned.
|
||||
|
||||
- `options`: optional object that inherits from `SWRConfiguration`
|
||||
|
||||
```ts
|
||||
type Options <Data> = import("swr").SWRConfiguration & {
|
||||
enabled? : boolean;
|
||||
refine? : (data: Data) => Data | any;
|
||||
}
|
||||
type Options<Data> = import("swr").SWRConfiguration & {
|
||||
enabled?: boolean;
|
||||
refine?: (data: Data) => Data | any;
|
||||
};
|
||||
```
|
||||
|
||||
* `enabled`: Determines whether this hook should trigger a fetch of the data or not.
|
||||
* `refine`: Optional refinement that is called after a response from the API has been
|
||||
received. Useful to omit irrelevant data from the response (see example below).
|
||||
|
||||
received. Useful to omit irrelevant data from the response (see example below).
|
||||
|
||||
### Using mutations
|
||||
|
||||
To query and mutate data using this hook, you can leverage the parameters returned. In the
|
||||
following example we'll also use a `refine` function as well as `revalidateOnFocus` (option from
|
||||
`SWRConfiguration`) so that our data keeps updating on window focus change.
|
||||
@@ -83,132 +90,151 @@ import { useEffect, useState } from "react";
|
||||
import { useApiQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const [text, setText] = useState("");
|
||||
const { data, api, mutate, ...q } = useApiQuery(
|
||||
(api) => api.data.readOne("comments", 1),
|
||||
{
|
||||
// filter to a subset of the response
|
||||
refine: (data) => data.data,
|
||||
revalidateOnFocus: true
|
||||
}
|
||||
);
|
||||
const [text, setText] = useState("");
|
||||
const { data, api, mutate, ...q } = useApiQuery(
|
||||
(api) => api.data.readOne("comments", 1),
|
||||
{
|
||||
// filter to a subset of the response
|
||||
refine: (data) => data.data,
|
||||
revalidateOnFocus: true,
|
||||
},
|
||||
);
|
||||
|
||||
const comment = data ? data : null;
|
||||
const comment = data ? data : null;
|
||||
|
||||
useEffect(() => {
|
||||
setText(comment?.content ?? "");
|
||||
}, [comment]);
|
||||
useEffect(() => {
|
||||
setText(comment?.content ?? "");
|
||||
}, [comment]);
|
||||
|
||||
if (q.error) return <div>Error</div>
|
||||
if (q.isLoading) return <div>Loading...</div>
|
||||
if (q.error) return <div>Error</div>;
|
||||
if (q.isLoading) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!comment) return;
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!comment) return;
|
||||
|
||||
// this will automatically revalidate the query
|
||||
await mutate(async () => {
|
||||
const res = await api.data.updateOne("comments", comment.id, {
|
||||
content: text
|
||||
});
|
||||
return res.data;
|
||||
});
|
||||
// this will automatically revalidate the query
|
||||
await mutate(async () => {
|
||||
const res = await api.data.updateOne("comments", comment.id, {
|
||||
content: text,
|
||||
});
|
||||
return res.data;
|
||||
});
|
||||
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
);
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## `useEntity()`
|
||||
|
||||
This hook wraps the endpoints of `DataApi` and returns CRUD options as parameters:
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEntity } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const [data, setData] = useState<any>();
|
||||
const { create, read, update, _delete } = useEntity("comments", 1);
|
||||
const [data, setData] = useState<any>();
|
||||
const { create, read, update, _delete } = useEntity("comments", 1);
|
||||
|
||||
useEffect(() => {
|
||||
read().then(setData);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
read().then(setData);
|
||||
}, []);
|
||||
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||
}
|
||||
```
|
||||
|
||||
If you only supply the entity name as string without an ID, the `read` method will fetch a list
|
||||
of entities instead of a single entry.
|
||||
|
||||
### Props
|
||||
|
||||
Following props are available when using `useEntityQuery([entity], [id?])`:
|
||||
|
||||
- `entity: string`: Specify the table name of the entity
|
||||
- `id?: number | string`: If an id given, it will fetch a single entry, otherwise a list
|
||||
|
||||
### Returned actions
|
||||
|
||||
The following actions are returned from this hook:
|
||||
|
||||
- `create: (input: object)`: Create a new entry
|
||||
- `read: (query: Partial<RepoQuery> = {})`: If an id was given,
|
||||
it returns a single item, otherwise a list
|
||||
it returns a single item, otherwise a list
|
||||
- `update: (input: object, id?: number | string)`: If an id was given, the id parameter is
|
||||
optional. Updates the given entry partially.
|
||||
optional. Updates the given entry partially.
|
||||
- `_delete: (id?: number | string)`: If an id was given, the id parameter is
|
||||
optional. Deletes the given entry.
|
||||
optional. Deletes the given entry.
|
||||
|
||||
## `useEntityQuery()`
|
||||
|
||||
This hook wraps the actions from `useEntity` around `SWR`. The previous example would look like
|
||||
this:
|
||||
|
||||
```tsx
|
||||
import { useEntityQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const { data } = useEntityQuery("comments", 1);
|
||||
const { data } = useEntityQuery("comments", 1);
|
||||
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||
}
|
||||
```
|
||||
|
||||
### Using mutations
|
||||
|
||||
All actions returned from `useEntityQuery` are conveniently wrapped around the `mutate` function,
|
||||
so you don't have think about this:
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEntityQuery } from "bknd/client";
|
||||
|
||||
export default function App() {
|
||||
const [text, setText] = useState("");
|
||||
const { data, update, ...q } = useEntityQuery("comments", 1);
|
||||
const [text, setText] = useState("");
|
||||
const { data, update, ...q } = useEntityQuery("comments", 1);
|
||||
|
||||
const comment = data ? data : null;
|
||||
const comment = data ? data : null;
|
||||
|
||||
useEffect(() => {
|
||||
setText(comment?.content ?? "");
|
||||
}, [comment]);
|
||||
useEffect(() => {
|
||||
setText(comment?.content ?? "");
|
||||
}, [comment]);
|
||||
|
||||
if (q.error) return <div>Error</div>
|
||||
if (q.isLoading) return <div>Loading...</div>
|
||||
if (q.error) return <div>Error</div>;
|
||||
if (q.isLoading) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!comment) return;
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!comment) return;
|
||||
|
||||
// this will automatically revalidate the query
|
||||
await update({ content: text });
|
||||
// this will automatically revalidate the query
|
||||
await update({ content: text });
|
||||
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
);
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
title: 'SDK (TypeScript)'
|
||||
description: 'Use the bknd SDK in TypeScript'
|
||||
title: "SDK (TypeScript)"
|
||||
description: "Use the bknd SDK in TypeScript"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
To start using the bknd API, start by creating a new API instance:
|
||||
|
||||
```ts
|
||||
import { Api } from "bknd/client";
|
||||
|
||||
@@ -17,9 +19,11 @@ The `Api` class is the main entry point for interacting with the bknd API. It pr
|
||||
for all available modules described below.
|
||||
|
||||
## Setup
|
||||
|
||||
You can initialize an API instance by providing the `Request` object, or manually specifying the details such as `host` and `token`.
|
||||
|
||||
### Using the `Request` object
|
||||
|
||||
The recommended way to create an API instance is by passing the current `Request` object. This will automatically point the API to your current instance and extract the token from the headers (either from cookies or `Authorization` header):
|
||||
|
||||
```ts
|
||||
@@ -40,23 +44,25 @@ import { Api } from "bknd/client";
|
||||
let request: Request;
|
||||
|
||||
const api = new Api({
|
||||
host: "https://<your-endpoint>",
|
||||
request,
|
||||
host: "https://<your-endpoint>",
|
||||
request,
|
||||
});
|
||||
```
|
||||
|
||||
### Using the `token` option
|
||||
|
||||
If you want to have an API instance that is using a different token, e.g. an admin token, you can create it by specifying the `host` and `token` option:
|
||||
|
||||
```ts
|
||||
import { Api } from "bknd/client";
|
||||
const api = new Api({
|
||||
host: "https://<your-endpoint>",
|
||||
token: "<your-token>"
|
||||
host: "https://<your-endpoint>",
|
||||
token: "<your-token>",
|
||||
});
|
||||
```
|
||||
|
||||
### Using a local API
|
||||
|
||||
In case the place where you're using the API is the same as your bknd instance (e.g. when using it embedded in a React framework), you can specify a `fetcher` option to point to your bknd app. This way, requests won't travel over the network and instead processed locally:
|
||||
|
||||
```ts
|
||||
@@ -67,141 +73,171 @@ import { Api } from "bknd/client";
|
||||
let app: App;
|
||||
|
||||
const api = new Api({
|
||||
fetcher: app.server.request as typeof fetch,
|
||||
// specify `host` and `token` or `request`
|
||||
fetcher: app.server.request as typeof fetch,
|
||||
// specify `host` and `token` or `request`
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Data (`api.data`)
|
||||
|
||||
Access the `Data` specific API methods at `api.data`.
|
||||
|
||||
### `data.readMany([entity], [query])`
|
||||
|
||||
To retrieve a list of records from an entity, use the `readMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.readMany("posts");
|
||||
```
|
||||
|
||||
You can also add additional query instructions:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.readMany("posts", {
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
select: ["id", "title", "views"],
|
||||
with: {
|
||||
// join last 2 comments
|
||||
comments: {
|
||||
with: {
|
||||
// along with the comments' user
|
||||
users: {}
|
||||
},
|
||||
limit: 2,
|
||||
sort: "-id"
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
select: ["id", "title", "views"],
|
||||
with: {
|
||||
// join last 2 comments
|
||||
comments: {
|
||||
with: {
|
||||
// along with the comments' user
|
||||
users: {},
|
||||
},
|
||||
// also get the first 2 images, but only the path
|
||||
images: {
|
||||
select: ["path"],
|
||||
limit: 2
|
||||
}
|
||||
},
|
||||
where: {
|
||||
// same as '{ title: { $eg: "Hello, World!" } }'
|
||||
title: "Hello, World!",
|
||||
// only with views greater than 100
|
||||
views: {
|
||||
$gt: 100
|
||||
}
|
||||
},
|
||||
// sort by views descending (without "-" would be ascending)
|
||||
sort: "-views"
|
||||
limit: 2,
|
||||
sort: "-id",
|
||||
},
|
||||
// also get the first 2 images, but only the path
|
||||
images: {
|
||||
select: ["path"],
|
||||
limit: 2,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
// same as '{ title: { $eg: "Hello, World!" } }'
|
||||
title: "Hello, World!",
|
||||
// only with views greater than 100
|
||||
views: {
|
||||
$gt: 100,
|
||||
},
|
||||
},
|
||||
// sort by views descending (without "-" would be ascending)
|
||||
sort: "-views",
|
||||
});
|
||||
```
|
||||
|
||||
The `with` property automatically adds the related entries to the response.
|
||||
|
||||
### `data.readOne([entity], [id])`
|
||||
|
||||
To retrieve a single record from an entity, use the `readOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.readOne("posts", 1);
|
||||
```
|
||||
|
||||
### `data.createOne([entity], [data])`
|
||||
|
||||
To create a single record of an entity, use the `createOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.createOne("posts", {
|
||||
title: "Hello, World!",
|
||||
content: "This is a test post.",
|
||||
views: 0
|
||||
title: "Hello, World!",
|
||||
content: "This is a test post.",
|
||||
views: 0,
|
||||
});
|
||||
```
|
||||
|
||||
### `data.createMany([entity], [data])`
|
||||
|
||||
To create many records of an entity, use the `createMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.createMany("posts", [
|
||||
{ title: "Hello, World!" },
|
||||
{ title: "Again, Hello." },
|
||||
{ title: "Hello, World!" },
|
||||
{ title: "Again, Hello." },
|
||||
]);
|
||||
```
|
||||
|
||||
### `data.updateOne([entity], [id], [data])`
|
||||
|
||||
To update a single record of an entity, use the `updateOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.updateOne("posts", 1, {
|
||||
views: 1
|
||||
views: 1,
|
||||
});
|
||||
```
|
||||
|
||||
### `data.updateMany([entity], [where], [update])`
|
||||
|
||||
To update many records of an entity, use the `updateMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.updateMany("posts", { views: { $gt: 1 } }, {
|
||||
title: "viewed more than once"
|
||||
});
|
||||
const { data } = await api.data.updateMany(
|
||||
"posts",
|
||||
{ views: { $gt: 1 } },
|
||||
{
|
||||
title: "viewed more than once",
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### `data.deleteOne([entity], [id])`
|
||||
|
||||
To delete a single record of an entity, use the `deleteOne` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.deleteOne("posts", 1);
|
||||
```
|
||||
|
||||
### `data.deleteMany([entity], [where])`
|
||||
|
||||
To delete many records of an entity, use the `deleteMany` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.data.deleteMany("posts", { views: { $lte: 1 } });
|
||||
```
|
||||
|
||||
## Auth (`api.auth`)
|
||||
|
||||
Access the `Auth` specific API methods at `api.auth`. If there is successful authentication, the
|
||||
API will automatically save the token and use it for subsequent requests.
|
||||
|
||||
### `auth.strategies()`
|
||||
|
||||
To retrieve the available authentication strategies, use the `strategies` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.strategies();
|
||||
```
|
||||
|
||||
### `auth.login([strategy], [input])`
|
||||
|
||||
To log in with a password, use the `login` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.login("password", {
|
||||
email: "...",
|
||||
password: "..."
|
||||
email: "...",
|
||||
password: "...",
|
||||
});
|
||||
```
|
||||
|
||||
### `auth.register([strategy], [input])`
|
||||
|
||||
To register with a password, use the `register` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.register("password", {
|
||||
email: "...",
|
||||
password: "..."
|
||||
email: "...",
|
||||
password: "...",
|
||||
});
|
||||
```
|
||||
|
||||
### `auth.me()`
|
||||
|
||||
To retrieve the current user, use the `me` method:
|
||||
|
||||
```ts
|
||||
const { data } = await api.auth.me();
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Delete many
|
||||
full: true
|
||||
_openapi:
|
||||
method: DELETE
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"delete"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Delete one
|
||||
full: true
|
||||
_openapi:
|
||||
method: DELETE
|
||||
route: /api/data/entity/{entity}/{id}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}","method":"delete"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/data/getApiData.mdx
Normal file
15
docs/content/docs/api-reference/data/getApiData.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve data configuration
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Read many
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Read one
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/entity/{entity}/{id}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Read many by reference
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/entity/{entity}/{id}/{reference}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}/{reference}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve entity info
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/info/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/info/{entity}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve data schema
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/schema.json
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/schema.json","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Retrieve entity schema
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/schemas/{entity}/{context}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/schemas/{entity}/{context}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/data/getApiDataSync.mdx
Normal file
15
docs/content/docs/api-reference/data/getApiDataSync.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Sync database schema
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/data/sync
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/sync","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Update many
|
||||
full: true
|
||||
_openapi:
|
||||
method: PATCH
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"patch"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Update one
|
||||
full: true
|
||||
_openapi:
|
||||
method: PATCH
|
||||
route: /api/data/entity/{entity}/{id}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/{id}","method":"patch"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Insert one or many
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Count entities
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}/fn/count
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/fn/count","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Check if entity exists
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}/fn/exists
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/fn/exists","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Query entities
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/data/entity/{entity}/query
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/data/entity/{entity}/query","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -1,14 +1,17 @@
|
||||
---
|
||||
title: 'Introduction'
|
||||
description: 'Example section for showcasing your API endpoints'
|
||||
title: "Introduction"
|
||||
description: "Example section for showcasing your API endpoints"
|
||||
icon: Album
|
||||
tags: ["openapi"]
|
||||
---
|
||||
|
||||
<Warning>
|
||||
<Callout type="warn">
|
||||
You need to make sure to point your request to your API server.
|
||||
</Warning>
|
||||
<Note>
|
||||
This section is a work in progress. Updates will be made soon.
|
||||
</Note>
|
||||
</Callout>
|
||||
|
||||
<Callout type="info">
|
||||
This section is a work in progress. Updates will be made soon.
|
||||
</Callout>
|
||||
|
||||
## Authentication
|
||||
|
||||
7
docs/content/docs/api-reference/meta.json
Normal file
7
docs/content/docs/api-reference/meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"title": "API Reference",
|
||||
"description": "Specs for endpoints and options",
|
||||
"icon": "Webhook",
|
||||
"root": true,
|
||||
"pages": ["introduction", "..."]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the config for a module
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/config/{module}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/config/{module}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the raw config
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/config/raw
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/config/raw","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/system/getApiSystemInfo.mdx
Normal file
15
docs/content/docs/api-reference/system/getApiSystemInfo.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the server info
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/info
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/info","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
15
docs/content/docs/api-reference/system/getApiSystemPing.mdx
Normal file
15
docs/content/docs/api-reference/system/getApiSystemPing.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Ping the server
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/ping
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/ping","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Get the schema for a module
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /api/system/schema/{module}
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/schema/{module}","method":"get"}]} webhooks={[]} hasHead={false} />
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Build the app
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /api/system/build
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
||||
|
||||
<APIPage document={"openapi.json"} operations={[{"path":"/api/system/build","method":"post"}]} webhooks={[]} hasHead={false} />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user