chaingrow-blog 0.2.2 → 0.7.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.
@@ -0,0 +1,114 @@
1
+ /** Inline formatting marks. Unknown marks should be dropped. */
2
+ type Mark = {
3
+ type: 'bold';
4
+ } | {
5
+ type: 'italic';
6
+ } | {
7
+ type: 'strike';
8
+ } | {
9
+ type: 'code';
10
+ } | {
11
+ type: 'link';
12
+ href: string;
13
+ };
14
+ /** A text run within a block's content. */
15
+ type Inline = {
16
+ text: string;
17
+ marks?: Mark[];
18
+ };
19
+ /** A list item. Content is a Block[] so items can contain paragraphs
20
+ * and/or nested lists (the only shapes TipTap actually emits inside
21
+ * list items). */
22
+ type ListItem = {
23
+ content: Block[];
24
+ };
25
+ /**
26
+ * Structured blog block. Maps one-to-one with TipTap/ProseMirror
27
+ * nodes — see `@chaingrow/contracts` for the canonical shape.
28
+ *
29
+ * Note: this is a MAJOR breaking change vs SDK v0.2.x. Consumers with
30
+ * custom `components` override maps must update keys from
31
+ * `list`/`quote`/`code`/`image_placeholder` to
32
+ * `bulletList`/`orderedList`/`blockquote`/`codeBlock`/`image`/`divider`.
33
+ */
34
+ type Block = {
35
+ type: 'heading';
36
+ level: 1 | 2 | 3;
37
+ content: Inline[];
38
+ } | {
39
+ type: 'paragraph';
40
+ content: Inline[];
41
+ } | {
42
+ type: 'bulletList';
43
+ items: ListItem[];
44
+ } | {
45
+ type: 'orderedList';
46
+ items: ListItem[];
47
+ } | {
48
+ type: 'blockquote';
49
+ content: Inline[];
50
+ } | {
51
+ type: 'codeBlock';
52
+ language?: string;
53
+ code: string;
54
+ } | {
55
+ type: 'image';
56
+ src: string;
57
+ alt: string;
58
+ caption?: string;
59
+ width?: number;
60
+ height?: number;
61
+ mediaId?: string;
62
+ } | {
63
+ type: 'divider';
64
+ };
65
+ type BlockType = Block['type'];
66
+ type FaqItem = {
67
+ question: string;
68
+ answer: string;
69
+ };
70
+ type SocialPost = {
71
+ provider: string;
72
+ content: string;
73
+ social_title: string | null;
74
+ };
75
+ /** An image or video attached to a blog. The SDK distributes them
76
+ * deterministically across the body — see `<BlogRenderer>`. */
77
+ type EmbeddedMedia = {
78
+ id: string;
79
+ blog_id: string | null;
80
+ media_url: string;
81
+ media_type: 'IMAGE' | 'VIDEO';
82
+ model_name: string;
83
+ prompt: string;
84
+ created_at: string;
85
+ };
86
+ type Blog = {
87
+ id: string;
88
+ slug: string;
89
+ language: string;
90
+ title: string;
91
+ description: string | null;
92
+ blocks: Block[];
93
+ faq: FaqItem[];
94
+ og_image: string | null;
95
+ image_url: string | null;
96
+ video_url: string | null;
97
+ video_prompt: string | null;
98
+ structured_data: Record<string, unknown> | null;
99
+ published_at: string;
100
+ social_posts: SocialPost[];
101
+ /** Images and videos attached to this blog, in the order they were linked.
102
+ * `<BlogRenderer>` distributes these across `blocks` at positions
103
+ * `floor(i * blocks.length / generatedMedia.length)` — first at the
104
+ * top of the body, the rest spread evenly. */
105
+ generatedMedia?: EmbeddedMedia[];
106
+ };
107
+ type WebhookEvent = 'blog.created' | 'blog.updated' | 'blog.published' | 'blog.unpublished' | 'blog.deleted';
108
+ type WebhookPayload = {
109
+ event: WebhookEvent;
110
+ timestamp: string;
111
+ blog: Blog & Record<string, unknown>;
112
+ };
113
+
114
+ export type { Blog as B, EmbeddedMedia as E, FaqItem as F, Inline as I, ListItem as L, Mark as M, SocialPost as S, WebhookEvent as W, Block as a, BlockType as b, WebhookPayload as c };
@@ -0,0 +1,114 @@
1
+ /** Inline formatting marks. Unknown marks should be dropped. */
2
+ type Mark = {
3
+ type: 'bold';
4
+ } | {
5
+ type: 'italic';
6
+ } | {
7
+ type: 'strike';
8
+ } | {
9
+ type: 'code';
10
+ } | {
11
+ type: 'link';
12
+ href: string;
13
+ };
14
+ /** A text run within a block's content. */
15
+ type Inline = {
16
+ text: string;
17
+ marks?: Mark[];
18
+ };
19
+ /** A list item. Content is a Block[] so items can contain paragraphs
20
+ * and/or nested lists (the only shapes TipTap actually emits inside
21
+ * list items). */
22
+ type ListItem = {
23
+ content: Block[];
24
+ };
25
+ /**
26
+ * Structured blog block. Maps one-to-one with TipTap/ProseMirror
27
+ * nodes — see `@chaingrow/contracts` for the canonical shape.
28
+ *
29
+ * Note: this is a MAJOR breaking change vs SDK v0.2.x. Consumers with
30
+ * custom `components` override maps must update keys from
31
+ * `list`/`quote`/`code`/`image_placeholder` to
32
+ * `bulletList`/`orderedList`/`blockquote`/`codeBlock`/`image`/`divider`.
33
+ */
34
+ type Block = {
35
+ type: 'heading';
36
+ level: 1 | 2 | 3;
37
+ content: Inline[];
38
+ } | {
39
+ type: 'paragraph';
40
+ content: Inline[];
41
+ } | {
42
+ type: 'bulletList';
43
+ items: ListItem[];
44
+ } | {
45
+ type: 'orderedList';
46
+ items: ListItem[];
47
+ } | {
48
+ type: 'blockquote';
49
+ content: Inline[];
50
+ } | {
51
+ type: 'codeBlock';
52
+ language?: string;
53
+ code: string;
54
+ } | {
55
+ type: 'image';
56
+ src: string;
57
+ alt: string;
58
+ caption?: string;
59
+ width?: number;
60
+ height?: number;
61
+ mediaId?: string;
62
+ } | {
63
+ type: 'divider';
64
+ };
65
+ type BlockType = Block['type'];
66
+ type FaqItem = {
67
+ question: string;
68
+ answer: string;
69
+ };
70
+ type SocialPost = {
71
+ provider: string;
72
+ content: string;
73
+ social_title: string | null;
74
+ };
75
+ /** An image or video attached to a blog. The SDK distributes them
76
+ * deterministically across the body — see `<BlogRenderer>`. */
77
+ type EmbeddedMedia = {
78
+ id: string;
79
+ blog_id: string | null;
80
+ media_url: string;
81
+ media_type: 'IMAGE' | 'VIDEO';
82
+ model_name: string;
83
+ prompt: string;
84
+ created_at: string;
85
+ };
86
+ type Blog = {
87
+ id: string;
88
+ slug: string;
89
+ language: string;
90
+ title: string;
91
+ description: string | null;
92
+ blocks: Block[];
93
+ faq: FaqItem[];
94
+ og_image: string | null;
95
+ image_url: string | null;
96
+ video_url: string | null;
97
+ video_prompt: string | null;
98
+ structured_data: Record<string, unknown> | null;
99
+ published_at: string;
100
+ social_posts: SocialPost[];
101
+ /** Images and videos attached to this blog, in the order they were linked.
102
+ * `<BlogRenderer>` distributes these across `blocks` at positions
103
+ * `floor(i * blocks.length / generatedMedia.length)` — first at the
104
+ * top of the body, the rest spread evenly. */
105
+ generatedMedia?: EmbeddedMedia[];
106
+ };
107
+ type WebhookEvent = 'blog.created' | 'blog.updated' | 'blog.published' | 'blog.unpublished' | 'blog.deleted';
108
+ type WebhookPayload = {
109
+ event: WebhookEvent;
110
+ timestamp: string;
111
+ blog: Blog & Record<string, unknown>;
112
+ };
113
+
114
+ export type { Blog as B, EmbeddedMedia as E, FaqItem as F, Inline as I, ListItem as L, Mark as M, SocialPost as S, WebhookEvent as W, Block as a, BlockType as b, WebhookPayload as c };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaingrow-blog",
3
- "version": "0.2.2",
3
+ "version": "0.7.0",
4
4
  "description": "Drop-in Next.js SDK for rendering blogs published from ChainGrow.",
