chaingrow-blog 0.1.0 → 0.2.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 +95 -10
- package/dist/{chunk-TDHCPX4T.js → chunk-OTLGKSYD.js} +25 -3
- package/dist/chunk-OTLGKSYD.js.map +1 -0
- package/dist/index.cjs +24 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/next/index.cjs +26 -8
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js +3 -7
- package/dist/next/index.js.map +1 -1
- package/dist/next/webhook.cjs +7 -5
- package/dist/next/webhook.cjs.map +1 -1
- package/dist/next/webhook.d.cts +1 -1
- package/dist/next/webhook.d.ts +1 -1
- package/dist/next/webhook.js +8 -6
- package/dist/next/webhook.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-TDHCPX4T.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# chaingrow-blog
|
|
2
2
|
|
|
3
|
-
Drop-in Next.js SDK for rendering blogs published from [ChainGrow](https://chaingrow.
|
|
3
|
+
Drop-in Next.js SDK for rendering blogs published from [ChainGrow](https://chaingrow.de).
|
|
4
4
|
|
|
5
5
|
- **Zero database.** Your site stores nothing — ChainGrow is the source of truth.
|
|
6
6
|
- **Instant publishing.** When you press Publish in ChainGrow, the SDK's webhook handler invalidates the relevant page via `revalidateTag`. Next request serves fresh HTML.
|
|
@@ -18,18 +18,20 @@ Requires Next.js ≥ 14 (App Router) and React ≥ 18.
|
|
|
18
18
|
|
|
19
19
|
## Setup (3 files)
|
|
20
20
|
|
|
21
|
-
### 1. Environment
|
|
21
|
+
### 1. Environment variable
|
|
22
22
|
|
|
23
23
|
```env
|
|
24
|
-
CHAINGROW_API_URL=https://api.chaingrow.ai
|
|
25
24
|
CHAINGROW_API_KEY=ck_live_xxx
|
|
26
|
-
CHAINGROW_WEBHOOK_SECRET=whsec_xxx
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
That's the only required variable. Generate the key in your ChainGrow dashboard under **Integrations → API Keys**. It authenticates your API calls AND verifies incoming webhooks, so there's only one secret to manage. The webhook delivery URL is derived automatically from the website you set on your business profile, so you never paste a URL anywhere.
|
|
28
|
+
|
|
29
|
+
(Optional: set `CHAINGROW_API_URL` if you're pointing at staging or a self-hosted deploy. Defaults to `https://api.chaingrow.de`.)
|
|
30
30
|
|
|
31
31
|
### 2. Webhook receiver
|
|
32
32
|
|
|
33
|
+
Mount the SDK's handler at the convention path `/api/chaingrow/webhook` — ChainGrow always POSTs to `${your-website}/api/chaingrow/webhook`:
|
|
34
|
+
|
|
33
35
|
```ts
|
|
34
36
|
// app/api/chaingrow/webhook/route.ts
|
|
35
37
|
export { POST } from 'chaingrow-blog/next/webhook';
|
|
@@ -37,11 +39,11 @@ export { POST } from 'chaingrow-blog/next/webhook';
|
|
|
37
39
|
|
|
38
40
|
### 3. Blog page
|
|
39
41
|
|
|
40
|
-
**Option A —
|
|
42
|
+
**Option A — Custom rendering (bring your own JSX):**
|
|
41
43
|
|
|
42
44
|
```tsx
|
|
43
45
|
// app/blog/[slug]/page.tsx
|
|
44
|
-
import { notFound } from 'next/navigation';
|
|
46
|
+
import { notFound } from 'next/navigation'; // Next.js built-in — serves a real HTTP 404 and renders app/not-found.tsx
|
|
45
47
|
import { getBlog } from 'chaingrow-blog/next';
|
|
46
48
|
|
|
47
49
|
export default async function BlogPage({
|
|
@@ -64,7 +66,7 @@ export default async function BlogPage({
|
|
|
64
66
|
}
|
|
65
67
|
```
|
|
66
68
|
|
|
67
|
-
**Option B —
|
|
69
|
+
**Option B — Default renderer with per-block overrides:**
|
|
68
70
|
|
|
69
71
|
```tsx
|
|
70
72
|
import { notFound } from 'next/navigation';
|
|
@@ -123,9 +125,13 @@ Renders all `blog.blocks` using unstyled default components. Every block type is
|
|
|
123
125
|
|
|
124
126
|
Block types: `heading`, `paragraph`, `list`, `quote`, `code`, `image_placeholder`.
|
|
125
127
|
|
|
126
|
-
### `createWebhookHandler({
|
|
128
|
+
### `createWebhookHandler({ apiKey?, onEvent? })`
|
|
129
|
+
|
|
130
|
+
Returns a `{ POST }` route handler. Verifies the `X-ChainGrow-Signature` HMAC (signed server-side with `sha256(apiKey)`) and calls `revalidateTag`. Pass `onEvent` to run custom logic after revalidation (e.g. cache warming, analytics). By default reads `CHAINGROW_API_KEY` from the environment.
|
|
131
|
+
|
|
132
|
+
### Rate-limit handling
|
|
127
133
|
|
|
128
|
-
|
|
134
|
+
Calls are transparently retried once on HTTP `429 Too Many Requests`, honoring the `Retry-After` header. No configuration needed. Other errors bubble up unchanged.
|
|
129
135
|
|
|
130
136
|
## How publishing works
|
|
131
137
|
|
|
@@ -156,6 +162,85 @@ Fresh HTML getBlog() → GET /api/v1/blogs/by-slug/<s
|
|
|
156
162
|
cached for next visitors
|
|
157
163
|
```
|
|
158
164
|
|
|
165
|
+
## Releasing new versions (maintainers only)
|
|
166
|
+
|
|
167
|
+
> Consumers installing `chaingrow-blog` can ignore this section — it's internal notes for ChainGrow maintainers cutting a new release.
|
|
168
|
+
|
|
169
|
+
The SDK is published to npm by a GitHub Actions workflow (`.github/workflows/publish-blog-sdk.yml`). The workflow only runs when you push a **git tag** matching `blog-sdk-v*`. Regular commits and branch pushes do **nothing** — npm is never touched unless you explicitly create and push a release tag.
|
|
170
|
+
|
|
171
|
+
### Triggers
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
on:
|
|
175
|
+
push:
|
|
176
|
+
tags:
|
|
177
|
+
- 'blog-sdk-v*' # only fires on matching tags
|
|
178
|
+
workflow_dispatch: # manual "Run workflow" button in GitHub Actions UI
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
That's it. Two ways to trigger a publish:
|
|
182
|
+
|
|
183
|
+
1. Push a tag named `blog-sdk-v<version>` (the normal flow)
|
|
184
|
+
2. Click "Run workflow" on the Actions page in GitHub (manual escape hatch)
|
|
185
|
+
|
|
186
|
+
No other event publishes. You can push commits to `main` all day without releasing.
|
|
187
|
+
|
|
188
|
+
### Cutting a release
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# 1. Bump the version in packages/blog-sdk/package.json
|
|
192
|
+
# "version": "0.1.0" → "version": "0.1.1"
|
|
193
|
+
|
|
194
|
+
# 2. Commit the version bump
|
|
195
|
+
git add packages/blog-sdk/package.json
|
|
196
|
+
git commit -m "chore(blog-sdk): release v0.1.1"
|
|
197
|
+
git push # normal push, nothing publishes yet
|
|
198
|
+
|
|
199
|
+
# 3. Create the release tag on that commit
|
|
200
|
+
git tag blog-sdk-v0.1.1
|
|
201
|
+
|
|
202
|
+
# 4. Push the tag (separate command — git push alone doesn't push tags)
|
|
203
|
+
git push origin blog-sdk-v0.1.1 # this is what fires the workflow
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
About a minute later the new version lands on [npmjs.com/package/chaingrow-blog](https://www.npmjs.com/package/chaingrow-blog).
|
|
207
|
+
|
|
208
|
+
### Why tags and not branches?
|
|
209
|
+
|
|
210
|
+
Tags are immutable bookmarks pointing at a specific commit, and git treats them as a separate namespace from branches. Splitting the release trigger onto tags means:
|
|
211
|
+
|
|
212
|
+
- Normal development pushes never accidentally publish
|
|
213
|
+
- Every published version has a matching tag you can `git checkout` to see exactly what was shipped
|
|
214
|
+
- Cutting a release is an explicit, deliberate action (`git tag` + `git push origin <tag>`), not a side effect of merging to main
|
|
215
|
+
|
|
216
|
+
### Versioning rules
|
|
217
|
+
|
|
218
|
+
Follow [semver](https://semver.org/):
|
|
219
|
+
|
|
220
|
+
| Change | Bump |
|
|
221
|
+
|---|---|
|
|
222
|
+
| Adding a new field to `Blog` (backwards-compatible) | patch (`0.1.0` → `0.1.1`) |
|
|
223
|
+
| Adding a new exported function or component | minor (`0.1.0` → `0.2.0`) |
|
|
224
|
+
| **Changing the `Block` type union** | **major (`0.1.0` → `1.0.0`)** — customer override maps will break |
|
|
225
|
+
| Removing or renaming any exported symbol | major |
|
|
226
|
+
|
|
227
|
+
### Fixing a mistake
|
|
228
|
+
|
|
229
|
+
If you tagged the wrong commit and it hasn't published yet:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
git tag -d blog-sdk-v0.1.1 # delete local tag
|
|
233
|
+
git push origin :refs/tags/blog-sdk-v0.1.1 # delete remote tag
|
|
234
|
+
# then re-tag correctly
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If it already published: npm blocks unpublishing after 72 hours. The recipe is to deprecate the bad version and bump:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
npm deprecate chaingrow-blog@0.1.1 "published in error — use 0.1.2"
|
|
241
|
+
# then cut a fresh release with the fix
|
|
242
|
+
```
|
|
243
|
+
|
|
159
244
|
## License
|
|
160
245
|
|
|
161
246
|
MIT
|
|
@@ -3,7 +3,9 @@ function createClient(opts) {
|
|
|
3
3
|
const baseUrl = opts.apiUrl.replace(/\/$/, "");
|
|
4
4
|
const doFetch = opts.fetch ?? fetch;
|
|
5
5
|
const request = async (path, init) => {
|
|
6
|
-
const
|
|
6
|
+
const url = `${baseUrl}${path}`;
|
|
7
|
+
const method = init?.method ?? "GET";
|
|
8
|
+
const buildInit = () => ({
|
|
7
9
|
...init,
|
|
8
10
|
headers: {
|
|
9
11
|
"X-API-Key": opts.apiKey,
|
|
@@ -11,10 +13,30 @@ function createClient(opts) {
|
|
|
11
13
|
...init?.headers ?? {}
|
|
12
14
|
}
|
|
13
15
|
});
|
|
16
|
+
let res;
|
|
17
|
+
try {
|
|
18
|
+
res = await doFetch(url, buildInit());
|
|
19
|
+
if (res.status === 429) {
|
|
20
|
+
const waitSec = parseInt(res.headers.get("Retry-After") ?? "1", 10);
|
|
21
|
+
await new Promise(
|
|
22
|
+
(resolve) => setTimeout(resolve, Math.max(0, waitSec) * 1e3)
|
|
23
|
+
);
|
|
24
|
+
res = await doFetch(url, buildInit());
|
|
25
|
+
}
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const cause = err instanceof Error ? err.cause : void 0;
|
|
28
|
+
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : void 0;
|
|
29
|
+
const detail = code ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ""}` : err instanceof Error ? err.message : String(err);
|
|
30
|
+
const e = new Error(
|
|
31
|
+
`chaingrow-blog: could not reach ${method} ${url} \u2014 ${detail}. Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`
|
|
32
|
+
);
|
|
33
|
+
e.cause = err;
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
14
36
|
if (res.status === 404) return null;
|
|
15
37
|
if (!res.ok) {
|
|
16
38
|
throw new Error(
|
|
17
|
-
`chaingrow-blog: ${
|
|
39
|
+
`chaingrow-blog: ${method} ${url} failed (${res.status})`
|
|
18
40
|
);
|
|
19
41
|
}
|
|
20
42
|
return await res.json();
|
|
@@ -49,4 +71,4 @@ function createClient(opts) {
|
|
|
49
71
|
export {
|
|
50
72
|
createClient
|
|
51
73
|
};
|
|
52
|
-
//# sourceMappingURL=chunk-
|
|
74
|
+
//# sourceMappingURL=chunk-OTLGKSYD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response;\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n } catch (err) {\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const cause = err instanceof Error ? err.cause : undefined;\n const code =\n cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) return [];\n const blogs = response.data ?? [];\n if (listOpts.language) {\n return blogs.filter((b) => b.language === listOpts.language);\n }\n return blogs;\n },\n };\n}\n"],"mappings":";AAmBO,SAAS,aAAa,MAAsC;AACjE,QAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,EAAE;AAC7C,QAAM,UAAU,KAAK,SAAS;AAE9B,QAAM,UAAU,OACd,MACA,SACsB;AACtB,UAAM,MAAM,GAAG,OAAO,GAAG,IAAI;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,YAAY,OAAoB;AAAA,MACpC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,QAAQ;AAAA,QACR,GAAI,MAAM,WAAW,CAAC;AAAA,MACxB;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,QAAQ,KAAK,UAAU,CAAC;AAGpC,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,UAAU,SAAS,IAAI,QAAQ,IAAI,aAAa,KAAK,KAAK,EAAE;AAClE,cAAM,IAAI;AAAA,UAAQ,CAAC,YACjB,WAAW,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,GAAI;AAAA,QACjD;AACA,cAAM,MAAM,QAAQ,KAAK,UAAU,CAAC;AAAA,MACtC;AAAA,IACF,SAAS,KAAK;AAKZ,YAAM,QAAQ,eAAe,QAAQ,IAAI,QAAQ;AACjD,YAAM,OACJ,SAAS,OAAO,UAAU,YAAY,UAAU,QAC5C,OAAQ,MAA6B,IAAI,IACzC;AACN,YAAM,SAAS,OACX,GAAG,IAAI,GAAG,eAAe,SAAS,IAAI,UAAU,KAAK,IAAI,OAAO,MAAM,EAAE,KACxE,eAAe,QACb,IAAI,UACJ,OAAO,GAAG;AAChB,YAAM,IAAI,IAAI;AAAA,QACZ,mCAAmC,MAAM,IAAI,GAAG,WAAM,MAAM;AAAA,MAE9D;AACA,MAAC,EAAkC,QAAQ;AAC3C,YAAM;AAAA,IACR;AAEA,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,mBAAmB,MAAM,IAAI,GAAG,YAAY,IAAI,MAAM;AAAA,MACxD;AAAA,IACF;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,UAAU;AAC5B,YAAM,KAAK,WAAW,aAAa,mBAAmB,QAAQ,CAAC,KAAK;AACpE,aAAO;AAAA,QACL,yBAAyB,mBAAmB,IAAI,CAAC,GAAG,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,IAEA,MAAM,YAAY,IAAI;AACpB,aAAO,QAAc,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,IAChE;AAAA,IAEA,MAAM,UAAU,WAAW,CAAC,GAAG;AAC7B,YAAM,SAAS,IAAI,gBAAgB;AACnC,UAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,SAAS,MAAM;AACzD,YAAM,KAAK,OAAO,SAAS;AAC3B,YAAM,WAAW,MAAM;AAAA,QACrB,gBAAgB,KAAK,IAAI,EAAE,KAAK,EAAE;AAAA,MACpC;AACA,UAAI,CAAC,SAAU,QAAO,CAAC;AACvB,YAAM,QAAQ,SAAS,QAAQ,CAAC;AAChC,UAAI,SAAS,UAAU;AACrB,eAAO,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,QAAQ;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -29,7 +29,9 @@ function createClient(opts) {
|
|
|
29
29
|
const baseUrl = opts.apiUrl.replace(/\/$/, "");
|
|
30
30
|
const doFetch = opts.fetch ?? fetch;
|
|
31
31
|
const request = async (path, init) => {
|
|
32
|
-
const
|
|
32
|
+
const url = `${baseUrl}${path}`;
|
|
33
|
+
const method = init?.method ?? "GET";
|
|
34
|
+
const buildInit = () => ({
|
|
33
35
|
...init,
|
|
34
36
|
headers: {
|
|
35
37
|
"X-API-Key": opts.apiKey,
|
|
@@ -37,10 +39,30 @@ function createClient(opts) {
|
|
|
37
39
|
...init?.headers ?? {}
|
|
38
40
|
}
|
|
39
41
|
});
|
|
42
|
+
let res;
|
|
43
|
+
try {
|
|
44
|
+
res = await doFetch(url, buildInit());
|
|
45
|
+
if (res.status === 429) {
|
|
46
|
+
const waitSec = parseInt(res.headers.get("Retry-After") ?? "1", 10);
|
|
47
|
+
await new Promise(
|
|
48
|
+
(resolve) => setTimeout(resolve, Math.max(0, waitSec) * 1e3)
|
|
49
|
+
);
|
|
50
|
+
res = await doFetch(url, buildInit());
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const cause = err instanceof Error ? err.cause : void 0;
|
|
54
|
+
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : void 0;
|
|
55
|
+
const detail = code ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ""}` : err instanceof Error ? err.message : String(err);
|
|
56
|
+
const e = new Error(
|
|
57
|
+
`chaingrow-blog: could not reach ${method} ${url} \u2014 ${detail}. Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`
|
|
58
|
+
);
|
|
59
|
+
e.cause = err;
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
40
62
|
if (res.status === 404) return null;
|
|
41
63
|
if (!res.ok) {
|
|
42
64
|
throw new Error(
|
|
43
|
-
`chaingrow-blog: ${
|
|
65
|
+
`chaingrow-blog: ${method} ${url} failed (${res.status})`
|
|
44
66
|
);
|
|
45
67
|
}
|
|
46
68
|
return await res.json();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from './types';\nexport { createClient, type ChaingrowClient, type ClientOptions } from './client';\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from './types';\nexport { createClient, type ChaingrowClient, type ClientOptions } from './client';\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response;\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n } catch (err) {\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const cause = err instanceof Error ? err.cause : undefined;\n const code =\n cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) return [];\n const blogs = response.data ?? [];\n if (listOpts.language) {\n return blogs.filter((b) => b.language === listOpts.language);\n }\n return blogs;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBO,SAAS,aAAa,MAAsC;AACjE,QAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,EAAE;AAC7C,QAAM,UAAU,KAAK,SAAS;AAE9B,QAAM,UAAU,OACd,MACA,SACsB;AACtB,UAAM,MAAM,GAAG,OAAO,GAAG,IAAI;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,YAAY,OAAoB;AAAA,MACpC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,QAAQ;AAAA,QACR,GAAI,MAAM,WAAW,CAAC;AAAA,MACxB;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,QAAQ,KAAK,UAAU,CAAC;AAGpC,UAAI,IAAI,WAAW,KAAK;AACtB,cAAM,UAAU,SAAS,IAAI,QAAQ,IAAI,aAAa,KAAK,KAAK,EAAE;AAClE,cAAM,IAAI;AAAA,UAAQ,CAAC,YACjB,WAAW,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,GAAI;AAAA,QACjD;AACA,cAAM,MAAM,QAAQ,KAAK,UAAU,CAAC;AAAA,MACtC;AAAA,IACF,SAAS,KAAK;AAKZ,YAAM,QAAQ,eAAe,QAAQ,IAAI,QAAQ;AACjD,YAAM,OACJ,SAAS,OAAO,UAAU,YAAY,UAAU,QAC5C,OAAQ,MAA6B,IAAI,IACzC;AACN,YAAM,SAAS,OACX,GAAG,IAAI,GAAG,eAAe,SAAS,IAAI,UAAU,KAAK,IAAI,OAAO,MAAM,EAAE,KACxE,eAAe,QACb,IAAI,UACJ,OAAO,GAAG;AAChB,YAAM,IAAI,IAAI;AAAA,QACZ,mCAAmC,MAAM,IAAI,GAAG,WAAM,MAAM;AAAA,MAE9D;AACA,MAAC,EAAkC,QAAQ;AAC3C,YAAM;AAAA,IACR;AAEA,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,mBAAmB,MAAM,IAAI,GAAG,YAAY,IAAI,MAAM;AAAA,MACxD;AAAA,IACF;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,UAAU;AAC5B,YAAM,KAAK,WAAW,aAAa,mBAAmB,QAAQ,CAAC,KAAK;AACpE,aAAO;AAAA,QACL,yBAAyB,mBAAmB,IAAI,CAAC,GAAG,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,IAEA,MAAM,YAAY,IAAI;AACpB,aAAO,QAAc,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,IAChE;AAAA,IAEA,MAAM,UAAU,WAAW,CAAC,GAAG;AAC7B,YAAM,SAAS,IAAI,gBAAgB;AACnC,UAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,SAAS,MAAM;AACzD,YAAM,KAAK,OAAO,SAAS;AAC3B,YAAM,WAAW,MAAM;AAAA,QACrB,gBAAgB,KAAK,IAAI,EAAE,KAAK,EAAE;AAAA,MACpC;AACA,UAAI,CAAC,SAAU,QAAO,CAAC;AACvB,YAAM,QAAQ,SAAS,QAAQ,CAAC;AAChC,UAAI,SAAS,UAAU;AACrB,eAAO,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,QAAQ;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.js
CHANGED
package/dist/next/index.cjs
CHANGED
|
@@ -30,7 +30,9 @@ function createClient(opts) {
|
|
|
30
30
|
const baseUrl = opts.apiUrl.replace(/\/$/, "");
|
|
31
31
|
const doFetch = opts.fetch ?? fetch;
|
|
32
32
|
const request = async (path, init) => {
|
|
33
|
-
const
|
|
33
|
+
const url = `${baseUrl}${path}`;
|
|
34
|
+
const method = init?.method ?? "GET";
|
|
35
|
+
const buildInit = () => ({
|
|
34
36
|
...init,
|
|
35
37
|
headers: {
|
|
36
38
|
"X-API-Key": opts.apiKey,
|
|
@@ -38,10 +40,30 @@ function createClient(opts) {
|
|
|
38
40
|
...init?.headers ?? {}
|
|
39
41
|
}
|
|
40
42
|
});
|
|
43
|
+
let res;
|
|
44
|
+
try {
|
|
45
|
+
res = await doFetch(url, buildInit());
|
|
46
|
+
if (res.status === 429) {
|
|
47
|
+
const waitSec = parseInt(res.headers.get("Retry-After") ?? "1", 10);
|
|
48
|
+
await new Promise(
|
|
49
|
+
(resolve) => setTimeout(resolve, Math.max(0, waitSec) * 1e3)
|
|
50
|
+
);
|
|
51
|
+
res = await doFetch(url, buildInit());
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const cause = err instanceof Error ? err.cause : void 0;
|
|
55
|
+
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : void 0;
|
|
56
|
+
const detail = code ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ""}` : err instanceof Error ? err.message : String(err);
|
|
57
|
+
const e = new Error(
|
|
58
|
+
`chaingrow-blog: could not reach ${method} ${url} \u2014 ${detail}. Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`
|
|
59
|
+
);
|
|
60
|
+
e.cause = err;
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
41
63
|
if (res.status === 404) return null;
|
|
42
64
|
if (!res.ok) {
|
|
43
65
|
throw new Error(
|
|
44
|
-
`chaingrow-blog: ${
|
|
66
|
+
`chaingrow-blog: ${method} ${url} failed (${res.status})`
|
|
45
67
|
);
|
|
46
68
|
}
|
|
47
69
|
return await res.json();
|
|
@@ -74,14 +96,10 @@ function createClient(opts) {
|
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
// src/next/index.ts
|
|
99
|
+
var DEFAULT_API_URL = "https://api.chaingrow.de";
|
|
77
100
|
var resolveEnv = (opts = {}) => {
|
|
78
|
-
const apiUrl = opts.apiUrl ?? process.env.CHAINGROW_API_URL;
|
|
101
|
+
const apiUrl = opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;
|
|
79
102
|
const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;
|
|
80
|
-
if (!apiUrl) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
"chaingrow-blog: CHAINGROW_API_URL is not set (env var or apiUrl option required)"
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
103
|
if (!apiKey) {
|
|
86
104
|
throw new Error(
|
|
87
105
|
"chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)"
|
package/dist/next/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/index.ts","../../src/client.ts"],"sourcesContent":["import type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl
|
|
1
|
+
{"version":3,"sources":["../../src/next/index.ts","../../src/client.ts"],"sourcesContent":["import type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n ...(revalidate === false\n ? { revalidate: false as const }\n : revalidate !== undefined\n ? { revalidate }\n : {}),\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nexport type { Blog } from '../types';\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response;\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n } catch (err) {\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const cause = err instanceof Error ? err.cause : undefined;\n const code =\n cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) return [];\n const blogs = response.data ?? [];\n if (listOpts.language) {\n return blogs.filter((b) => b.language === listOpts.language);\n }\n return blogs;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/next/index.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createClient
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-OTLGKSYD.js";
|
|
4
4
|
|
|
5
5
|
// src/next/index.ts
|
|
6
|
+
var DEFAULT_API_URL = "https://api.chaingrow.de";
|
|
6
7
|
var resolveEnv = (opts = {}) => {
|
|
7
|
-
const apiUrl = opts.apiUrl ?? process.env.CHAINGROW_API_URL;
|
|
8
|
+
const apiUrl = opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;
|
|
8
9
|
const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;
|
|
9
|
-
if (!apiUrl) {
|
|
10
|
-
throw new Error(
|
|
11
|
-
"chaingrow-blog: CHAINGROW_API_URL is not set (env var or apiUrl option required)"
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
10
|
if (!apiKey) {
|
|
15
11
|
throw new Error(
|
|
16
12
|
"chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)"
|
package/dist/next/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/index.ts"],"sourcesContent":["import type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl
|
|
1
|
+
{"version":3,"sources":["../../src/next/index.ts"],"sourcesContent":["import type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n ...(revalidate === false\n ? { revalidate: false as const }\n : revalidate !== undefined\n ? { revalidate }\n : {}),\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nexport type { Blog } from '../types';\n"],"mappings":";;;;;AASA,IAAM,kBAAkB;AAExB,IAAM,aAAa,CAAC,OAA0B,CAAC,MAAM;AACnD,QAAM,SACJ,KAAK,UAAU,QAAQ,IAAI,qBAAqB;AAClD,QAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEA,IAAM,aAAa,CAAC,KAAa,eAA2C;AAC1E,UAAQ,CAAC,OAA0B,SACjC,MAAM,OAAO;AAAA,IACX,GAAG;AAAA,IACH,MAAM;AAAA,MACJ,MAAM,CAAC,kBAAkB,GAAG;AAAA,MAC5B,GAAI,eAAe,QACf,EAAE,YAAY,MAAe,IAC7B,eAAe,SACb,EAAE,WAAW,IACb,CAAC;AAAA,IACT;AAAA,EACF,CAAgB;AACpB;AAEA,eAAsB,QACpB,MACA,UACA,OAA0B,CAAC,GACL;AACtB,QAAM,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI;AAC1C,QAAM,SAAS,aAAa;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,OAAO,WAAW,kBAAkB,IAAI,IAAI,KAAK,UAAU;AAAA,EAC7D,CAAC;AACD,SAAO,OAAO,QAAQ,MAAM,QAAQ;AACtC;AAEA,eAAsB,UACpB,WAAkE,CAAC,GACnE,OAA0B,CAAC,GACV;AACjB,QAAM,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI;AAC1C,QAAM,SAAS,aAAa;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,OAAO,WAAW,uBAAuB,KAAK,UAAU;AAAA,EAC1D,CAAC;AACD,SAAO,OAAO,UAAU,QAAQ;AAClC;","names":[]}
|
package/dist/next/webhook.cjs
CHANGED
|
@@ -26,8 +26,9 @@ __export(webhook_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(webhook_exports);
|
|
27
27
|
var import_cache = require("next/cache");
|
|
28
28
|
var import_node_crypto = require("crypto");
|
|
29
|
-
var
|
|
30
|
-
|
|
29
|
+
var deriveSigningKey = (apiKey) => (0, import_node_crypto.createHash)("sha256").update(apiKey).digest("hex");
|
|
30
|
+
var verifySignature = (body, signature, signingKey) => {
|
|
31
|
+
const expected = (0, import_node_crypto.createHmac)("sha256", signingKey).update(body).digest("hex");
|
|
31
32
|
const provided = signature.trim();
|
|
32
33
|
if (expected.length !== provided.length) return false;
|
|
33
34
|
try {
|
|
@@ -42,11 +43,12 @@ var json = (status, body) => new Response(JSON.stringify(body), {
|
|
|
42
43
|
});
|
|
43
44
|
function createWebhookHandler(opts = {}) {
|
|
44
45
|
const POST2 = async (req) => {
|
|
45
|
-
const
|
|
46
|
+
const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;
|
|
46
47
|
const body = await req.text();
|
|
47
|
-
if (
|
|
48
|
+
if (apiKey) {
|
|
48
49
|
const signature = req.headers.get("x-chaingrow-signature") ?? req.headers.get("X-ChainGrow-Signature") ?? "";
|
|
49
|
-
|
|
50
|
+
const signingKey = deriveSigningKey(apiKey);
|
|
51
|
+
if (!signature || !verifySignature(body, signature, signingKey)) {
|
|
50
52
|
return json(401, { error: "Invalid signature" });
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/webhook.ts"],"sourcesContent":["import { revalidateTag } from 'next/cache';\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { WebhookPayload } from '../types';\n\nexport type WebhookHandlerOptions = {\n
|
|
1
|
+
{"version":3,"sources":["../../src/next/webhook.ts"],"sourcesContent":["import { revalidateTag } from 'next/cache';\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\nimport type { WebhookPayload } from '../types';\n\nexport type WebhookHandlerOptions = {\n apiKey?: string;\n onEvent?: (payload: WebhookPayload) => void | Promise<void>;\n};\n\n// Backend signs webhooks with sha256(apiKey) — the same hash it stores for auth.\n// We derive it here so customers only need CHAINGROW_API_KEY in their env.\nconst deriveSigningKey = (apiKey: string) =>\n createHash('sha256').update(apiKey).digest('hex');\n\nconst verifySignature = (\n body: string,\n signature: string,\n signingKey: string,\n) => {\n const expected = createHmac('sha256', signingKey).update(body).digest('hex');\n const provided = signature.trim();\n if (expected.length !== provided.length) return false;\n try {\n return timingSafeEqual(Buffer.from(expected), Buffer.from(provided));\n } catch {\n return false;\n }\n};\n\nconst json = (status: number, body: Record<string, unknown>) =>\n new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n\nexport function createWebhookHandler(opts: WebhookHandlerOptions = {}) {\n const POST = async (req: Request): Promise<Response> => {\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n const body = await req.text();\n\n if (apiKey) {\n const signature =\n req.headers.get('x-chaingrow-signature') ??\n req.headers.get('X-ChainGrow-Signature') ??\n '';\n const signingKey = deriveSigningKey(apiKey);\n if (!signature || !verifySignature(body, signature, signingKey)) {\n return json(401, { error: 'Invalid signature' });\n }\n }\n\n let payload: WebhookPayload;\n try {\n payload = JSON.parse(body) as WebhookPayload;\n } catch {\n return json(400, { error: 'Invalid JSON body' });\n }\n\n const slug =\n typeof payload?.blog?.slug === 'string' ? payload.blog.slug : null;\n\n revalidateTag('chaingrow:blog');\n revalidateTag('chaingrow:blog:list');\n if (slug) revalidateTag(`chaingrow:blog:${slug}`);\n\n if (opts.onEvent) {\n try {\n await opts.onEvent(payload);\n } catch (err) {\n console.error('chaingrow-blog onEvent handler failed:', err);\n }\n }\n\n return json(200, { ok: true, revalidated: slug ?? null });\n };\n\n return { POST };\n}\n\nexport const { POST } = createWebhookHandler();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA8B;AAC9B,yBAAwD;AAUxD,IAAM,mBAAmB,CAAC,eACxB,+BAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAElD,IAAM,kBAAkB,CACtB,MACA,WACA,eACG;AACH,QAAM,eAAW,+BAAW,UAAU,UAAU,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC3E,QAAM,WAAW,UAAU,KAAK;AAChC,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI;AACF,eAAO,oCAAgB,OAAO,KAAK,QAAQ,GAAG,OAAO,KAAK,QAAQ,CAAC;AAAA,EACrE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,OAAO,CAAC,QAAgB,SAC5B,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,EACjC;AAAA,EACA,SAAS,EAAE,gBAAgB,mBAAmB;AAChD,CAAC;AAEI,SAAS,qBAAqB,OAA8B,CAAC,GAAG;AACrE,QAAMA,QAAO,OAAO,QAAoC;AACtD,UAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,UAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,QAAI,QAAQ;AACV,YAAM,YACJ,IAAI,QAAQ,IAAI,uBAAuB,KACvC,IAAI,QAAQ,IAAI,uBAAuB,KACvC;AACF,YAAM,aAAa,iBAAiB,MAAM;AAC1C,UAAI,CAAC,aAAa,CAAC,gBAAgB,MAAM,WAAW,UAAU,GAAG;AAC/D,eAAO,KAAK,KAAK,EAAE,OAAO,oBAAoB,CAAC;AAAA,MACjD;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,IAAI;AAAA,IAC3B,QAAQ;AACN,aAAO,KAAK,KAAK,EAAE,OAAO,oBAAoB,CAAC;AAAA,IACjD;AAEA,UAAM,OACJ,OAAO,SAAS,MAAM,SAAS,WAAW,QAAQ,KAAK,OAAO;AAEhE,oCAAc,gBAAgB;AAC9B,oCAAc,qBAAqB;AACnC,QAAI,KAAM,iCAAc,kBAAkB,IAAI,EAAE;AAEhD,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,cAAM,KAAK,QAAQ,OAAO;AAAA,MAC5B,SAAS,KAAK;AACZ,gBAAQ,MAAM,0CAA0C,GAAG;AAAA,MAC7D;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,EAAE,IAAI,MAAM,aAAa,QAAQ,KAAK,CAAC;AAAA,EAC1D;AAEA,SAAO,EAAE,MAAAA,MAAK;AAChB;AAEO,IAAM,EAAE,KAAK,IAAI,qBAAqB;","names":["POST"]}
|
package/dist/next/webhook.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { b as WebhookPayload } from '../types-CoPD0ejB.cjs';
|
|
2
2
|
|
|
3
3
|
type WebhookHandlerOptions = {
|
|
4
|
-
|
|
4
|
+
apiKey?: string;
|
|
5
5
|
onEvent?: (payload: WebhookPayload) => void | Promise<void>;
|
|
6
6
|
};
|
|
7
7
|
declare function createWebhookHandler(opts?: WebhookHandlerOptions): {
|
package/dist/next/webhook.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { b as WebhookPayload } from '../types-CoPD0ejB.js';
|
|
2
2
|
|
|
3
3
|
type WebhookHandlerOptions = {
|
|
4
|
-
|
|
4
|
+
apiKey?: string;
|
|
5
5
|
onEvent?: (payload: WebhookPayload) => void | Promise<void>;
|
|
6
6
|
};
|
|
7
7
|
declare function createWebhookHandler(opts?: WebhookHandlerOptions): {
|
package/dist/next/webhook.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/next/webhook.ts
|
|
2
2
|
import { revalidateTag } from "next/cache";
|
|
3
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
4
|
-
var
|
|
5
|
-
|
|
3
|
+
import { createHash, createHmac, timingSafeEqual } from "crypto";
|
|
4
|
+
var deriveSigningKey = (apiKey) => createHash("sha256").update(apiKey).digest("hex");
|
|
5
|
+
var verifySignature = (body, signature, signingKey) => {
|
|
6
|
+
const expected = createHmac("sha256", signingKey).update(body).digest("hex");
|
|
6
7
|
const provided = signature.trim();
|
|
7
8
|
if (expected.length !== provided.length) return false;
|
|
8
9
|
try {
|
|
@@ -17,11 +18,12 @@ var json = (status, body) => new Response(JSON.stringify(body), {
|
|
|
17
18
|
});
|
|
18
19
|
function createWebhookHandler(opts = {}) {
|
|
19
20
|
const POST2 = async (req) => {
|
|
20
|
-
const
|
|
21
|
+
const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;
|
|
21
22
|
const body = await req.text();
|
|
22
|
-
if (
|
|
23
|
+
if (apiKey) {
|
|
23
24
|
const signature = req.headers.get("x-chaingrow-signature") ?? req.headers.get("X-ChainGrow-Signature") ?? "";
|
|
24
|
-
|
|
25
|
+
const signingKey = deriveSigningKey(apiKey);
|
|
26
|
+
if (!signature || !verifySignature(body, signature, signingKey)) {
|
|
25
27
|
return json(401, { error: "Invalid signature" });
|
|
26
28
|
}
|
|
27
29
|
}
|
package/dist/next/webhook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/webhook.ts"],"sourcesContent":["import { revalidateTag } from 'next/cache';\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { WebhookPayload } from '../types';\n\nexport type WebhookHandlerOptions = {\n
|
|
1
|
+
{"version":3,"sources":["../../src/next/webhook.ts"],"sourcesContent":["import { revalidateTag } from 'next/cache';\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\nimport type { WebhookPayload } from '../types';\n\nexport type WebhookHandlerOptions = {\n apiKey?: string;\n onEvent?: (payload: WebhookPayload) => void | Promise<void>;\n};\n\n// Backend signs webhooks with sha256(apiKey) — the same hash it stores for auth.\n// We derive it here so customers only need CHAINGROW_API_KEY in their env.\nconst deriveSigningKey = (apiKey: string) =>\n createHash('sha256').update(apiKey).digest('hex');\n\nconst verifySignature = (\n body: string,\n signature: string,\n signingKey: string,\n) => {\n const expected = createHmac('sha256', signingKey).update(body).digest('hex');\n const provided = signature.trim();\n if (expected.length !== provided.length) return false;\n try {\n return timingSafeEqual(Buffer.from(expected), Buffer.from(provided));\n } catch {\n return false;\n }\n};\n\nconst json = (status: number, body: Record<string, unknown>) =>\n new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n\nexport function createWebhookHandler(opts: WebhookHandlerOptions = {}) {\n const POST = async (req: Request): Promise<Response> => {\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n const body = await req.text();\n\n if (apiKey) {\n const signature =\n req.headers.get('x-chaingrow-signature') ??\n req.headers.get('X-ChainGrow-Signature') ??\n '';\n const signingKey = deriveSigningKey(apiKey);\n if (!signature || !verifySignature(body, signature, signingKey)) {\n return json(401, { error: 'Invalid signature' });\n }\n }\n\n let payload: WebhookPayload;\n try {\n payload = JSON.parse(body) as WebhookPayload;\n } catch {\n return json(400, { error: 'Invalid JSON body' });\n }\n\n const slug =\n typeof payload?.blog?.slug === 'string' ? payload.blog.slug : null;\n\n revalidateTag('chaingrow:blog');\n revalidateTag('chaingrow:blog:list');\n if (slug) revalidateTag(`chaingrow:blog:${slug}`);\n\n if (opts.onEvent) {\n try {\n await opts.onEvent(payload);\n } catch (err) {\n console.error('chaingrow-blog onEvent handler failed:', err);\n }\n }\n\n return json(200, { ok: true, revalidated: slug ?? null });\n };\n\n return { POST };\n}\n\nexport const { POST } = createWebhookHandler();\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,SAAS,YAAY,YAAY,uBAAuB;AAUxD,IAAM,mBAAmB,CAAC,WACxB,WAAW,QAAQ,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK;AAElD,IAAM,kBAAkB,CACtB,MACA,WACA,eACG;AACH,QAAM,WAAW,WAAW,UAAU,UAAU,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC3E,QAAM,WAAW,UAAU,KAAK;AAChC,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAChD,MAAI;AACF,WAAO,gBAAgB,OAAO,KAAK,QAAQ,GAAG,OAAO,KAAK,QAAQ,CAAC;AAAA,EACrE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,OAAO,CAAC,QAAgB,SAC5B,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,EACjC;AAAA,EACA,SAAS,EAAE,gBAAgB,mBAAmB;AAChD,CAAC;AAEI,SAAS,qBAAqB,OAA8B,CAAC,GAAG;AACrE,QAAMA,QAAO,OAAO,QAAoC;AACtD,UAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,UAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,QAAI,QAAQ;AACV,YAAM,YACJ,IAAI,QAAQ,IAAI,uBAAuB,KACvC,IAAI,QAAQ,IAAI,uBAAuB,KACvC;AACF,YAAM,aAAa,iBAAiB,MAAM;AAC1C,UAAI,CAAC,aAAa,CAAC,gBAAgB,MAAM,WAAW,UAAU,GAAG;AAC/D,eAAO,KAAK,KAAK,EAAE,OAAO,oBAAoB,CAAC;AAAA,MACjD;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,IAAI;AAAA,IAC3B,QAAQ;AACN,aAAO,KAAK,KAAK,EAAE,OAAO,oBAAoB,CAAC;AAAA,IACjD;AAEA,UAAM,OACJ,OAAO,SAAS,MAAM,SAAS,WAAW,QAAQ,KAAK,OAAO;AAEhE,kBAAc,gBAAgB;AAC9B,kBAAc,qBAAqB;AACnC,QAAI,KAAM,eAAc,kBAAkB,IAAI,EAAE;AAEhD,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,cAAM,KAAK,QAAQ,OAAO;AAAA,MAC5B,SAAS,KAAK;AACZ,gBAAQ,MAAM,0CAA0C,GAAG;AAAA,MAC7D;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,EAAE,IAAI,MAAM,aAAa,QAAQ,KAAK,CAAC;AAAA,EAC1D;AAEA,SAAO,EAAE,MAAAA,MAAK;AAChB;AAEO,IAAM,EAAE,KAAK,IAAI,qBAAqB;","names":["POST"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const res = await doFetch(`${baseUrl}${path}`, {\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${init?.method ?? 'GET'} ${path} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) return [];\n const blogs = response.data ?? [];\n if (listOpts.language) {\n return blogs.filter((b) => b.language === listOpts.language);\n }\n return blogs;\n },\n };\n}\n"],"mappings":";AAmBO,SAAS,aAAa,MAAsC;AACjE,QAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,EAAE;AAC7C,QAAM,UAAU,KAAK,SAAS;AAE9B,QAAM,UAAU,OACd,MACA,SACsB;AACtB,UAAM,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,IAAI;AAAA,MAC7C,GAAG;AAAA,MACH,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,QAAQ;AAAA,QACR,GAAI,MAAM,WAAW,CAAC;AAAA,MACxB;AAAA,IACF,CAAC;AACD,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,mBAAmB,MAAM,UAAU,KAAK,IAAI,IAAI,YAAY,IAAI,MAAM;AAAA,MACxE;AAAA,IACF;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,UAAU;AAC5B,YAAM,KAAK,WAAW,aAAa,mBAAmB,QAAQ,CAAC,KAAK;AACpE,aAAO;AAAA,QACL,yBAAyB,mBAAmB,IAAI,CAAC,GAAG,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,IAEA,MAAM,YAAY,IAAI;AACpB,aAAO,QAAc,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,IAChE;AAAA,IAEA,MAAM,UAAU,WAAW,CAAC,GAAG;AAC7B,YAAM,SAAS,IAAI,gBAAgB;AACnC,UAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,SAAS,MAAM;AACzD,YAAM,KAAK,OAAO,SAAS;AAC3B,YAAM,WAAW,MAAM;AAAA,QACrB,gBAAgB,KAAK,IAAI,EAAE,KAAK,EAAE;AAAA,MACpC;AACA,UAAI,CAAC,SAAU,QAAO,CAAC;AACvB,YAAM,QAAQ,SAAS,QAAQ,CAAC;AAChC,UAAI,SAAS,UAAU;AACrB,eAAO,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,QAAQ;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|