Skip to content

Commit f20472d

Browse files
authored
Merge pull request #114 from better-stack-ai/feat/blog-plugin-mutations-access
feat: add CRUD operations for blog post plugin
2 parents 476ab7a + e5d3f7f commit f20472d

5 files changed

Lines changed: 382 additions & 190 deletions

File tree

docs/content/docs/plugins/blog.mdx

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -585,18 +585,18 @@ You can import the hooks from `"@btst/stack/plugins/blog/client/hooks"` to use i
585585

586586
## Server-side Data Access
587587

588-
The blog plugin exposes standalone getter functions for server-side and SSG use cases. These bypass the HTTP layer entirely and query the database directly.
588+
The blog plugin exposes standalone getter and mutation functions for server-side use cases. These bypass the HTTP layer entirely and query the database directly — no authorization hooks are called, so the caller is responsible for any access-control checks.
589589

590590
### Two patterns
591591

592592
**Pattern 1 — via `stack().api` (recommended for runtime server code)**
593593

594-
After calling `stack()`, the returned object includes a fully-typed `api` namespace. Getters are pre-bound to the adapter:
594+
After calling `stack()`, the returned object includes a fully-typed `api` namespace. Getters and mutations are pre-bound to the adapter:
595595

596596
```ts title="app/lib/stack.ts"
597597
import { myStack } from "./stack"; // your stack() instance
598598

599-
// In a Server Component, generateStaticParams, etc.
599+
// Getters — read-only
600600
const result = await myStack.api.blog.getAllPosts({ published: true });
601601
// result.items — Post[]
602602
// result.total — total count before pagination
@@ -605,22 +605,45 @@ const result = await myStack.api.blog.getAllPosts({ published: true });
605605

606606
const post = await myStack.api.blog.getPostBySlug("hello-world");
607607
const tags = await myStack.api.blog.getAllTags();
608+
609+
// Mutations — write operations (no auth hooks are called)
610+
const newPost = await myStack.api.blog.createPost({
611+
title: "Hello World",
612+
slug: "hello-world",
613+
content: "...",
614+
excerpt: "...",
615+
});
616+
await myStack.api.blog.updatePost(newPost.id, { published: true });
617+
await myStack.api.blog.deletePost(newPost.id);
608618
```
609619

610620
**Pattern 2 — direct import (SSG, build-time, or custom adapter)**
611621

612-
Import getters directly and pass any `Adapter`:
622+
Import getters and mutations directly and pass any `Adapter`:
613623

614624
```ts
615-
import { getAllPosts, getPostBySlug, getAllTags } from "@btst/stack/plugins/blog/api";
625+
import { getAllPosts, createPost, updatePost, deletePost } from "@btst/stack/plugins/blog/api";
616626

617627
// e.g. in Next.js generateStaticParams
618628
export async function generateStaticParams() {
619629
const { items } = await getAllPosts(myAdapter, { published: true });
620630
return items.map((p) => ({ slug: p.slug }));
621631
}
632+
633+
// e.g. seeding or scripting
634+
const post = await createPost(myAdapter, {
635+
title: "Seeded Post",
636+
slug: "seeded-post",
637+
content: "Content here",
638+
excerpt: "Short excerpt",
639+
});
640+
await updatePost(myAdapter, post.id, { published: true });
622641
```
623642

643+
<Callout type="warn">
644+
**No authorization hooks are called** when using `stack().api.*` or direct imports. These functions hit the database directly. Always perform your own access-control checks before calling them from user-facing code.
645+
</Callout>
646+
624647
### Available getters
625648

626649
| Function | Returns | Description |
@@ -637,6 +660,22 @@ export async function generateStaticParams() {
637660

638661
<AutoTypeTable path="../packages/stack/src/plugins/blog/api/getters.ts" name="PostListResult" />
639662

663+
### Available mutations
664+
665+
| Function | Returns | Description |
666+
|---|---|---|
667+
| `createPost(adapter, input)` | `Post` | Create a new post with optional tag associations |
668+
| `updatePost(adapter, id, input)` | `Post \| null` | Update a post and reconcile its tags; `null` if not found |
669+
| `deletePost(adapter, id)` | `void` | Delete a post by ID |
670+
671+
### `CreatePostInput`
672+
673+
<AutoTypeTable path="../packages/stack/src/plugins/blog/api/mutations.ts" name="CreatePostInput" />
674+
675+
### `UpdatePostInput`
676+
677+
<AutoTypeTable path="../packages/stack/src/plugins/blog/api/mutations.ts" name="UpdatePostInput" />
678+
640679
## Static Site Generation (SSG)
641680

642681
`route.loader()` makes HTTP requests to `apiBaseURL`, which silently fails during `next build` because no dev server is running. Use `prefetchForRoute()` instead — it reads directly from the database and pre-populates the React Query cache before rendering.

packages/stack/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@btst/stack",
3-
"version": "2.11.1",
3+
"version": "2.11.2",
44
"description": "A composable, plugin-based library for building full-stack applications.",
55
"repository": {
66
"type": "git",

packages/stack/src/plugins/blog/api/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ export {
66
type PostListParams,
77
type PostListResult,
88
} from "./getters";
9+
export {
10+
createPost,
11+
updatePost,
12+
deletePost,
13+
type CreatePostInput,
14+
type UpdatePostInput,
15+
} from "./mutations";
916
export { serializePost, serializeTag } from "./serializers";
1017
export { BLOG_QUERY_KEYS } from "./query-key-defs";
1118
export { createBlogQueryKeys } from "../query-keys";

0 commit comments

Comments
 (0)