5
5
  "license": "MIT",
6
6
  "author": "ChainGrow",
@@ -1,74 +0,0 @@
1
- // src/client.ts
2
- function createClient(opts) {
3
- const baseUrl = opts.apiUrl.replace(/\/$/, "");
4
- const doFetch = opts.fetch ?? fetch;
5
- const request = async (path, init) => {
6
- const url = `${baseUrl}${path}`;
7
- const method = init?.method ?? "GET";
8
- const buildInit = () => ({
9
- ...init,
10
- headers: {
11
- "X-API-Key": opts.apiKey,
12
- Accept: "application/json",
13
- ...init?.headers ?? {}
14
- }
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
- }
36
- if (res.status === 404) return null;
37
- if (!res.ok) {
38
- throw new Error(
39
- `chaingrow-blog: ${method} ${url} failed (${res.status})`
40
- );
41
- }
42
- return await res.json();
43
- };
44
- return {
45
- async getBlog(slug, language) {
46
- const qs = language ? `?language=${encodeURIComponent(language)}` : "";
47
- return request(
48
- `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`
49
- );
50
- },
51
- async getBlogById(id) {
52
- return request(`/api/v1/blogs/${encodeURIComponent(id)}`);
53
- },
54
- async listBlogs(listOpts = {}) {
55
- const params = new URLSearchParams();
56
- if (listOpts.status) params.set("status", listOpts.status);
57
- const qs = params.toString();
58
- const response = await request(
59
- `/api/v1/blogs${qs ? `?${qs}` : ""}`
60
- );
61
- if (!response) return [];
62
- const blogs = response.data ?? [];
63
- if (listOpts.language) {
64
- return blogs.filter((b) => b.language === listOpts.language);
65
- }
66
- return blogs;
67
- }
68
- };
69
- }
70
-
71
- export {
72
- createClient
73
- };
74
- //# sourceMappingURL=chunk-OTLGKSYD.js.map
@@ -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 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":[]}
@@ -1,53 +0,0 @@
1
- type Block = {
2
- type: 'heading';
3
- level?: number;
4
- content: string;
5
- } | {
6
- type: 'paragraph';
7
- content: string;
8
- } | {
9
- type: 'list';
10
- content: string;
11
- } | {
12
- type: 'quote';
13
- content: string;
14
- } | {
15
- type: 'code';
16
- content: string;
17
- } | {
18
- type: 'image_placeholder';
19
- content: string;
20
- };
21
- type FaqItem = {
22
- question: string;
23
- answer: string;
24
- };
25
- type SocialPost = {
26
- provider: string;
27
- content: string;
28
- social_title: string | null;
29
- };
30
- type Blog = {
31
- id: string;
32
- slug: string;
33
- language: string;
34
- title: string;
35
- description: string | null;
36
- blocks: Block[];
37
- faq: FaqItem[];
38
- og_image: string | null;
39
- image_url: string | null;
40
- video_url: string | null;
41
- video_prompt: string | null;
42
- structured_data: Record<string, unknown> | null;
43
- published_at: string;
44
- social_posts: SocialPost[];
45
- };
46
- type WebhookEvent = 'blog.created' | 'blog.updated' | 'blog.published';
47
- type WebhookPayload = {
48
- event: WebhookEvent;
49
- timestamp: string;
50
- blog: Blog & Record<string, unknown>;
51
- };
52
-
53
- export type { Blog as B, FaqItem as F, SocialPost as S, WebhookEvent as W, Block as a, WebhookPayload as b };
@@ -1,53 +0,0 @@
1
- type Block = {
2
- type: 'heading';
3
- level?: number;
4
- content: string;
5
- } | {
6
- type: 'paragraph';
7
- content: string;
8
- } | {
9
- type: 'list';
10
- content: string;
11
- } | {
12
- type: 'quote';
13
- content: string;
14
- } | {
15
- type: 'code';
16
- content: string;
17
- } | {
18
- type: 'image_placeholder';
19
- content: string;
20
- };
21
- type FaqItem = {
22
- question: string;
23
- answer: string;
24
- };
25
- type SocialPost = {
26
- provider: string;
27
- content: string;
28
- social_title: string | null;
29
- };
30
- type Blog = {
31
- id: string;
32
- slug: string;
33
- language: string;
34
- title: string;
35
- description: string | null;
36
- blocks: Block[];
37
- faq: FaqItem[];
38
- og_image: string | null;
39
- image_url: string | null;
40
- video_url: string | null;
41
- video_prompt: string | null;
42
- structured_data: Record<string, unknown> | null;
43
- published_at: string;
44
- social_posts: SocialPost[];
45
- };
46
- type WebhookEvent = 'blog.created' | 'blog.updated' | 'blog.published';
47
- type WebhookPayload = {
48
- event: WebhookEvent;
49
- timestamp: string;
50
- blog: Blog & Record<string, unknown>;
51
- };
52
-
53
- export type { Blog as B, FaqItem as F, SocialPost as S, WebhookEvent as W, Block as a, WebhookPayload as b };