@autoblogwriter/sdk 1.0.2
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 +296 -0
- package/dist/index.cjs +592 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +542 -0
- package/dist/index.js.map +1 -0
- package/dist/revalidate-445OJMx_.d.cts +116 -0
- package/dist/revalidate-445OJMx_.d.ts +116 -0
- package/dist/revalidate.cjs +185 -0
- package/dist/revalidate.cjs.map +1 -0
- package/dist/revalidate.d.cts +1 -0
- package/dist/revalidate.d.ts +1 -0
- package/dist/revalidate.js +149 -0
- package/dist/revalidate.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# @blogauto/sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for BlogAuto. It keeps server-side integration tiny, adds helpers for Next.js App Router and Vite/React apps, and ships revalidation utilities that respect BlogAuto's API contract.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @blogauto/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> Requires Node.js 18+ and native `fetch`. Bundles ship as ESM, CJS, and types via `tsup`.
|
|
12
|
+
|
|
13
|
+
## Security Notes (Read First)
|
|
14
|
+
- Never ship BlogAuto API keys in public, client-side code.
|
|
15
|
+
- Run the SDK inside Next.js server components, route handlers, or your own backend proxy.
|
|
16
|
+
- If you must expose functionality to the browser, issue scoped public tokens or proxy through your server. Do **not** mint new auth schemes.
|
|
17
|
+
|
|
18
|
+
## Client Configuration
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createBlogAutoClient } from "@blogauto/sdk";
|
|
22
|
+
|
|
23
|
+
const client = createBlogAutoClient({
|
|
24
|
+
apiUrl: "https://api.blogauto.io",
|
|
25
|
+
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
26
|
+
workspaceId: process.env.BLOGAUTO_WORKSPACE_ID,
|
|
27
|
+
workspaceSlug: process.env.BLOGAUTO_WORKSPACE_SLUG,
|
|
28
|
+
authMode: "bearer", // or "x-api-key"
|
|
29
|
+
headers: { "x-trace-id": "..." },
|
|
30
|
+
timeoutMs: 10_000,
|
|
31
|
+
fetch: globalThis.fetch, // optional custom fetch
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Option | Type | Required | Description |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| `apiUrl` | `string` | yes | Root API URL, e.g. `https://api.blogauto.io`. Trailing slash removed automatically. |
|
|
38
|
+
| `apiKey` | `string` | yes | Workspace-scoped API key created in the BlogAuto dashboard. |
|
|
39
|
+
| `workspaceId` | `string` | one of `workspaceId` or `workspaceSlug` | Optional; used for account scoping. |
|
|
40
|
+
| `workspaceSlug` | `string` | one of `workspaceId` or `workspaceSlug` | Required for public content APIs (`/v1/public/:workspaceSlug/...`). |
|
|
41
|
+
| `authMode` | `"bearer" \| "x-api-key"` | no (default `"bearer"`) | Sends the API key via `Authorization: Bearer` or `x-api-key`. |
|
|
42
|
+
| `headers` | `Record<string, string>` | no | Additional static headers (merged on each request). |
|
|
43
|
+
| `fetch` | `typeof fetch` | no | Provide a custom fetch implementation (e.g., for SSR polyfills). |
|
|
44
|
+
| `timeoutMs` | `number` | no | Optional request timeout enforced client-side. |
|
|
45
|
+
|
|
46
|
+
## Next.js App Router Usage
|
|
47
|
+
|
|
48
|
+
All examples assume you run in server files where environment variables are available. **Do not** move these snippets to client components without a secure proxy.
|
|
49
|
+
|
|
50
|
+
### `app/blog/page.tsx`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// app/blog/page.tsx
|
|
54
|
+
import { createBlogAutoClient, renderMarkdownToHtml } from "@blogauto/sdk";
|
|
55
|
+
|
|
56
|
+
const workspaceSlug = process.env.BLOGAUTO_WORKSPACE!;
|
|
57
|
+
const blogClient = createBlogAutoClient({
|
|
58
|
+
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
59
|
+
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
60
|
+
workspaceSlug,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default async function BlogIndexPage() {
|
|
64
|
+
const { posts } = await blogClient.getPosts({
|
|
65
|
+
limit: 20,
|
|
66
|
+
next: { tags: [`blogauto:${workspaceSlug}:posts`] },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
{posts.map((post) => (
|
|
72
|
+
<article key={post.id}>
|
|
73
|
+
<h2>{post.title}</h2>
|
|
74
|
+
<div dangerouslySetInnerHTML={{ __html: renderMarkdownToHtml(post.excerpt ?? "") }} />
|
|
75
|
+
</article>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `app/blog/[slug]/page.tsx`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// app/blog/[slug]/page.tsx
|
|
86
|
+
import { notFound } from "next/navigation";
|
|
87
|
+
import { createBlogAutoClient, renderMarkdownToHtml } from "@blogauto/sdk";
|
|
88
|
+
|
|
89
|
+
const workspaceSlug = process.env.BLOGAUTO_WORKSPACE!;
|
|
90
|
+
const blogClient = createBlogAutoClient({
|
|
91
|
+
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
92
|
+
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
93
|
+
workspaceSlug,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
interface Props {
|
|
97
|
+
params: { slug: string };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default async function BlogPostPage({ params }: Props) {
|
|
101
|
+
const post = await blogClient.getPostBySlug(params.slug, {
|
|
102
|
+
next: { tags: [`blogauto:${workspaceSlug}:post:${params.slug}`] },
|
|
103
|
+
});
|
|
104
|
+
if (!post) {
|
|
105
|
+
notFound();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<article>
|
|
110
|
+
<h1>{post.title}</h1>
|
|
111
|
+
<time dateTime={post.publishedAt ?? post.updatedAt}>{post.publishedAt}</time>
|
|
112
|
+
<div dangerouslySetInnerHTML={{ __html: renderMarkdownToHtml(post.content) }} />
|
|
113
|
+
</article>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `app/sitemap.ts`
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// app/sitemap.ts
|
|
122
|
+
import { buildSitemap, createBlogAutoClient } from "@blogauto/sdk";
|
|
123
|
+
|
|
124
|
+
const blogClient = createBlogAutoClient({
|
|
125
|
+
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
126
|
+
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
127
|
+
workspaceSlug: process.env.BLOGAUTO_WORKSPACE!,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export default async function sitemap() {
|
|
131
|
+
const entries = await blogClient.getSitemapEntries();
|
|
132
|
+
return buildSitemap({
|
|
133
|
+
siteUrl: process.env.SITE_URL!,
|
|
134
|
+
entries,
|
|
135
|
+
routePrefix: "/blog",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `app/robots.ts`
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// app/robots.ts
|
|
144
|
+
import { buildRobots } from "@blogauto/sdk";
|
|
145
|
+
|
|
146
|
+
export default function robots() {
|
|
147
|
+
return buildRobots({
|
|
148
|
+
siteUrl: process.env.SITE_URL!,
|
|
149
|
+
sitemapPath: "/sitemap.xml",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Vite/React Usage with Server Proxy
|
|
155
|
+
|
|
156
|
+
Never pipe the API key directly to the browser. Instead, add a tiny proxy route inside your server (Express, Fastify, Next API route, etc.) that calls the SDK.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// server/routes/blogauto.ts (Express example)
|
|
160
|
+
import express from "express";
|
|
161
|
+
import { createBlogAutoClient } from "@blogauto/sdk";
|
|
162
|
+
|
|
163
|
+
const router = express.Router();
|
|
164
|
+
const blogClient = createBlogAutoClient({
|
|
165
|
+
apiUrl: process.env.BLOGAUTO_API_URL!,
|
|
166
|
+
apiKey: process.env.BLOGAUTO_API_KEY!,
|
|
167
|
+
workspaceId: process.env.BLOGAUTO_WORKSPACE_ID!,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
router.get("/posts", async (req, res) => {
|
|
171
|
+
const { posts, nextCursor } = await blogClient.getPosts({
|
|
172
|
+
limit: Number(req.query.limit) || 10,
|
|
173
|
+
cursor: req.query.cursor?.toString(),
|
|
174
|
+
});
|
|
175
|
+
res.json({ posts, nextCursor });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
router.get("/posts/:slug", async (req, res) => {
|
|
179
|
+
const post = await blogClient.getPostBySlug(req.params.slug);
|
|
180
|
+
if (!post) {
|
|
181
|
+
return res.status(404).json({ error: "Not Found" });
|
|
182
|
+
}
|
|
183
|
+
res.json(post);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export default router;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
React (client) calls your proxy:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// src/pages/Blog.tsx
|
|
193
|
+
import useSWR from "swr";
|
|
194
|
+
|
|
195
|
+
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
|
196
|
+
|
|
197
|
+
export function BlogList() {
|
|
198
|
+
const { data } = useSWR("/api/blogauto/posts", fetcher);
|
|
199
|
+
if (!data) return <p>Loading…</p>;
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<ul>
|
|
203
|
+
{data.posts.map((post: any) => (
|
|
204
|
+
<li key={post.id}>{post.title}</li>
|
|
205
|
+
))}
|
|
206
|
+
</ul>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Automatic Revalidation Setup
|
|
212
|
+
|
|
213
|
+
When BlogAuto publishes a post it can ping your Next.js App Router site so sitemap, robots, and cached blog pages refresh immediately.
|
|
214
|
+
|
|
215
|
+
1. **Configure your workspace**
|
|
216
|
+
- In the BlogAuto dashboard open **Workspace Settings → Revalidation URL** and set your endpoint (e.g., `https://yoursite.com/api/blogauto/revalidate`).
|
|
217
|
+
- Generate a **Revalidation Webhook secret**, copy the plain secret once, and store it as `BLOGAUTO_REVALIDATE_SECRET`.
|
|
218
|
+
2. **Create the route handler**
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
// app/api/blogauto/revalidate/route.ts
|
|
222
|
+
import { revalidatePath, revalidateTag } from "next/cache";
|
|
223
|
+
import { createRevalidateRouteHandler } from "@blogauto/sdk/revalidate";
|
|
224
|
+
|
|
225
|
+
const secret = process.env.BLOGAUTO_REVALIDATE_SECRET;
|
|
226
|
+
if (!secret) {
|
|
227
|
+
throw new Error("Set BLOGAUTO_REVALIDATE_SECRET on the server");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const POST = createRevalidateRouteHandler({
|
|
231
|
+
secret,
|
|
232
|
+
revalidatePath,
|
|
233
|
+
revalidateTag,
|
|
234
|
+
// Optional overrides:
|
|
235
|
+
// allowedSkewSeconds: 600,
|
|
236
|
+
// revalidatePaths: (payload) => [...custom paths...],
|
|
237
|
+
// revalidateTags: (payload) => [...custom tags...],
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The handler verifies the `X-BlogAuto-Signature` HMAC header, enforces timestamp skew (default 5 minutes), and revalidates:
|
|
242
|
+
|
|
243
|
+
- Paths: `/sitemap.xml`, `/robots.txt`, `/blog`, `/blog/:slug`
|
|
244
|
+
- Tags: `blogauto:<workspaceSlug>:sitemap`, `blogauto:<workspaceSlug>:posts`, and `blogauto:<workspaceSlug>:post:<slug>`
|
|
245
|
+
|
|
246
|
+
3. **Tag your data fetches**
|
|
247
|
+
|
|
248
|
+
Use the `next` option when calling SDK methods so `revalidateTag` knows which caches to expire:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
await blogClient.getPosts({
|
|
252
|
+
limit: 20,
|
|
253
|
+
next: { tags: [`blogauto:${workspaceSlug}:posts`] },
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await blogClient.getPostBySlug(slug, {
|
|
257
|
+
next: { tags: [`blogauto:${workspaceSlug}:post:${slug}`] },
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Cache Invalidation Notes
|
|
262
|
+
|
|
263
|
+
- In production, rely on tag-based revalidation via the webhook handler above. This prevents users from needing hard refreshes after publishing.
|
|
264
|
+
- For very dynamic pages, you can opt out of caching per request with `cache: "no-store"` in the fetch options.
|
|
265
|
+
- For client-side lists (SWR/React Query), call `mutate`/`invalidateQueries` after your proxy endpoint updates, or on a webhook-driven event from your backend.
|
|
266
|
+
|
|
267
|
+
4. **Need manual verification?**
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { verifyWebhookSignature } from "@blogauto/sdk/revalidate";
|
|
271
|
+
|
|
272
|
+
const isValid = verifyWebhookSignature({
|
|
273
|
+
rawBody, // string or Buffer
|
|
274
|
+
signature: req.headers.get("x-blogauto-signature"),
|
|
275
|
+
secret: process.env.BLOGAUTO_REVALIDATE_SECRET!,
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Reject missing/invalid signatures or stale timestamps to prevent replay attacks, and avoid logging the secret or signature hash.
|
|
280
|
+
|
|
281
|
+
## Markdown Rendering Helper
|
|
282
|
+
|
|
283
|
+
`renderMarkdownToHtml(markdown: string)` ships a dependency-free renderer that covers headings, links, emphasis, inline/code fences, and paragraphs. For richer formatting you can bring your own renderer—this helper simply provides a safe default.
|
|
284
|
+
|
|
285
|
+
## Dynamic Sitemaps & Robots
|
|
286
|
+
|
|
287
|
+
- `buildSitemap({ siteUrl, entries, routePrefix })` returns a `MetadataRoute.Sitemap` array ready for `app/sitemap.ts`.
|
|
288
|
+
- `buildRobots({ siteUrl, sitemapPath })` outputs a `MetadataRoute.Robots` structure. Defaults: route prefix `/blog`, sitemap path `/sitemap.xml`.
|
|
289
|
+
|
|
290
|
+
## Testing
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
npm test
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Tests cover config validation, auth headers, 404 handling, sitemap output, and revalidation helpers.
|