@dispatchcms/next 0.0.3 → 0.0.4

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.3 build /Users/jamescalmus/Documents/dispatch/packages/next
3
+ > @dispatchcms/next@0.0.4 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 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
14
+ ESM dist/index.mjs 2.04 KB
15
+ ESM dist/index.mjs.map 4.20 KB
16
+ ESM ⚡️ Build success in 7ms
17
+ CJS dist/index.js 3.18 KB
18
+ CJS dist/index.js.map 4.45 KB
19
+ CJS ⚡️ Build success in 7ms
20
20
  DTS Build start
21
- DTS ⚡️ Build success in 317ms
21
+ DTS ⚡️ Build success in 329ms
22
22
  DTS dist/index.d.ts 938.00 B
23
23
  DTS dist/index.d.mts 938.00 B
package/README.md CHANGED
@@ -36,20 +36,20 @@ You can omit `initDispatch` entirely if `NEXT_PUBLIC_DISPATCH_SITE_KEY` is set;
36
36
 
37
37
  ## Caching
38
38
 
39
- The client caches responses in memory so that repeated requests (e.g. when navigating around a site) do not trigger unnecessary API calls. If you have already fetched all posts with `getPosts()`, single-post lookups with `getPost(slug)` are served from that cache when the slug is in the list, so no extra request is made.
39
+ The package does **not** cache responses in memory. Each call to `getPosts()` or `getPost(slug)` fetches from the API, so your app always sees up-to-date data (e.g. when you unpublish a post in the CMS it disappears on the next load). For performance, rely on Next.js: use Server Components and the default `fetch` caching, or `revalidate` / ISR, so that responses are cached at the request level and stay fresh according to your revalidation settings.
40
40
 
41
41
  ## API
42
42
 
43
43
  ### `getPosts(siteKey?)`
44
44
 
45
- Returns all published posts for the site. Results are cached per site key.
45
+ Returns all published posts for the site. Always fetches from the API (no in-memory cache).
46
46
 
47
47
  - **Returns:** `Promise<Post[]>`
48
48
  - **Optional:** pass `siteKey` to override the configured site for this call.
49
49
 
50
50
  ### `getPost(slug, siteKey?)`
51
51
 
52
- Returns a single published post by slug, or `null` if not found. If the full list was already loaded via `getPosts()` for the same site, the post is returned from cache when present; otherwise a single-post request is made and cached.
52
+ Returns a single published post by slug, or `null` if not found. Always fetches from the API (no in-memory cache).
53
53
 
54
54
  - **Returns:** `Promise<Post | null>`
55
55
  - **Optional:** pass `siteKey` as the second argument to override the configured site.
package/dist/index.js CHANGED
@@ -31,9 +31,6 @@ module.exports = __toCommonJS(index_exports);
31
31
  // src/client.ts
32
32
  var DISPATCH_API_BASE = "https://dispatch-cms.vercel.app";
33
33
  var config = null;
34
- var allPostsBySiteKey = /* @__PURE__ */ new Map();
35
- var postBySlugBySiteKey = /* @__PURE__ */ new Map();
36
- var fullListFetchedForSiteKey = /* @__PURE__ */ new Set();
37
34
  function initDispatch(options) {
38
35
  config = { siteKey: options.siteKey };
39
36
  }
