@btst/stack 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -709
- package/dist/api/index.cjs +2 -1
- package/dist/api/index.d.cts +4 -3
- package/dist/api/index.d.mts +4 -3
- package/dist/api/index.d.ts +4 -3
- package/dist/api/index.mjs +1 -1
- package/dist/client/components/compose.cjs +68 -0
- package/dist/client/components/compose.mjs +65 -0
- package/dist/client/components/error-boundary.cjs +24 -0
- package/dist/client/components/error-boundary.mjs +22 -0
- package/dist/client/components/index.cjs +10 -0
- package/dist/client/components/index.d.cts +52 -0
- package/dist/client/components/index.d.mts +52 -0
- package/dist/client/components/index.d.ts +52 -0
- package/dist/client/components/index.mjs +2 -0
- package/dist/client/index.cjs +24 -5
- package/dist/client/index.d.cts +125 -8
- package/dist/client/index.d.mts +125 -8
- package/dist/client/index.d.ts +125 -8
- package/dist/client/index.mjs +21 -4
- package/dist/client/meta-utils.cjs +162 -0
- package/dist/client/meta-utils.mjs +160 -0
- package/dist/client/path-utils.cjs +15 -0
- package/dist/client/path-utils.mjs +13 -0
- package/dist/client/sitemap-utils.cjs +14 -0
- package/dist/client/sitemap-utils.mjs +12 -0
- package/dist/context/index.cjs +6 -63
- package/dist/context/index.d.cts +21 -24
- package/dist/context/index.d.mts +21 -24
- package/dist/context/index.d.ts +21 -24
- package/dist/context/index.mjs +1 -61
- package/dist/context/provider.cjs +51 -0
- package/dist/context/provider.mjs +46 -0
- package/dist/index.cjs +2 -3
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +1 -2
- package/dist/plugins/api/index.cjs +13 -0
- package/dist/plugins/api/index.d.cts +40 -0
- package/dist/plugins/api/index.d.mts +40 -0
- package/dist/plugins/api/index.d.ts +40 -0
- package/dist/plugins/api/index.mjs +8 -0
- package/dist/plugins/blog/api/index.cjs +11 -0
- package/dist/plugins/blog/api/index.d.cts +7 -0
- package/dist/plugins/blog/api/index.d.mts +7 -0
- package/dist/plugins/blog/api/index.d.ts +7 -0
- package/dist/plugins/blog/api/index.mjs +2 -0
- package/dist/plugins/blog/api/plugin.cjs +569 -0
- package/dist/plugins/blog/api/plugin.mjs +565 -0
- package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
- package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
- package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
- package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
- package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
- package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
- package/dist/plugins/blog/client/components/index.cjs +17 -0
- package/dist/plugins/blog/client/components/index.d.cts +22 -0
- package/dist/plugins/blog/client/components/index.d.mts +22 -0
- package/dist/plugins/blog/client/components/index.d.ts +22 -0
- package/dist/plugins/blog/client/components/index.mjs +12 -0
- package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
- package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
- package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
- package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
- package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
- package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
- package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
- package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
- package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
- package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
- package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
- package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
- package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
- package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
- package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
- package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
- package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
- package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
- package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
- package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
- package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
- package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
- package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
- package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
- package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
- package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
- package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
- package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
- package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
- package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
- package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
- package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
- package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
- package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
- package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
- package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
- package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
- package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
- package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
- package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
- package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
- package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
- package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
- package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
- package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
- package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
- package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
- package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
- package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
- package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
- package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
- package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
- package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
- package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
- package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
- package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
- package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
- package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
- package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
- package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
- package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
- package/dist/plugins/blog/client/hooks/index.cjs +17 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
- package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
- package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
- package/dist/plugins/blog/client/hooks/index.mjs +1 -0
- package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
- package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
- package/dist/plugins/blog/client/index.cjs +7 -0
- package/dist/plugins/blog/client/index.d.cts +414 -0
- package/dist/plugins/blog/client/index.d.mts +414 -0
- package/dist/plugins/blog/client/index.d.ts +414 -0
- package/dist/plugins/blog/client/index.mjs +1 -0
- package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
- package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
- package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
- package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
- package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
- package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
- package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
- package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
- package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
- package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
- package/dist/plugins/blog/client/localization/index.cjs +17 -0
- package/dist/plugins/blog/client/localization/index.mjs +15 -0
- package/dist/plugins/blog/client/plugin.cjs +462 -0
- package/dist/plugins/blog/client/plugin.mjs +460 -0
- package/dist/plugins/blog/client.css +3 -0
- package/dist/plugins/blog/db.cjs +90 -0
- package/dist/plugins/blog/db.mjs +88 -0
- package/dist/plugins/blog/query-keys.cjs +181 -0
- package/dist/plugins/blog/query-keys.d.cts +530 -0
- package/dist/plugins/blog/query-keys.d.mts +530 -0
- package/dist/plugins/blog/query-keys.d.ts +530 -0
- package/dist/plugins/blog/query-keys.mjs +179 -0
- package/dist/plugins/blog/schemas.cjs +39 -0
- package/dist/plugins/blog/schemas.mjs +35 -0
- package/dist/plugins/blog/style.css +22 -0
- package/dist/plugins/blog/utils.cjs +97 -0
- package/dist/plugins/blog/utils.mjs +87 -0
- package/dist/plugins/client/index.cjs +15 -0
- package/dist/plugins/client/index.d.cts +57 -0
- package/dist/plugins/client/index.d.mts +57 -0
- package/dist/plugins/client/index.d.ts +57 -0
- package/dist/plugins/client/index.mjs +9 -0
- package/dist/{shared/stack.3OUyGp_E.mjs → plugins/utils.mjs} +1 -1
- package/dist/shared/{stack.DORw_1ps.d.cts → stack.ByOugz9d.d.cts} +17 -1
- package/dist/shared/{stack.DORw_1ps.d.mts → stack.ByOugz9d.d.mts} +17 -1
- package/dist/shared/{stack.DORw_1ps.d.ts → stack.ByOugz9d.d.ts} +17 -1
- package/dist/shared/stack.CoPoHVfV.d.cts +76 -0
- package/dist/shared/stack.CoPoHVfV.d.mts +76 -0
- package/dist/shared/stack.CoPoHVfV.d.ts +76 -0
- package/package.json +102 -14
- package/src/__tests__/plugins.test.tsx +539 -0
- package/src/__tests__/sitemap.test.ts +60 -0
- package/src/api/index.ts +75 -0
- package/src/client/components/compose.tsx +116 -0
- package/src/client/components/error-boundary.tsx +30 -0
- package/src/client/components/index.tsx +2 -0
- package/src/client/index.ts +109 -0
- package/src/client/meta-utils.ts +228 -0
- package/src/client/path-utils.ts +38 -0
- package/src/client/sitemap-utils.ts +46 -0
- package/src/context/index.ts +1 -0
- package/src/context/provider.tsx +157 -0
- package/src/index.ts +1 -0
- package/src/plugins/api/index.ts +50 -0
- package/src/plugins/blog/api/index.ts +2 -0
- package/src/plugins/blog/api/plugin.ts +759 -0
- package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
- package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
- package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
- package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
- package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
- package/src/plugins/blog/client/components/index.tsx +11 -0
- package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
- package/src/plugins/blog/client/components/loading/index.tsx +27 -0
- package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
- package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
- package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
- package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
- package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
- package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
- package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
- package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
- package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
- package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
- package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
- package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
- package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
- package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
- package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
- package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
- package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
- package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
- package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
- package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
- package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
- package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
- package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
- package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
- package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
- package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
- package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
- package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
- package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
- package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
- package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
- package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
- package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
- package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
- package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
- package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
- package/src/plugins/blog/client/hooks/index.tsx +1 -0
- package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
- package/src/plugins/blog/client/index.ts +9 -0
- package/src/plugins/blog/client/localization/blog-card.ts +3 -0
- package/src/plugins/blog/client/localization/blog-common.ts +7 -0
- package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
- package/src/plugins/blog/client/localization/blog-list.ts +14 -0
- package/src/plugins/blog/client/localization/blog-post.ts +9 -0
- package/src/plugins/blog/client/localization/index.ts +15 -0
- package/src/plugins/blog/client/overrides.ts +123 -0
- package/src/plugins/blog/client/plugin.tsx +672 -0
- package/src/plugins/blog/client.css +3 -0
- package/src/plugins/blog/db.ts +90 -0
- package/src/plugins/blog/query-keys.ts +267 -0
- package/src/plugins/blog/schemas.ts +39 -0
- package/src/plugins/blog/style.css +22 -0
- package/src/plugins/blog/types.ts +37 -0
- package/src/plugins/blog/utils.ts +144 -0
- package/src/plugins/client/index.ts +53 -0
- package/src/plugins/index.ts +0 -0
- package/src/plugins/utils.ts +35 -0
- package/src/types.ts +209 -0
- package/dist/plugins/index.cjs +0 -15
- package/dist/plugins/index.d.cts +0 -64
- package/dist/plugins/index.d.mts +0 -64
- package/dist/plugins/index.d.ts +0 -64
- package/dist/plugins/index.mjs +0 -11
- package/dist/shared/stack.DrUAVfIH.d.cts +0 -17
- package/dist/shared/stack.DrUAVfIH.d.mts +0 -17
- package/dist/shared/stack.DrUAVfIH.d.ts +0 -17
- /package/dist/{shared/stack.CktCg4PJ.cjs → plugins/utils.cjs} +0 -0
package/README.md
CHANGED
|
@@ -1,791 +1,238 @@
|
|
|
1
|
-
# Better Stack
|
|
1
|
+
# @BTST - Better Stack
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Composable full-stack plugin system for React frameworks**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@btst/stack)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
+
[📖 Documentation](https://www.better-stack.ai) • [🐛 Issues](https://github.com/olliethedev/better-stack/issues)
|
|
11
|
+
|
|
10
12
|
</div>
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
---
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
## What Problem Does This Solve?
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
- **🔄 Full-Stack Type Safety** - End-to-end TypeScript with automatic type inference, no casts needed
|
|
18
|
-
- **🗄️ Schema-First Database** - Define your data models once using Better DB with typed adapters
|
|
19
|
-
- **🚀 Production Ready** - Built on proven libraries (Better Call, Better DB, Yar Router)
|
|
20
|
-
- **⚡ Zero Config** - Works out of the box with sensible defaults
|
|
21
|
-
- **✨ Developer Experience** - Helper functions preserve route keys, hook names, and endpoint types
|
|
18
|
+
Your app needs a blog. Or a scheduling system. Or user feedback collection. Or an AI assistant. These are **horizontal features**—capabilities that cut across your entire app, not specific to your core domain.
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
Building them from scratch means weeks of work: routes, API endpoints, database schemas, authentication, SSR, metadata, hooks, forms, error handling...
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
npm install @btst/stack
|
|
27
|
-
# or
|
|
28
|
-
pnpm add @btst/stack
|
|
29
|
-
# or
|
|
30
|
-
yarn add @btst/stack
|
|
31
|
-
```
|
|
22
|
+
Better Stack lets you **add these features in minutes** as composable plugins that work across any React framework.
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
- **Composable architecture** - Mix and match features like LEGO blocks. Add blog + scheduling + feedback + newsletters, all working together seamlessly
|
|
25
|
+
- **Framework agnostic** - One feature works with Next.js App Router, React Router, TanStack Router, Remix—switch frameworks without rewriting
|
|
26
|
+
- **Plugin overrides** - Leverage framework-specific features via overrides. Use Next.js `Image` and `Link`, React Router's `Link`, or any framework's components
|
|
27
|
+
- **Full-stack in one package** - Each feature includes routes, API endpoints, database schemas, React components, hooks, loaders, and metadata
|
|
28
|
+
- **Zero boilerplate** - No wiring up routes, API handlers, or query clients. Just configure and it works
|
|
29
|
+
- **First-class SSR** - Server-side rendering, data prefetching, and SEO metadata generation built-in
|
|
30
|
+
- **Lifecycle hooks** - Intercept at any point: authorization, data transformation, analytics, caching, webhooks
|
|
31
|
+
- **Horizontal features** - Perfect for blog, scheduling, feedback, newsletters, AI assistants, comments—anything reusable across apps
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
## What Can You Add?
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
npm install @btst/db @btst/adapter-memory better-call @btst/yar zod
|
|
39
|
-
```
|
|
35
|
+
**Blog** - Content management, editor, drafts, publishing, SEO, RSS feeds
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
**Scheduling** - Calendar views, time slot booking, availability management, reminders
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
**Feedback** - In-app feedback widgets, user surveys, bug reporting, feature requests
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
// app/api/route.ts
|
|
47
|
-
import { betterStack } from "@btst/stack/api";
|
|
48
|
-
import { myPlugin } from "./plugins/my-plugin";
|
|
49
|
-
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
41
|
+
**Newsletters** - Subscriber management, email campaigns, unsubscribe handling, analytics
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
plugins: {
|
|
53
|
-
myFeature: myPlugin.backend,
|
|
54
|
-
},
|
|
55
|
-
adapter: createMemoryAdapter,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Export for Next.js App Router
|
|
59
|
-
export const GET = api.handler;
|
|
60
|
-
export const POST = api.handler;
|
|
61
|
-
export const PUT = api.handler;
|
|
62
|
-
export const DELETE = api.handler;
|
|
63
|
-
export const PATCH = api.handler;
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 2. Create Your Client
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
// app/lib/client.ts
|
|
70
|
-
import { createStackClient } from "@btst/stack/client";
|
|
71
|
-
import { myPlugin } from "./plugins/my-plugin";
|
|
43
|
+
**AI Assistant** - Chat interfaces, prompt templates, conversation history, streaming responses
|
|
72
44
|
|
|
73
|
-
|
|
74
|
-
plugins: {
|
|
75
|
-
myFeature: myPlugin.client,
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Access router and hooks
|
|
80
|
-
export const { router, hooks } = client;
|
|
81
|
-
```
|
|
45
|
+
**Comments** - Threaded discussions, moderation, reactions, notifications
|
|
82
46
|
|
|
83
|
-
|
|
47
|
+
And any other horizontal feature your app needs. Each comes with a complete UI, backend, and data layer.
|
|
84
48
|
|
|
85
|
-
|
|
86
|
-
// app/components/MyComponent.tsx
|
|
87
|
-
"use client";
|
|
88
|
-
import { hooks } from "../lib/client";
|
|
89
|
-
|
|
90
|
-
export function MyComponent() {
|
|
91
|
-
const { useMyData } = hooks.myFeature;
|
|
92
|
-
const { data, isLoading } = useMyData();
|
|
93
|
-
|
|
94
|
-
if (isLoading) return <div>Loading...</div>;
|
|
49
|
+
## Installation
|
|
95
50
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{data?.items.map((item) => (
|
|
99
|
-
<div key={item.id}>{item.name}</div>
|
|
100
|
-
))}
|
|
101
|
-
</div>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
51
|
+
```bash
|
|
52
|
+
npm install @btst/stack
|
|
104
53
|
```
|
|
105
54
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Better Stack's power comes from its plugin architecture. Here's how to build your own:
|
|
109
|
-
|
|
110
|
-
### Plugin Architecture
|
|
111
|
-
|
|
112
|
-
Better Stack uses **separate backend and client plugins** to:
|
|
113
|
-
- ✅ Prevent SSR issues
|
|
114
|
-
- ✅ Enable better code splitting
|
|
115
|
-
- ✅ Allow independent deployment and versioning
|
|
116
|
-
- ✅ Improve tree-shaking
|
|
117
|
-
|
|
118
|
-
Each plugin type is completely independent:
|
|
119
|
-
|
|
120
|
-
1. **Backend Plugin** - API endpoints, database schema, and business logic
|
|
121
|
-
2. **Client Plugin** - Routes, components, and React hooks
|
|
122
|
-
|
|
123
|
-
### Example: Messages Plugin (Backend)
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
// plugins/messages/backend.ts
|
|
127
|
-
import { createDbPlugin } from "@btst/db";
|
|
128
|
-
import { createEndpoint } from "better-call";
|
|
129
|
-
import { z } from "zod";
|
|
130
|
-
import {
|
|
131
|
-
defineBackendPlugin,
|
|
132
|
-
type BetterAuthDBSchema,
|
|
133
|
-
} from "@btst/stack/plugins";
|
|
134
|
-
|
|
135
|
-
// 1. Define your schema
|
|
136
|
-
const messagesSchema: BetterAuthDBSchema = {
|
|
137
|
-
messages: {
|
|
138
|
-
modelName: "Message",
|
|
139
|
-
fields: {
|
|
140
|
-
id: {
|
|
141
|
-
type: "number",
|
|
142
|
-
unique: true,
|
|
143
|
-
required: true,
|
|
144
|
-
},
|
|
145
|
-
content: {
|
|
146
|
-
type: "string",
|
|
147
|
-
required: true,
|
|
148
|
-
},
|
|
149
|
-
userId: {
|
|
150
|
-
type: "string",
|
|
151
|
-
required: true,
|
|
152
|
-
},
|
|
153
|
-
createdAt: {
|
|
154
|
-
type: "number",
|
|
155
|
-
required: true,
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// 2. Create and export the backend plugin with full type inference
|
|
162
|
-
export const messagesBackendPlugin = defineBackendPlugin({
|
|
163
|
-
name: "messages",
|
|
164
|
-
dbPlugin: createDbPlugin("messages", messagesSchema),
|
|
165
|
-
routes: (adapter) => ({
|
|
166
|
-
// List messages
|
|
167
|
-
list: createEndpoint(
|
|
168
|
-
"/messages",
|
|
169
|
-
{
|
|
170
|
-
method: "GET",
|
|
171
|
-
query: z.object({
|
|
172
|
-
userId: z.string().optional(),
|
|
173
|
-
}),
|
|
174
|
-
},
|
|
175
|
-
async ({ query }) => {
|
|
176
|
-
const messages = await adapter.findMany({
|
|
177
|
-
model: "messages",
|
|
178
|
-
where: query.userId
|
|
179
|
-
? [{ field: "userId", value: query.userId, operator: "eq" }]
|
|
180
|
-
: undefined,
|
|
181
|
-
});
|
|
182
|
-
return {
|
|
183
|
-
status: 200,
|
|
184
|
-
body: messages,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
),
|
|
188
|
-
|
|
189
|
-
// Create message
|
|
190
|
-
create: createEndpoint(
|
|
191
|
-
"/messages",
|
|
192
|
-
{
|
|
193
|
-
method: "POST",
|
|
194
|
-
body: z.object({
|
|
195
|
-
content: z.string().min(1),
|
|
196
|
-
userId: z.string().min(1),
|
|
197
|
-
}),
|
|
198
|
-
},
|
|
199
|
-
async ({ body }) => {
|
|
200
|
-
const message = await adapter.create({
|
|
201
|
-
model: "messages",
|
|
202
|
-
data: {
|
|
203
|
-
...body,
|
|
204
|
-
id: Date.now(),
|
|
205
|
-
createdAt: Date.now(),
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
return {
|
|
209
|
-
status: 201,
|
|
210
|
-
body: message,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
),
|
|
214
|
-
|
|
215
|
-
// Delete message
|
|
216
|
-
delete: createEndpoint(
|
|
217
|
-
"/messages/:id",
|
|
218
|
-
{
|
|
219
|
-
method: "DELETE",
|
|
220
|
-
params: z.object({
|
|
221
|
-
id: z.coerce.number(),
|
|
222
|
-
}),
|
|
223
|
-
},
|
|
224
|
-
async ({ params }) => {
|
|
225
|
-
await adapter.delete({
|
|
226
|
-
model: "messages",
|
|
227
|
-
where: [{ field: "id", value: params.id, operator: "eq" }],
|
|
228
|
-
});
|
|
229
|
-
return {
|
|
230
|
-
status: 204,
|
|
231
|
-
body: null,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
),
|
|
235
|
-
}),
|
|
236
|
-
});
|
|
237
|
-
```
|
|
55
|
+
For database schema management, install the CLI:
|
|
238
56
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
// plugins/messages/client.ts
|
|
243
|
-
import { defineClientPlugin } from "@btst/stack/plugins";
|
|
244
|
-
import { createRoute } from "@btst/yar";
|
|
245
|
-
import { useQuery } from "@tanstack/react-query";
|
|
246
|
-
|
|
247
|
-
// Components
|
|
248
|
-
const MessagesPage = ({ data }: { data: any }) => (
|
|
249
|
-
<div>{/* Render messages */}</div>
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
// Create and export the client plugin with full type inference
|
|
253
|
-
export const messagesClientPlugin = defineClientPlugin({
|
|
254
|
-
name: "messages",
|
|
255
|
-
routes: () => ({
|
|
256
|
-
// Use Yar's createRoute for proper route creation
|
|
257
|
-
messagesList: createRoute(
|
|
258
|
-
"/messages",
|
|
259
|
-
() => ({
|
|
260
|
-
PageComponent: MessagesPage,
|
|
261
|
-
loader: async () => {
|
|
262
|
-
const response = await fetch("/api/messages");
|
|
263
|
-
return response.json();
|
|
264
|
-
},
|
|
265
|
-
meta: (data) => [
|
|
266
|
-
{ name: "title", content: "Messages" },
|
|
267
|
-
{ name: "description", content: `${data.length} messages` },
|
|
268
|
-
],
|
|
269
|
-
})
|
|
270
|
-
),
|
|
271
|
-
}),
|
|
272
|
-
hooks: () => ({
|
|
273
|
-
useMessages: () => {
|
|
274
|
-
// Use React Query or your preferred data fetching library
|
|
275
|
-
return useQuery({
|
|
276
|
-
queryKey: ["messages"],
|
|
277
|
-
queryFn: async () => {
|
|
278
|
-
const response = await fetch("/api/messages");
|
|
279
|
-
return response.json();
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
},
|
|
283
|
-
}),
|
|
284
|
-
});
|
|
57
|
+
```bash
|
|
58
|
+
npm install -D @btst/cli
|
|
285
59
|
```
|
|
286
60
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
### Using Your Plugins
|
|
290
|
-
|
|
291
|
-
Backend and client plugins are used independently in their respective contexts:
|
|
292
|
-
|
|
293
|
-
#### Backend Usage
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
// app/api/route.ts (Backend only)
|
|
297
|
-
import { betterStack } from "@btst/stack/api";
|
|
298
|
-
import { messagesBackendPlugin } from "./plugins/messages/backend";
|
|
299
|
-
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
300
|
-
|
|
301
|
-
const api = betterStack({
|
|
302
|
-
plugins: {
|
|
303
|
-
messages: messagesBackendPlugin,
|
|
304
|
-
},
|
|
305
|
-
adapter: createMemoryAdapter,
|
|
306
|
-
});
|
|
61
|
+
The CLI helps generate migrations, Prisma schemas, and other database artifacts from your plugin schemas.
|
|
307
62
|
|
|
308
|
-
|
|
309
|
-
export const POST = api.handler;
|
|
310
|
-
```
|
|
63
|
+
## Quick Example: Add a Blog to Next.js
|
|
311
64
|
|
|
312
|
-
|
|
65
|
+
### 1. Backend API (`lib/better-stack.ts`)
|
|
313
66
|
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
import {
|
|
317
|
-
import {
|
|
67
|
+
```ts
|
|
68
|
+
import { betterStack } from "@btst/stack"
|
|
69
|
+
import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"
|
|
70
|
+
import { createPrismaAdapter } from "@btst/adapter-prisma"
|
|
318
71
|
|
|
319
|
-
const
|
|
72
|
+
const { handler, dbSchema } = betterStack({
|
|
73
|
+
basePath: "/api/data",
|
|
320
74
|
plugins: {
|
|
321
|
-
|
|
75
|
+
blog: blogBackendPlugin()
|
|
322
76
|
},
|
|
323
|
-
})
|
|
77
|
+
adapter: (db) => createPrismaAdapter(db)({})
|
|
78
|
+
})
|
|
324
79
|
|
|
325
|
-
export
|
|
326
|
-
|
|
327
|
-
// Use in components
|
|
328
|
-
// const { useMessages } = hooks.messages;
|
|
80
|
+
export { handler, dbSchema }
|
|
329
81
|
```
|
|
330
82
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
### 1. Plugins
|
|
334
|
-
|
|
335
|
-
Plugins are self-contained features that can be composed together. Each plugin can provide:
|
|
336
|
-
|
|
337
|
-
- **Database Schema** - Table definitions using Better DB
|
|
338
|
-
- **API Endpoints** - Type-safe REST endpoints using Better Call
|
|
339
|
-
- **Client Routes** - Page routing using Yar Router
|
|
340
|
-
- **React Hooks** - Data fetching and mutations
|
|
341
|
-
|
|
342
|
-
### 2. Adapters
|
|
343
|
-
|
|
344
|
-
Adapters connect your plugins to different databases:
|
|
83
|
+
**Note:** `betterStack()` returns both `handler` and `dbSchema`. The `dbSchema` contains all merged database schemas from your plugins. Use [@btst/cli](https://www.npmjs.com/package/@btst/cli) to generate migrations, Prisma schemas, or other database artifacts from your `dbSchema`.
|
|
345
84
|
|
|
346
|
-
|
|
347
|
-
// Memory (for testing)
|
|
348
|
-
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
85
|
+
For example, to generate a Prisma schema from your `dbSchema`:
|
|
349
86
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// MongoDB
|
|
354
|
-
import { createMongoAdapter } from "@btst/adapter-mongo";
|
|
355
|
-
|
|
356
|
-
const api = betterStack({
|
|
357
|
-
plugins: { /* ... */ },
|
|
358
|
-
adapter: createMemoryAdapter, // or createPgAdapter, createMongoAdapter
|
|
359
|
-
});
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### 3. Type Safety
|
|
363
|
-
|
|
364
|
-
Better Stack provides end-to-end type safety with automatic type inference:
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
// ✅ Backend: Route keys and endpoint types are preserved
|
|
368
|
-
const myPlugin = defineBackendPlugin({
|
|
369
|
-
name: "users",
|
|
370
|
-
routes: (adapter) => ({
|
|
371
|
-
getUser: createEndpoint(
|
|
372
|
-
"/users/:id",
|
|
373
|
-
{
|
|
374
|
-
method: "GET",
|
|
375
|
-
params: z.object({ id: z.string() }),
|
|
376
|
-
},
|
|
377
|
-
async ({ params }) => {
|
|
378
|
-
// params.id is typed as string
|
|
379
|
-
return { status: 200, body: { id: params.id, name: "John" } };
|
|
380
|
-
}
|
|
381
|
-
),
|
|
382
|
-
listUsers: createEndpoint(/* ... */),
|
|
383
|
-
})
|
|
384
|
-
});
|
|
385
|
-
// Type: BackendPlugin<{ getUser: Endpoint, listUsers: Endpoint }>
|
|
386
|
-
|
|
387
|
-
// ✅ Client: Route keys and hook types are preserved
|
|
388
|
-
const myClientPlugin = defineClientPlugin({
|
|
389
|
-
name: "users",
|
|
390
|
-
routes: () => ({
|
|
391
|
-
userProfile: { path: "/users/:id", Component: UserProfile },
|
|
392
|
-
usersList: { path: "/users", Component: UsersList },
|
|
393
|
-
}),
|
|
394
|
-
hooks: () => ({
|
|
395
|
-
useUser: (id: string) => { /* ... */ },
|
|
396
|
-
useUsers: () => { /* ... */ },
|
|
397
|
-
})
|
|
398
|
-
});
|
|
399
|
-
// Routes "userProfile" and "usersList" are fully typed!
|
|
400
|
-
// Hooks "useUser" and "useUsers" are accessible via hooks.users.*
|
|
401
|
-
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
## Plugin Utilities
|
|
405
|
-
|
|
406
|
-
Better Stack exports utilities for building plugins:
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
// Type-safe plugin helpers (recommended)
|
|
410
|
-
import {
|
|
411
|
-
defineBackendPlugin,
|
|
412
|
-
defineClientPlugin,
|
|
413
|
-
} from "@btst/stack/plugins";
|
|
414
|
-
|
|
415
|
-
// Type definitions for backend plugins
|
|
416
|
-
import type {
|
|
417
|
-
BackendPlugin,
|
|
418
|
-
Adapter,
|
|
419
|
-
DatabaseDefinition,
|
|
420
|
-
Endpoint,
|
|
421
|
-
Router,
|
|
422
|
-
} from "@btst/stack/plugins";
|
|
423
|
-
|
|
424
|
-
// Type definitions for client plugins
|
|
425
|
-
import type {
|
|
426
|
-
ClientPlugin,
|
|
427
|
-
Route,
|
|
428
|
-
} from "@btst/stack/plugins";
|
|
429
|
-
|
|
430
|
-
// Utilities (can be used in both)
|
|
431
|
-
import {
|
|
432
|
-
createApiClient,
|
|
433
|
-
} from "@btst/stack/plugins";
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### Type-Safe Plugin Helpers
|
|
437
|
-
|
|
438
|
-
Use `defineBackendPlugin` and `defineClientPlugin` for automatic type inference:
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
// ✅ Recommended: Full type inference, no casts needed
|
|
442
|
-
const myPlugin = defineBackendPlugin({
|
|
443
|
-
name: "myFeature",
|
|
444
|
-
routes: (adapter) => ({
|
|
445
|
-
list: endpoint(...),
|
|
446
|
-
create: endpoint(...),
|
|
447
|
-
})
|
|
448
|
-
});
|
|
449
|
-
// Route keys "list" and "create" are fully typed!
|
|
450
|
-
|
|
451
|
-
// ❌ Old way: Manual type annotation
|
|
452
|
-
const myPlugin: BackendPlugin = {
|
|
453
|
-
name: "myFeature",
|
|
454
|
-
routes: (adapter) => ({
|
|
455
|
-
list: endpoint(...),
|
|
456
|
-
create: endpoint(...),
|
|
457
|
-
})
|
|
458
|
-
};
|
|
459
|
-
// Route keys are erased to generic Record<string, Endpoint>
|
|
87
|
+
```bash
|
|
88
|
+
npx @btst/cli generate --orm prisma --config lib/better-db.ts --output prisma/schema.prisma --filter-auth
|
|
460
89
|
```
|
|
461
90
|
|
|
462
|
-
|
|
91
|
+
This reads your `dbSchema` export and generates the corresponding Prisma schema file.
|
|
463
92
|
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
// ❌ This can cause SSR issues and bundle bloat
|
|
467
|
-
export const myPlugin = {
|
|
468
|
-
backend: { /* server-side code */ },
|
|
469
|
-
client: { /* client-side code */ },
|
|
470
|
-
};
|
|
471
|
-
```
|
|
93
|
+
### 2. API Route (`app/api/[[...]]/route.ts`)
|
|
472
94
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
// ✅ Backend stays on the server
|
|
476
|
-
// plugins/my-plugin/backend.ts
|
|
477
|
-
export const myBackendPlugin: BackendPlugin = { /* ... */ };
|
|
95
|
+
```ts
|
|
96
|
+
import { handler } from "@/lib/better-stack"
|
|
478
97
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
export const myClientPlugin: ClientPlugin = { /* ... */ };
|
|
98
|
+
export const GET = handler
|
|
99
|
+
export const POST = handler
|
|
482
100
|
```
|
|
483
101
|
|
|
484
|
-
|
|
485
|
-
1. **No SSR Issues** - Server code never reaches the client bundle
|
|
486
|
-
2. **Better Code Splitting** - Frontend and backend can be deployed separately
|
|
487
|
-
3. **Smaller Bundles** - Client bundles don't include server dependencies
|
|
488
|
-
4. **Independent Versioning** - Update backend without touching frontend
|
|
489
|
-
5. **Type Safety** - TypeScript ensures correct usage in each context
|
|
490
|
-
|
|
491
|
-
### API Client Utility
|
|
492
|
-
|
|
493
|
-
Create a typed API client for server-side or client-side requests:
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
import { createApiClient } from "@btst/stack/plugins";
|
|
102
|
+
### 3. Client Setup (`lib/better-stack-client.tsx`)
|
|
497
103
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
basePath: "/api",
|
|
502
|
-
});
|
|
104
|
+
```ts
|
|
105
|
+
import { createStackClient } from "@btst/stack/client"
|
|
106
|
+
import { blogClientPlugin } from "@btst/stack/plugins/blog/client"
|
|
503
107
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
```typescript
|
|
515
|
-
// __tests__/my-plugin.test.ts
|
|
516
|
-
import { describe, it, expect } from "vitest";
|
|
517
|
-
import { betterStack } from "@btst/stack/api";
|
|
518
|
-
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
519
|
-
import { myBackendPlugin } from "../plugins/my-plugin/backend";
|
|
520
|
-
|
|
521
|
-
describe("My Backend Plugin", () => {
|
|
522
|
-
it("should create and retrieve items", async () => {
|
|
523
|
-
// Adapter wrapper for testing
|
|
524
|
-
const testAdapter = (db) => createMemoryAdapter(db)({});
|
|
525
|
-
|
|
526
|
-
const api = betterStack({
|
|
527
|
-
plugins: { myFeature: myBackendPlugin },
|
|
528
|
-
adapter: testAdapter,
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
// Create an item
|
|
532
|
-
const createResponse = await api.handler(
|
|
533
|
-
new Request("http://localhost:3000/api/items", {
|
|
534
|
-
method: "POST",
|
|
535
|
-
headers: { "Content-Type": "application/json" },
|
|
536
|
-
body: JSON.stringify({ name: "Test Item" }),
|
|
108
|
+
export const getStackClient = (queryClient: QueryClient) => {
|
|
109
|
+
return createStackClient({
|
|
110
|
+
plugins: {
|
|
111
|
+
blog: blogClientPlugin({
|
|
112
|
+
queryClient,
|
|
113
|
+
apiBaseURL: baseURL,
|
|
114
|
+
apiBasePath: "/api/data",
|
|
115
|
+
siteBaseURL: baseURL,
|
|
116
|
+
siteBasePath: "/pages"
|
|
537
117
|
})
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
expect(createResponse.status).toBe(201);
|
|
541
|
-
|
|
542
|
-
// Retrieve items
|
|
543
|
-
const listResponse = await api.handler(
|
|
544
|
-
new Request("http://localhost:3000/api/items", {
|
|
545
|
-
method: "GET",
|
|
546
|
-
})
|
|
547
|
-
);
|
|
548
|
-
|
|
549
|
-
const items = await listResponse.json();
|
|
550
|
-
expect(items).toHaveLength(1);
|
|
551
|
-
expect(items[0].name).toBe("Test Item");
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
## Framework Integration
|
|
557
|
-
|
|
558
|
-
### Next.js App Router
|
|
559
|
-
|
|
560
|
-
```typescript
|
|
561
|
-
// app/api/[...route]/route.ts
|
|
562
|
-
import { betterStack } from "@btst/stack/api";
|
|
563
|
-
|
|
564
|
-
const api = betterStack({
|
|
565
|
-
plugins: { /* ... */ },
|
|
566
|
-
adapter: /* ... */,
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
export const GET = api.handler;
|
|
570
|
-
export const POST = api.handler;
|
|
571
|
-
export const PUT = api.handler;
|
|
572
|
-
export const DELETE = api.handler;
|
|
573
|
-
export const PATCH = api.handler;
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### Next.js Pages Router
|
|
577
|
-
|
|
578
|
-
```typescript
|
|
579
|
-
// pages/api/[...route].ts
|
|
580
|
-
import { betterStack } from "@btst/stack/api";
|
|
581
|
-
|
|
582
|
-
const api = betterStack({
|
|
583
|
-
plugins: { /* ... */ },
|
|
584
|
-
adapter: /* ... */,
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
588
|
-
const request = new Request(
|
|
589
|
-
`http://localhost:3000${req.url}`,
|
|
590
|
-
{
|
|
591
|
-
method: req.method,
|
|
592
|
-
headers: req.headers as HeadersInit,
|
|
593
|
-
body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
|
|
594
118
|
}
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
const response = await api.handler(request);
|
|
598
|
-
const data = await response.json();
|
|
599
|
-
|
|
600
|
-
res.status(response.status).json(data);
|
|
119
|
+
})
|
|
601
120
|
}
|
|
602
121
|
```
|
|
603
122
|
|
|
604
|
-
###
|
|
605
|
-
|
|
606
|
-
```typescript
|
|
607
|
-
import express from "express";
|
|
608
|
-
import { betterStack } from "@btst/stack/api";
|
|
609
|
-
|
|
610
|
-
const api = betterStack({
|
|
611
|
-
plugins: { /* ... */ },
|
|
612
|
-
adapter: /* ... */,
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
const app = express();
|
|
616
|
-
|
|
617
|
-
app.all("/api/*", async (req, res) => {
|
|
618
|
-
const request = new Request(`http://localhost:3000${req.url}`, {
|
|
619
|
-
method: req.method,
|
|
620
|
-
headers: req.headers as HeadersInit,
|
|
621
|
-
body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
const response = await api.handler(request);
|
|
625
|
-
const data = await response.json();
|
|
626
|
-
|
|
627
|
-
res.status(response.status).json(data);
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
app.listen(3000);
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
## Architecture
|
|
634
|
-
|
|
635
|
-
Better Stack is built on top of proven libraries:
|
|
636
|
-
|
|
637
|
-
```
|
|
638
|
-
┌─────────────────────────────────────────┐
|
|
639
|
-
│ Better Stack │
|
|
640
|
-
│ (Plugin Composition & Type Safety) │
|
|
641
|
-
├─────────────────────────────────────────┤
|
|
642
|
-
│ Better Call │ Better DB │ Yar │
|
|
643
|
-
│ (API Layer) │ (Database) │ (Router) │
|
|
644
|
-
└─────────────────────────────────────────┘
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
### Core Libraries
|
|
648
|
-
|
|
649
|
-
- **[Better Call](https://github.com/olliethedev/better-call)** - Type-safe API endpoints with automatic request/response handling
|
|
650
|
-
- **[Better DB](https://github.com/olliethedev/better-auth)** - Schema-first database ORM with multiple adapter support
|
|
651
|
-
- **[Yar Router](https://github.com/olliethedev/yar)** - Lightweight client-side router for React
|
|
652
|
-
|
|
653
|
-
## Publishing Your Plugin
|
|
654
|
-
|
|
655
|
-
Want to share your plugin with the community? Publish backend and client as separate packages for maximum flexibility.
|
|
123
|
+
### 4. Page Handler (`app/pages/[[...all]]/page.tsx`)
|
|
656
124
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
│ └── index.ts # Export BackendPlugin
|
|
671
|
-
├── package.json
|
|
672
|
-
└── tsconfig.json
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
3. Add peer dependencies:
|
|
676
|
-
```json
|
|
677
|
-
{
|
|
678
|
-
"name": "@yourorg/my-plugin-backend",
|
|
679
|
-
"peerDependencies": {
|
|
680
|
-
"@btst/stack": "^1.0.0",
|
|
681
|
-
"@btst/db": "^1.0.0",
|
|
682
|
-
"better-call": "^1.0.19"
|
|
683
|
-
}
|
|
125
|
+
```ts
|
|
126
|
+
export default async function Page({ params }) {
|
|
127
|
+
const path = `/${(await params).all?.join("/") || ""}`
|
|
128
|
+
const stackClient = getStackClient(queryClient)
|
|
129
|
+
const route = stackClient.router.getRoute(path)
|
|
130
|
+
|
|
131
|
+
if (route?.loader) await route.loader()
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<HydrationBoundary state={dehydrate(queryClient)}>
|
|
135
|
+
<ClientRouteResolver path={path} />
|
|
136
|
+
</HydrationBoundary>
|
|
137
|
+
)
|
|
684
138
|
}
|
|
685
139
|
```
|
|
686
140
|
|
|
687
|
-
###
|
|
141
|
+
### 5. Layout Provider (`app/pages/[[...all]]/layout.tsx`)
|
|
688
142
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
2. Structure:
|
|
697
|
-
```
|
|
698
|
-
my-plugin-client/
|
|
699
|
-
├── src/
|
|
700
|
-
│ └── index.tsx # Export ClientPlugin
|
|
701
|
-
├── package.json
|
|
702
|
-
└── tsconfig.json
|
|
703
|
-
```
|
|
143
|
+
```ts
|
|
144
|
+
import { BetterStackProvider } from "@btst/stack/context"
|
|
145
|
+
import Link from "next/link"
|
|
146
|
+
import Image from "next/image"
|
|
147
|
+
import { useRouter } from "next/navigation"
|
|
704
148
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
149
|
+
export default function Layout({ children }) {
|
|
150
|
+
const router = useRouter()
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<BetterStackProvider
|
|
154
|
+
basePath="/pages"
|
|
155
|
+
overrides={{
|
|
156
|
+
blog: {
|
|
157
|
+
// Use Next.js optimized Image component
|
|
158
|
+
Image: (props) => (
|
|
159
|
+
<Image
|
|
160
|
+
alt={props.alt || ""}
|
|
161
|
+
src={props.src || ""}
|
|
162
|
+
width={400}
|
|
163
|
+
height={300}
|
|
164
|
+
/>
|
|
165
|
+
),
|
|
166
|
+
// Use Next.js Link for client-side navigation
|
|
167
|
+
navigate: (path) => router.push(path),
|
|
168
|
+
refresh: () => router.refresh()
|
|
169
|
+
}
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{children}
|
|
173
|
+
</BetterStackProvider>
|
|
174
|
+
)
|
|
714
175
|
}
|
|
715
176
|
```
|
|
716
177
|
|
|
717
|
-
|
|
718
|
-
```bash
|
|
719
|
-
# Server-only deployment
|
|
720
|
-
npm install @yourorg/my-plugin-backend
|
|
721
|
-
|
|
722
|
-
# Client-only deployment
|
|
723
|
-
npm install @yourorg/my-plugin-client
|
|
724
|
-
|
|
725
|
-
# Full-stack deployment
|
|
726
|
-
npm install @yourorg/my-plugin-backend @yourorg/my-plugin-client
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
## Examples
|
|
730
|
-
|
|
731
|
-
Check out example plugins and applications:
|
|
178
|
+
### 6. Sitemap Generation (`app/sitemap.ts`)
|
|
732
179
|
|
|
733
|
-
|
|
734
|
-
|
|
180
|
+
```ts
|
|
181
|
+
import type { MetadataRoute } from "next"
|
|
182
|
+
import { QueryClient } from "@tanstack/react-query"
|
|
183
|
+
import { getStackClient } from "@/lib/better-stack-client"
|
|
735
184
|
|
|
736
|
-
|
|
185
|
+
export const dynamic = "force-dynamic"
|
|
737
186
|
|
|
738
|
-
|
|
187
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
188
|
+
const queryClient = new QueryClient()
|
|
189
|
+
const stackClient = getStackClient(queryClient)
|
|
190
|
+
const entries = await stackClient.generateSitemap()
|
|
739
191
|
|
|
740
|
-
|
|
741
|
-
|
|
192
|
+
return entries.map((e) => ({
|
|
193
|
+
url: e.url,
|
|
194
|
+
lastModified: e.lastModified,
|
|
195
|
+
changeFrequency: e.changeFrequency,
|
|
196
|
+
priority: e.priority,
|
|
197
|
+
}))
|
|
198
|
+
}
|
|
742
199
|
```
|
|
743
200
|
|
|
744
|
-
|
|
201
|
+
**That's it.** Your blog feature is live with:
|
|
202
|
+
- ✅ `/blog` - Post listing page
|
|
203
|
+
- ✅ `/blog/[slug]` - Individual post pages
|
|
204
|
+
- ✅ `/blog/new` - Create post editor
|
|
205
|
+
- ✅ `/blog/[slug]/edit` - Edit post page
|
|
206
|
+
- ✅ Full CRUD API (`/api/data/blog/*`)
|
|
207
|
+
- ✅ Server-side rendering
|
|
208
|
+
- ✅ Automatic metadata generation
|
|
209
|
+
- ✅ Automatic sitemap generation
|
|
210
|
+
- ✅ React Query hooks (`usePosts`, `usePost`, etc.)
|
|
745
211
|
|
|
746
|
-
|
|
747
|
-
- `plugins` - Record of plugin instances
|
|
748
|
-
- `adapter` - Database adapter function
|
|
749
|
-
- `dbSchema` - (Optional) Additional database schema
|
|
212
|
+
Now add scheduling, feedback, or newsletters the same way. Each feature is independent and composable.
|
|
750
213
|
|
|
751
|
-
|
|
752
|
-
- `handler` - Request handler for your framework
|
|
753
|
-
- `router` - Better Call router instance
|
|
754
|
-
- `dbSchema` - Combined database schema
|
|
214
|
+
## The Bigger Picture
|
|
755
215
|
|
|
756
|
-
|
|
216
|
+
Better Stack transforms how you think about building apps:
|
|
217
|
+
|
|
218
|
+
- **Open source** - Share complete features, not just code snippets. Someone can add a newsletter plugin to their Next.js app in minutes
|
|
219
|
+
- **Fast development** - Add 5 features in an afternoon instead of 5 weeks. Validate ideas faster
|
|
220
|
+
- **Agencies** - Create a library of reusable features. Drop scheduling into client A's app, feedback into client B's app.
|
|
221
|
+
- **SaaS platforms** - Offer feature plugins your customers can compose. They pick blog + scheduling + AI assistant, mix and match to build their ideal app
|
|
757
222
|
|
|
758
|
-
```typescript
|
|
759
|
-
createStackClient<TPlugins>(config: ClientLibConfig<TPlugins>): ClientLib
|
|
760
|
-
```
|
|
761
223
|
|
|
762
|
-
|
|
224
|
+
Each plugin is a complete, self-contained horizontal full-stack feature. No framework lock-in. Just add it and it works.
|
|
763
225
|
|
|
764
|
-
|
|
765
|
-
- `plugins` - Record of plugin instances
|
|
766
|
-
- `baseURL` - (Optional) Base URL for API calls
|
|
767
|
-
- `basePath` - (Optional) API path prefix (default: "/api")
|
|
226
|
+
## Learn More
|
|
768
227
|
|
|
769
|
-
**
|
|
770
|
-
- `router` - Yar router instance
|
|
771
|
-
- `hooks` - Plugin hooks organized by plugin name
|
|
228
|
+
For complete documentation, examples, and plugin development guides, visit **[https://www.better-stack.ai](https://www.better-stack.ai)**
|
|
772
229
|
|
|
773
|
-
##
|
|
230
|
+
## Examples
|
|
774
231
|
|
|
775
|
-
|
|
232
|
+
- [Next.js App Router](./examples/nextjs) - Next.js App Router example
|
|
233
|
+
- [React Router](./examples/react-router) - React Router example
|
|
234
|
+
- [TanStack Router](./examples/tanstack) - TanStack Router example
|
|
776
235
|
|
|
777
236
|
## License
|
|
778
237
|
|
|
779
238
|
MIT © [olliethedev](https://github.com/olliethedev)
|
|
780
|
-
|
|
781
|
-
## Support
|
|
782
|
-
|
|
783
|
-
- 📖 [Documentation](https://github.com/olliethedev/better-stack)
|
|
784
|
-
- 🐛 [Issue Tracker](https://github.com/olliethedev/better-stack/issues)
|
|
785
|
-
- 💬 [Discussions](https://github.com/olliethedev/better-stack/discussions)
|
|
786
|
-
|
|
787
|
-
---
|
|
788
|
-
|
|
789
|
-
<div align="center">
|
|
790
|
-
<strong>Built with ❤️ using Better Stack</strong>
|
|
791
|
-
</div>
|