@codesocietyou/contentedge-cms-sdk 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import e from"axios";import{queryOptions as t,useQuery as n,useQueryClient as r}from"@tanstack/react-query";var i=class extends Error{status;data;constructor(e,t){super(e,t?.cause?{cause:t.cause}:void 0),this.name=`CmsError`,this.status=t?.status,this.data=t?.data}};let a=null;function o(e){return e?/^(\$\{[^}]+\}|\$[A-Z_]+|\{\{[^}]+\}\})$/.test(e.trim()):!1}function s(){if(!a)throw Error(`SDK not initialized. Call createApiClient() first.`);return a}function c(e){let t=e.baseUrl.replace(/\/+$/,``),n=e.fileBaseUrl?.replace(/\/+$/,``);if(!t||o(t))throw Error(`Invalid baseUrl: must be a valid URL string`);a={...e,baseUrl:t,fileBaseUrl:n,timeoutMs:e.timeoutMs??3e4}}function l(){return a!==null}function u(){a=null}let d=null;function f(t){c(t);let n=s();return d=e.create({baseURL:n.baseUrl,timeout:n.timeoutMs??3e4,headers:{"Content-Type":`application/json`,Accept:`application/json`,...n.tenant?{"X-Tenant":n.tenant}:{},...n.apiKey?{"X-API-Key":n.apiKey}:{}}}),d.interceptors.response.use(e=>e,e=>{let t=e.response?.status,r=e.response?.data;if(n.logger?.error){let i=`API request failed`;t&&(i+=` (${t})`),t===401&&(i+=`: Unauthorized - check your API key`),t===403&&(i+=`: Permission denied`),t===404&&(i+=`: Resource not found`),t===429&&(i+=`: Rate limit exceeded`),t&&t>=500&&(i+=`: Server error`),n.logger.error(i,{status:t,data:r,url:e.config?.url})}throw new i(`CMS request failed${t?` (${t})`:``}`,{status:t,data:r,cause:e})}),d}function p(){if(!d||!l())throw Error(`API client not initialized. Call createApiClient() first.`);return d}function m(){d=null}const h=new Proxy({},{get(e,t){let n=p(),r=n[t];return typeof r==`function`?r.bind(n):r}});async function g(e={}){let t=p(),n=encodeURIComponent(e.type??`ALL`),r=Number.isInteger(e.page)&&e.page>=0?e.page:0,i=Number.isInteger(e.size)&&e.size>0&&e.size<=1e3?e.size:10,a=new URLSearchParams({page:String(r),size:String(i),sortBy:e.sortBy??`id`,direction:e.direction??`DESC`});if(e.filters)for(let[t,n]of Object.entries(e.filters))n!=null&&a.append(t,String(n));let o=`/content/type/${n}?${a.toString()}`;return(await t.get(o)).data}async function _(e){return(await p().get(`/content/${e}`)).data}async function v(t){let n=s(),r=t.toLowerCase(),i=(n.baseUrl||``).toLowerCase(),a=(n.fileBaseUrl||``).toLowerCase();return i&&r.startsWith(i)||a&&r.startsWith(a)?(await p().get(t,{responseType:`blob`})).data:(await e.get(t,{responseType:`blob`})).data}async function y(e,t={}){let n=e.size??100,r=e.sortBy??`id`,i=e.direction??`DESC`,a=t.mapItem??(e=>e),o=t.dedupeBy??(e=>e.id),s=t.hardStopMaxPages??20,c=0,l=new Map;for(let t=0;t<s;t++){let t=(await g({...e,page:c,size:n,sortBy:r,direction:i}))?.data;if(!t)break;let s=t.content??[];if(s.length===0)break;let u=0;for(let e of s){let t=a(e),n=o(t);l.has(n)||(l.set(n,t),u++)}if(u===0)break;let d=typeof t.number==`number`?t.number:c,f=typeof t.size==`number`?t.size:n,p=typeof t.numberOfElements==`number`?t.numberOfElements:s.length;if(!(typeof t.totalPages==`number`?d+1<t.totalPages:typeof t.totalElements==`number`?d*f+p<t.totalElements:typeof t.last==`boolean`?!t.last:p===f))break;c=d+1}return Array.from(l.values())}const b={all:[`content`],lists:()=>[...b.all,`list`],list:(e,t)=>[...b.lists(),e??`ALL`,t],details:()=>[...b.all,`detail`],detail:e=>[...b.details(),e],allLists:()=>[...b.all,`all`],allList:(e,t)=>[...b.allLists(),e??`ALL`,{...t,mode:`all`}]},x={list:(e={})=>t({queryKey:b.list(e.type,e),queryFn:()=>g(e),staleTime:5*6e4,gcTime:10*6e4}),detail:e=>t({queryKey:b.detail(e),queryFn:()=>_(e),staleTime:10*6e4,gcTime:30*6e4}),listAll:(e,n={})=>t({queryKey:b.allList(e.type,e),queryFn:()=>y(e,n),staleTime:5*6e4,gcTime:10*6e4})};function S(e={}){return n(x.list(e))}function C(e){return n(x.detail(e))}function w(e,t={}){return n(x.listAll(e,t))}function T(){let e=r();return{prefetchList:t=>e.prefetchQuery(x.list(t)),prefetchDetail:t=>e.prefetchQuery(x.detail(t)),invalidateLists:()=>e.invalidateQueries({queryKey:b.lists()}),invalidateDetail:t=>e.invalidateQueries({queryKey:b.detail(t)}),invalidateAll:()=>e.invalidateQueries({queryKey:b.all})}}const E=(e,t)=>{if(!e)return``;let n=e.trim();if(!n)return``;let r=(t?.apiBase||``).replace(/\/+$/,``),i=(t?.fileBase||``).replace(/\/+$/,``),a=r.replace(/\/api$/,``),o=i||a||r,s=n.toLowerCase();if(s.startsWith(`http://`)||s.startsWith(`https://`)||s.startsWith(`data:`)||s.startsWith(`blob:`)||n.startsWith(`//`)){if(r&&o&&s.startsWith(r.toLowerCase())){let e=n.slice(r.length);return`${o}${e.startsWith(`/`)?``:`/`}${e}`}let e=`${a}/api`.toLowerCase();if(a&&o&&s.startsWith(e)){let e=n.slice((a+`/api`).length);return`${o}${e.startsWith(`/`)?``:`/`}${e}`}return i&&s.startsWith(i.toLowerCase()),n}let c=n.replace(/^\/+/,``);return c=c.replace(/^api\//,``),o?`${o}/${c}`:`/${c}`};function D(e){let t=s(),n=e.customFields,r=e=>{let t=n[e];return typeof t==`string`?t:null},i=(()=>{let t=e.type.toUpperCase();return t===`GAMEHEARTS_PUBLICATION`||t===`REPORT`?r(`publication_pdf`)??r(`pdfPath`):t===`EXTERNAL_PUBLICATION`?r(`pdf`)??r(`pdfPath`):r(`pdfPath`)})();return{id:e.id,title:r(`title`)||e.title,text:r(`text`)||e.text,type:e.type,insideImage:E(r(`insideImage`)||``,{apiBase:t.baseUrl,fileBase:t.fileBaseUrl}),outsideImage:E(r(`outsideImage`)||``,{apiBase:t.baseUrl,fileBase:t.fileBaseUrl}),pdfPath:E(i,{apiBase:t.baseUrl,fileBase:t.fileBaseUrl}),references:r(`referencesList`)??r(`references`),citation:r(`citation`),abstract:r(`abstractText`)??r(`abstract`),team:r(`team`),publicationType:r(`publicationType`),fake:n.fake===!0?!0:null}}function O(e){let t=s();return E(e,{apiBase:t.baseUrl,fileBase:t.fileBaseUrl})}function k(e){return e?e.replace(/<\/(p|li|h[1-6]|blockquote|div|tr)>/gi,` `).replace(/<br\s*\/?>/gi,` `).replace(/<[^>]*>/g,``).replace(/\s+/g,` `).trim():``}function A(e){return e?/<([a-zA-Z][a-zA-Z0-9]*)(\s[^>]*)?>/.test(e):!1}export{i as CmsError,h as apiClient,E as buildAssetUrl,O as buildAssetUrlWithConfig,b as contentKeys,x as contentQueries,f as createApiClient,v as downloadFile,y as fetchAllContent,_ as fetchContentById,g as fetchContentByType,p as getApiClient,s as getConfig,A as isHtmlContent,l as isInitialized,D as normalizeContentItem,m as resetApiClient,u as resetConfig,c as setConfig,k as stripHtmlTags,w as useContentAll,C as useContentDetail,S as useContentList,T as useContentPrefetch};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/errors/CmsError.ts","../src/config/sdkConfig.ts","../src/services/apiClient.ts","../src/services/contentApi.ts","../src/services/contentFetchAll.ts","../src/queries/queryKeys.ts","../src/queries/contentQueries.ts","../src/queries/useContentQueries.ts","../src/utils/assetUrl.ts","../src/utils/normalization.ts","../src/utils/richText.ts"],"sourcesContent":["export class CmsError extends Error {\n readonly status?: number;\n readonly data?: unknown;\n constructor(message: string, opts?: { status?: number; data?: unknown; cause?: unknown }) {\n super(message, opts?.cause ? { cause: opts.cause } : undefined);\n this.name = 'CmsError';\n this.status = opts?.status;\n this.data = opts?.data;\n }\n}","// src/config/sdkConfig.ts\nimport type { SdkConfig } from '../types/config';\n\nlet currentConfig: SdkConfig | null = null;\n\n/**\n * Checks if a value looks like an unresolved CI/CD template variable\n */\nfunction isTemplateString(value: string | undefined): boolean {\n if (!value) return false;\n // Detect common CI/CD template patterns: ${VAR}, $VAR, {{VAR}}, etc.\n return /^(\\$\\{[^}]+\\}|\\$[A-Z_]+|\\{\\{[^}]+\\}\\})$/.test(value.trim());\n}\n\n/**\n * Get the current SDK configuration\n */\nexport function getConfig(): SdkConfig {\n if (!currentConfig) {\n throw new Error('SDK not initialized. Call createApiClient() first.');\n }\n return currentConfig;\n}\n\n/**\n * Set the SDK configuration\n */\nexport function setConfig(config: SdkConfig): void {\n // Clean up base URLs\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n const fileBaseUrl = config.fileBaseUrl?.replace(/\\/+$/, '');\n\n // Validate required fields\n if (!baseUrl || isTemplateString(baseUrl)) {\n throw new Error('Invalid baseUrl: must be a valid URL string');\n }\n\n currentConfig = {\n ...config,\n baseUrl,\n fileBaseUrl,\n timeoutMs: config.timeoutMs ?? 30_000,\n };\n}\n\n/**\n * Check if SDK is initialized\n */\nexport function isInitialized(): boolean {\n return currentConfig !== null;\n}\n\n/**\n * Reset configuration (useful for testing)\n */\nexport function resetConfig(): void {\n currentConfig = null;\n}\n","import axios, { type AxiosInstance, type AxiosResponse, AxiosError } from 'axios';\nimport { CmsError } from '../errors/CmsError';\nimport { getConfig, setConfig, isInitialized } from '../config/sdkConfig';\nimport type { SdkConfig } from '../types/config';\n\nlet axiosInstance: AxiosInstance | null = null;\n\n/**\n * Create and configure the API client with the given configuration\n */\nexport function createApiClient(config: SdkConfig): AxiosInstance {\n setConfig(config);\n \n const cfg = getConfig();\n \n axiosInstance = axios.create({\n baseURL: cfg.baseUrl,\n timeout: cfg.timeoutMs ?? 30_000,\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...(cfg.tenant ? { 'X-Tenant': cfg.tenant } : {}),\n ...(cfg.apiKey ? { 'X-API-Key': cfg.apiKey } : {}),\n },\n });\n\n // Response interceptor for error handling\n axiosInstance.interceptors.response.use(\n (response: AxiosResponse) => response,\n (error: AxiosError) => {\n const status = error.response?.status;\n const data = error.response?.data;\n \n // Log errors if logger is configured\n if (cfg.logger?.error) {\n let message = `API request failed`;\n if (status) message += ` (${status})`;\n if (status === 401) message += ': Unauthorized - check your API key';\n if (status === 403) message += ': Permission denied';\n if (status === 404) message += ': Resource not found';\n if (status === 429) message += ': Rate limit exceeded';\n if (status && status >= 500) message += ': Server error';\n \n cfg.logger.error(message, { status, data, url: error.config?.url });\n }\n \n throw new CmsError(\n `CMS request failed${status ? ` (${status})` : ''}`,\n { status, data, cause: error }\n );\n }\n );\n\n return axiosInstance;\n}\n\n/**\n * Get the current API client instance\n * Throws if not initialized\n */\nexport function getApiClient(): AxiosInstance {\n if (!axiosInstance || !isInitialized()) {\n throw new Error('API client not initialized. Call createApiClient() first.');\n }\n return axiosInstance;\n}\n\n/**\n * Reset the API client (useful for testing)\n */\nexport function resetApiClient(): void {\n axiosInstance = null;\n}\n\n// Export a default instance getter for convenience\nexport const apiClient = new Proxy({} as AxiosInstance, {\n get(_target, prop) {\n const instance = getApiClient();\n const value = instance[prop as keyof AxiosInstance];\n return typeof value === 'function' ? value.bind(instance) : value;\n },\n});\n","import axios from 'axios';\nimport { getApiClient } from './apiClient';\nimport { getConfig } from '../config/sdkConfig';\nimport type {\n ApiResponse,\n ContentDto,\n ContentListParams,\n ContentResponse,\n CustomFields,\n} from '../types/content';\n\n/**\n * Fetch content by type with pagination and filters\n */\nexport async function fetchContentByType<C extends CustomFields = CustomFields>(\n params: ContentListParams = {}\n): Promise<ContentResponse<C>> {\n const client = getApiClient();\n \n // Sanitize and validate inputs\n const type = encodeURIComponent(params.type ?? 'ALL');\n const page = Number.isInteger(params.page) && (params.page as number) >= 0 ? params.page : 0;\n const size = Number.isInteger(params.size) && (params.size as number) > 0 && (params.size as number) <= 1000\n ? params.size\n : 10;\n\n // Build query parameters\n const queryParams = new URLSearchParams({\n page: String(page),\n size: String(size),\n sortBy: params.sortBy ?? 'id',\n direction: params.direction ?? 'DESC',\n });\n\n // Add arbitrary filters\n if (params.filters) {\n for (const [key, value] of Object.entries(params.filters)) {\n if (value === undefined || value === null) continue;\n queryParams.append(key, String(value));\n }\n }\n\n const url = `/content/type/${type}?${queryParams.toString()}`;\n const response = await client.get<ContentResponse<C>>(url);\n return response.data;\n}\n\n/**\n * Fetch a single content item by ID\n */\nexport async function fetchContentById<C extends CustomFields = CustomFields>(\n id: number\n): Promise<ApiResponse<ContentDto<C>>> {\n const client = getApiClient();\n const response = await client.get<ApiResponse<ContentDto<C>>>(`/content/${id}`);\n return response.data;\n}\n\n/**\n * Download a file from a given path\n * @param path - Full URL or relative path to the file\n * @returns Blob containing the file data\n */\nexport async function downloadFile(path: string): Promise<Blob> {\n const config = getConfig();\n const lower = path.toLowerCase();\n const api = (config.baseUrl || '').toLowerCase();\n const file = (config.fileBaseUrl || '').toLowerCase();\n \n // Check if this is a CMS-hosted file\n const isCmsFile = (api && lower.startsWith(api)) || (file && lower.startsWith(file));\n\n if (isCmsFile) {\n // Use authenticated client for CMS files\n const client = getApiClient();\n const response = await client.get(path, { responseType: 'blob' });\n return response.data as Blob;\n }\n \n // For external files, use axios directly without auth headers\n const response = await axios.get(path, { responseType: 'blob' });\n return response.data as Blob;\n}\n","import { fetchContentByType } from './contentApi';\nimport type {\n ContentDto,\n ContentListParams,\n CustomFields,\n PaginatedData,\n} from '../types/content';\n\nexport interface FetchAllOptions<C extends CustomFields, T> {\n /**\n * Optional function to transform each content item\n */\n mapItem?: (item: ContentDto<C>) => T;\n \n /**\n * Optional function to extract a unique key for deduplication\n */\n dedupeBy?: (item: T) => string | number;\n \n /**\n * Maximum number of pages to fetch (safety limit)\n * @default 20\n */\n hardStopMaxPages?: number;\n}\n\n/**\n * Fetch all content items across multiple pages with automatic pagination\n * \n * This function will continue fetching pages until:\n * - No more items are returned\n * - The last page is reached (based on pagination metadata)\n * - The hard stop limit is reached\n * - No new items are added (all items are duplicates)\n * \n * @param params - Content list parameters (without page number)\n * @param options - Fetch options including mapping and deduplication\n * @returns Array of all fetched items\n */\nexport async function fetchAllContent<\n C extends CustomFields = CustomFields,\n T = ContentDto<C>\n>(\n params: Omit<ContentListParams, 'page'> & { page?: never },\n options: FetchAllOptions<C, T> = {}\n): Promise<T[]> {\n const size = params.size ?? 100;\n const sortBy = params.sortBy ?? 'id';\n const direction = params.direction ?? 'DESC';\n \n const mapItem = options.mapItem ?? ((item: ContentDto<C>) => item as unknown as T);\n const dedupeBy = options.dedupeBy ?? ((item: T) => (item as { id: string | number }).id);\n const hardStopMaxPages = options.hardStopMaxPages ?? 20;\n\n let page = 0;\n const dedupMap = new Map<string | number, T>();\n\n for (let i = 0; i < hardStopMaxPages; i++) {\n const response = await fetchContentByType<C>({\n ...params,\n page,\n size,\n sortBy,\n direction,\n });\n\n const data = response?.data as PaginatedData<ContentDto<C>> | undefined;\n if (!data) break;\n\n const items = data.content ?? [];\n if (items.length === 0) break;\n\n // Process and deduplicate items\n let addedThisPage = 0;\n for (const item of items) {\n const mapped = mapItem(item);\n const key = dedupeBy(mapped);\n if (!dedupMap.has(key)) {\n dedupMap.set(key, mapped);\n addedThisPage++;\n }\n }\n\n // Stop if no new items were added (all duplicates)\n if (addedThisPage === 0) break;\n\n // Determine if there are more pages\n const current = typeof data.number === 'number' ? data.number : page;\n const pageSize = typeof data.size === 'number' ? data.size : size;\n const numberOfElements =\n typeof data.numberOfElements === 'number' ? data.numberOfElements : items.length;\n\n const hasNext =\n typeof data.totalPages === 'number'\n ? current + 1 < data.totalPages\n : typeof data.totalElements === 'number'\n ? current * pageSize + numberOfElements < data.totalElements\n : typeof data.last === 'boolean'\n ? !data.last\n : numberOfElements === pageSize;\n\n if (!hasNext) break;\n page = current + 1;\n }\n\n return Array.from(dedupMap.values());\n}\n","// src/queries/queryKeys.ts\nimport type { ContentListParams } from '../types/content';\n\n/**\n * Hierarchical query key factory for content queries\n * Following TanStack Query best practices for key structure\n */\nexport const contentKeys = {\n /**\n * Base key for all content queries\n */\n all: ['content'] as const,\n \n /**\n * Key for all list queries\n */\n lists: () => [...contentKeys.all, 'list'] as const,\n \n /**\n * Key for a specific list query with parameters\n * @param type - Content type (e.g., 'NEWS', 'REPORT')\n * @param params - Additional list parameters\n */\n list: (type: string | undefined, params: ContentListParams) =>\n [...contentKeys.lists(), type ?? 'ALL', params] as const,\n \n /**\n * Key for all detail queries\n */\n details: () => [...contentKeys.all, 'detail'] as const,\n \n /**\n * Key for a specific detail query by ID\n * @param id - Content item ID\n */\n detail: (id: number) => [...contentKeys.details(), id] as const,\n \n /**\n * Key for all \"fetch all\" queries\n */\n allLists: () => [...contentKeys.all, 'all'] as const,\n \n /**\n * Key for a specific \"fetch all\" query\n * @param type - Content type\n * @param params - List parameters (without page)\n */\n allList: (type: string | undefined, params: Omit<ContentListParams, 'page'>) =>\n [...contentKeys.allLists(), type ?? 'ALL', { ...params, mode: 'all' }] as const,\n};\n","import { queryOptions } from '@tanstack/react-query';\nimport { fetchContentByType, fetchContentById } from '../services/contentApi';\nimport { fetchAllContent, type FetchAllOptions } from '../services/contentFetchAll';\nimport { contentKeys } from './queryKeys';\nimport type {\n ContentDto,\n ContentListParams,\n CustomFields,\n} from '../types/content';\n\n/**\n * Query options factory for content queries\n * Use with TanStack Query's useQuery hook\n */\nexport const contentQueries = {\n /**\n * Query options for paginated content list\n * @param params - List parameters including type, pagination, filters\n */\n list: <C extends CustomFields = CustomFields>(params: ContentListParams = {}) =>\n queryOptions({\n queryKey: contentKeys.list(params.type, params),\n queryFn: () => fetchContentByType<C>(params),\n staleTime: 5 * 60_000, // 5 minutes\n gcTime: 10 * 60_000, // 10 minutes (formerly cacheTime)\n }),\n\n /**\n * Query options for a single content item by ID\n * @param id - Content item ID\n */\n detail: <C extends CustomFields = CustomFields>(id: number) =>\n queryOptions({\n queryKey: contentKeys.detail(id),\n queryFn: () => fetchContentById<C>(id),\n staleTime: 10 * 60_000, // 10 minutes\n gcTime: 30 * 60_000, // 30 minutes\n }),\n\n /**\n * Query options for fetching all content items (across all pages)\n * @param params - List parameters (without page)\n * @param options - Fetch options including mapping and deduplication\n */\n listAll: <C extends CustomFields = CustomFields, T = ContentDto<C>>(\n params: Omit<ContentListParams, 'page'> & { page?: never },\n options: FetchAllOptions<C, T> = {}\n ) =>\n queryOptions({\n queryKey: contentKeys.allList(params.type, params),\n queryFn: () => fetchAllContent<C, T>(params, options),\n staleTime: 5 * 60_000, // 5 minutes\n gcTime: 10 * 60_000, // 10 minutes\n }),\n};\n","// src/queries/useContentQueries.ts\nimport { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';\nimport { contentQueries } from './contentQueries';\nimport { contentKeys } from './queryKeys';\nimport type { FetchAllOptions } from '../services/contentFetchAll';\nimport type {\n ApiResponse,\n ContentDto,\n ContentListParams,\n ContentResponse,\n CustomFields,\n} from '../types/content';\n\n/**\n * Hook for fetching paginated content list\n * @param params - List parameters including type, pagination, filters\n */\nexport function useContentList<C extends CustomFields = CustomFields>(\n params: ContentListParams = {}\n): UseQueryResult<ContentResponse<C>> {\n return useQuery(contentQueries.list<C>(params));\n}\n\n/**\n * Hook for fetching a single content item by ID\n * @param id - Content item ID\n */\nexport function useContentDetail<C extends CustomFields = CustomFields>(\n id: number\n): UseQueryResult<ApiResponse<ContentDto<C>>> {\n return useQuery(contentQueries.detail<C>(id));\n}\n\n/**\n * Hook for fetching all content items across pages\n * @param params - List parameters (without page)\n * @param options - Fetch options including mapping and deduplication\n */\nexport function useContentAll<C extends CustomFields = CustomFields, T = ContentDto<C>>(\n params: Omit<ContentListParams, 'page'> & { page?: never },\n options: FetchAllOptions<C, T> = {}\n): UseQueryResult<T[]> {\n return useQuery(contentQueries.listAll<C, T>(params, options));\n}\n\n/**\n * Hook for prefetching content queries\n * Useful for optimistic navigation and hover effects\n */\nexport function useContentPrefetch() {\n const queryClient = useQueryClient();\n\n return {\n /**\n * Prefetch a content list\n */\n prefetchList: <C extends CustomFields = CustomFields>(params: ContentListParams) => {\n return queryClient.prefetchQuery(contentQueries.list<C>(params));\n },\n\n /**\n * Prefetch a content detail\n */\n prefetchDetail: <C extends CustomFields = CustomFields>(id: number) => {\n return queryClient.prefetchQuery(contentQueries.detail<C>(id));\n },\n\n /**\n * Invalidate content lists (force refetch)\n */\n invalidateLists: () => {\n return queryClient.invalidateQueries({ queryKey: contentKeys.lists() });\n },\n\n /**\n * Invalidate a specific content detail\n */\n invalidateDetail: (id: number) => {\n return queryClient.invalidateQueries({ queryKey: contentKeys.detail(id) });\n },\n\n /**\n * Invalidate all content queries\n */\n invalidateAll: () => {\n return queryClient.invalidateQueries({ queryKey: contentKeys.all });\n },\n };\n}\n","// src/utils/assetUrl.ts\ntype BuildAssetUrlOpts = {\n apiBase?: string;\n fileBase?: string;\n};\n\nexport const buildAssetUrl = (rawPath?: string | null, opts?: BuildAssetUrlOpts): string => {\n if (!rawPath) return '';\n const trimmed = rawPath.trim();\n if (!trimmed) return '';\n\n const apiBase = (opts?.apiBase || '').replace(/\\/+$/, '');\n const fileBase = (opts?.fileBase || '').replace(/\\/+$/, '');\n const originBase = apiBase.replace(/\\/api$/, '');\n const preferredBase = fileBase || originBase || apiBase;\n\n const lower = trimmed.toLowerCase();\n const isAbsolute = lower.startsWith('http://')\n || lower.startsWith('https://')\n || lower.startsWith('data:')\n || lower.startsWith('blob:')\n || trimmed.startsWith('//');\n\n if (isAbsolute) {\n if (apiBase && preferredBase && lower.startsWith(apiBase.toLowerCase())) {\n const afterApiBase = trimmed.slice(apiBase.length);\n return `${preferredBase}${afterApiBase.startsWith('/') ? '' : '/'}${afterApiBase}`;\n }\n const originApiPrefix = `${originBase}/api`.toLowerCase();\n if (originBase && preferredBase && lower.startsWith(originApiPrefix)) {\n const afterOriginApi = trimmed.slice((originBase + '/api').length);\n return `${preferredBase}${afterOriginApi.startsWith('/') ? '' : '/'}${afterOriginApi}`;\n }\n if (fileBase && lower.startsWith(fileBase.toLowerCase())) return trimmed;\n return trimmed;\n }\n\n let relative = trimmed.replace(/^\\/+/, '');\n relative = relative.replace(/^api\\//, '');\n return preferredBase ? `${preferredBase}/${relative}` : `/${relative}`;\n};","import { buildAssetUrl } from './assetUrl';\nimport { getConfig } from '../config/sdkConfig';\nimport type { ContentDto, CustomFields, NormalizedContentItem } from '../types/content';\n\n/**\n * Normalize a content item to a standard format with asset URLs resolved\n * \n * This function provides a default normalization strategy that:\n * - Extracts common fields from customFields\n * - Resolves asset URLs using SDK configuration\n * - Handles PDF path resolution for different content types\n * - Provides sensible defaults for missing fields\n * \n * @param item - Raw content item from the API\n * @returns Normalized content item\n */\nexport function normalizeContentItem<C extends CustomFields = CustomFields>(\n item: ContentDto<C>\n): NormalizedContentItem {\n const config = getConfig();\n const customFields = item.customFields as Record<string, unknown>;\n\n // Helper to get custom field value\n const getField = (key: string): string | null => {\n const value = customFields[key];\n return typeof value === 'string' ? value : null;\n };\n\n // Resolve PDF path based on content type\n const resolvePdfPath = (): string | null => {\n const type = item.type.toUpperCase();\n \n // For GAMEHEARTS_PUBLICATION type\n if (type === 'GAMEHEARTS_PUBLICATION' || type === 'REPORT') {\n return getField('publication_pdf') ?? getField('pdfPath');\n }\n \n // For EXTERNAL_PUBLICATION type\n if (type === 'EXTERNAL_PUBLICATION') {\n return getField('pdf') ?? getField('pdfPath');\n }\n \n // Default to pdfPath field\n return getField('pdfPath');\n };\n\n const pdfPath = resolvePdfPath();\n\n return {\n id: item.id,\n title: getField('title') || item.title,\n text: getField('text') || item.text,\n type: item.type,\n insideImage: buildAssetUrl(getField('insideImage') || '', {\n apiBase: config.baseUrl,\n fileBase: config.fileBaseUrl,\n }),\n outsideImage: buildAssetUrl(getField('outsideImage') || '', {\n apiBase: config.baseUrl,\n fileBase: config.fileBaseUrl,\n }),\n pdfPath: buildAssetUrl(pdfPath, {\n apiBase: config.baseUrl,\n fileBase: config.fileBaseUrl,\n }),\n references: getField('referencesList') ?? getField('references'),\n citation: getField('citation'),\n abstract: getField('abstractText') ?? getField('abstract'),\n team: getField('team'),\n publicationType: getField('publicationType'),\n fake: customFields.fake === true ? true : null,\n };\n}\n\n/**\n * Build an asset URL using the SDK's configured base URLs\n * This is a convenience wrapper that automatically uses SDK config\n * \n * @param path - Path to the asset (can be relative or absolute)\n * @returns Full URL to the asset\n */\nexport function buildAssetUrlWithConfig(path?: string | null): string {\n const config = getConfig();\n return buildAssetUrl(path, {\n apiBase: config.baseUrl,\n fileBase: config.fileBaseUrl,\n });\n}\n","/**\n * Strips HTML tags from a string, returning plain text.\n *\n * Uses a regex approach rather than the browser DOM (`document.createElement`)\n * so it is safe to call in any environment: browser, Node.js, SSR, edge functions.\n *\n * Designed for TipTap-generated HTML (paragraphs, bold, italic, lists, links).\n * Block-level elements (`<p>`, `<li>`, `<br>`) are replaced with a space so\n * adjacent words from different blocks are not concatenated. The result is\n * then trimmed and excess internal whitespace is normalized.\n *\n * @example\n * stripHtmlTags('<p>Hello <strong>world</strong></p>') // → 'Hello world'\n * stripHtmlTags('<ul><li>A</li><li>B</li></ul>') // → 'A B'\n */\nexport function stripHtmlTags(html: string): string {\n if (!html) return '';\n\n return html\n // Replace block-level closing tags with a space separator so words don't merge\n .replace(/<\\/(p|li|h[1-6]|blockquote|div|tr)>/gi, ' ')\n // Replace <br> variants with a space\n .replace(/<br\\s*\\/?>/gi, ' ')\n // Remove all remaining HTML tags\n .replace(/<[^>]*>/g, '')\n // Collapse multiple whitespace characters into a single space\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/**\n * Returns `true` if the given string contains HTML markup.\n *\n * Specifically detects well-formed opening HTML tags — a `<` followed by a\n * known tag name and at least one non-`>` character or an immediate `>`.\n * This avoids false-positives from plain-text angle brackets such as\n * comparison operators (\"if x < 10 then\").\n *\n * Useful for deciding whether to render a value via a rich text renderer\n * (TipTap, `dangerouslySetInnerHTML`) or as plain text.\n *\n * @example\n * isHtmlContent('<p>Bold</p>') // → true\n * isHtmlContent('Just text') // → false\n * isHtmlContent('if x < 10 then') // → false\n */\nexport function isHtmlContent(value: string): boolean {\n if (!value) return false;\n // Matches an opening tag: <tagname followed by whitespace, attribute chars, or >\n return /<([a-zA-Z][a-zA-Z0-9]*)(\\s[^>]*)?>/.test(value);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@codesocietyou/contentedge-cms-sdk",
3
3
  "private": false,
4
- "version": "1.0.0",
4
+ "version": "1.0.3",
5
5
  "description": "A lightweight, framework-agnostic TypeScript client for the ContentEdge headless CMS with API key authentication and optional React Query integration.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
@@ -58,7 +58,7 @@
58
58
  "provenance": true
59
59
  },
60
60
  "dependencies": {
61
- "axios": "^1.13.3"
61
+ "axios": "^1.15.2"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@tanstack/react-query": ">=5.90.0"
@@ -75,16 +75,16 @@
75
75
  "@types/node": "^25.2.3",
76
76
  "@types/react": "^19.2.14",
77
77
  "@types/react-dom": "^19.2.3",
78
- "@vitejs/plugin-react": "^5.1.4",
78
+ "@vitejs/plugin-react": "^6.0.1",
79
79
  "@vitest/coverage-v8": "^4.0.18",
80
80
  "eslint": "^10.0.0",
81
81
  "globals": "^17.3.0",
82
82
  "react": "^19.2.4",
83
83
  "react-dom": "^19.2.4",
84
- "tsdown": "^0.3.0",
85
- "typescript": "~5.9.3",
86
- "typescript-eslint": "8.55.1-alpha.4",
87
- "vite": "^7.3.1",
84
+ "tsdown": "^0.21.10",
85
+ "typescript": "^6.0.3",
86
+ "typescript-eslint": "^8.59.1",
87
+ "vite": "^8.0.10",
88
88
  "vitest": "^4.0.18"
89
89
  }
90
90
  }