@dsaplatform/content-sdk 1.0.0 → 1.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.
package/dist/server.d.ts CHANGED
@@ -53,10 +53,14 @@ interface ArticleListItem {
53
53
  excerpt: string | null;
54
54
  featured_image_url: string | null;
55
55
  featured_image_alt: string | null;
56
+ target_keyword: string | null;
56
57
  published_at: string | null;
57
58
  pillar_name?: string;
58
59
  cluster_name?: string;
59
60
  content_type?: string;
61
+ funnel_stage?: string;
62
+ intent?: string;
63
+ word_count: number | null;
60
64
  reading_time_minutes: number | null;
61
65
  }
62
66
  /**
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var l=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var m=(e,t)=>{for(var i in t)l(e,i,{get:t[i],enumerable:!0})},d=(e,t,i,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of g(t))!u.call(e,r)&&r!==i&&l(e,r,{get:()=>t[r],enumerable:!(a=p(t,r))||a.enumerable});return e};var y=e=>d(l({},"__esModule",{value:!0}),e);var P={};m(P,{fetchArticleBySlug:()=>h,fetchArticleList:()=>f,fetchCategories:()=>C,fetchRelatedArticles:()=>A,fetchSitemap:()=>_,generateArticleMetadata:()=>S});module.exports=y(P);var n=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,i){let a=new URL(`${this.apiUrl}${t}`);i&&Object.entries(i).forEach(([o,c])=>{c!=null&&c!==""&&a.searchParams.set(o,String(c))}),a.searchParams.set("site_key",this.apiKey);let r={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(r.next={revalidate:this.revalidateSeconds});let s=await fetch(a.toString(),r);if(!s.ok){let o=await s.text().catch(()=>"");throw new Error(`DSA Content API error ${s.status}: ${o||s.statusText}`)}return s.json()}async getArticles(t){return this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search})}async getArticleBySlug(t){return this.request(`/api/public/articles/${encodeURIComponent(t)}`)}async getRelatedArticles(t,i=3){return this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:i})}async getCategories(){return this.request("/api/public/categories")}async getSitemap(){return this.request("/api/public/sitemap")}};async function h(e,t){return new n(e).getArticleBySlug(t)}async function f(e,t){return new n(e).getArticles(t)}async function A(e,t,i=3){return new n(e).getRelatedArticles(t,i)}async function C(e){return new n(e).getCategories()}async function _(e){return new n(e).getSitemap()}function S(e,t){let i=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...i?{url:i}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:i?{alternates:{canonical:i}}:{}}}0&&(module.exports={fetchArticleBySlug,fetchArticleList,fetchCategories,fetchRelatedArticles,fetchSitemap,generateArticleMetadata});
1
+ "use strict";var l=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var d=(t,e)=>{for(var i in e)l(t,i,{get:e[i],enumerable:!0})},m=(t,e,i,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of g(e))!u.call(t,n)&&n!==i&&l(t,n,{get:()=>e[n],enumerable:!(r=p(e,n))||r.enumerable});return t};var y=t=>m(l({},"__esModule",{value:!0}),t);var w={};d(w,{fetchArticleBySlug:()=>h,fetchArticleList:()=>A,fetchCategories:()=>f,fetchRelatedArticles:()=>_,fetchSitemap:()=>C,generateArticleMetadata:()=>S});module.exports=y(w);var a=class{constructor(e){this.apiUrl=e.apiUrl.replace(/\/+$/,""),this.apiKey=e.apiKey,this.cacheStrategy=e.cacheStrategy==="revalidate"?"default":e.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=e.revalidateSeconds}async request(e,i){let r=new URL(`${this.apiUrl}${e}`);i&&Object.entries(i).forEach(([o,c])=>{c!=null&&c!==""&&r.searchParams.set(o,String(c))}),r.searchParams.set("site_key",this.apiKey);let n={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(n.next={revalidate:this.revalidateSeconds});let s=await fetch(r.toString(),n);if(!s.ok){let o=await s.text().catch(()=>"");throw new Error(`DSA Content API error ${s.status}: ${o||s.statusText}`)}return s.json()}normalizeArticle(e){return{...e,headings:e.headings??[],faq:e.faq??[],internal_links:e.internal_links??[],secondary_keywords:e.secondary_keywords??[],schema_json:e.schema_json??null,content_json:e.content_json??null}}async getArticles(e){let i=await this.request("/api/public/articles",{page:e?.page,per_page:e?.per_page,pillar:e?.pillar,cluster:e?.cluster,content_type:e?.content_type,search:e?.search});return{items:i.items??i.data??[],total:i.total??0,page:i.page??1,per_page:i.per_page??20,total_pages:i.total_pages??i.pages??1}}async getArticleBySlug(e){let i=await this.request(`/api/public/articles/${encodeURIComponent(e)}`);return this.normalizeArticle(i)}async getRelatedArticles(e,i=3){let r=await this.request(`/api/public/articles/${encodeURIComponent(e)}/related`,{limit:i});return r.items??(Array.isArray(r)?r:[])}async getCategories(){let e=await this.request("/api/public/categories");return e.items??(Array.isArray(e)?e:[])}async getSitemap(){let e=await this.request("/api/public/sitemap");return e.items??(Array.isArray(e)?e:[])}};async function h(t,e){return new a(t).getArticleBySlug(e)}async function A(t,e){return new a(t).getArticles(e)}async function _(t,e,i=3){return new a(t).getRelatedArticles(e,i)}async function f(t){return new a(t).getCategories()}async function C(t){return new a(t).getSitemap()}function S(t,e){let i=e?`${e.replace(/\/+$/,"")}/blog/${t.slug}`:void 0;return{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",openGraph:{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",type:"article",publishedTime:t.published_at||void 0,modifiedTime:t.updated_at||void 0,...i?{url:i}:{},...t.featured_image_url?{images:[{url:t.featured_image_url,alt:t.featured_image_alt||t.title}]}:{}},twitter:{card:"summary_large_image",title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",...t.featured_image_url?{images:[t.featured_image_url]}:{}},...t.canonical_url?{alternates:{canonical:t.canonical_url}}:i?{alternates:{canonical:i}}:{}}}0&&(module.exports={fetchArticleBySlug,fetchArticleList,fetchCategories,fetchRelatedArticles,fetchSitemap,generateArticleMetadata});
2
2
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/client.ts"],"sourcesContent":["/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\n","import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n return this.request<PaginatedResponse<ArticleListItem>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n return this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n return this.request<ArticleListItem[]>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n return this.request<Category[]>('/api/public/categories');\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n return this.request<SitemapEntry[]>('/api/public/sitemap');\n }\n}\n"],"mappings":"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","names":["server_exports","__export","fetchArticleBySlug","fetchArticleList","fetchCategories","fetchRelatedArticles","fetchSitemap","generateArticleMetadata","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","filters","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/client.ts"],"sourcesContent":["/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\n","import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n"],"mappings":"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","names":["server_exports","__export","fetchArticleBySlug","fetchArticleList","fetchCategories","fetchRelatedArticles","fetchSitemap","generateArticleMetadata","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
package/dist/server.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var r=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,i){let a=new URL(`${this.apiUrl}${t}`);i&&Object.entries(i).forEach(([c,s])=>{s!=null&&s!==""&&a.searchParams.set(c,String(s))}),a.searchParams.set("site_key",this.apiKey);let o={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(o.next={revalidate:this.revalidateSeconds});let n=await fetch(a.toString(),o);if(!n.ok){let c=await n.text().catch(()=>"");throw new Error(`DSA Content API error ${n.status}: ${c||n.statusText}`)}return n.json()}async getArticles(t){return this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search})}async getArticleBySlug(t){return this.request(`/api/public/articles/${encodeURIComponent(t)}`)}async getRelatedArticles(t,i=3){return this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:i})}async getCategories(){return this.request("/api/public/categories")}async getSitemap(){return this.request("/api/public/sitemap")}};async function g(e,t){return new r(e).getArticleBySlug(t)}async function u(e,t){return new r(e).getArticles(t)}async function m(e,t,i=3){return new r(e).getRelatedArticles(t,i)}async function d(e){return new r(e).getCategories()}async function y(e){return new r(e).getSitemap()}function h(e,t){let i=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...i?{url:i}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:i?{alternates:{canonical:i}}:{}}}export{g as fetchArticleBySlug,u as fetchArticleList,d as fetchCategories,m as fetchRelatedArticles,y as fetchSitemap,h as generateArticleMetadata};
1
+ var n=class{constructor(e){this.apiUrl=e.apiUrl.replace(/\/+$/,""),this.apiKey=e.apiKey,this.cacheStrategy=e.cacheStrategy==="revalidate"?"default":e.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=e.revalidateSeconds}async request(e,i){let r=new URL(`${this.apiUrl}${e}`);i&&Object.entries(i).forEach(([c,s])=>{s!=null&&s!==""&&r.searchParams.set(c,String(s))}),r.searchParams.set("site_key",this.apiKey);let o={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(o.next={revalidate:this.revalidateSeconds});let a=await fetch(r.toString(),o);if(!a.ok){let c=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${c||a.statusText}`)}return a.json()}normalizeArticle(e){return{...e,headings:e.headings??[],faq:e.faq??[],internal_links:e.internal_links??[],secondary_keywords:e.secondary_keywords??[],schema_json:e.schema_json??null,content_json:e.content_json??null}}async getArticles(e){let i=await this.request("/api/public/articles",{page:e?.page,per_page:e?.per_page,pillar:e?.pillar,cluster:e?.cluster,content_type:e?.content_type,search:e?.search});return{items:i.items??i.data??[],total:i.total??0,page:i.page??1,per_page:i.per_page??20,total_pages:i.total_pages??i.pages??1}}async getArticleBySlug(e){let i=await this.request(`/api/public/articles/${encodeURIComponent(e)}`);return this.normalizeArticle(i)}async getRelatedArticles(e,i=3){let r=await this.request(`/api/public/articles/${encodeURIComponent(e)}/related`,{limit:i});return r.items??(Array.isArray(r)?r:[])}async getCategories(){let e=await this.request("/api/public/categories");return e.items??(Array.isArray(e)?e:[])}async getSitemap(){let e=await this.request("/api/public/sitemap");return e.items??(Array.isArray(e)?e:[])}};async function g(t,e){return new n(t).getArticleBySlug(e)}async function u(t,e){return new n(t).getArticles(e)}async function d(t,e,i=3){return new n(t).getRelatedArticles(e,i)}async function m(t){return new n(t).getCategories()}async function y(t){return new n(t).getSitemap()}function h(t,e){let i=e?`${e.replace(/\/+$/,"")}/blog/${t.slug}`:void 0;return{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",openGraph:{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",type:"article",publishedTime:t.published_at||void 0,modifiedTime:t.updated_at||void 0,...i?{url:i}:{},...t.featured_image_url?{images:[{url:t.featured_image_url,alt:t.featured_image_alt||t.title}]}:{}},twitter:{card:"summary_large_image",title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",...t.featured_image_url?{images:[t.featured_image_url]}:{}},...t.canonical_url?{alternates:{canonical:t.canonical_url}}:i?{alternates:{canonical:i}}:{}}}export{g as fetchArticleBySlug,u as fetchArticleList,m as fetchCategories,d as fetchRelatedArticles,y as fetchSitemap,h as generateArticleMetadata};
2
2
  //# sourceMappingURL=server.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/server.ts"],"sourcesContent":["import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n return this.request<PaginatedResponse<ArticleListItem>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n return this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n return this.request<ArticleListItem[]>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n return this.request<Category[]>('/api/public/categories');\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n return this.request<SitemapEntry[]>('/api/public/sitemap');\n }\n}\n","/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","filters","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/server.ts"],"sourcesContent":["import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsaplatform/content-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "React SDK for DSA Content Operating System — fetch and render SEO content from central engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -15,6 +15,11 @@
15
15
  "types": "./dist/server.d.ts",
16
16
  "import": "./dist/server.mjs",
17
17
  "require": "./dist/server.js"
18
+ },
19
+ "./headless": {
20
+ "types": "./dist/headless.d.ts",
21
+ "import": "./dist/headless.mjs",
22
+ "require": "./dist/headless.js"
18
23
  }
19
24
  },
20
25
  "files": [