@@ -48,10 +45,6 @@ function getConfig() {
48
45
  async function getPosts(siteKey) {
49
46
  const { siteKey: key } = getConfig();
50
47
  const resolvedKey = siteKey ?? key;
51
- const cached = allPostsBySiteKey.get(resolvedKey);
52
- if (cached !== void 0) {
53
- return cached;
54
- }
55
48
  const url = `${DISPATCH_API_BASE}/api/posts`;
56
49
  const res = await fetch(url, {
57
50
  headers: { "X-Site-Key": resolvedKey }
@@ -62,31 +55,11 @@ async function getPosts(siteKey) {
62
55
  }
63
56
  throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);
64
57
  }
65
- const posts = await res.json();
66
- allPostsBySiteKey.set(resolvedKey, posts);
67
- fullListFetchedForSiteKey.add(resolvedKey);
68
- const bySlug = /* @__PURE__ */ new Map();
69
- for (const post of posts) {
70
- bySlug.set(post.slug, post);
71
- }
72
- postBySlugBySiteKey.set(resolvedKey, bySlug);
73
- return posts;
58
+ return await res.json();
74
59
  }
75
60
  async function getPost(slug, siteKey) {
76
61
  const { siteKey: key } = getConfig();
77
62
  const resolvedKey = siteKey ?? key;
78
- if (fullListFetchedForSiteKey.has(resolvedKey)) {
79
- const list = allPostsBySiteKey.get(resolvedKey);
80
- if (list) {
81
- const post2 = list.find((p) => p.slug === slug);
82
- return post2 ?? null;
83
- }
84
- }
85
- const bySlug = postBySlugBySiteKey.get(resolvedKey);
86
- const cachedPost = bySlug?.get(slug);
87
- if (cachedPost !== void 0) {
88
- return cachedPost;
89
- }
90
63
  const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;
91
64
  const res = await fetch(url, {
92
65
  headers: { "X-Site-Key": resolvedKey }
@@ -100,12 +73,7 @@ async function getPost(slug, siteKey) {
100
73
  }
101
74
  throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);
102
75
  }
103
- const post = await res.json();
104
- if (!postBySlugBySiteKey.has(resolvedKey)) {
105
- postBySlugBySiteKey.set(resolvedKey, /* @__PURE__ */ new Map());
106
- }
107
- postBySlugBySiteKey.get(resolvedKey).set(slug, post);
108
- return post;
76
+ return await res.json();
109
77
  }
110
78
  async function getPostByPreviewToken(token) {
111
79
  if (!token?.trim()) return null;
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 {\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"]}
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\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 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 return (await res.json()) as Post[];\n}\n\nexport async function getPost(slug: string, siteKey?: string): Promise<Post | null> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\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 return (await res.json()) as 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcA,IAAM,oBAAoB;AAM1B,IAAI,SAAgC;AAE7B,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,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,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAsB,QAAQ,MAAc,SAAwC;AAClF,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU;AACnC,QAAM,cAAc,WAAW;AAC/B,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,SAAQ,MAAM,IAAI,KAAK;AACzB;AAMA,eAAsB,sBAAsB,OAAqC;AAC/E,MAAI,CAAC,OAAO,KAAK,EAAG,QAAO;AAC3B,QAAM,MAAM,GAAG,iBAAiB,sBAAsB,mBAAmB,MAAM,KAAK,CAAC,CAAC;AACtF,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,4BAA4B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EAC5E;AACA,SAAQ,MAAM,IAAI,KAAK;AACzB;","names":[]}
package/dist/index.mjs CHANGED
@@ -1,9 +1,6 @@
1
1
  // src/client.ts
2
2
  var DISPATCH_API_BASE = "https://dispatch-cms.vercel.app";
3
3
  var config = null;
4
- var allPostsBySiteKey = /* @__PURE__ */ new Map();
5
- var postBySlugBySiteKey = /* @__PURE__ */ new Map();
6
- var fullListFetchedForSiteKey = /* @__PURE__ */ new Set();
7
4
  function initDispatch(options) {
8
5
  config = { siteKey: options.siteKey };
9
6
  }
@@ -18,10 +15,6 @@ function getConfig() {
18
15
  async function getPosts(siteKey) {
19
16
  const { siteKey: key } = getConfig();
20
17
  const resolvedKey = siteKey ?? key;
21
- const cached = allPostsBySiteKey.get(resolvedKey);
22
- if (cached !== void 0) {
23
- return cached;
24
- }
25
18
  const url = `${DISPATCH_API_BASE}/api/posts`;
26
19
  const res = await fetch(url, {
27
20
  headers: { "X-Site-Key": resolvedKey }
@@ -32,31 +25,11 @@ async function getPosts(siteKey) {
32
25
  }
33
26
  throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);
34
27
  }
35
- const posts = await res.json();
36
- allPostsBySiteKey.set(resolvedKey, posts);
37
- fullListFetchedForSiteKey.add(resolvedKey);
38
- const bySlug = /* @__PURE__ */ new Map();
39
- for (const post of posts) {
40
- bySlug.set(post.slug, post);
41
- }
42
- postBySlugBySiteKey.set(resolvedKey, bySlug);
43
- return posts;
28
+ return await res.json();
44
29
  }
45
30
  async function getPost(slug, siteKey) {
46
31
  const { siteKey: key } = getConfig();
47
32
  const resolvedKey = siteKey ?? key;
48
- if (fullListFetchedForSiteKey.has(resolvedKey)) {
49
- const list = allPostsBySiteKey.get(resolvedKey);
50
- if (list) {
51
- const post2 = list.find((p) => p.slug === slug);
52
- return post2 ?? null;
53
- }
54
- }
55
- const bySlug = postBySlugBySiteKey.get(resolvedKey);
56
- const cachedPost = bySlug?.get(slug);
57
- if (cachedPost !== void 0) {
58
- return cachedPost;
59
- }
60
33
  const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;
61
34
  const res = await fetch(url, {
62
35
  headers: { "X-Site-Key": resolvedKey }
@@ -70,12 +43,7 @@ async function getPost(slug, siteKey) {
70
43
  }
71
44
  throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);
72
45
  }
73
- const post = await res.json();
74
- if (!postBySlugBySiteKey.has(resolvedKey)) {
75
- postBySlugBySiteKey.set(resolvedKey, /* @__PURE__ */ new Map());
76
- }
77
- postBySlugBySiteKey.get(resolvedKey).set(slug, post);
78
- return post;
46
+ return await res.json();
79
47
  }
80
48
  async function getPostByPreviewToken(token) {
81
49
  if (!token?.trim()) return null;
@@ -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\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"]}
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\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 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 return (await res.json()) as Post[];\n}\n\nexport async function getPost(slug: string, siteKey?: string): Promise<Post | null> {\n const { siteKey: key } = getConfig();\n const resolvedKey = siteKey ?? key;\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 return (await res.json()) as 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":";AAcA,IAAM,oBAAoB;AAM1B,IAAI,SAAgC;AAE7B,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,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,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAsB,QAAQ,MAAc,SAAwC;AAClF,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU;AACnC,QAAM,cAAc,WAAW;AAC/B,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,SAAQ,MAAM,IAAI,KAAK;AACzB;AAMA,eAAsB,sBAAsB,OAAqC;AAC/E,MAAI,CAAC,OAAO,KAAK,EAAG,QAAO;AAC3B,QAAM,MAAM,GAAG,iBAAiB,sBAAsB,mBAAmB,MAAM,KAAK,CAAC,CAAC;AACtF,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,4BAA4B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,EAC5E;AACA,SAAQ,MAAM,IAAI,KAAK;AACzB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dispatchcms/next",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
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
@@ -20,10 +20,6 @@ export type DispatchConfig = {
20
20
 
21
21
  let config: DispatchConfig | null = null;
22
22
 
23
- const allPostsBySiteKey = new Map<string, Post[]>();
24
- const postBySlugBySiteKey = new Map<string, Map<string, Post>>();
25
- const fullListFetchedForSiteKey = new Set<string>();
26
-
27
23
  export function initDispatch(options: DispatchConfig): void {
28
24
  config = { siteKey: options.siteKey };
29
25
  }
@@ -40,10 +36,6 @@ export function getConfig(): { siteKey: string } {
40
36
  export async function getPosts(siteKey?: string): Promise<Post[]> {
41
37
  const { siteKey: key } = getConfig();
42
38
  const resolvedKey = siteKey ?? key;
43
- const cached = allPostsBySiteKey.get(resolvedKey);
44
- if (cached !== undefined) {
45
- return cached;
46
- }
47
39
  const url = `${DISPATCH_API_BASE}/api/posts`;
48
40
  const res = await fetch(url, {
49
41
  headers: { "X-Site-Key": resolvedKey },
@@ -54,32 +46,12 @@ export async function getPosts(siteKey?: string): Promise<Post[]> {
54
46
  }
55
47
  throw new Error(`Failed to fetch posts: ${res.status} ${res.statusText}`);
56
48
  }
57
- const posts = (await res.json()) as Post[];
58
- allPostsBySiteKey.set(resolvedKey, posts);
59
- fullListFetchedForSiteKey.add(resolvedKey);
60
- const bySlug = new Map<string, Post>();
61
- for (const post of posts) {
62
- bySlug.set(post.slug, post);
63
- }
64
- postBySlugBySiteKey.set(resolvedKey, bySlug);
65
- return posts;
49
+ return (await res.json()) as Post[];
66
50
  }
67
51
 
68
52
  export async function getPost(slug: string, siteKey?: string): Promise<Post | null> {
69
53
  const { siteKey: key } = getConfig();
70
54
  const resolvedKey = siteKey ?? key;
71
- if (fullListFetchedForSiteKey.has(resolvedKey)) {
72
- const list = allPostsBySiteKey.get(resolvedKey);
73
- if (list) {
74
- const post = list.find((p) => p.slug === slug);
75
- return post ?? null;
76
- }
77
- }
78
- const bySlug = postBySlugBySiteKey.get(resolvedKey);
79
- const cachedPost = bySlug?.get(slug);
80
- if (cachedPost !== undefined) {
81
- return cachedPost;
82
- }
83
55
  const url = `${DISPATCH_API_BASE}/api/posts/${encodeURIComponent(slug)}`;
84
56
  const res = await fetch(url, {
85
57
  headers: { "X-Site-Key": resolvedKey },
@@ -93,12 +65,7 @@ export async function getPost(slug: string, siteKey?: string): Promise<Post | nu
93
65
  }
94
66
  throw new Error(`Failed to fetch post: ${res.status} ${res.statusText}`);
95
67
  }
96
- const post = (await res.json()) as Post;
97
- if (!postBySlugBySiteKey.has(resolvedKey)) {
98
- postBySlugBySiteKey.set(resolvedKey, new Map());
99
- }
100
- postBySlugBySiteKey.get(resolvedKey)!.set(slug, post);
101
- return post;
68
+ return (await res.json()) as Post;
102
69
  }
103
70
 
104
71
  /**
@@ -20,7 +20,7 @@ let listResponse: ReturnType<typeof mockPost>[] = [];
20
20
  let singleResponse: ReturnType<typeof mockPost> | null = null;
21
21
  let singleStatus = 200;
22
22
 
23
- function mockFetch(url: string, options?: RequestInit): Promise<Response> {
23
+ function mockFetch(url: string, _options?: RequestInit): Promise<Response> {
24
24
  fetchCalls.push({ url });
25
25
  const u = new URL(url);
26
26
  if (u.pathname === "/api/posts") {
@@ -61,43 +61,40 @@ async function run(): Promise<void> {
61
61
  initDispatch({ siteKey: "pk_test" });
62
62
 
63
63
  try {
64
- // Test 1: Second getPosts() with same siteKey does not call fetch
64
+ // Test 1: getPosts() fetches and returns the list (no in-memory cache)
65
65
  fetchCalls.length = 0;
66
66
  listResponse = [mockPost("a", "Post A"), mockPost("b", "Post B")];
67
- const list1 = await getPosts("pk_cache1");
68
- const list2 = await getPosts("pk_cache1");
69
- assert(fetchCalls.length === 1, "getPosts should be fetched once, then served from cache");
70
- assert(list1.length === 2 && list2.length === 2, "both calls return same list");
71
- assert(list1[0].slug === "a" && list2[0].slug === "a", "content matches");
67
+ const list1 = await getPosts("pk_1");
68
+ assert(fetchCalls.length === 1, "getPosts triggers one fetch");
69
+ assert(list1.length === 2 && list1[0].slug === "a", "returns correct list");
70
+ const list2 = await getPosts("pk_1");
71
+ assert(fetchCalls.length === 2, "second getPosts triggers another fetch (no cache)");
72
+ assert(list2.length === 2, "second call returns same list");
72
73
 
73
- // Test 2: After getPosts(), getPost(slug) for slug in list does not call fetch
74
+ // Test 2: getPost(slug) fetches and returns the post
74
75
  fetchCalls.length = 0;
75
- listResponse = [mockPost("my-slug", "My Post")];
76
- await getPosts("pk_cache2");
77
- const post = await getPost("my-slug", "pk_cache2");
78
- assert(fetchCalls.length === 1, "only getPosts should trigger fetch, getPost uses cache");
79
- assert(post !== null && post.slug === "my-slug" && post.title === "My Post", "getPost returns correct post from list");
76
+ singleResponse = mockPost("my-slug", "My Post");
77
+ const post = await getPost("my-slug", "pk_2");
78
+ assert(fetchCalls.length === 1, "getPost triggers one fetch");
79
+ assert(post !== null && post.slug === "my-slug" && post.title === "My Post", "returns correct post");
80
80
 
81
- // Test 3: After getPosts(), getPost(slug) for slug not in list returns null without fetch
81
+ // Test 3: getPost(slug) for missing slug returns null
82
82
  fetchCalls.length = 0;
83
- listResponse = [mockPost("other", "Other")];
84
- await getPosts("pk_cache3");
85
- const missing = await getPost("nonexistent", "pk_cache3");
86
- assert(fetchCalls.length === 1, "getPost(nonexistent) should not fetch when full list is cached");
87
- assert(missing === null, "getPost returns null for missing slug");
83
+ singleStatus = 404;
84
+ const missing = await getPost("nonexistent", "pk_3");
85
+ assert(fetchCalls.length === 1, "getPost triggers one fetch");
86
+ assert(missing === null, "returns null for 404");
87
+ singleStatus = 200;
88
88
 
89
- // Test 4: getPost(slug) first triggers one fetch; then getPosts() triggers one fetch; then getPost(slug) uses cache
89
+ // Test 4: getPost(slug) then getPosts() each trigger their own fetch
90
90
  fetchCalls.length = 0;
91
- listResponse = [];
92
91
  singleResponse = mockPost("first", "First Post");
93
- const singleFirst = await getPost("first", "pk_cache4");
94
- assert(fetchCalls.length === 1 && singleFirst?.slug === "first", "first getPost fetches");
95
92
  listResponse = [mockPost("first", "First Post")];
96
- await getPosts("pk_cache4");
93
+ const singleFirst = await getPost("first", "pk_4");
94
+ assert(fetchCalls.length === 1 && singleFirst?.slug === "first", "getPost fetches");
95
+ const listAfter = await getPosts("pk_4");
97
96
  assert(fetchCalls.length === 2, "getPosts triggers second fetch");
98
- const singleCached = await getPost("first", "pk_cache4");
99
- assert(fetchCalls.length === 2, "getPost after getPosts does not fetch again");
100
- assert(singleCached !== null && singleCached.slug === "first", "getPost returns post from list cache");
97
+ assert(listAfter.length === 1 && listAfter[0].slug === "first", "getPosts returns correct list");
101
98
  } finally {
102
99
  globalThis.fetch = originalFetch;
103
100
  }
@@ -105,7 +102,7 @@ async function run(): Promise<void> {
105
102
 
106
103
  run()
107
104
  .then(() => {
108
- console.log("All cache tests passed.");
105
+ console.log("All client tests passed.");
109
106
  })
110
107
  .catch((err) => {
111
108
  console.error(err);