@census-ai/census-sdk 0.4.6 → 0.5.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/content/index.cjs +22 -0
- package/dist/content/index.cjs.map +1 -0
- package/dist/content/index.d.cts +240 -0
- package/dist/content/index.d.ts +240 -0
- package/dist/content/index.js +22 -0
- package/dist/content/index.js.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +7 -7
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +6 -1
- package/dist/react/index.d.ts +6 -1
- package/dist/react/index.js +7 -7
- package/dist/react/index.js.map +1 -1
- package/dist/server/server.cjs +2 -2
- package/dist/server/server.cjs.map +1 -1
- package/dist/server/server.js +2 -2
- package/dist/server/server.js.map +1 -1
- package/package.json +12 -5
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';var m="https://api.census.ai",c=class{constructor(e){this.apiKey=e.apiKey,this.baseUrl=(e.baseUrl||m).replace(/\/$/,""),this.timeout=e.timeout??3e4;}async getBlogPosts(e){let t=new URLSearchParams;e?.limit&&t.set("limit",String(e.limit)),e?.offset&&t.set("offset",String(e.offset)),e?.tag&&t.set("tag",e.tag);let s=t.toString();return this.request(`/api/sdk/blog-posts${s?`?${s}`:""}`)}async getBlogPost(e){try{return (await this.request(`/api/sdk/blog-posts/${encodeURIComponent(e)}`)).post}catch(t){if(t&&typeof t=="object"&&"status"in t&&t.status===404)return null;throw t}}async getBlogTags(){return (await this.request("/api/sdk/blog-posts/tags")).tags}async getHelpArticles(e){let t=new URLSearchParams;e?.category&&t.set("category",e.category),e?.search&&t.set("search",e.search),e?.limit&&t.set("limit",String(e.limit));let s=t.toString(),i=await this.request(`/api/sdk/articles${s?`?${s}`:""}`);return {articles:i.articles,total:i.pagination.total}}async getHelpArticle(e){try{return (await this.request(`/api/sdk/articles/${encodeURIComponent(e)}`)).article}catch(t){if(t&&typeof t=="object"&&"status"in t&&t.status===404)return null;throw t}}async getHelpCategories(){return (await this.request("/api/sdk/feature-groups")).feature_groups.map(t=>({id:t.id,name:t.name,slug:t.slug,description:t.description,article_count:t.article_count}))}async getSitemapEntries(){return (await this.request("/api/sdk/content/sitemap")).entries}async getRSSFeed(){return (await this.rawRequest("/api/sdk/content/rss")).text()}async request(e){return (await this.rawRequest(e)).json()}async rawRequest(e){let t=new AbortController,s=setTimeout(()=>t.abort(),this.timeout);try{let i=await fetch(`${this.baseUrl}${e}`,{headers:{"X-Census-Key":this.apiKey},signal:t.signal});if(!i.ok){let a={error:`Content request failed: ${i.status}`,status:i.status};try{let l=await i.json();a.error=l.error||a.error;}catch{}throw a}return i}finally{clearTimeout(s);}}};function d(r,e){let t=e?.siteUrl||"",s=e?.siteName||"",i=r.seo_title||r.title,a=r.seo_description||"",l=r.canonical_url||`${t}/blog/${r.slug}`;return {title:i,description:a,alternates:{canonical:l},openGraph:{title:i,description:a,url:l,type:"article",publishedTime:r.published_at||void 0,modifiedTime:r.updated_at||void 0,authors:r.author_name?[r.author_name]:void 0,images:r.og_image_url?[{url:r.og_image_url,width:1200,height:630}]:void 0,...s?{siteName:s}:{}},twitter:{card:"summary_large_image",title:i,description:a,images:r.og_image_url?[r.og_image_url]:void 0}}}function y(r,e){let t=e?.siteUrl||"",s=e?.siteName||"",i=r.seo_title||r.title,a=r.seo_description||"",l=`${t}/help/${r.slug}`;return {title:i,description:a,alternates:{canonical:l},openGraph:{title:i,description:a,url:l,type:"article",...s?{siteName:s}:{}}}}async function f(r,e){try{return (await r.getSitemapEntries()).map(s=>({url:s.url.startsWith("http")?s.url:`${e}${s.url}`,lastModified:s.lastmod,changeFrequency:s.changefreq,priority:s.priority}))}catch{return []}}function h(r,e){let{title:t,description:s,siteUrl:i,feedUrl:a,language:l="en-us"}=e,o=n=>n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),u=r.filter(n=>n.published_at).map(n=>{let g=`${i}/blog/${n.slug}`;return ` <item>
|
|
2
|
+
<title>${o(n.title)}</title>
|
|
3
|
+
<link>${o(g)}</link>
|
|
4
|
+
<guid isPermaLink="true">${o(g)}</guid>
|
|
5
|
+
<description>${o(n.seo_description||"")}</description>
|
|
6
|
+
<pubDate>${new Date(n.published_at).toUTCString()}</pubDate>${n.author_name?`
|
|
7
|
+
<dc:creator>${o(n.author_name)}</dc:creator>`:""}${n.tags?.length?n.tags.map(p=>`
|
|
8
|
+
<category>${o(p)}</category>`).join(""):""}
|
|
9
|
+
</item>`}).join(`
|
|
10
|
+
`);return `<?xml version="1.0" encoding="UTF-8"?>
|
|
11
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
12
|
+
<channel>
|
|
13
|
+
<title>${o(t)}</title>
|
|
14
|
+
<link>${o(i)}</link>
|
|
15
|
+
<description>${o(s)}</description>
|
|
16
|
+
<language>${l}</language>
|
|
17
|
+
<atom:link href="${o(a)}" rel="self" type="application/rss+xml"/>
|
|
18
|
+
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
|
19
|
+
${u}
|
|
20
|
+
</channel>
|
|
21
|
+
</rss>`}function P(r){return new c(r)}exports.CensusContentClient=c;exports.createCensusContent=P;exports.generateArticleMetadata=y;exports.generateBlogMetadata=d;exports.generateContentSitemap=f;exports.generateRSSFeed=h;//# sourceMappingURL=index.cjs.map
|
|
22
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/content/client.ts","../../src/content/helpers/metadata.ts","../../src/content/helpers/sitemap.ts","../../src/content/helpers/rss.ts","../../src/content/index.ts"],"names":["DEFAULT_BASE_URL","CensusContentClient","config","options","params","qs","slug","err","data","g","path","controller","timeoutId","res","error","body","generateBlogMetadata","post","siteUrl","siteName","title","description","canonical","generateArticleMetadata","article","generateContentSitemap","client","entry","generateRSSFeed","posts","feedUrl","language","escapeXml","str","items","p","link","t","createCensusContent"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n ContentConfig,\n BlogPost,\n BlogPostsResponse,\n HelpArticle,\n HelpArticlesResponse,\n HelpCategory,\n SitemapEntry,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://api.census.ai';\n\n/**\n * Server-side content client for fetching blog posts and help articles.\n * Designed for use in Next.js `generateStaticParams`, `generateMetadata`, and server components.\n */\nexport class CensusContentClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: ContentConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.timeout = config.timeout ?? 30_000;\n }\n\n // ── Blog ────────────────────────────────────────────────────────────\n\n async getBlogPosts(options?: {\n limit?: number;\n offset?: number;\n tag?: string;\n }): Promise<BlogPostsResponse> {\n const params = new URLSearchParams();\n if (options?.limit) params.set('limit', String(options.limit));\n if (options?.offset) params.set('offset', String(options.offset));\n if (options?.tag) params.set('tag', options.tag);\n\n const qs = params.toString();\n return this.request<BlogPostsResponse>(\n `/api/sdk/blog-posts${qs ? `?${qs}` : ''}`\n );\n }\n\n async getBlogPost(slug: string): Promise<BlogPost | null> {\n try {\n const data = await this.request<{ post: BlogPost }>(\n `/api/sdk/blog-posts/${encodeURIComponent(slug)}`\n );\n return data.post;\n } catch (err) {\n if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 404) {\n return null;\n }\n throw err;\n }\n }\n\n async getBlogTags(): Promise<string[]> {\n const data = await this.request<{ tags: string[] }>('/api/sdk/blog-posts/tags');\n return data.tags;\n }\n\n // ── Help Articles ───────────────────────────────────────────────────\n\n async getHelpArticles(options?: {\n category?: string;\n search?: string;\n limit?: number;\n }): Promise<HelpArticlesResponse> {\n const params = new URLSearchParams();\n if (options?.category) params.set('category', options.category);\n if (options?.search) params.set('search', options.search);\n if (options?.limit) params.set('limit', String(options.limit));\n\n const qs = params.toString();\n const data = await this.request<{\n articles: HelpArticle[];\n pagination: { total: number };\n }>(`/api/sdk/articles${qs ? `?${qs}` : ''}`);\n\n return {\n articles: data.articles,\n total: data.pagination.total,\n };\n }\n\n async getHelpArticle(slug: string): Promise<HelpArticle | null> {\n try {\n const data = await this.request<{ article: HelpArticle }>(\n `/api/sdk/articles/${encodeURIComponent(slug)}`\n );\n return data.article;\n } catch (err) {\n if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 404) {\n return null;\n }\n throw err;\n }\n }\n\n async getHelpCategories(): Promise<HelpCategory[]> {\n const data = await this.request<{ feature_groups: HelpCategory[] }>(\n '/api/sdk/feature-groups'\n );\n return data.feature_groups.map((g) => ({\n id: g.id,\n name: g.name,\n slug: g.slug,\n description: g.description,\n article_count: g.article_count,\n }));\n }\n\n // ── SEO ─────────────────────────────────────────────────────────────\n\n async getSitemapEntries(): Promise<SitemapEntry[]> {\n const data = await this.request<{ entries: SitemapEntry[] }>(\n '/api/sdk/content/sitemap'\n );\n return data.entries;\n }\n\n async getRSSFeed(): Promise<string> {\n const res = await this.rawRequest('/api/sdk/content/rss');\n return res.text();\n }\n\n // ── Internal ────────────────────────────────────────────────────────\n\n private async request<T>(path: string): Promise<T> {\n const res = await this.rawRequest(path);\n return res.json();\n }\n\n private async rawRequest(path: string): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}${path}`, {\n headers: {\n 'X-Census-Key': this.apiKey,\n },\n signal: controller.signal,\n });\n\n if (!res.ok) {\n const error: { error: string; status: number } = {\n error: `Content request failed: ${res.status}`,\n status: res.status,\n };\n try {\n const body = await res.json();\n error.error = body.error || error.error;\n } catch {\n // use default\n }\n throw error;\n }\n\n return res;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n}\n","import type { BlogPost, HelpArticle } from '../types';\n\n/**\n * Generate Next.js Metadata object for a blog post.\n * Use in `generateMetadata()` in your blog/[slug]/page.tsx.\n */\nexport function generateBlogMetadata(\n post: BlogPost,\n options?: { siteUrl?: string; siteName?: string }\n) {\n const siteUrl = options?.siteUrl || '';\n const siteName = options?.siteName || '';\n const title = post.seo_title || post.title;\n const description = post.seo_description || '';\n const canonical = post.canonical_url || `${siteUrl}/blog/${post.slug}`;\n\n return {\n title,\n description,\n alternates: { canonical },\n openGraph: {\n title,\n description,\n url: canonical,\n type: 'article' as const,\n publishedTime: post.published_at || undefined,\n modifiedTime: post.updated_at || undefined,\n authors: post.author_name ? [post.author_name] : undefined,\n images: post.og_image_url ? [{ url: post.og_image_url, width: 1200, height: 630 }] : undefined,\n ...(siteName ? { siteName } : {}),\n },\n twitter: {\n card: 'summary_large_image' as const,\n title,\n description,\n images: post.og_image_url ? [post.og_image_url] : undefined,\n },\n };\n}\n\n/**\n * Generate Next.js Metadata object for a help article.\n */\nexport function generateArticleMetadata(\n article: HelpArticle,\n options?: { siteUrl?: string; siteName?: string }\n) {\n const siteUrl = options?.siteUrl || '';\n const siteName = options?.siteName || '';\n const title = article.seo_title || article.title;\n const description = article.seo_description || '';\n const canonical = `${siteUrl}/help/${article.slug}`;\n\n return {\n title,\n description,\n alternates: { canonical },\n openGraph: {\n title,\n description,\n url: canonical,\n type: 'article' as const,\n ...(siteName ? { siteName } : {}),\n },\n };\n}\n","import type { SitemapEntry } from '../types';\nimport type { CensusContentClient } from '../client';\n\n/**\n * Fetch Census content URLs and return them in Next.js sitemap format.\n * Merge this with your static sitemap entries.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { censusContent } from '@/lib/census'\n * import { generateContentSitemap } from '@census-ai/census-sdk/content'\n *\n * export default async function sitemap() {\n * const contentEntries = await generateContentSitemap(censusContent, 'https://everre.co')\n * return [...staticEntries, ...contentEntries]\n * }\n * ```\n */\nexport async function generateContentSitemap(\n client: CensusContentClient,\n siteUrl: string\n): Promise<Array<{\n url: string;\n lastModified?: string;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}>> {\n try {\n const entries = await client.getSitemapEntries();\n return entries.map((entry: SitemapEntry) => ({\n url: entry.url.startsWith('http') ? entry.url : `${siteUrl}${entry.url}`,\n lastModified: entry.lastmod,\n changeFrequency: entry.changefreq,\n priority: entry.priority,\n }));\n } catch {\n // Return empty on error so sitemap generation doesn't fail\n return [];\n }\n}\n","import type { BlogPost } from '../types';\n\n/**\n * Generate an RSS 2.0 XML feed from blog posts.\n *\n * @example\n * ```ts\n * // app/blog/rss.xml/route.ts\n * import { censusContent } from '@/lib/census'\n * import { generateRSSFeed } from '@census-ai/census-sdk/content'\n *\n * export async function GET() {\n * const { posts } = await censusContent.getBlogPosts({ limit: 50 })\n * const xml = generateRSSFeed(posts, {\n * title: 'Everre Blog',\n * description: 'CRE insights',\n * siteUrl: 'https://everre.co',\n * feedUrl: 'https://everre.co/blog/rss.xml',\n * })\n * return new Response(xml, {\n * headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },\n * })\n * }\n * ```\n */\nexport function generateRSSFeed(\n posts: BlogPost[],\n options: {\n title: string;\n description: string;\n siteUrl: string;\n feedUrl: string;\n language?: string;\n }\n): string {\n const { title, description, siteUrl, feedUrl, language = 'en-us' } = options;\n\n const escapeXml = (str: string) =>\n str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\n const items = posts\n .filter((p) => p.published_at)\n .map((post) => {\n const link = `${siteUrl}/blog/${post.slug}`;\n return ` <item>\n <title>${escapeXml(post.title)}</title>\n <link>${escapeXml(link)}</link>\n <guid isPermaLink=\"true\">${escapeXml(link)}</guid>\n <description>${escapeXml(post.seo_description || '')}</description>\n <pubDate>${new Date(post.published_at!).toUTCString()}</pubDate>${\n post.author_name\n ? `\\n <dc:creator>${escapeXml(post.author_name)}</dc:creator>`\n : ''\n }${\n post.tags?.length\n ? post.tags.map((t) => `\\n <category>${escapeXml(t)}</category>`).join('')\n : ''\n }\n </item>`;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n <channel>\n <title>${escapeXml(title)}</title>\n <link>${escapeXml(siteUrl)}</link>\n <description>${escapeXml(description)}</description>\n <language>${language}</language>\n <atom:link href=\"${escapeXml(feedUrl)}\" rel=\"self\" type=\"application/rss+xml\"/>\n <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>\n${items}\n </channel>\n</rss>`;\n}\n","/**\n * @census-ai/census-sdk/content - Server-side content fetcher for SEO\n *\n * Use this module to fetch blog posts and help articles from Census\n * for server-side rendering in Next.js or similar frameworks.\n *\n * @example\n * ```typescript\n * import { createCensusContent } from '@census-ai/census-sdk/content';\n *\n * const content = createCensusContent({\n * apiKey: process.env.CENSUS_API_KEY!,\n * baseUrl: 'https://api.census.ai',\n * });\n *\n * // Fetch blog posts for SSR\n * const { posts } = await content.getBlogPosts({ limit: 20 });\n *\n * // Fetch a single post with full SEO metadata\n * const post = await content.getBlogPost('my-post-slug');\n *\n * // Generate sitemap entries\n * const entries = await content.getSitemapEntries();\n * ```\n */\n\nexport { CensusContentClient } from './client';\nexport type {\n ContentConfig,\n BlogPost,\n BlogPostsResponse,\n HelpArticle,\n HelpArticlesResponse,\n HelpCategory,\n SitemapEntry,\n} from './types';\n\nexport { generateBlogMetadata, generateArticleMetadata } from './helpers/metadata';\nexport { generateContentSitemap } from './helpers/sitemap';\nexport { generateRSSFeed } from './helpers/rss';\n\nimport { CensusContentClient } from './client';\nimport type { ContentConfig } from './types';\n\n/**\n * Create a new Census Content client for server-side content fetching.\n */\nexport function createCensusContent(config: ContentConfig): CensusContentClient {\n return new CensusContentClient(config);\n}\n"]}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Census Content Module
|
|
3
|
+
*/
|
|
4
|
+
interface BlogPost {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
content: string | null;
|
|
9
|
+
content_html: string | null;
|
|
10
|
+
seo_title: string | null;
|
|
11
|
+
seo_description: string | null;
|
|
12
|
+
og_image_url: string | null;
|
|
13
|
+
canonical_url: string | null;
|
|
14
|
+
category: string | null;
|
|
15
|
+
tags: string[];
|
|
16
|
+
author_name: string | null;
|
|
17
|
+
author_avatar_url: string | null;
|
|
18
|
+
read_time_minutes: number | null;
|
|
19
|
+
published_at: string | null;
|
|
20
|
+
updated_at: string | null;
|
|
21
|
+
sort_order: number;
|
|
22
|
+
}
|
|
23
|
+
interface HelpArticle {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
content: string | null;
|
|
28
|
+
content_html: string | null;
|
|
29
|
+
seo_title: string | null;
|
|
30
|
+
seo_description: string | null;
|
|
31
|
+
category: string | null;
|
|
32
|
+
read_time_minutes: number | null;
|
|
33
|
+
published_at: string | null;
|
|
34
|
+
updated_at: string | null;
|
|
35
|
+
sort_order: number;
|
|
36
|
+
features?: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
slug: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface HelpCategory {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
description: string | null;
|
|
47
|
+
article_count: number;
|
|
48
|
+
}
|
|
49
|
+
interface SitemapEntry {
|
|
50
|
+
url: string;
|
|
51
|
+
lastmod?: string;
|
|
52
|
+
changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
53
|
+
priority?: number;
|
|
54
|
+
}
|
|
55
|
+
interface BlogPostsResponse {
|
|
56
|
+
posts: BlogPost[];
|
|
57
|
+
total: number;
|
|
58
|
+
}
|
|
59
|
+
interface HelpArticlesResponse {
|
|
60
|
+
articles: HelpArticle[];
|
|
61
|
+
total: number;
|
|
62
|
+
}
|
|
63
|
+
interface ContentConfig {
|
|
64
|
+
apiKey: string;
|
|
65
|
+
baseUrl?: string;
|
|
66
|
+
/** Request timeout in ms @default 30000 */
|
|
67
|
+
timeout?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Server-side content client for fetching blog posts and help articles.
|
|
72
|
+
* Designed for use in Next.js `generateStaticParams`, `generateMetadata`, and server components.
|
|
73
|
+
*/
|
|
74
|
+
declare class CensusContentClient {
|
|
75
|
+
private readonly apiKey;
|
|
76
|
+
private readonly baseUrl;
|
|
77
|
+
private readonly timeout;
|
|
78
|
+
constructor(config: ContentConfig);
|
|
79
|
+
getBlogPosts(options?: {
|
|
80
|
+
limit?: number;
|
|
81
|
+
offset?: number;
|
|
82
|
+
tag?: string;
|
|
83
|
+
}): Promise<BlogPostsResponse>;
|
|
84
|
+
getBlogPost(slug: string): Promise<BlogPost | null>;
|
|
85
|
+
getBlogTags(): Promise<string[]>;
|
|
86
|
+
getHelpArticles(options?: {
|
|
87
|
+
category?: string;
|
|
88
|
+
search?: string;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}): Promise<HelpArticlesResponse>;
|
|
91
|
+
getHelpArticle(slug: string): Promise<HelpArticle | null>;
|
|
92
|
+
getHelpCategories(): Promise<HelpCategory[]>;
|
|
93
|
+
getSitemapEntries(): Promise<SitemapEntry[]>;
|
|
94
|
+
getRSSFeed(): Promise<string>;
|
|
95
|
+
private request;
|
|
96
|
+
private rawRequest;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate Next.js Metadata object for a blog post.
|
|
101
|
+
* Use in `generateMetadata()` in your blog/[slug]/page.tsx.
|
|
102
|
+
*/
|
|
103
|
+
declare function generateBlogMetadata(post: BlogPost, options?: {
|
|
104
|
+
siteUrl?: string;
|
|
105
|
+
siteName?: string;
|
|
106
|
+
}): {
|
|
107
|
+
title: string;
|
|
108
|
+
description: string;
|
|
109
|
+
alternates: {
|
|
110
|
+
canonical: string;
|
|
111
|
+
};
|
|
112
|
+
openGraph: {
|
|
113
|
+
siteName?: string | undefined;
|
|
114
|
+
title: string;
|
|
115
|
+
description: string;
|
|
116
|
+
url: string;
|
|
117
|
+
type: "article";
|
|
118
|
+
publishedTime: string | undefined;
|
|
119
|
+
modifiedTime: string | undefined;
|
|
120
|
+
authors: string[] | undefined;
|
|
121
|
+
images: {
|
|
122
|
+
url: string;
|
|
123
|
+
width: number;
|
|
124
|
+
height: number;
|
|
125
|
+
}[] | undefined;
|
|
126
|
+
};
|
|
127
|
+
twitter: {
|
|
128
|
+
card: "summary_large_image";
|
|
129
|
+
title: string;
|
|
130
|
+
description: string;
|
|
131
|
+
images: string[] | undefined;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Generate Next.js Metadata object for a help article.
|
|
136
|
+
*/
|
|
137
|
+
declare function generateArticleMetadata(article: HelpArticle, options?: {
|
|
138
|
+
siteUrl?: string;
|
|
139
|
+
siteName?: string;
|
|
140
|
+
}): {
|
|
141
|
+
title: string;
|
|
142
|
+
description: string;
|
|
143
|
+
alternates: {
|
|
144
|
+
canonical: string;
|
|
145
|
+
};
|
|
146
|
+
openGraph: {
|
|
147
|
+
siteName?: string | undefined;
|
|
148
|
+
title: string;
|
|
149
|
+
description: string;
|
|
150
|
+
url: string;
|
|
151
|
+
type: "article";
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Fetch Census content URLs and return them in Next.js sitemap format.
|
|
157
|
+
* Merge this with your static sitemap entries.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* // app/sitemap.ts
|
|
162
|
+
* import { censusContent } from '@/lib/census'
|
|
163
|
+
* import { generateContentSitemap } from '@census-ai/census-sdk/content'
|
|
164
|
+
*
|
|
165
|
+
* export default async function sitemap() {
|
|
166
|
+
* const contentEntries = await generateContentSitemap(censusContent, 'https://everre.co')
|
|
167
|
+
* return [...staticEntries, ...contentEntries]
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function generateContentSitemap(client: CensusContentClient, siteUrl: string): Promise<Array<{
|
|
172
|
+
url: string;
|
|
173
|
+
lastModified?: string;
|
|
174
|
+
changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
175
|
+
priority?: number;
|
|
176
|
+
}>>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate an RSS 2.0 XML feed from blog posts.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* // app/blog/rss.xml/route.ts
|
|
184
|
+
* import { censusContent } from '@/lib/census'
|
|
185
|
+
* import { generateRSSFeed } from '@census-ai/census-sdk/content'
|
|
186
|
+
*
|
|
187
|
+
* export async function GET() {
|
|
188
|
+
* const { posts } = await censusContent.getBlogPosts({ limit: 50 })
|
|
189
|
+
* const xml = generateRSSFeed(posts, {
|
|
190
|
+
* title: 'Everre Blog',
|
|
191
|
+
* description: 'CRE insights',
|
|
192
|
+
* siteUrl: 'https://everre.co',
|
|
193
|
+
* feedUrl: 'https://everre.co/blog/rss.xml',
|
|
194
|
+
* })
|
|
195
|
+
* return new Response(xml, {
|
|
196
|
+
* headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },
|
|
197
|
+
* })
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
declare function generateRSSFeed(posts: BlogPost[], options: {
|
|
202
|
+
title: string;
|
|
203
|
+
description: string;
|
|
204
|
+
siteUrl: string;
|
|
205
|
+
feedUrl: string;
|
|
206
|
+
language?: string;
|
|
207
|
+
}): string;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @census-ai/census-sdk/content - Server-side content fetcher for SEO
|
|
211
|
+
*
|
|
212
|
+
* Use this module to fetch blog posts and help articles from Census
|
|
213
|
+
* for server-side rendering in Next.js or similar frameworks.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* import { createCensusContent } from '@census-ai/census-sdk/content';
|
|
218
|
+
*
|
|
219
|
+
* const content = createCensusContent({
|
|
220
|
+
* apiKey: process.env.CENSUS_API_KEY!,
|
|
221
|
+
* baseUrl: 'https://api.census.ai',
|
|
222
|
+
* });
|
|
223
|
+
*
|
|
224
|
+
* // Fetch blog posts for SSR
|
|
225
|
+
* const { posts } = await content.getBlogPosts({ limit: 20 });
|
|
226
|
+
*
|
|
227
|
+
* // Fetch a single post with full SEO metadata
|
|
228
|
+
* const post = await content.getBlogPost('my-post-slug');
|
|
229
|
+
*
|
|
230
|
+
* // Generate sitemap entries
|
|
231
|
+
* const entries = await content.getSitemapEntries();
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create a new Census Content client for server-side content fetching.
|
|
237
|
+
*/
|
|
238
|
+
declare function createCensusContent(config: ContentConfig): CensusContentClient;
|
|
239
|
+
|
|
240
|
+
export { type BlogPost, type BlogPostsResponse, CensusContentClient, type ContentConfig, type HelpArticle, type HelpArticlesResponse, type HelpCategory, type SitemapEntry, createCensusContent, generateArticleMetadata, generateBlogMetadata, generateContentSitemap, generateRSSFeed };
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Census Content Module
|
|
3
|
+
*/
|
|
4
|
+
interface BlogPost {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
content: string | null;
|
|
9
|
+
content_html: string | null;
|
|
10
|
+
seo_title: string | null;
|
|
11
|
+
seo_description: string | null;
|
|
12
|
+
og_image_url: string | null;
|
|
13
|
+
canonical_url: string | null;
|
|
14
|
+
category: string | null;
|
|
15
|
+
tags: string[];
|
|
16
|
+
author_name: string | null;
|
|
17
|
+
author_avatar_url: string | null;
|
|
18
|
+
read_time_minutes: number | null;
|
|
19
|
+
published_at: string | null;
|
|
20
|
+
updated_at: string | null;
|
|
21
|
+
sort_order: number;
|
|
22
|
+
}
|
|
23
|
+
interface HelpArticle {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
content: string | null;
|
|
28
|
+
content_html: string | null;
|
|
29
|
+
seo_title: string | null;
|
|
30
|
+
seo_description: string | null;
|
|
31
|
+
category: string | null;
|
|
32
|
+
read_time_minutes: number | null;
|
|
33
|
+
published_at: string | null;
|
|
34
|
+
updated_at: string | null;
|
|
35
|
+
sort_order: number;
|
|
36
|
+
features?: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
slug: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface HelpCategory {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
description: string | null;
|
|
47
|
+
article_count: number;
|
|
48
|
+
}
|
|
49
|
+
interface SitemapEntry {
|
|
50
|
+
url: string;
|
|
51
|
+
lastmod?: string;
|
|
52
|
+
changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
53
|
+
priority?: number;
|
|
54
|
+
}
|
|
55
|
+
interface BlogPostsResponse {
|
|
56
|
+
posts: BlogPost[];
|
|
57
|
+
total: number;
|
|
58
|
+
}
|
|
59
|
+
interface HelpArticlesResponse {
|
|
60
|
+
articles: HelpArticle[];
|
|
61
|
+
total: number;
|
|
62
|
+
}
|
|
63
|
+
interface ContentConfig {
|
|
64
|
+
apiKey: string;
|
|
65
|
+
baseUrl?: string;
|
|
66
|
+
/** Request timeout in ms @default 30000 */
|
|
67
|
+
timeout?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Server-side content client for fetching blog posts and help articles.
|
|
72
|
+
* Designed for use in Next.js `generateStaticParams`, `generateMetadata`, and server components.
|
|
73
|
+
*/
|
|
74
|
+
declare class CensusContentClient {
|
|
75
|
+
private readonly apiKey;
|
|
76
|
+
private readonly baseUrl;
|
|
77
|
+
private readonly timeout;
|
|
78
|
+
constructor(config: ContentConfig);
|
|
79
|
+
getBlogPosts(options?: {
|
|
80
|
+
limit?: number;
|
|
81
|
+
offset?: number;
|
|
82
|
+
tag?: string;
|
|
83
|
+
}): Promise<BlogPostsResponse>;
|
|
84
|
+
getBlogPost(slug: string): Promise<BlogPost | null>;
|
|
85
|
+
getBlogTags(): Promise<string[]>;
|
|
86
|
+
getHelpArticles(options?: {
|
|
87
|
+
category?: string;
|
|
88
|
+
search?: string;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}): Promise<HelpArticlesResponse>;
|
|
91
|
+
getHelpArticle(slug: string): Promise<HelpArticle | null>;
|
|
92
|
+
getHelpCategories(): Promise<HelpCategory[]>;
|
|
93
|
+
getSitemapEntries(): Promise<SitemapEntry[]>;
|
|
94
|
+
getRSSFeed(): Promise<string>;
|
|
95
|
+
private request;
|
|
96
|
+
private rawRequest;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate Next.js Metadata object for a blog post.
|
|
101
|
+
* Use in `generateMetadata()` in your blog/[slug]/page.tsx.
|
|
102
|
+
*/
|
|
103
|
+
declare function generateBlogMetadata(post: BlogPost, options?: {
|
|
104
|
+
siteUrl?: string;
|
|
105
|
+
siteName?: string;
|
|
106
|
+
}): {
|
|
107
|
+
title: string;
|
|
108
|
+
description: string;
|
|
109
|
+
alternates: {
|
|
110
|
+
canonical: string;
|
|
111
|
+
};
|
|
112
|
+
openGraph: {
|
|
113
|
+
siteName?: string | undefined;
|
|
114
|
+
title: string;
|
|
115
|
+
description: string;
|
|
116
|
+
url: string;
|
|
117
|
+
type: "article";
|
|
118
|
+
publishedTime: string | undefined;
|
|
119
|
+
modifiedTime: string | undefined;
|
|
120
|
+
authors: string[] | undefined;
|
|
121
|
+
images: {
|
|
122
|
+
url: string;
|
|
123
|
+
width: number;
|
|
124
|
+
height: number;
|
|
125
|
+
}[] | undefined;
|
|
126
|
+
};
|
|
127
|
+
twitter: {
|
|
128
|
+
card: "summary_large_image";
|
|
129
|
+
title: string;
|
|
130
|
+
description: string;
|
|
131
|
+
images: string[] | undefined;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Generate Next.js Metadata object for a help article.
|
|
136
|
+
*/
|
|
137
|
+
declare function generateArticleMetadata(article: HelpArticle, options?: {
|
|
138
|
+
siteUrl?: string;
|
|
139
|
+
siteName?: string;
|
|
140
|
+
}): {
|
|
141
|
+
title: string;
|
|
142
|
+
description: string;
|
|
143
|
+
alternates: {
|
|
144
|
+
canonical: string;
|
|
145
|
+
};
|
|
146
|
+
openGraph: {
|
|
147
|
+
siteName?: string | undefined;
|
|
148
|
+
title: string;
|
|
149
|
+
description: string;
|
|
150
|
+
url: string;
|
|
151
|
+
type: "article";
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Fetch Census content URLs and return them in Next.js sitemap format.
|
|
157
|
+
* Merge this with your static sitemap entries.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* // app/sitemap.ts
|
|
162
|
+
* import { censusContent } from '@/lib/census'
|
|
163
|
+
* import { generateContentSitemap } from '@census-ai/census-sdk/content'
|
|
164
|
+
*
|
|
165
|
+
* export default async function sitemap() {
|
|
166
|
+
* const contentEntries = await generateContentSitemap(censusContent, 'https://everre.co')
|
|
167
|
+
* return [...staticEntries, ...contentEntries]
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function generateContentSitemap(client: CensusContentClient, siteUrl: string): Promise<Array<{
|
|
172
|
+
url: string;
|
|
173
|
+
lastModified?: string;
|
|
174
|
+
changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
175
|
+
priority?: number;
|
|
176
|
+
}>>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generate an RSS 2.0 XML feed from blog posts.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* // app/blog/rss.xml/route.ts
|
|
184
|
+
* import { censusContent } from '@/lib/census'
|
|
185
|
+
* import { generateRSSFeed } from '@census-ai/census-sdk/content'
|
|
186
|
+
*
|
|
187
|
+
* export async function GET() {
|
|
188
|
+
* const { posts } = await censusContent.getBlogPosts({ limit: 50 })
|
|
189
|
+
* const xml = generateRSSFeed(posts, {
|
|
190
|
+
* title: 'Everre Blog',
|
|
191
|
+
* description: 'CRE insights',
|
|
192
|
+
* siteUrl: 'https://everre.co',
|
|
193
|
+
* feedUrl: 'https://everre.co/blog/rss.xml',
|
|
194
|
+
* })
|
|
195
|
+
* return new Response(xml, {
|
|
196
|
+
* headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },
|
|
197
|
+
* })
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
declare function generateRSSFeed(posts: BlogPost[], options: {
|
|
202
|
+
title: string;
|
|
203
|
+
description: string;
|
|
204
|
+
siteUrl: string;
|
|
205
|
+
feedUrl: string;
|
|
206
|
+
language?: string;
|
|
207
|
+
}): string;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @census-ai/census-sdk/content - Server-side content fetcher for SEO
|
|
211
|
+
*
|
|
212
|
+
* Use this module to fetch blog posts and help articles from Census
|
|
213
|
+
* for server-side rendering in Next.js or similar frameworks.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* import { createCensusContent } from '@census-ai/census-sdk/content';
|
|
218
|
+
*
|
|
219
|
+
* const content = createCensusContent({
|
|
220
|
+
* apiKey: process.env.CENSUS_API_KEY!,
|
|
221
|
+
* baseUrl: 'https://api.census.ai',
|
|
222
|
+
* });
|
|
223
|
+
*
|
|
224
|
+
* // Fetch blog posts for SSR
|
|
225
|
+
* const { posts } = await content.getBlogPosts({ limit: 20 });
|
|
226
|
+
*
|
|
227
|
+
* // Fetch a single post with full SEO metadata
|
|
228
|
+
* const post = await content.getBlogPost('my-post-slug');
|
|
229
|
+
*
|
|
230
|
+
* // Generate sitemap entries
|
|
231
|
+
* const entries = await content.getSitemapEntries();
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create a new Census Content client for server-side content fetching.
|
|
237
|
+
*/
|
|
238
|
+
declare function createCensusContent(config: ContentConfig): CensusContentClient;
|
|
239
|
+
|
|
240
|
+
export { type BlogPost, type BlogPostsResponse, CensusContentClient, type ContentConfig, type HelpArticle, type HelpArticlesResponse, type HelpCategory, type SitemapEntry, createCensusContent, generateArticleMetadata, generateBlogMetadata, generateContentSitemap, generateRSSFeed };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var m="https://api.census.ai",c=class{constructor(e){this.apiKey=e.apiKey,this.baseUrl=(e.baseUrl||m).replace(/\/$/,""),this.timeout=e.timeout??3e4;}async getBlogPosts(e){let t=new URLSearchParams;e?.limit&&t.set("limit",String(e.limit)),e?.offset&&t.set("offset",String(e.offset)),e?.tag&&t.set("tag",e.tag);let s=t.toString();return this.request(`/api/sdk/blog-posts${s?`?${s}`:""}`)}async getBlogPost(e){try{return (await this.request(`/api/sdk/blog-posts/${encodeURIComponent(e)}`)).post}catch(t){if(t&&typeof t=="object"&&"status"in t&&t.status===404)return null;throw t}}async getBlogTags(){return (await this.request("/api/sdk/blog-posts/tags")).tags}async getHelpArticles(e){let t=new URLSearchParams;e?.category&&t.set("category",e.category),e?.search&&t.set("search",e.search),e?.limit&&t.set("limit",String(e.limit));let s=t.toString(),i=await this.request(`/api/sdk/articles${s?`?${s}`:""}`);return {articles:i.articles,total:i.pagination.total}}async getHelpArticle(e){try{return (await this.request(`/api/sdk/articles/${encodeURIComponent(e)}`)).article}catch(t){if(t&&typeof t=="object"&&"status"in t&&t.status===404)return null;throw t}}async getHelpCategories(){return (await this.request("/api/sdk/feature-groups")).feature_groups.map(t=>({id:t.id,name:t.name,slug:t.slug,description:t.description,article_count:t.article_count}))}async getSitemapEntries(){return (await this.request("/api/sdk/content/sitemap")).entries}async getRSSFeed(){return (await this.rawRequest("/api/sdk/content/rss")).text()}async request(e){return (await this.rawRequest(e)).json()}async rawRequest(e){let t=new AbortController,s=setTimeout(()=>t.abort(),this.timeout);try{let i=await fetch(`${this.baseUrl}${e}`,{headers:{"X-Census-Key":this.apiKey},signal:t.signal});if(!i.ok){let a={error:`Content request failed: ${i.status}`,status:i.status};try{let l=await i.json();a.error=l.error||a.error;}catch{}throw a}return i}finally{clearTimeout(s);}}};function d(r,e){let t=e?.siteUrl||"",s=e?.siteName||"",i=r.seo_title||r.title,a=r.seo_description||"",l=r.canonical_url||`${t}/blog/${r.slug}`;return {title:i,description:a,alternates:{canonical:l},openGraph:{title:i,description:a,url:l,type:"article",publishedTime:r.published_at||void 0,modifiedTime:r.updated_at||void 0,authors:r.author_name?[r.author_name]:void 0,images:r.og_image_url?[{url:r.og_image_url,width:1200,height:630}]:void 0,...s?{siteName:s}:{}},twitter:{card:"summary_large_image",title:i,description:a,images:r.og_image_url?[r.og_image_url]:void 0}}}function y(r,e){let t=e?.siteUrl||"",s=e?.siteName||"",i=r.seo_title||r.title,a=r.seo_description||"",l=`${t}/help/${r.slug}`;return {title:i,description:a,alternates:{canonical:l},openGraph:{title:i,description:a,url:l,type:"article",...s?{siteName:s}:{}}}}async function f(r,e){try{return (await r.getSitemapEntries()).map(s=>({url:s.url.startsWith("http")?s.url:`${e}${s.url}`,lastModified:s.lastmod,changeFrequency:s.changefreq,priority:s.priority}))}catch{return []}}function h(r,e){let{title:t,description:s,siteUrl:i,feedUrl:a,language:l="en-us"}=e,o=n=>n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),u=r.filter(n=>n.published_at).map(n=>{let g=`${i}/blog/${n.slug}`;return ` <item>
|
|
2
|
+
<title>${o(n.title)}</title>
|
|
3
|
+
<link>${o(g)}</link>
|
|
4
|
+
<guid isPermaLink="true">${o(g)}</guid>
|
|
5
|
+
<description>${o(n.seo_description||"")}</description>
|
|
6
|
+
<pubDate>${new Date(n.published_at).toUTCString()}</pubDate>${n.author_name?`
|
|
7
|
+
<dc:creator>${o(n.author_name)}</dc:creator>`:""}${n.tags?.length?n.tags.map(p=>`
|
|
8
|
+
<category>${o(p)}</category>`).join(""):""}
|
|
9
|
+
</item>`}).join(`
|
|
10
|
+
`);return `<?xml version="1.0" encoding="UTF-8"?>
|
|
11
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
12
|
+
<channel>
|
|
13
|
+
<title>${o(t)}</title>
|
|
14
|
+
<link>${o(i)}</link>
|
|
15
|
+
<description>${o(s)}</description>
|
|
16
|
+
<language>${l}</language>
|
|
17
|
+
<atom:link href="${o(a)}" rel="self" type="application/rss+xml"/>
|
|
18
|
+
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
|
19
|
+
${u}
|
|
20
|
+
</channel>
|
|
21
|
+
</rss>`}function P(r){return new c(r)}export{c as CensusContentClient,P as createCensusContent,y as generateArticleMetadata,d as generateBlogMetadata,f as generateContentSitemap,h as generateRSSFeed};//# sourceMappingURL=index.js.map
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/content/client.ts","../../src/content/helpers/metadata.ts","../../src/content/helpers/sitemap.ts","../../src/content/helpers/rss.ts","../../src/content/index.ts"],"names":["DEFAULT_BASE_URL","CensusContentClient","config","options","params","qs","slug","err","data","g","path","controller","timeoutId","res","error","body","generateBlogMetadata","post","siteUrl","siteName","title","description","canonical","generateArticleMetadata","article","generateContentSitemap","client","entry","generateRSSFeed","posts","feedUrl","language","escapeXml","str","items","p","link","t","createCensusContent"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n ContentConfig,\n BlogPost,\n BlogPostsResponse,\n HelpArticle,\n HelpArticlesResponse,\n HelpCategory,\n SitemapEntry,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://api.census.ai';\n\n/**\n * Server-side content client for fetching blog posts and help articles.\n * Designed for use in Next.js `generateStaticParams`, `generateMetadata`, and server components.\n */\nexport class CensusContentClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: ContentConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n this.timeout = config.timeout ?? 30_000;\n }\n\n // ── Blog ────────────────────────────────────────────────────────────\n\n async getBlogPosts(options?: {\n limit?: number;\n offset?: number;\n tag?: string;\n }): Promise<BlogPostsResponse> {\n const params = new URLSearchParams();\n if (options?.limit) params.set('limit', String(options.limit));\n if (options?.offset) params.set('offset', String(options.offset));\n if (options?.tag) params.set('tag', options.tag);\n\n const qs = params.toString();\n return this.request<BlogPostsResponse>(\n `/api/sdk/blog-posts${qs ? `?${qs}` : ''}`\n );\n }\n\n async getBlogPost(slug: string): Promise<BlogPost | null> {\n try {\n const data = await this.request<{ post: BlogPost }>(\n `/api/sdk/blog-posts/${encodeURIComponent(slug)}`\n );\n return data.post;\n } catch (err) {\n if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 404) {\n return null;\n }\n throw err;\n }\n }\n\n async getBlogTags(): Promise<string[]> {\n const data = await this.request<{ tags: string[] }>('/api/sdk/blog-posts/tags');\n return data.tags;\n }\n\n // ── Help Articles ───────────────────────────────────────────────────\n\n async getHelpArticles(options?: {\n category?: string;\n search?: string;\n limit?: number;\n }): Promise<HelpArticlesResponse> {\n const params = new URLSearchParams();\n if (options?.category) params.set('category', options.category);\n if (options?.search) params.set('search', options.search);\n if (options?.limit) params.set('limit', String(options.limit));\n\n const qs = params.toString();\n const data = await this.request<{\n articles: HelpArticle[];\n pagination: { total: number };\n }>(`/api/sdk/articles${qs ? `?${qs}` : ''}`);\n\n return {\n articles: data.articles,\n total: data.pagination.total,\n };\n }\n\n async getHelpArticle(slug: string): Promise<HelpArticle | null> {\n try {\n const data = await this.request<{ article: HelpArticle }>(\n `/api/sdk/articles/${encodeURIComponent(slug)}`\n );\n return data.article;\n } catch (err) {\n if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 404) {\n return null;\n }\n throw err;\n }\n }\n\n async getHelpCategories(): Promise<HelpCategory[]> {\n const data = await this.request<{ feature_groups: HelpCategory[] }>(\n '/api/sdk/feature-groups'\n );\n return data.feature_groups.map((g) => ({\n id: g.id,\n name: g.name,\n slug: g.slug,\n description: g.description,\n article_count: g.article_count,\n }));\n }\n\n // ── SEO ─────────────────────────────────────────────────────────────\n\n async getSitemapEntries(): Promise<SitemapEntry[]> {\n const data = await this.request<{ entries: SitemapEntry[] }>(\n '/api/sdk/content/sitemap'\n );\n return data.entries;\n }\n\n async getRSSFeed(): Promise<string> {\n const res = await this.rawRequest('/api/sdk/content/rss');\n return res.text();\n }\n\n // ── Internal ────────────────────────────────────────────────────────\n\n private async request<T>(path: string): Promise<T> {\n const res = await this.rawRequest(path);\n return res.json();\n }\n\n private async rawRequest(path: string): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(`${this.baseUrl}${path}`, {\n headers: {\n 'X-Census-Key': this.apiKey,\n },\n signal: controller.signal,\n });\n\n if (!res.ok) {\n const error: { error: string; status: number } = {\n error: `Content request failed: ${res.status}`,\n status: res.status,\n };\n try {\n const body = await res.json();\n error.error = body.error || error.error;\n } catch {\n // use default\n }\n throw error;\n }\n\n return res;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n}\n","import type { BlogPost, HelpArticle } from '../types';\n\n/**\n * Generate Next.js Metadata object for a blog post.\n * Use in `generateMetadata()` in your blog/[slug]/page.tsx.\n */\nexport function generateBlogMetadata(\n post: BlogPost,\n options?: { siteUrl?: string; siteName?: string }\n) {\n const siteUrl = options?.siteUrl || '';\n const siteName = options?.siteName || '';\n const title = post.seo_title || post.title;\n const description = post.seo_description || '';\n const canonical = post.canonical_url || `${siteUrl}/blog/${post.slug}`;\n\n return {\n title,\n description,\n alternates: { canonical },\n openGraph: {\n title,\n description,\n url: canonical,\n type: 'article' as const,\n publishedTime: post.published_at || undefined,\n modifiedTime: post.updated_at || undefined,\n authors: post.author_name ? [post.author_name] : undefined,\n images: post.og_image_url ? [{ url: post.og_image_url, width: 1200, height: 630 }] : undefined,\n ...(siteName ? { siteName } : {}),\n },\n twitter: {\n card: 'summary_large_image' as const,\n title,\n description,\n images: post.og_image_url ? [post.og_image_url] : undefined,\n },\n };\n}\n\n/**\n * Generate Next.js Metadata object for a help article.\n */\nexport function generateArticleMetadata(\n article: HelpArticle,\n options?: { siteUrl?: string; siteName?: string }\n) {\n const siteUrl = options?.siteUrl || '';\n const siteName = options?.siteName || '';\n const title = article.seo_title || article.title;\n const description = article.seo_description || '';\n const canonical = `${siteUrl}/help/${article.slug}`;\n\n return {\n title,\n description,\n alternates: { canonical },\n openGraph: {\n title,\n description,\n url: canonical,\n type: 'article' as const,\n ...(siteName ? { siteName } : {}),\n },\n };\n}\n","import type { SitemapEntry } from '../types';\nimport type { CensusContentClient } from '../client';\n\n/**\n * Fetch Census content URLs and return them in Next.js sitemap format.\n * Merge this with your static sitemap entries.\n *\n * @example\n * ```ts\n * // app/sitemap.ts\n * import { censusContent } from '@/lib/census'\n * import { generateContentSitemap } from '@census-ai/census-sdk/content'\n *\n * export default async function sitemap() {\n * const contentEntries = await generateContentSitemap(censusContent, 'https://everre.co')\n * return [...staticEntries, ...contentEntries]\n * }\n * ```\n */\nexport async function generateContentSitemap(\n client: CensusContentClient,\n siteUrl: string\n): Promise<Array<{\n url: string;\n lastModified?: string;\n changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\n priority?: number;\n}>> {\n try {\n const entries = await client.getSitemapEntries();\n return entries.map((entry: SitemapEntry) => ({\n url: entry.url.startsWith('http') ? entry.url : `${siteUrl}${entry.url}`,\n lastModified: entry.lastmod,\n changeFrequency: entry.changefreq,\n priority: entry.priority,\n }));\n } catch {\n // Return empty on error so sitemap generation doesn't fail\n return [];\n }\n}\n","import type { BlogPost } from '../types';\n\n/**\n * Generate an RSS 2.0 XML feed from blog posts.\n *\n * @example\n * ```ts\n * // app/blog/rss.xml/route.ts\n * import { censusContent } from '@/lib/census'\n * import { generateRSSFeed } from '@census-ai/census-sdk/content'\n *\n * export async function GET() {\n * const { posts } = await censusContent.getBlogPosts({ limit: 50 })\n * const xml = generateRSSFeed(posts, {\n * title: 'Everre Blog',\n * description: 'CRE insights',\n * siteUrl: 'https://everre.co',\n * feedUrl: 'https://everre.co/blog/rss.xml',\n * })\n * return new Response(xml, {\n * headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },\n * })\n * }\n * ```\n */\nexport function generateRSSFeed(\n posts: BlogPost[],\n options: {\n title: string;\n description: string;\n siteUrl: string;\n feedUrl: string;\n language?: string;\n }\n): string {\n const { title, description, siteUrl, feedUrl, language = 'en-us' } = options;\n\n const escapeXml = (str: string) =>\n str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\n const items = posts\n .filter((p) => p.published_at)\n .map((post) => {\n const link = `${siteUrl}/blog/${post.slug}`;\n return ` <item>\n <title>${escapeXml(post.title)}</title>\n <link>${escapeXml(link)}</link>\n <guid isPermaLink=\"true\">${escapeXml(link)}</guid>\n <description>${escapeXml(post.seo_description || '')}</description>\n <pubDate>${new Date(post.published_at!).toUTCString()}</pubDate>${\n post.author_name\n ? `\\n <dc:creator>${escapeXml(post.author_name)}</dc:creator>`\n : ''\n }${\n post.tags?.length\n ? post.tags.map((t) => `\\n <category>${escapeXml(t)}</category>`).join('')\n : ''\n }\n </item>`;\n })\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n <channel>\n <title>${escapeXml(title)}</title>\n <link>${escapeXml(siteUrl)}</link>\n <description>${escapeXml(description)}</description>\n <language>${language}</language>\n <atom:link href=\"${escapeXml(feedUrl)}\" rel=\"self\" type=\"application/rss+xml\"/>\n <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>\n${items}\n </channel>\n</rss>`;\n}\n","/**\n * @census-ai/census-sdk/content - Server-side content fetcher for SEO\n *\n * Use this module to fetch blog posts and help articles from Census\n * for server-side rendering in Next.js or similar frameworks.\n *\n * @example\n * ```typescript\n * import { createCensusContent } from '@census-ai/census-sdk/content';\n *\n * const content = createCensusContent({\n * apiKey: process.env.CENSUS_API_KEY!,\n * baseUrl: 'https://api.census.ai',\n * });\n *\n * // Fetch blog posts for SSR\n * const { posts } = await content.getBlogPosts({ limit: 20 });\n *\n * // Fetch a single post with full SEO metadata\n * const post = await content.getBlogPost('my-post-slug');\n *\n * // Generate sitemap entries\n * const entries = await content.getSitemapEntries();\n * ```\n */\n\nexport { CensusContentClient } from './client';\nexport type {\n ContentConfig,\n BlogPost,\n BlogPostsResponse,\n HelpArticle,\n HelpArticlesResponse,\n HelpCategory,\n SitemapEntry,\n} from './types';\n\nexport { generateBlogMetadata, generateArticleMetadata } from './helpers/metadata';\nexport { generateContentSitemap } from './helpers/sitemap';\nexport { generateRSSFeed } from './helpers/rss';\n\nimport { CensusContentClient } from './client';\nimport type { ContentConfig } from './types';\n\n/**\n * Create a new Census Content client for server-side content fetching.\n */\nexport function createCensusContent(config: ContentConfig): CensusContentClient {\n return new CensusContentClient(config);\n}\n"]}
|