@amboras-dev/blog 0.2.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,58 @@
1
+ type BlogStatus = 'active' | 'archived';
2
+ type ArticleStatus = 'draft' | 'published' | 'archived';
3
+ /** A blog as returned by the public store API. */
4
+ interface Blog {
5
+ id: string;
6
+ title: string;
7
+ handle: string;
8
+ description: string | null;
9
+ status: BlogStatus;
10
+ created_at: string;
11
+ updated_at: string;
12
+ }
13
+ /** A published article as returned by the public store API. */
14
+ interface Article {
15
+ id: string;
16
+ blog_id: string;
17
+ title: string;
18
+ handle: string;
19
+ /** Sanitized HTML (already safe to render). */
20
+ content: string | null;
21
+ excerpt: string | null;
22
+ author_name: string | null;
23
+ featured_image_url: string | null;
24
+ tags: string[];
25
+ status: ArticleStatus;
26
+ published_at: string | null;
27
+ seo_title: string | null;
28
+ seo_description: string | null;
29
+ created_at: string;
30
+ updated_at: string;
31
+ }
32
+ /** GET /store/content/blogs */
33
+ interface BlogsResponse {
34
+ blogs: Blog[];
35
+ count: number;
36
+ }
37
+ /** GET /store/content/blogs/:blog/articles */
38
+ interface BlogArticlesResponse {
39
+ blog: Blog;
40
+ articles: Article[];
41
+ count: number;
42
+ offset: number;
43
+ limit: number;
44
+ }
45
+ /** GET /store/content/blogs/:blog/articles/:handle */
46
+ interface ArticleResponse {
47
+ blog: Blog;
48
+ article: Article;
49
+ }
50
+ /** Shared connection options for fetch helpers + hooks. */
51
+ interface BlogFetchOptions {
52
+ /** Override the Medusa base URL (defaults to NEXT_PUBLIC_MEDUSA_BACKEND_URL). */
53
+ baseUrl?: string;
54
+ /** Override the publishable API key (defaults to NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY). */
55
+ publishableApiKey?: string;
56
+ }
57
+
58
+ export type { ArticleResponse as A, BlogFetchOptions as B, BlogArticlesResponse as a, BlogsResponse as b, Article as c, ArticleStatus as d, Blog as e, BlogStatus as f };
@@ -0,0 +1,58 @@
1
+ type BlogStatus = 'active' | 'archived';
2
+ type ArticleStatus = 'draft' | 'published' | 'archived';
3
+ /** A blog as returned by the public store API. */
4
+ interface Blog {
5
+ id: string;
6
+ title: string;
7
+ handle: string;
8
+ description: string | null;
9
+ status: BlogStatus;
10
+ created_at: string;
11
+ updated_at: string;
12
+ }
13
+ /** A published article as returned by the public store API. */
14
+ interface Article {
15
+ id: string;
16
+ blog_id: string;
17
+ title: string;
18
+ handle: string;
19
+ /** Sanitized HTML (already safe to render). */
20
+ content: string | null;
21
+ excerpt: string | null;
22
+ author_name: string | null;
23
+ featured_image_url: string | null;
24
+ tags: string[];
25
+ status: ArticleStatus;
26
+ published_at: string | null;
27
+ seo_title: string | null;
28
+ seo_description: string | null;
29
+ created_at: string;
30
+ updated_at: string;
31
+ }
32
+ /** GET /store/content/blogs */
33
+ interface BlogsResponse {
34
+ blogs: Blog[];
35
+ count: number;
36
+ }
37
+ /** GET /store/content/blogs/:blog/articles */
38
+ interface BlogArticlesResponse {
39
+ blog: Blog;
40
+ articles: Article[];
41
+ count: number;
42
+ offset: number;
43
+ limit: number;
44
+ }
45
+ /** GET /store/content/blogs/:blog/articles/:handle */
46
+ interface ArticleResponse {
47
+ blog: Blog;
48
+ article: Article;
49
+ }
50
+ /** Shared connection options for fetch helpers + hooks. */
51
+ interface BlogFetchOptions {
52
+ /** Override the Medusa base URL (defaults to NEXT_PUBLIC_MEDUSA_BACKEND_URL). */
53
+ baseUrl?: string;
54
+ /** Override the publishable API key (defaults to NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY). */
55
+ publishableApiKey?: string;
56
+ }
57
+
58
+ export type { ArticleResponse as A, BlogFetchOptions as B, BlogArticlesResponse as a, BlogsResponse as b, Article as c, ArticleStatus as d, Blog as e, BlogStatus as f };
@@ -0,0 +1,22 @@
1
+ import * as _tanstack_react_query from '@tanstack/react-query';
2
+ import { B as BlogFetchOptions, A as ArticleResponse, a as BlogArticlesResponse, b as BlogsResponse } from './index-CwQSYuu5.mjs';
3
+ export { c as Article, d as ArticleStatus, e as Blog, f as BlogStatus } from './index-CwQSYuu5.mjs';
4
+
5
+ /** List the store's active blogs. */
6
+ declare function useBlogs(options?: BlogFetchOptions): _tanstack_react_query.UseQueryResult<BlogsResponse, Error>;
7
+ /** Paginated published articles in a blog — used by interactive islands. */
8
+ declare function useBlogArticles(blogHandle: string, options?: BlogFetchOptions & {
9
+ page?: number;
10
+ perPage?: number;
11
+ }): _tanstack_react_query.UseQueryResult<BlogArticlesResponse, Error>;
12
+ /** A single published article by blog + article handle. */
13
+ declare function useArticle(blogHandle: string, articleHandle: string, options?: BlogFetchOptions): _tanstack_react_query.UseQueryResult<ArticleResponse, Error>;
14
+
15
+ /**
16
+ * Client-side fetch helper for blog API calls (interactive islands:
17
+ * search, pagination). Attaches the publishable key header. Public reads
18
+ * only — no auth needed.
19
+ */
20
+ declare function blogFetch<T>(path: string, options?: BlogFetchOptions): Promise<T>;
21
+
22
+ export { ArticleResponse, BlogArticlesResponse, BlogFetchOptions, BlogsResponse, blogFetch, useArticle, useBlogArticles, useBlogs };
@@ -0,0 +1,22 @@
1
+ import * as _tanstack_react_query from '@tanstack/react-query';
2
+ import { B as BlogFetchOptions, A as ArticleResponse, a as BlogArticlesResponse, b as BlogsResponse } from './index-CwQSYuu5.js';
3
+ export { c as Article, d as ArticleStatus, e as Blog, f as BlogStatus } from './index-CwQSYuu5.js';
4
+
5
+ /** List the store's active blogs. */
6
+ declare function useBlogs(options?: BlogFetchOptions): _tanstack_react_query.UseQueryResult<BlogsResponse, Error>;
7
+ /** Paginated published articles in a blog — used by interactive islands. */
8
+ declare function useBlogArticles(blogHandle: string, options?: BlogFetchOptions & {
9
+ page?: number;
10
+ perPage?: number;
11
+ }): _tanstack_react_query.UseQueryResult<BlogArticlesResponse, Error>;
12
+ /** A single published article by blog + article handle. */
13
+ declare function useArticle(blogHandle: string, articleHandle: string, options?: BlogFetchOptions): _tanstack_react_query.UseQueryResult<ArticleResponse, Error>;
14
+
15
+ /**
16
+ * Client-side fetch helper for blog API calls (interactive islands:
17
+ * search, pagination). Attaches the publishable key header. Public reads
18
+ * only — no auth needed.
19
+ */
20
+ declare function blogFetch<T>(path: string, options?: BlogFetchOptions): Promise<T>;
21
+
22
+ export { ArticleResponse, BlogArticlesResponse, BlogFetchOptions, BlogsResponse, blogFetch, useArticle, useBlogArticles, useBlogs };
package/dist/index.js ADDED
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ blogFetch: () => blogFetch,
24
+ useArticle: () => useArticle,
25
+ useBlogArticles: () => useBlogArticles,
26
+ useBlogs: () => useBlogs
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/hooks/use-blogs.ts
31
+ var import_react_query = require("@tanstack/react-query");
32
+
33
+ // src/lib/fetch.ts
34
+ function resolveEnv(key) {
35
+ if (key === "NEXT_PUBLIC_MEDUSA_BACKEND_URL") return process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL;
36
+ if (key === "NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY")
37
+ return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY;
38
+ return void 0;
39
+ }
40
+ async function blogFetch(path, options = {}) {
41
+ const {
42
+ baseUrl = resolveEnv("NEXT_PUBLIC_MEDUSA_BACKEND_URL") ?? "http://localhost:9000",
43
+ publishableApiKey = resolveEnv("NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY")
44
+ } = options;
45
+ const headers = {};
46
+ if (publishableApiKey) headers["x-publishable-api-key"] = publishableApiKey;
47
+ const res = await fetch(`${baseUrl}${path}`, { method: "GET", headers });
48
+ if (!res.ok) {
49
+ const err = await res.json().catch(() => ({}));
50
+ throw new Error(err.error ?? `Request failed (${res.status})`);
51
+ }
52
+ return res.json();
53
+ }
54
+
55
+ // src/hooks/use-blogs.ts
56
+ function useBlogs(options = {}) {
57
+ return (0, import_react_query.useQuery)({
58
+ queryKey: ["blogs"],
59
+ queryFn: () => blogFetch("/store/content/blogs", options),
60
+ staleTime: 1e3 * 60 * 2
61
+ });
62
+ }
63
+ function useBlogArticles(blogHandle, options = {}) {
64
+ const { page = 1, perPage = 20, ...fetchOpts } = options;
65
+ return (0, import_react_query.useQuery)({
66
+ queryKey: ["blog-articles", blogHandle, page, perPage],
67
+ queryFn: () => {
68
+ const offset = (page - 1) * perPage;
69
+ return blogFetch(
70
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles?limit=${perPage}&offset=${offset}`,
71
+ fetchOpts
72
+ );
73
+ },
74
+ enabled: !!blogHandle,
75
+ staleTime: 1e3 * 60 * 2
76
+ });
77
+ }
78
+ function useArticle(blogHandle, articleHandle, options = {}) {
79
+ return (0, import_react_query.useQuery)({
80
+ queryKey: ["blog-article", blogHandle, articleHandle],
81
+ queryFn: () => blogFetch(
82
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,
83
+ options
84
+ ),
85
+ enabled: !!blogHandle && !!articleHandle,
86
+ staleTime: 1e3 * 60 * 2
87
+ });
88
+ }
89
+ // Annotate the CommonJS export names for ESM import in node:
90
+ 0 && (module.exports = {
91
+ blogFetch,
92
+ useArticle,
93
+ useBlogArticles,
94
+ useBlogs
95
+ });
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/hooks/use-blogs.ts","../src/lib/fetch.ts"],"sourcesContent":["// Client hooks (interactive islands: search/pagination)\nexport { useBlogs, useBlogArticles, useArticle } from './hooks/use-blogs'\nexport { blogFetch } from './lib/fetch'\n\n// Types\nexport type {\n BlogStatus,\n ArticleStatus,\n Blog,\n Article,\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from './types'\n","'use client'\n\nimport { useQuery } from '@tanstack/react-query'\nimport { blogFetch } from '../lib/fetch'\nimport type {\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from '../types'\n\n/** List the store's active blogs. */\nexport function useBlogs(options: BlogFetchOptions = {}) {\n return useQuery<BlogsResponse, Error>({\n queryKey: ['blogs'],\n queryFn: () => blogFetch<BlogsResponse>('/store/content/blogs', options),\n staleTime: 1000 * 60 * 2,\n })\n}\n\n/** Paginated published articles in a blog — used by interactive islands. */\nexport function useBlogArticles(\n blogHandle: string,\n options: BlogFetchOptions & { page?: number; perPage?: number } = {},\n) {\n const { page = 1, perPage = 20, ...fetchOpts } = options\n return useQuery<BlogArticlesResponse, Error>({\n queryKey: ['blog-articles', blogHandle, page, perPage],\n queryFn: () => {\n const offset = (page - 1) * perPage\n return blogFetch<BlogArticlesResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles?limit=${perPage}&offset=${offset}`,\n fetchOpts,\n )\n },\n enabled: !!blogHandle,\n staleTime: 1000 * 60 * 2,\n })\n}\n\n/** A single published article by blog + article handle. */\nexport function useArticle(\n blogHandle: string,\n articleHandle: string,\n options: BlogFetchOptions = {},\n) {\n return useQuery<ArticleResponse, Error>({\n queryKey: ['blog-article', blogHandle, articleHandle],\n queryFn: () =>\n blogFetch<ArticleResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,\n options,\n ),\n enabled: !!blogHandle && !!articleHandle,\n staleTime: 1000 * 60 * 2,\n })\n}\n","'use client'\n\nimport type { BlogFetchOptions } from '../types'\n\nfunction resolveEnv(key: string): string | undefined {\n if (key === 'NEXT_PUBLIC_MEDUSA_BACKEND_URL') return process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL\n if (key === 'NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY')\n return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY\n return undefined\n}\n\n/**\n * Client-side fetch helper for blog API calls (interactive islands:\n * search, pagination). Attaches the publishable key header. Public reads\n * only — no auth needed.\n */\nexport async function blogFetch<T>(path: string, options: BlogFetchOptions = {}): Promise<T> {\n const {\n baseUrl = resolveEnv('NEXT_PUBLIC_MEDUSA_BACKEND_URL') ?? 'http://localhost:9000',\n publishableApiKey = resolveEnv('NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY'),\n } = options\n\n const headers: Record<string, string> = {}\n if (publishableApiKey) headers['x-publishable-api-key'] = publishableApiKey\n\n const res = await fetch(`${baseUrl}${path}`, { method: 'GET', headers })\n if (!res.ok) {\n const err = await res.json().catch(() => ({}))\n throw new Error(err.error ?? `Request failed (${res.status})`)\n }\n return res.json()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,yBAAyB;;;ACEzB,SAAS,WAAW,KAAiC;AACnD,MAAI,QAAQ,iCAAkC,QAAO,QAAQ,IAAI;AACjE,MAAI,QAAQ;AACV,WAAO,QAAQ,IAAI;AACrB,SAAO;AACT;AAOA,eAAsB,UAAa,MAAc,UAA4B,CAAC,GAAe;AAC3F,QAAM;AAAA,IACJ,UAAU,WAAW,gCAAgC,KAAK;AAAA,IAC1D,oBAAoB,WAAW,oCAAoC;AAAA,EACrE,IAAI;AAEJ,QAAM,UAAkC,CAAC;AACzC,MAAI,kBAAmB,SAAQ,uBAAuB,IAAI;AAE1D,QAAM,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,IAAI,IAAI,EAAE,QAAQ,OAAO,QAAQ,CAAC;AACvE,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC7C,UAAM,IAAI,MAAM,IAAI,SAAS,mBAAmB,IAAI,MAAM,GAAG;AAAA,EAC/D;AACA,SAAO,IAAI,KAAK;AAClB;;;ADnBO,SAAS,SAAS,UAA4B,CAAC,GAAG;AACvD,aAAO,6BAA+B;AAAA,IACpC,UAAU,CAAC,OAAO;AAAA,IAClB,SAAS,MAAM,UAAyB,wBAAwB,OAAO;AAAA,IACvE,WAAW,MAAO,KAAK;AAAA,EACzB,CAAC;AACH;AAGO,SAAS,gBACd,YACA,UAAkE,CAAC,GACnE;AACA,QAAM,EAAE,OAAO,GAAG,UAAU,IAAI,GAAG,UAAU,IAAI;AACjD,aAAO,6BAAsC;AAAA,IAC3C,UAAU,CAAC,iBAAiB,YAAY,MAAM,OAAO;AAAA,IACrD,SAAS,MAAM;AACb,YAAM,UAAU,OAAO,KAAK;AAC5B,aAAO;AAAA,QACL,wBAAwB,mBAAmB,UAAU,CAAC,mBAAmB,OAAO,WAAW,MAAM;AAAA,QACjG;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS,CAAC,CAAC;AAAA,IACX,WAAW,MAAO,KAAK;AAAA,EACzB,CAAC;AACH;AAGO,SAAS,WACd,YACA,eACA,UAA4B,CAAC,GAC7B;AACA,aAAO,6BAAiC;AAAA,IACtC,UAAU,CAAC,gBAAgB,YAAY,aAAa;AAAA,IACpD,SAAS,MACP;AAAA,MACE,wBAAwB,mBAAmB,UAAU,CAAC,aAAa,mBAAmB,aAAa,CAAC;AAAA,MACpG;AAAA,IACF;AAAA,IACF,SAAS,CAAC,CAAC,cAAc,CAAC,CAAC;AAAA,IAC3B,WAAW,MAAO,KAAK;AAAA,EACzB,CAAC;AACH;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,66 @@
1
+ // src/hooks/use-blogs.ts
2
+ import { useQuery } from "@tanstack/react-query";
3
+
4
+ // src/lib/fetch.ts
5
+ function resolveEnv(key) {
6
+ if (key === "NEXT_PUBLIC_MEDUSA_BACKEND_URL") return process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL;
7
+ if (key === "NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY")
8
+ return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY;
9
+ return void 0;
10
+ }
11
+ async function blogFetch(path, options = {}) {
12
+ const {
13
+ baseUrl = resolveEnv("NEXT_PUBLIC_MEDUSA_BACKEND_URL") ?? "http://localhost:9000",
14
+ publishableApiKey = resolveEnv("NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY")
15
+ } = options;
16
+ const headers = {};
17
+ if (publishableApiKey) headers["x-publishable-api-key"] = publishableApiKey;
18
+ const res = await fetch(`${baseUrl}${path}`, { method: "GET", headers });
19
+ if (!res.ok) {
20
+ const err = await res.json().catch(() => ({}));
21
+ throw new Error(err.error ?? `Request failed (${res.status})`);
22
+ }
23
+ return res.json();
24
+ }
25
+
26
+ // src/hooks/use-blogs.ts
27
+ function useBlogs(options = {}) {
28
+ return useQuery({
29
+ queryKey: ["blogs"],
30
+ queryFn: () => blogFetch("/store/content/blogs", options),
31
+ staleTime: 1e3 * 60 * 2
32
+ });
33
+ }
34
+ function useBlogArticles(blogHandle, options = {}) {
35
+ const { page = 1, perPage = 20, ...fetchOpts } = options;
36
+ return useQuery({
37
+ queryKey: ["blog-articles", blogHandle, page, perPage],
38
+ queryFn: () => {
39
+ const offset = (page - 1) * perPage;
40
+ return blogFetch(
41
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles?limit=${perPage}&offset=${offset}`,
42
+ fetchOpts
43
+ );
44
+ },
45
+ enabled: !!blogHandle,
46
+ staleTime: 1e3 * 60 * 2
47
+ });
48
+ }
49
+ function useArticle(blogHandle, articleHandle, options = {}) {
50
+ return useQuery({
51
+ queryKey: ["blog-article", blogHandle, articleHandle],
52
+ queryFn: () => blogFetch(
53
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,
54
+ options
55
+ ),
56
+ enabled: !!blogHandle && !!articleHandle,
57
+ staleTime: 1e3 * 60 * 2
58
+ });
59
+ }
60
+ export {
61
+ blogFetch,
62
+ useArticle,
63
+ useBlogArticles,
64
+ useBlogs
65
+ };
66
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-blogs.ts","../src/lib/fetch.ts"],"sourcesContent":["'use client'\n\nimport { useQuery } from '@tanstack/react-query'\nimport { blogFetch } from '../lib/fetch'\nimport type {\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from '../types'\n\n/** List the store's active blogs. */\nexport function useBlogs(options: BlogFetchOptions = {}) {\n return useQuery<BlogsResponse, Error>({\n queryKey: ['blogs'],\n queryFn: () => blogFetch<BlogsResponse>('/store/content/blogs', options),\n staleTime: 1000 * 60 * 2,\n })\n}\n\n/** Paginated published articles in a blog — used by interactive islands. */\nexport function useBlogArticles(\n blogHandle: string,\n options: BlogFetchOptions & { page?: number; perPage?: number } = {},\n) {\n const { page = 1, perPage = 20, ...fetchOpts } = options\n return useQuery<BlogArticlesResponse, Error>({\n queryKey: ['blog-articles', blogHandle, page, perPage],\n queryFn: () => {\n const offset = (page - 1) * perPage\n return blogFetch<BlogArticlesResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles?limit=${perPage}&offset=${offset}`,\n fetchOpts,\n )\n },\n enabled: !!blogHandle,\n staleTime: 1000 * 60 * 2,\n })\n}\n\n/** A single published article by blog + article handle. */\nexport function useArticle(\n blogHandle: string,\n articleHandle: string,\n options: BlogFetchOptions = {},\n) {\n return useQuery<ArticleResponse, Error>({\n queryKey: ['blog-article', blogHandle, articleHandle],\n queryFn: () =>\n blogFetch<ArticleResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,\n options,\n ),\n enabled: !!blogHandle && !!articleHandle,\n staleTime: 1000 * 60 * 2,\n })\n}\n","'use client'\n\nimport type { BlogFetchOptions } from '../types'\n\nfunction resolveEnv(key: string): string | undefined {\n if (key === 'NEXT_PUBLIC_MEDUSA_BACKEND_URL') return process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL\n if (key === 'NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY')\n return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY\n return undefined\n}\n\n/**\n * Client-side fetch helper for blog API calls (interactive islands:\n * search, pagination). Attaches the publishable key header. Public reads\n * only — no auth needed.\n */\nexport async function blogFetch<T>(path: string, options: BlogFetchOptions = {}): Promise<T> {\n const {\n baseUrl = resolveEnv('NEXT_PUBLIC_MEDUSA_BACKEND_URL') ?? 'http://localhost:9000',\n publishableApiKey = resolveEnv('NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY'),\n } = options\n\n const headers: Record<string, string> = {}\n if (publishableApiKey) headers['x-publishable-api-key'] = publishableApiKey\n\n const res = await fetch(`${baseUrl}${path}`, { method: 'GET', headers })\n if (!res.ok) {\n const err = await res.json().catch(() => ({}))\n throw new Error(err.error ?? `Request failed (${res.status})`)\n }\n return res.json()\n}\n"],"mappings":";AAEA,SAAS,gBAAgB;;;ACEzB,SAAS,WAAW,KAAiC;AACnD,MAAI,QAAQ,iCAAkC,QAAO,QAAQ,IAAI;AACjE,MAAI,QAAQ;AACV,WAAO,QAAQ,IAAI;AACrB,SAAO;AACT;AAOA,eAAsB,UAAa,MAAc,UAA4B,CAAC,GAAe;AAC3F,QAAM;AAAA,IACJ,UAAU,WAAW,gCAAgC,KAAK;AAAA,IAC1D,oBAAoB,WAAW,oCAAoC;AAAA,EACrE,IAAI;AAEJ,QAAM,UAAkC,CAAC;AACzC,MAAI,kBAAmB,SAAQ,uBAAuB,IAAI;AAE1D,QAAM,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,IAAI,IAAI,EAAE,QAAQ,OAAO,QAAQ,CAAC;AACvE,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC7C,UAAM,IAAI,MAAM,IAAI,SAAS,mBAAmB,IAAI,MAAM,GAAG;AAAA,EAC/D;AACA,SAAO,IAAI,KAAK;AAClB;;;ADnBO,SAAS,SAAS,UAA4B,CAAC,GAAG;AACvD,SAAO,SAA+B;AAAA,IACpC,UAAU,CAAC,OAAO;AAAA,IAClB,SAAS,MAAM,UAAyB,wBAAwB,OAAO;AAAA,IACvE,WAAW,MAAO,KAAK;AAAA,EACzB,CAAC;AACH;AAGO,SAAS,gBACd,YACA,UAAkE,CAAC,GACnE;AACA,QAAM,EAAE,OAAO,GAAG,UAAU,IAAI,GAAG,UAAU,IAAI;AACjD,SAAO,SAAsC;AAAA,IAC3C,UAAU,CAAC,iBAAiB,YAAY,MAAM,OAAO;AAAA,IACrD,SAAS,MAAM;AACb,YAAM,UAAU,OAAO,KAAK;AAC5B,aAAO;AAAA,QACL,wBAAwB,mBAAmB,UAAU,CAAC,mBAAmB,OAAO,WAAW,MAAM;AAAA,QACjG;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS,CAAC,CAAC;AAAA,IACX,WAAW,MAAO,KAAK;AAAA,EACzB,CAAC;AACH;AAGO,SAAS,WACd,YACA,eACA,UAA4B,CAAC,GAC7B;AACA,SAAO,SAAiC;AAAA,IACtC,UAAU,CAAC,gBAAgB,YAAY,aAAa;AAAA,IACpD,SAAS,MACP;AAAA,MACE,wBAAwB,mBAAmB,UAAU,CAAC,aAAa,mBAAmB,aAAa,CAAC;AAAA,MACpG;AAAA,IACF;AAAA,IACF,SAAS,CAAC,CAAC,cAAc,CAAC,CAAC;AAAA,IAC3B,WAAW,MAAO,KAAK;AAAA,EACzB,CAAC;AACH;","names":[]}
@@ -0,0 +1,31 @@
1
+ import { B as BlogFetchOptions, c as Article, e as Blog, A as ArticleResponse, a as BlogArticlesResponse, b as BlogsResponse } from '../index-CwQSYuu5.mjs';
2
+ export { d as ArticleStatus, f as BlogStatus } from '../index-CwQSYuu5.mjs';
3
+
4
+ /**
5
+ * Server-side fetch helpers for React Server Components.
6
+ *
7
+ * Used by the storefront blog route templates (page.tsx + generateMetadata).
8
+ * No 'use client' — these run on the server, fetch the public store API,
9
+ * and rely on Next's fetch cache (revalidate + tags) for ISR. The article
10
+ * content returned is already sanitized server-side, so it is safe to render.
11
+ */
12
+
13
+ /** List the store's active blogs. */
14
+ declare function getBlogs(opts?: BlogFetchOptions): Promise<BlogsResponse>;
15
+ /** List published, visible-now articles in a blog (by handle). Null if the blog doesn't exist. */
16
+ declare function getBlogArticles(blogHandle: string, params?: {
17
+ limit?: number;
18
+ offset?: number;
19
+ }, opts?: BlogFetchOptions): Promise<BlogArticlesResponse | null>;
20
+ /**
21
+ * Every published article across every active blog, newest first. Each item
22
+ * carries its parent `blog` so callers can build `/blogs/:blog/:article` links.
23
+ * Used by the top-level /blogs "Journal" index.
24
+ */
25
+ declare function getAllArticles(opts?: BlogFetchOptions): Promise<Array<Article & {
26
+ blog: Blog;
27
+ }>>;
28
+ /** Fetch a single published article (by blog handle + article handle). Null if not found/visible. */
29
+ declare function getArticle(blogHandle: string, articleHandle: string, opts?: BlogFetchOptions): Promise<ArticleResponse | null>;
30
+
31
+ export { Article, ArticleResponse, Blog, BlogArticlesResponse, BlogFetchOptions, BlogsResponse, getAllArticles, getArticle, getBlogArticles, getBlogs };
@@ -0,0 +1,31 @@
1
+ import { B as BlogFetchOptions, c as Article, e as Blog, A as ArticleResponse, a as BlogArticlesResponse, b as BlogsResponse } from '../index-CwQSYuu5.js';
2
+ export { d as ArticleStatus, f as BlogStatus } from '../index-CwQSYuu5.js';
3
+
4
+ /**
5
+ * Server-side fetch helpers for React Server Components.
6
+ *
7
+ * Used by the storefront blog route templates (page.tsx + generateMetadata).
8
+ * No 'use client' — these run on the server, fetch the public store API,
9
+ * and rely on Next's fetch cache (revalidate + tags) for ISR. The article
10
+ * content returned is already sanitized server-side, so it is safe to render.
11
+ */
12
+
13
+ /** List the store's active blogs. */
14
+ declare function getBlogs(opts?: BlogFetchOptions): Promise<BlogsResponse>;
15
+ /** List published, visible-now articles in a blog (by handle). Null if the blog doesn't exist. */
16
+ declare function getBlogArticles(blogHandle: string, params?: {
17
+ limit?: number;
18
+ offset?: number;
19
+ }, opts?: BlogFetchOptions): Promise<BlogArticlesResponse | null>;
20
+ /**
21
+ * Every published article across every active blog, newest first. Each item
22
+ * carries its parent `blog` so callers can build `/blogs/:blog/:article` links.
23
+ * Used by the top-level /blogs "Journal" index.
24
+ */
25
+ declare function getAllArticles(opts?: BlogFetchOptions): Promise<Array<Article & {
26
+ blog: Blog;
27
+ }>>;
28
+ /** Fetch a single published article (by blog handle + article handle). Null if not found/visible. */
29
+ declare function getArticle(blogHandle: string, articleHandle: string, opts?: BlogFetchOptions): Promise<ArticleResponse | null>;
30
+
31
+ export { Article, ArticleResponse, Blog, BlogArticlesResponse, BlogFetchOptions, BlogsResponse, getAllArticles, getArticle, getBlogArticles, getBlogs };
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ getAllArticles: () => getAllArticles,
24
+ getArticle: () => getArticle,
25
+ getBlogArticles: () => getBlogArticles,
26
+ getBlogs: () => getBlogs
27
+ });
28
+ module.exports = __toCommonJS(server_exports);
29
+ var DEFAULT_REVALIDATE = 60;
30
+ function resolveBaseUrl(opts) {
31
+ return opts.baseUrl ?? process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ?? "http://localhost:9000";
32
+ }
33
+ function resolveKey(opts) {
34
+ return opts.publishableApiKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY;
35
+ }
36
+ async function serverFetch(path, opts, tags) {
37
+ const headers = {};
38
+ const key = resolveKey(opts);
39
+ if (key) headers["x-publishable-api-key"] = key;
40
+ const init = {
41
+ headers,
42
+ next: { revalidate: DEFAULT_REVALIDATE, tags }
43
+ };
44
+ const res = await fetch(`${resolveBaseUrl(opts)}${path}`, init);
45
+ if (res.status === 404) return null;
46
+ if (!res.ok) {
47
+ throw new Error(`Blog request failed (${res.status})`);
48
+ }
49
+ return await res.json();
50
+ }
51
+ async function getBlogs(opts = {}) {
52
+ const data = await serverFetch("/store/content/blogs", opts, ["blogs"]);
53
+ return data ?? { blogs: [], count: 0 };
54
+ }
55
+ async function getBlogArticles(blogHandle, params = {}, opts = {}) {
56
+ const qs = new URLSearchParams();
57
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
58
+ if (params.offset !== void 0) qs.set("offset", String(params.offset));
59
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
60
+ return serverFetch(
61
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles${suffix}`,
62
+ opts,
63
+ ["blogs", `blog:${blogHandle}`]
64
+ );
65
+ }
66
+ async function getAllArticles(opts = {}) {
67
+ const { blogs } = await getBlogs(opts);
68
+ const lists = await Promise.all(
69
+ blogs.map(async (b) => {
70
+ const data = await getBlogArticles(b.handle, { limit: 100 }, opts);
71
+ return (data?.articles ?? []).map((a) => ({ ...a, blog: b }));
72
+ })
73
+ );
74
+ return lists.flat().sort(
75
+ (x, y) => new Date(y.published_at ?? 0).getTime() - new Date(x.published_at ?? 0).getTime()
76
+ );
77
+ }
78
+ async function getArticle(blogHandle, articleHandle, opts = {}) {
79
+ return serverFetch(
80
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,
81
+ opts,
82
+ ["blogs", `blog:${blogHandle}`, `article:${blogHandle}/${articleHandle}`]
83
+ );
84
+ }
85
+ // Annotate the CommonJS export names for ESM import in node:
86
+ 0 && (module.exports = {
87
+ getAllArticles,
88
+ getArticle,
89
+ getBlogArticles,
90
+ getBlogs
91
+ });
92
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * Server-side fetch helpers for React Server Components.\n *\n * Used by the storefront blog route templates (page.tsx + generateMetadata).\n * No 'use client' — these run on the server, fetch the public store API,\n * and rely on Next's fetch cache (revalidate + tags) for ISR. The article\n * content returned is already sanitized server-side, so it is safe to render.\n */\n\nimport type {\n Blog,\n Article,\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from '../types'\n\nconst DEFAULT_REVALIDATE = 60\n\n// Next.js augments RequestInit with a `next` field; this package doesn't\n// depend on Next types, so we widen the type locally instead.\ntype NextFetchInit = RequestInit & {\n next?: { revalidate?: number; tags?: string[] }\n}\n\nfunction resolveBaseUrl(opts: BlogFetchOptions): string {\n return (\n opts.baseUrl ??\n process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ??\n 'http://localhost:9000'\n )\n}\n\nfunction resolveKey(opts: BlogFetchOptions): string | undefined {\n return opts.publishableApiKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY\n}\n\nasync function serverFetch<T>(\n path: string,\n opts: BlogFetchOptions,\n tags: string[],\n): Promise<T | null> {\n const headers: Record<string, string> = {}\n const key = resolveKey(opts)\n if (key) headers['x-publishable-api-key'] = key\n\n const init: NextFetchInit = {\n headers,\n next: { revalidate: DEFAULT_REVALIDATE, tags },\n }\n\n const res = await fetch(`${resolveBaseUrl(opts)}${path}`, init)\n if (res.status === 404) return null\n if (!res.ok) {\n throw new Error(`Blog request failed (${res.status})`)\n }\n return (await res.json()) as T\n}\n\n/** List the store's active blogs. */\nexport async function getBlogs(opts: BlogFetchOptions = {}): Promise<BlogsResponse> {\n const data = await serverFetch<BlogsResponse>('/store/content/blogs', opts, ['blogs'])\n return data ?? { blogs: [], count: 0 }\n}\n\n/** List published, visible-now articles in a blog (by handle). Null if the blog doesn't exist. */\nexport async function getBlogArticles(\n blogHandle: string,\n params: { limit?: number; offset?: number } = {},\n opts: BlogFetchOptions = {},\n): Promise<BlogArticlesResponse | null> {\n const qs = new URLSearchParams()\n if (params.limit !== undefined) qs.set('limit', String(params.limit))\n if (params.offset !== undefined) qs.set('offset', String(params.offset))\n const suffix = qs.toString() ? `?${qs.toString()}` : ''\n return serverFetch<BlogArticlesResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles${suffix}`,\n opts,\n ['blogs', `blog:${blogHandle}`],\n )\n}\n\n/**\n * Every published article across every active blog, newest first. Each item\n * carries its parent `blog` so callers can build `/blogs/:blog/:article` links.\n * Used by the top-level /blogs \"Journal\" index.\n */\nexport async function getAllArticles(\n opts: BlogFetchOptions = {},\n): Promise<Array<Article & { blog: Blog }>> {\n const { blogs } = await getBlogs(opts)\n const lists = await Promise.all(\n blogs.map(async (b) => {\n const data = await getBlogArticles(b.handle, { limit: 100 }, opts)\n return (data?.articles ?? []).map((a) => ({ ...a, blog: b }))\n }),\n )\n return lists\n .flat()\n .sort(\n (x, y) =>\n new Date(y.published_at ?? 0).getTime() - new Date(x.published_at ?? 0).getTime(),\n )\n}\n\n/** Fetch a single published article (by blog handle + article handle). Null if not found/visible. */\nexport async function getArticle(\n blogHandle: string,\n articleHandle: string,\n opts: BlogFetchOptions = {},\n): Promise<ArticleResponse | null> {\n return serverFetch<ArticleResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,\n opts,\n ['blogs', `blog:${blogHandle}`, `article:${blogHandle}/${articleHandle}`],\n )\n}\n\nexport type {\n Blog,\n Article,\n BlogStatus,\n ArticleStatus,\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from '../types'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBA,IAAM,qBAAqB;AAQ3B,SAAS,eAAe,MAAgC;AACtD,SACE,KAAK,WACL,QAAQ,IAAI,kCACZ;AAEJ;AAEA,SAAS,WAAW,MAA4C;AAC9D,SAAO,KAAK,qBAAqB,QAAQ,IAAI;AAC/C;AAEA,eAAe,YACb,MACA,MACA,MACmB;AACnB,QAAM,UAAkC,CAAC;AACzC,QAAM,MAAM,WAAW,IAAI;AAC3B,MAAI,IAAK,SAAQ,uBAAuB,IAAI;AAE5C,QAAM,OAAsB;AAAA,IAC1B;AAAA,IACA,MAAM,EAAE,YAAY,oBAAoB,KAAK;AAAA,EAC/C;AAEA,QAAM,MAAM,MAAM,MAAM,GAAG,eAAe,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI;AAC9D,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,wBAAwB,IAAI,MAAM,GAAG;AAAA,EACvD;AACA,SAAQ,MAAM,IAAI,KAAK;AACzB;AAGA,eAAsB,SAAS,OAAyB,CAAC,GAA2B;AAClF,QAAM,OAAO,MAAM,YAA2B,wBAAwB,MAAM,CAAC,OAAO,CAAC;AACrF,SAAO,QAAQ,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AACvC;AAGA,eAAsB,gBACpB,YACA,SAA8C,CAAC,GAC/C,OAAyB,CAAC,GACY;AACtC,QAAM,KAAK,IAAI,gBAAgB;AAC/B,MAAI,OAAO,UAAU,OAAW,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACpE,MAAI,OAAO,WAAW,OAAW,IAAG,IAAI,UAAU,OAAO,OAAO,MAAM,CAAC;AACvE,QAAM,SAAS,GAAG,SAAS,IAAI,IAAI,GAAG,SAAS,CAAC,KAAK;AACrD,SAAO;AAAA,IACL,wBAAwB,mBAAmB,UAAU,CAAC,YAAY,MAAM;AAAA,IACxE;AAAA,IACA,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,EAChC;AACF;AAOA,eAAsB,eACpB,OAAyB,CAAC,GACgB;AAC1C,QAAM,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI;AACrC,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,MAAM,IAAI,OAAO,MAAM;AACrB,YAAM,OAAO,MAAM,gBAAgB,EAAE,QAAQ,EAAE,OAAO,IAAI,GAAG,IAAI;AACjE,cAAQ,MAAM,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,EAAE;AAAA,IAC9D,CAAC;AAAA,EACH;AACA,SAAO,MACJ,KAAK,EACL;AAAA,IACC,CAAC,GAAG,MACF,IAAI,KAAK,EAAE,gBAAgB,CAAC,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,gBAAgB,CAAC,EAAE,QAAQ;AAAA,EACpF;AACJ;AAGA,eAAsB,WACpB,YACA,eACA,OAAyB,CAAC,GACO;AACjC,SAAO;AAAA,IACL,wBAAwB,mBAAmB,UAAU,CAAC,aAAa,mBAAmB,aAAa,CAAC;AAAA,IACpG;AAAA,IACA,CAAC,SAAS,QAAQ,UAAU,IAAI,WAAW,UAAU,IAAI,aAAa,EAAE;AAAA,EAC1E;AACF;","names":[]}
@@ -0,0 +1,64 @@
1
+ // src/server/index.ts
2
+ var DEFAULT_REVALIDATE = 60;
3
+ function resolveBaseUrl(opts) {
4
+ return opts.baseUrl ?? process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ?? "http://localhost:9000";
5
+ }
6
+ function resolveKey(opts) {
7
+ return opts.publishableApiKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY;
8
+ }
9
+ async function serverFetch(path, opts, tags) {
10
+ const headers = {};
11
+ const key = resolveKey(opts);
12
+ if (key) headers["x-publishable-api-key"] = key;
13
+ const init = {
14
+ headers,
15
+ next: { revalidate: DEFAULT_REVALIDATE, tags }
16
+ };
17
+ const res = await fetch(`${resolveBaseUrl(opts)}${path}`, init);
18
+ if (res.status === 404) return null;
19
+ if (!res.ok) {
20
+ throw new Error(`Blog request failed (${res.status})`);
21
+ }
22
+ return await res.json();
23
+ }
24
+ async function getBlogs(opts = {}) {
25
+ const data = await serverFetch("/store/content/blogs", opts, ["blogs"]);
26
+ return data ?? { blogs: [], count: 0 };
27
+ }
28
+ async function getBlogArticles(blogHandle, params = {}, opts = {}) {
29
+ const qs = new URLSearchParams();
30
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
31
+ if (params.offset !== void 0) qs.set("offset", String(params.offset));
32
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
33
+ return serverFetch(
34
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles${suffix}`,
35
+ opts,
36
+ ["blogs", `blog:${blogHandle}`]
37
+ );
38
+ }
39
+ async function getAllArticles(opts = {}) {
40
+ const { blogs } = await getBlogs(opts);
41
+ const lists = await Promise.all(
42
+ blogs.map(async (b) => {
43
+ const data = await getBlogArticles(b.handle, { limit: 100 }, opts);
44
+ return (data?.articles ?? []).map((a) => ({ ...a, blog: b }));
45
+ })
46
+ );
47
+ return lists.flat().sort(
48
+ (x, y) => new Date(y.published_at ?? 0).getTime() - new Date(x.published_at ?? 0).getTime()
49
+ );
50
+ }
51
+ async function getArticle(blogHandle, articleHandle, opts = {}) {
52
+ return serverFetch(
53
+ `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,
54
+ opts,
55
+ ["blogs", `blog:${blogHandle}`, `article:${blogHandle}/${articleHandle}`]
56
+ );
57
+ }
58
+ export {
59
+ getAllArticles,
60
+ getArticle,
61
+ getBlogArticles,
62
+ getBlogs
63
+ };
64
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * Server-side fetch helpers for React Server Components.\n *\n * Used by the storefront blog route templates (page.tsx + generateMetadata).\n * No 'use client' — these run on the server, fetch the public store API,\n * and rely on Next's fetch cache (revalidate + tags) for ISR. The article\n * content returned is already sanitized server-side, so it is safe to render.\n */\n\nimport type {\n Blog,\n Article,\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from '../types'\n\nconst DEFAULT_REVALIDATE = 60\n\n// Next.js augments RequestInit with a `next` field; this package doesn't\n// depend on Next types, so we widen the type locally instead.\ntype NextFetchInit = RequestInit & {\n next?: { revalidate?: number; tags?: string[] }\n}\n\nfunction resolveBaseUrl(opts: BlogFetchOptions): string {\n return (\n opts.baseUrl ??\n process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL ??\n 'http://localhost:9000'\n )\n}\n\nfunction resolveKey(opts: BlogFetchOptions): string | undefined {\n return opts.publishableApiKey ?? process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY\n}\n\nasync function serverFetch<T>(\n path: string,\n opts: BlogFetchOptions,\n tags: string[],\n): Promise<T | null> {\n const headers: Record<string, string> = {}\n const key = resolveKey(opts)\n if (key) headers['x-publishable-api-key'] = key\n\n const init: NextFetchInit = {\n headers,\n next: { revalidate: DEFAULT_REVALIDATE, tags },\n }\n\n const res = await fetch(`${resolveBaseUrl(opts)}${path}`, init)\n if (res.status === 404) return null\n if (!res.ok) {\n throw new Error(`Blog request failed (${res.status})`)\n }\n return (await res.json()) as T\n}\n\n/** List the store's active blogs. */\nexport async function getBlogs(opts: BlogFetchOptions = {}): Promise<BlogsResponse> {\n const data = await serverFetch<BlogsResponse>('/store/content/blogs', opts, ['blogs'])\n return data ?? { blogs: [], count: 0 }\n}\n\n/** List published, visible-now articles in a blog (by handle). Null if the blog doesn't exist. */\nexport async function getBlogArticles(\n blogHandle: string,\n params: { limit?: number; offset?: number } = {},\n opts: BlogFetchOptions = {},\n): Promise<BlogArticlesResponse | null> {\n const qs = new URLSearchParams()\n if (params.limit !== undefined) qs.set('limit', String(params.limit))\n if (params.offset !== undefined) qs.set('offset', String(params.offset))\n const suffix = qs.toString() ? `?${qs.toString()}` : ''\n return serverFetch<BlogArticlesResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles${suffix}`,\n opts,\n ['blogs', `blog:${blogHandle}`],\n )\n}\n\n/**\n * Every published article across every active blog, newest first. Each item\n * carries its parent `blog` so callers can build `/blogs/:blog/:article` links.\n * Used by the top-level /blogs \"Journal\" index.\n */\nexport async function getAllArticles(\n opts: BlogFetchOptions = {},\n): Promise<Array<Article & { blog: Blog }>> {\n const { blogs } = await getBlogs(opts)\n const lists = await Promise.all(\n blogs.map(async (b) => {\n const data = await getBlogArticles(b.handle, { limit: 100 }, opts)\n return (data?.articles ?? []).map((a) => ({ ...a, blog: b }))\n }),\n )\n return lists\n .flat()\n .sort(\n (x, y) =>\n new Date(y.published_at ?? 0).getTime() - new Date(x.published_at ?? 0).getTime(),\n )\n}\n\n/** Fetch a single published article (by blog handle + article handle). Null if not found/visible. */\nexport async function getArticle(\n blogHandle: string,\n articleHandle: string,\n opts: BlogFetchOptions = {},\n): Promise<ArticleResponse | null> {\n return serverFetch<ArticleResponse>(\n `/store/content/blogs/${encodeURIComponent(blogHandle)}/articles/${encodeURIComponent(articleHandle)}`,\n opts,\n ['blogs', `blog:${blogHandle}`, `article:${blogHandle}/${articleHandle}`],\n )\n}\n\nexport type {\n Blog,\n Article,\n BlogStatus,\n ArticleStatus,\n BlogFetchOptions,\n BlogsResponse,\n BlogArticlesResponse,\n ArticleResponse,\n} from '../types'\n"],"mappings":";AAkBA,IAAM,qBAAqB;AAQ3B,SAAS,eAAe,MAAgC;AACtD,SACE,KAAK,WACL,QAAQ,IAAI,kCACZ;AAEJ;AAEA,SAAS,WAAW,MAA4C;AAC9D,SAAO,KAAK,qBAAqB,QAAQ,IAAI;AAC/C;AAEA,eAAe,YACb,MACA,MACA,MACmB;AACnB,QAAM,UAAkC,CAAC;AACzC,QAAM,MAAM,WAAW,IAAI;AAC3B,MAAI,IAAK,SAAQ,uBAAuB,IAAI;AAE5C,QAAM,OAAsB;AAAA,IAC1B;AAAA,IACA,MAAM,EAAE,YAAY,oBAAoB,KAAK;AAAA,EAC/C;AAEA,QAAM,MAAM,MAAM,MAAM,GAAG,eAAe,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI;AAC9D,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,wBAAwB,IAAI,MAAM,GAAG;AAAA,EACvD;AACA,SAAQ,MAAM,IAAI,KAAK;AACzB;AAGA,eAAsB,SAAS,OAAyB,CAAC,GAA2B;AAClF,QAAM,OAAO,MAAM,YAA2B,wBAAwB,MAAM,CAAC,OAAO,CAAC;AACrF,SAAO,QAAQ,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AACvC;AAGA,eAAsB,gBACpB,YACA,SAA8C,CAAC,GAC/C,OAAyB,CAAC,GACY;AACtC,QAAM,KAAK,IAAI,gBAAgB;AAC/B,MAAI,OAAO,UAAU,OAAW,IAAG,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACpE,MAAI,OAAO,WAAW,OAAW,IAAG,IAAI,UAAU,OAAO,OAAO,MAAM,CAAC;AACvE,QAAM,SAAS,GAAG,SAAS,IAAI,IAAI,GAAG,SAAS,CAAC,KAAK;AACrD,SAAO;AAAA,IACL,wBAAwB,mBAAmB,UAAU,CAAC,YAAY,MAAM;AAAA,IACxE;AAAA,IACA,CAAC,SAAS,QAAQ,UAAU,EAAE;AAAA,EAChC;AACF;AAOA,eAAsB,eACpB,OAAyB,CAAC,GACgB;AAC1C,QAAM,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI;AACrC,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,MAAM,IAAI,OAAO,MAAM;AACrB,YAAM,OAAO,MAAM,gBAAgB,EAAE,QAAQ,EAAE,OAAO,IAAI,GAAG,IAAI;AACjE,cAAQ,MAAM,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,EAAE;AAAA,IAC9D,CAAC;AAAA,EACH;AACA,SAAO,MACJ,KAAK,EACL;AAAA,IACC,CAAC,GAAG,MACF,IAAI,KAAK,EAAE,gBAAgB,CAAC,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,gBAAgB,CAAC,EAAE,QAAQ;AAAA,EACpF;AACJ;AAGA,eAAsB,WACpB,YACA,eACA,OAAyB,CAAC,GACO;AACjC,SAAO;AAAA,IACL,wBAAwB,mBAAmB,UAAU,CAAC,aAAa,mBAAmB,aAAa,CAAC;AAAA,IACpG;AAAA,IACA,CAAC,SAAS,QAAQ,UAAU,IAAI,WAAW,UAAU,IAAI,aAAa,EAAE;AAAA,EAC1E;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@amboras-dev/blog",
3
+ "version": "0.2.0",
4
+ "description": "Multi-blog CMS hooks + server helpers for Amboras storefronts",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./server": {
15
+ "types": "./dist/server/index.d.ts",
16
+ "import": "./dist/server/index.mjs",
17
+ "require": "./dist/server/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "peerDependencies": {
24
+ "react": ">=18",
25
+ "@tanstack/react-query": ">=5"
26
+ },
27
+ "devDependencies": {
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.0.0",
30
+ "@tanstack/react-query": "^5.0.0",
31
+ "@types/react": "^18.0.0",
32
+ "@types/node": "^20.0.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "typecheck": "tsc --noEmit"
40
+ }
41
+ }