@dispatchcms/next 0.0.2 → 0.0.3

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @dispatchcms/next@0.0.2 build /Users/jamescalmus/Documents/dispatch/packages/next
3
+ > @dispatchcms/next@0.0.3 build /Users/jamescalmus/Documents/dispatch/packages/next
4
4
  > tsup
5
5
 
6
6
  CLI Building entry: src/index.ts
@@ -11,13 +11,13 @@
11
11
  CLI Cleaning output folder
12
12
  CJS Build start
13
13
  ESM Build start
14
- CJS dist/index.js 3.81 KB
15
- CJS dist/index.js.map 5.37 KB
16
- CJS ⚡️ Build success in 4ms
17
- ESM dist/index.mjs 2.72 KB
18
- ESM dist/index.mjs.map 5.17 KB
19
- ESM ⚡️ Build success in 4ms
14
+ CJS dist/index.js 4.25 KB
15
+ CJS dist/index.js.map 6.31 KB
16
+ CJS ⚡️ Build success in 5ms
17
+ ESM dist/index.mjs 3.11 KB
18
+ ESM dist/index.mjs.map 6.06 KB
19
+ ESM ⚡️ Build success in 5ms
20
20
  DTS Build start
21
- DTS ⚡️ Build success in 314ms
22
- DTS dist/index.d.ts 670.00 B
23
- DTS dist/index.d.mts 670.00 B
21
+ DTS ⚡️ Build success in 317ms
22
+ DTS dist/index.d.ts 938.00 B
23
+ DTS dist/index.d.mts 938.00 B
package/README.md CHANGED
@@ -54,6 +54,13 @@ Returns a single published post by slug, or `null` if not found. If the full lis
54
54
  - **Returns:** `Promise<Post | null>`
55
55
  - **Optional:** pass `siteKey` as the second argument to override the configured site.
56
56
 
57
+ ### `getPostByPreviewToken(token)`
58
+
59
+ Returns a single post by its preview token (draft or published). Use this in your app’s **preview** route so editors can open a shareable link and see the post as it will appear. No site key is required; the token is the secret.
60
+
61
+ - **Returns:** `Promise<Post | null>`
62
+ - **Example:** Implement a route at `/preview` (or `/blog/preview`) that reads `token` from the query and renders the post with the same layout as your live post page (see below).
63
+
57
64
  ## Types
58
65
 
59
66
  Exportable types:
@@ -106,3 +113,34 @@ export default async function PostPage({ params }: { params: { slug: string } })
106
113
  ```
107
114
 
108
115
  The CMS API only returns **published** posts; drafts are not included.
116
+
117
+ ## Preview route
118
+
119
+ To support public preview links from the CMS (e.g. `https://yoursite.com/preview?token=xxx`), add a preview page that fetches by token and renders the post:
120
+
121
+ ```tsx
122
+ // app/preview/page.tsx
123
+ import { getPostByPreviewToken } from "@dispatchcms/next";
124
+ import { notFound } from "next/navigation";
125
+
126
+ export default async function PreviewPage({
127
+ searchParams,
128
+ }: {
129
+ searchParams: Promise<{ token?: string }>;
130
+ }) {
131
+ const { token } = await searchParams;
132
+ if (!token) notFound();
133
+ const post = await getPostByPreviewToken(token);
134
+ if (!post) notFound();
135
+ return (
136
+ <article>
137
+ <p className="text-sm text-muted-foreground">Preview</p>
138
+ <h1>{post.title}</h1>
139
+ {post.excerpt && <p>{post.excerpt}</p>}
140
+ {/* Render post.content the same way as your live post page */}
141
+ </article>
142
+ );
143
+ }
144
+ ```
145
+
146
+ In the CMS, set **Site URL** in Site settings to your site’s base URL (e.g. `https://yoursite.com`). The preview link will use that URL plus `/preview?token=...`.
package/dist/index.d.mts CHANGED
@@ -20,5 +20,10 @@ declare function getConfig(): {
20
20
  };
21
21
  declare function getPosts(siteKey?: string): Promise<Post[]>;
