@btst/stack 1.0.1 → 1.1.0
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 +0 -1
- package/dist/api/index.d.cts +3 -3
- package/dist/api/index.d.mts +3 -3
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.mjs +0 -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 +22 -5
- package/dist/client/index.d.cts +102 -8
- package/dist/client/index.d.mts +102 -8
- package/dist/client/index.d.ts +102 -8
- package/dist/client/index.mjs +20 -4
- package/dist/client/meta-utils.cjs +162 -0
- package/dist/client/meta-utils.mjs +160 -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 +0 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +0 -2
- package/dist/plugins/api/index.cjs +15 -0
- package/dist/plugins/api/index.d.cts +41 -0
- package/dist/plugins/api/index.d.mts +41 -0
- package/dist/plugins/api/index.d.ts +41 -0
- package/dist/plugins/api/index.mjs +9 -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.Cr2JoQdo.d.cts +76 -0
- package/dist/shared/stack.Cr2JoQdo.d.mts +76 -0
- package/dist/shared/stack.Cr2JoQdo.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 +73 -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 +107 -0
- package/src/client/meta-utils.ts +228 -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 +51 -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
|
+
**Add complete full-stack features to your React app in minutes, not weeks**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@btst/stack)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
+
[📖 Documentation](https://www.btst.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
|
|
61
|
+
The CLI helps generate migrations, Prisma schemas, and other database artifacts from your plugin schemas.
|
|
290
62
|
|
|
291
|
-
|
|
63
|
+
## Quick Example: Add a Blog to Next.js
|
|
292
64
|
|
|
293
|
-
|
|
65
|
+
### 1. Backend API (`lib/better-stack.ts`)
|
|
294
66
|
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
import {
|
|
298
|
-
import {
|
|
299
|
-
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
67
|
+
```ts
|
|
68
|
+
import { betterStack } from "@btst/stack"
|
|
69
|
+
import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"
|
|
70
|
+
import { createPrismaAdapter } from "@btst/adapter-prisma"
|
|
300
71
|
|
|
301
|
-
const
|
|
72
|
+
const { handler, dbSchema } = betterStack({
|
|
73
|
+
basePath: "/api/data",
|
|
302
74
|
plugins: {
|
|
303
|
-
|
|
75
|
+
blog: blogBackendPlugin()
|
|
304
76
|
},
|
|
305
|
-
adapter:
|
|
306
|
-
})
|
|
77
|
+
adapter: (db) => createPrismaAdapter(db)({})
|
|
78
|
+
})
|
|
307
79
|
|
|
308
|
-
export
|
|
309
|
-
export const POST = api.handler;
|
|
80
|
+
export { handler, dbSchema }
|
|
310
81
|
```
|
|
311
82
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
```typescript
|
|
315
|
-
// app/lib/client.ts (Client only)
|
|
316
|
-
import { createStackClient } from "@btst/stack/client";
|
|
317
|
-
import { messagesClientPlugin } from "./plugins/messages/client";
|
|
318
|
-
|
|
319
|
-
const client = createStackClient({
|
|
320
|
-
plugins: {
|
|
321
|
-
messages: messagesClientPlugin,
|
|
322
|
-
},
|
|
323
|
-
});
|
|
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`.
|
|
324
84
|
|
|
325
|
-
|
|
85
|
+
For example, to generate a Prisma schema from your `dbSchema`:
|
|
326
86
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
## Core Concepts
|
|
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:
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
// Memory (for testing)
|
|
348
|
-
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
349
|
-
|
|
350
|
-
// PostgreSQL
|
|
351
|
-
import { createPgAdapter } from "@btst/adapter-pg";
|
|
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>
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
### Why Separate Backend and Client Plugins?
|
|
463
|
-
|
|
464
|
-
**Traditional Approach (Unified Plugin):**
|
|
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
|
-
```
|
|
472
|
-
|
|
473
|
-
**Better Stack Approach (Separated Plugins):**
|
|
474
|
-
```typescript
|
|
475
|
-
// ✅ Backend stays on the server
|
|
476
|
-
// plugins/my-plugin/backend.ts
|
|
477
|
-
export const myBackendPlugin: BackendPlugin = { /* ... */ };
|
|
478
|
-
|
|
479
|
-
// ✅ Client stays on the client
|
|
480
|
-
// plugins/my-plugin/client.ts
|
|
481
|
-
export const myClientPlugin: ClientPlugin = { /* ... */ };
|
|
87
|
+
```bash
|
|
88
|
+
npx @btst/cli generate --orm prisma --config lib/better-db.ts --output prisma/schema.prisma --filter-auth
|
|
482
89
|
```
|
|
483
90
|
|
|
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
|
|
91
|
+
This reads your `dbSchema` export and generates the corresponding Prisma schema file.
|
|
492
92
|
|
|
493
|
-
|
|
93
|
+
### 2. API Route (`app/api/[[...]]/route.ts`)
|
|
494
94
|
|
|
495
|
-
```
|
|
496
|
-
import {
|
|
95
|
+
```ts
|
|
96
|
+
import { handler } from "@/lib/better-stack"
|
|
497
97
|
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
baseURL: "http://localhost:3000",
|
|
501
|
-
basePath: "/api",
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// For client-side (uses relative URLs)
|
|
505
|
-
const api = createApiClient({
|
|
506
|
-
basePath: "/api",
|
|
507
|
-
});
|
|
98
|
+
export const GET = handler
|
|
99
|
+
export const POST = handler
|
|
508
100
|
```
|
|
509
101
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
Better Stack includes comprehensive testing utilities:
|
|
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" }),
|
|
537
|
-
})
|
|
538
|
-
);
|
|
102
|
+
### 3. Client Setup (`lib/better-stack-client.tsx`)
|
|
539
103
|
|
|
540
|
-
|
|
104
|
+
```ts
|
|
105
|
+
import { createStackClient } from "@btst/stack/client"
|
|
106
|
+
import { blogClientPlugin } from "@btst/stack/plugins/blog/client"
|
|
541
107
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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"
|
|
546
117
|
})
|
|
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
|
|
123
|
+
### 4. Page Handler (`app/pages/[[...all]]/page.tsx`)
|
|
654
124
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
my-plugin-backend/
|
|
669
|
-
├── src/
|
|
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
|
-
###
|
|
688
|
-
|
|
689
|
-
1. Create client package:
|
|
690
|
-
```bash
|
|
691
|
-
mkdir my-plugin-client
|
|
692
|
-
cd my-plugin-client
|
|
693
|
-
npm init
|
|
694
|
-
```
|
|
141
|
+
### 5. Layout Provider (`app/pages/[[...all]]/layout.tsx`)
|
|
695
142
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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:
|
|
757
217
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
218
|
+
• **Internal teams** - Build shared features once, use across multiple apps. Your marketing team's blog plugin works in the main app, the docs site, and the landing page
|
|
219
|
+
• **Open source** - Share complete features, not just code snippets. Someone can add your newsletter feature to their Next.js app in minutes
|
|
220
|
+
• **Agencies** - Create a library of reusable features. Drop scheduling into client A's app, feedback into client B's app, both using React Router
|
|
221
|
+
• **SaaS platforms** - Offer feature plugins your customers can compose. They pick blog + scheduling + AI assistant, mix and match to build their ideal app
|
|
222
|
+
• **Rapid prototyping** - Add 5 features in an afternoon instead of 5 weeks. Validate ideas faster
|
|
761
223
|
|
|
762
|
-
|
|
224
|
+
Each feature is a complete, self-contained full-stack capability. No configuration files. No code generation. 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.btst.ai](https://www.btst.ai)**
|
|
772
229
|
|
|
773
|
-
##
|
|
230
|
+
## Examples
|
|
774
231
|
|
|
775
|
-
|
|
232
|
+
- [Next.js App Router](./examples/nextjs) - Full SSR with App Router
|
|
233
|
+
- [React Router](./examples/react-router) - Client-side routing with React Router
|
|
234
|
+
- [TanStack Router](./examples/tanstack) - Type-safe routing with TanStack Router
|
|
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>
|