22
22
  declare function getPost(slug: string, siteKey?: string): Promise<Post | null>;
23
+ /**
24
+ * Fetch a single post by its preview token (for preview/draft pages).
25
+ * No site key required. Use this in your app's preview route (e.g. /preview?token=...).
26
+ */
27
+ declare function getPostByPreviewToken(token: string): Promise<Post | null>;
23
28
 
24
- export { type DispatchConfig, type Post, getConfig, getPost, getPosts, initDispatch };
29
+ export { type DispatchConfig, type Post, getConfig, getPost, getPostByPreviewToken, getPosts, initDispatch };
package/dist/index.d.ts CHANGED
@@ -20,5 +20,10 @@ declare function getConfig(): {
20
20
  };
21
21
  declare function getPosts(siteKey?: string): Promise<Post[]>;
22
22
  declare function getPost(slug: string, siteKey?: string): Promise<Post | null>;
23
+ /**
24
+ * Fetch a single post by its preview token (for preview/draft pages).
25
+ * No site key required. Use this in your app's preview route (e.g. /preview?token=...).
26
+ */
27
+ declare function getPostByPreviewToken(token: string): Promise<Post | null>;
23
28
 
24
- export { type DispatchConfig, type Post, getConfig, getPost, getPosts, initDispatch };
29
+ export { type DispatchConfig, type Post, getConfig, getPost, getPostByPreviewToken, getPosts, initDispatch };
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  getConfig: () => getConfig,
24
24
  getPost: () => getPost,
25
+ getPostByPreviewToken: () => getPostByPreviewToken,
25
26
  getPosts: () => getPosts,
26
27
  initDispatch: () => initDispatch
27
28
  });
@@ -106,10 +107,21 @@ async function getPost(slug, siteKey) {
106
107
  postBySlugBySiteKey.get(resolvedKey).set(slug, post);
107
108
  return post;
108
109
  }
110
+ async function getPostByPreviewToken(token) {
111
+ if (!token?.trim()) return null;
112
+ const url = `${DISPATCH_API_BASE}/api/preview?token=${encodeURIComponent(token.trim())}`;
113
+ const res = await fetch(url);
114
+ if (res.status === 404) return null;
115
+ if (!res.ok) {
116
+ throw new Error(`Failed to fetch preview: ${res.status} ${res.statusText}`);
117
+ }
118
+ return await res.json();
119
+ }
109
120
  // Annotate the CommonJS export names for ESM import in node:
110
121
  0 && (module.exports = {
111
122
  getConfig,
112
123
  getPost,
124
+ getPostByPreviewToken,
113
125
  getPosts,
114
126
  initDispatch
115
127
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export type { Post, DispatchConfig } from \"./client\";\nexport { initDispatch, getConfig, getPosts, getPost } from \"./client\";\n","export type Post = {\n id: string;\n created_at: string;\n published_at: string;\n updated_at: string;\n site_id: string;\n title: string;\n slug: string;\n content: unknown;\n excerpt: string | null;\n featured_image: string | null;\n published: boolean;\n};\n\nconst DISPATCH_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport type DispatchConfig = {\n siteKey: string;\n};\n\nlet config: DispatchConfig | null = null;\n\nconst allPostsBySiteKey = new Map<string, Post[]>();\nconst postBySlugBySiteKey = new Map<string, Map<string, Post>>();\nconst fullListFetchedForSiteKey = new Set<string>();\n\nexport function initDispatch(options: DispatchConfig): void {\n config = { siteKey: options.siteKey };\n}\n\nexport function getConfig(): { siteKey: string } {\n const envKey = typeof process !== \"undefined\" ? process.env?.NEXT_PUBLIC_DISPATCH_SITE_KEY : undefined;\n const siteKey = config?.siteKey ?? (typeof envKey === \"string\" ? envKey : undefined);\n if (!siteKey || typeof siteKey !== \"string\") {\n throw new Error(\"Dispatch site key is required. Call initDispatch({ siteKey }) or set NEXT_PUBLIC_DISPATCH_SITE_KEY.\");\n }\n return { siteKey };\n}\n\nexport async function getPosts(siteKey?: string): Promise<Post[]> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n const cached = allPostsBySiteKey.get(resolvedKey);\n if (cached !== undefined) {\n return cached;\n }\n const url = `${DISPATCH_API_BASE}/api/posts`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);\n }\n const posts = (await res.json()) as Post[];\n allPostsBySiteKey.set(resolvedKey, posts);\n fullListFetchedForSiteKey.add(resolvedKey);\n const bySlug = new Map<string, Post>();\n for (const post of posts) {\n bySlug.set(post.slug, post);\n }\n postBySlugBySiteKey.set(resolvedKey, bySlug);\n return posts;\n}\n\nexport async function getPost(slug: string, siteKey?: string): Promise<Post | null> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n if (fullListFetchedForSiteKey.has(resolvedKey)) {\n const list = allPostsBySiteKey.get(resolvedKey);\n if (list) {\n const post = list.find((p) => p.slug === slug);\n return post ?? null;\n }\n }\n const bySlug = postBySlugBySiteKey.get(resolvedKey);\n const cachedPost = bySlug?.get(slug);\n if (cachedPost !== undefined) {\n return cachedPost;\n }\n const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (res.status === 404) {\n return null;\n }\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);\n }\n const post = (await res.json()) as Post;\n if (!postBySlugBySiteKey.has(resolvedKey)) {\n postBySlugBySiteKey.set(resolvedKey, new Map());\n }\n postBySlugBySiteKey.get(resolvedKey)!.set(slug, post);\n return post;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcA,IAAM,oBAAoB;AAM1B,IAAI,SAAgC;AAEpC,IAAM,oBAAoB,oBAAI,IAAoB;AAClD,IAAM,sBAAsB,oBAAI,IAA+B;AAC/D,IAAM,4BAA4B,oBAAI,IAAY;AAE3C,SAAS,aAAa,SAA+B;AAC1D,WAAS,EAAE,SAAS,QAAQ,QAAQ;AACtC;AAEO,SAAS,YAAiC;AAC/C,QAAM,SAAS,OAAO,YAAY,cAAc,QAAQ,KAAK,gCAAgC;AAC7F,QAAM,UAAU,QAAQ,YAAY,OAAO,WAAW,WAAW,SAAS;AAC1E,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,qGAAqG;AAAA,EACvH;AACA,SAAO,EAAE,QAAQ;AACnB;AAEA,eAAsB,SAAS,SAAmC;AAChE,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU;AACnC,QAAM,cAAc,WAAW;AAC/B,QAAM,SAAS,kBAAkB,IAAI,WAAW;AAChD,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AACA,QAAM,MAAM,GAAG,iBAAiB;AAChC,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,SAAS,EAAE,cAAc,YAAY;AAAA,EACvC,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,UAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EAC1E;AACA,QAAM,QAAS,MAAM,IAAI,KAAK;AAC9B,oBAAkB,IAAI,aAAa,KAAK;AACxC,4BAA0B,IAAI,WAAW;AACzC,QAAM,SAAS,oBAAI,IAAkB;AACrC,aAAW,QAAQ,OAAO;AACxB,WAAO,IAAI,KAAK,MAAM,IAAI;AAAA,EAC5B;AACA,sBAAoB,IAAI,aAAa,MAAM;AAC3C,SAAO;AACT;AAEA,eAAsB,QAAQ,MAAc,SAAwC;AAClF,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU;AACnC,QAAM,cAAc,WAAW;AAC/B,MAAI,0BAA0B,IAAI,WAAW,GAAG;AAC9C,UAAM,OAAO,kBAAkB,IAAI,WAAW;AAC9C,QAAI,MAAM;AACR,YAAMA,QAAO,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC7C,aAAOA,SAAQ;AAAA,IACjB;AAAA,EACF;AACA,QAAM,SAAS,oBAAoB,IAAI,WAAW;AAClD,QAAM,aAAa,QAAQ,IAAI,IAAI;AACnC,MAAI,eAAe,QAAW;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,MAAM,GAAG,iBAAiB,cAAc,mBAAmB,IAAI,CAAC;AACtE,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,SAAS,EAAE,cAAc,YAAY;AAAA,EACvC,CAAC;AACD,MAAI,IAAI,WAAW,KAAK;AACtB,WAAO;AAAA,EACT;AACA,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,UAAM,IAAI,MAAM,yBAAyB,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EACzE;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,CAAC,oBAAoB,IAAI,WAAW,GAAG;AACzC,wBAAoB,IAAI,aAAa,oBAAI,IAAI,CAAC;AAAA,EAChD;AACA,sBAAoB,IAAI,WAAW,EAAG,IAAI,MAAM,IAAI;AACpD,SAAO;AACT;","names":["post"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export type { Post, DispatchConfig } from \"./client\";\nexport {\n initDispatch,\n getConfig,\n getPosts,\n getPost,\n getPostByPreviewToken,\n} from \"./client\";\n","export type Post = {\n id: string;\n created_at: string;\n published_at: string;\n updated_at: string;\n site_id: string;\n title: string;\n slug: string;\n content: unknown;\n excerpt: string | null;\n featured_image: string | null;\n published: boolean;\n};\n\nconst DISPATCH_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport type DispatchConfig = {\n siteKey: string;\n};\n\nlet config: DispatchConfig | null = null;\n\nconst allPostsBySiteKey = new Map<string, Post[]>();\nconst postBySlugBySiteKey = new Map<string, Map<string, Post>>();\nconst fullListFetchedForSiteKey = new Set<string>();\n\nexport function initDispatch(options: DispatchConfig): void {\n config = { siteKey: options.siteKey };\n}\n\nexport function getConfig(): { siteKey: string } {\n const envKey = typeof process !== \"undefined\" ? process.env?.NEXT_PUBLIC_DISPATCH_SITE_KEY : undefined;\n const siteKey = config?.siteKey ?? (typeof envKey === \"string\" ? envKey : undefined);\n if (!siteKey || typeof siteKey !== \"string\") {\n throw new Error(\"Dispatch site key is required. Call initDispatch({ siteKey }) or set NEXT_PUBLIC_DISPATCH_SITE_KEY.\");\n }\n return { siteKey };\n}\n\nexport async function getPosts(siteKey?: string): Promise<Post[]> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n const cached = allPostsBySiteKey.get(resolvedKey);\n if (cached !== undefined) {\n return cached;\n }\n const url = `${DISPATCH_API_BASE}/api/posts`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);\n }\n const posts = (await res.json()) as Post[];\n allPostsBySiteKey.set(resolvedKey, posts);\n fullListFetchedForSiteKey.add(resolvedKey);\n const bySlug = new Map<string, Post>();\n for (const post of posts) {\n bySlug.set(post.slug, post);\n }\n postBySlugBySiteKey.set(resolvedKey, bySlug);\n return posts;\n}\n\nexport async function getPost(slug: string, siteKey?: string): Promise<Post | null> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n if (fullListFetchedForSiteKey.has(resolvedKey)) {\n const list = allPostsBySiteKey.get(resolvedKey);\n if (list) {\n const post = list.find((p) => p.slug === slug);\n return post ?? null;\n }\n }\n const bySlug = postBySlugBySiteKey.get(resolvedKey);\n const cachedPost = bySlug?.get(slug);\n if (cachedPost !== undefined) {\n return cachedPost;\n }\n const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (res.status === 404) {\n return null;\n }\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);\n }\n const post = (await res.json()) as Post;\n if (!postBySlugBySiteKey.has(resolvedKey)) {\n postBySlugBySiteKey.set(resolvedKey, new Map());\n }\n postBySlugBySiteKey.get(resolvedKey)!.set(slug, post);\n return post;\n}\n\n/**\n * Fetch a single post by its preview token (for preview/draft pages).\n * No site key required. Use this in your app's preview route (e.g. /preview?token=...).\n */\nexport async function getPostByPreviewToken(token: string): Promise<Post | null> {\n if (!token?.trim()) return null;\n const url = `${DISPATCH_API_BASE}/api/preview?token=${encodeURIComponent(token.trim())}`;\n const res = await fetch(url);\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(`Failed to fetch preview: ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as Post;\n}\n"],"mappings":"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","names":["post"]}
package/dist/index.mjs CHANGED
@@ -77,9 +77,20 @@ async function getPost(slug, siteKey) {
77
77
  postBySlugBySiteKey.get(resolvedKey).set(slug, post);
78
78
  return post;
79
79
  }
80
+ async function getPostByPreviewToken(token) {
81
+ if (!token?.trim()) return null;
82
+ const url = `${DISPATCH_API_BASE}/api/preview?token=${encodeURIComponent(token.trim())}`;
83
+ const res = await fetch(url);
84
+ if (res.status === 404) return null;
85
+ if (!res.ok) {
86
+ throw new Error(`Failed to fetch preview: ${res.status} ${res.statusText}`);
87
+ }
88
+ return await res.json();
89
+ }
80
90
  export {
81
91
  getConfig,
82
92
  getPost,
93
+ getPostByPreviewToken,
83
94
  getPosts,
84
95
  initDispatch
85
96
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["export type Post = {\n id: string;\n created_at: string;\n published_at: string;\n updated_at: string;\n site_id: string;\n title: string;\n slug: string;\n content: unknown;\n excerpt: string | null;\n featured_image: string | null;\n published: boolean;\n};\n\nconst DISPATCH_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport type DispatchConfig = {\n siteKey: string;\n};\n\nlet config: DispatchConfig | null = null;\n\nconst allPostsBySiteKey = new Map<string, Post[]>();\nconst postBySlugBySiteKey = new Map<string, Map<string, Post>>();\nconst fullListFetchedForSiteKey = new Set<string>();\n\nexport function initDispatch(options: DispatchConfig): void {\n config = { siteKey: options.siteKey };\n}\n\nexport function getConfig(): { siteKey: string } {\n const envKey = typeof process !== \"undefined\" ? process.env?.NEXT_PUBLIC_DISPATCH_SITE_KEY : undefined;\n const siteKey = config?.siteKey ?? (typeof envKey === \"string\" ? envKey : undefined);\n if (!siteKey || typeof siteKey !== \"string\") {\n throw new Error(\"Dispatch site key is required. Call initDispatch({ siteKey }) or set NEXT_PUBLIC_DISPATCH_SITE_KEY.\");\n }\n return { siteKey };\n}\n\nexport async function getPosts(siteKey?: string): Promise<Post[]> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n const cached = allPostsBySiteKey.get(resolvedKey);\n if (cached !== undefined) {\n return cached;\n }\n const url = `${DISPATCH_API_BASE}/api/posts`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);\n }\n const posts = (await res.json()) as Post[];\n allPostsBySiteKey.set(resolvedKey, posts);\n fullListFetchedForSiteKey.add(resolvedKey);\n const bySlug = new Map<string, Post>();\n for (const post of posts) {\n bySlug.set(post.slug, post);\n }\n postBySlugBySiteKey.set(resolvedKey, bySlug);\n return posts;\n}\n\nexport async function getPost(slug: string, siteKey?: string): Promise<Post | null> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n if (fullListFetchedForSiteKey.has(resolvedKey)) {\n const list = allPostsBySiteKey.get(resolvedKey);\n if (list) {\n const post = list.find((p) => p.slug === slug);\n return post ?? null;\n }\n }\n const bySlug = postBySlugBySiteKey.get(resolvedKey);\n const cachedPost = bySlug?.get(slug);\n if (cachedPost !== undefined) {\n return cachedPost;\n }\n const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (res.status === 404) {\n return null;\n }\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);\n }\n const post = (await res.json()) as Post;\n if (!postBySlugBySiteKey.has(resolvedKey)) {\n postBySlugBySiteKey.set(resolvedKey, new Map());\n }\n postBySlugBySiteKey.get(resolvedKey)!.set(slug, post);\n return post;\n}\n"],"mappings":";AAcA,IAAM,oBAAoB;AAM1B,IAAI,SAAgC;AAEpC,IAAM,oBAAoB,oBAAI,IAAoB;AAClD,IAAM,sBAAsB,oBAAI,IAA+B;AAC/D,IAAM,4BAA4B,oBAAI,IAAY;AAE3C,SAAS,aAAa,SAA+B;AAC1D,WAAS,EAAE,SAAS,QAAQ,QAAQ;AACtC;AAEO,SAAS,YAAiC;AAC/C,QAAM,SAAS,OAAO,YAAY,cAAc,QAAQ,KAAK,gCAAgC;AAC7F,QAAM,UAAU,QAAQ,YAAY,OAAO,WAAW,WAAW,SAAS;AAC1E,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,qGAAqG;AAAA,EACvH;AACA,SAAO,EAAE,QAAQ;AACnB;AAEA,eAAsB,SAAS,SAAmC;AAChE,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU;AACnC,QAAM,cAAc,WAAW;AAC/B,QAAM,SAAS,kBAAkB,IAAI,WAAW;AAChD,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AACA,QAAM,MAAM,GAAG,iBAAiB;AAChC,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,SAAS,EAAE,cAAc,YAAY;AAAA,EACvC,CAAC;AACD,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,UAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EAC1E;AACA,QAAM,QAAS,MAAM,IAAI,KAAK;AAC9B,oBAAkB,IAAI,aAAa,KAAK;AACxC,4BAA0B,IAAI,WAAW;AACzC,QAAM,SAAS,oBAAI,IAAkB;AACrC,aAAW,QAAQ,OAAO;AACxB,WAAO,IAAI,KAAK,MAAM,IAAI;AAAA,EAC5B;AACA,sBAAoB,IAAI,aAAa,MAAM;AAC3C,SAAO;AACT;AAEA,eAAsB,QAAQ,MAAc,SAAwC;AAClF,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU;AACnC,QAAM,cAAc,WAAW;AAC/B,MAAI,0BAA0B,IAAI,WAAW,GAAG;AAC9C,UAAM,OAAO,kBAAkB,IAAI,WAAW;AAC9C,QAAI,MAAM;AACR,YAAMA,QAAO,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC7C,aAAOA,SAAQ;AAAA,IACjB;AAAA,EACF;AACA,QAAM,SAAS,oBAAoB,IAAI,WAAW;AAClD,QAAM,aAAa,QAAQ,IAAI,IAAI;AACnC,MAAI,eAAe,QAAW;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,MAAM,GAAG,iBAAiB,cAAc,mBAAmB,IAAI,CAAC;AACtE,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,SAAS,EAAE,cAAc,YAAY;AAAA,EACvC,CAAC;AACD,MAAI,IAAI,WAAW,KAAK;AACtB,WAAO;AAAA,EACT;AACA,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,UAAM,IAAI,MAAM,yBAAyB,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EACzE;AACA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,CAAC,oBAAoB,IAAI,WAAW,GAAG;AACzC,wBAAoB,IAAI,aAAa,oBAAI,IAAI,CAAC;AAAA,EAChD;AACA,sBAAoB,IAAI,WAAW,EAAG,IAAI,MAAM,IAAI;AACpD,SAAO;AACT;","names":["post"]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["export type Post = {\n id: string;\n created_at: string;\n published_at: string;\n updated_at: string;\n site_id: string;\n title: string;\n slug: string;\n content: unknown;\n excerpt: string | null;\n featured_image: string | null;\n published: boolean;\n};\n\nconst DISPATCH_API_BASE = \"https://dispatch-cms.vercel.app\";\n\nexport type DispatchConfig = {\n siteKey: string;\n};\n\nlet config: DispatchConfig | null = null;\n\nconst allPostsBySiteKey = new Map<string, Post[]>();\nconst postBySlugBySiteKey = new Map<string, Map<string, Post>>();\nconst fullListFetchedForSiteKey = new Set<string>();\n\nexport function initDispatch(options: DispatchConfig): void {\n config = { siteKey: options.siteKey };\n}\n\nexport function getConfig(): { siteKey: string } {\n const envKey = typeof process !== \"undefined\" ? process.env?.NEXT_PUBLIC_DISPATCH_SITE_KEY : undefined;\n const siteKey = config?.siteKey ?? (typeof envKey === \"string\" ? envKey : undefined);\n if (!siteKey || typeof siteKey !== \"string\") {\n throw new Error(\"Dispatch site key is required. Call initDispatch({ siteKey }) or set NEXT_PUBLIC_DISPATCH_SITE_KEY.\");\n }\n return { siteKey };\n}\n\nexport async function getPosts(siteKey?: string): Promise<Post[]> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n const cached = allPostsBySiteKey.get(resolvedKey);\n if (cached !== undefined) {\n return cached;\n }\n const url = `${DISPATCH_API_BASE}/api/posts`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);\n }\n const posts = (await res.json()) as Post[];\n allPostsBySiteKey.set(resolvedKey, posts);\n fullListFetchedForSiteKey.add(resolvedKey);\n const bySlug = new Map<string, Post>();\n for (const post of posts) {\n bySlug.set(post.slug, post);\n }\n postBySlugBySiteKey.set(resolvedKey, bySlug);\n return posts;\n}\n\nexport async function getPost(slug: string, siteKey?: string): Promise<Post | null> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\n if (fullListFetchedForSiteKey.has(resolvedKey)) {\n const list = allPostsBySiteKey.get(resolvedKey);\n if (list) {\n const post = list.find((p) => p.slug === slug);\n return post ?? null;\n }\n }\n const bySlug = postBySlugBySiteKey.get(resolvedKey);\n const cachedPost = bySlug?.get(slug);\n if (cachedPost !== undefined) {\n return cachedPost;\n }\n const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;\n const res = await fetch(url, {\n headers: { \"X-Site-Key\": resolvedKey },\n });\n if (res.status === 404) {\n return null;\n }\n if (!res.ok) {\n if (res.status === 401) {\n throw new Error(\"Invalid or missing site key\");\n }\n throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);\n }\n const post = (await res.json()) as Post;\n if (!postBySlugBySiteKey.has(resolvedKey)) {\n postBySlugBySiteKey.set(resolvedKey, new Map());\n }\n postBySlugBySiteKey.get(resolvedKey)!.set(slug, post);\n return post;\n}\n\n/**\n * Fetch a single post by its preview token (for preview/draft pages).\n * No site key required. Use this in your app's preview route (e.g. /preview?token=...).\n */\nexport async function getPostByPreviewToken(token: string): Promise<Post | null> {\n if (!token?.trim()) return null;\n const url = `${DISPATCH_API_BASE}/api/preview?token=${encodeURIComponent(token.trim())}`;\n const res = await fetch(url);\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(`Failed to fetch preview: ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as Post;\n}\n"],"mappings":"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","names":["post"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dispatchcms/next",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Fetch published posts from a Dispatch CMS in Next.js",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/client.ts CHANGED
@@ -100,3 +100,18 @@ export async function getPost(slug: string, siteKey?: string): Promise<Post | nu
100
100
  postBySlugBySiteKey.get(resolvedKey)!.set(slug, post);
101
101
  return post;
102
102
  }
103
+
104
+ /**
105
+ * Fetch a single post by its preview token (for preview/draft pages).
106
+ * No site key required. Use this in your app's preview route (e.g. /preview?token=...).
107
+ */
108
+ export async function getPostByPreviewToken(token: string): Promise<Post | null> {
109
+ if (!token?.trim()) return null;
110
+ const url = `${DISPATCH_API_BASE}/api/preview?token=${encodeURIComponent(token.trim())}`;
111
+ const res = await fetch(url);
112
+ if (res.status === 404) return null;
113
+ if (!res.ok) {
114
+ throw new Error(`Failed to fetch preview: ${res.status} ${res.statusText}`);
115
+ }
116
+ return (await res.json()) as Post;
117
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  export type { Post, DispatchConfig } from "./client";
2
- export { initDispatch, getConfig, getPosts, getPost } from "./client";
2
+ export {
3
+ initDispatch,
4
+ getConfig,
5
+ getPosts,
6
+ getPost,
7
+ getPostByPreviewToken,
8
+ } from "./client";