// Typed client for the anonymous FAQ API. It wraps an injected fetcher, so // the Nuxt composable decides the base URL and headers and `node --test` can // drive it with a stub. import type { Faq, FaqEnvelope, FaqItem } from './types.ts' /** Issues one GET for `path` (e.g. "/faqs/shipping") with `query`, resolving the parsed JSON. */ export type FaqFetcher = (path: string, query?: Record) => Promise /** True for a fetch error that carries HTTP status 404 (ofetch, h3 or plain). */ export function isNotFound(err: unknown): boolean { if (!err || typeof err !== 'object') return false const e = err as { status?: unknown; statusCode?: unknown; response?: { status?: unknown } } return e.status === 404 || e.statusCode === 404 || e.response?.status === 404 } /** The API path of a FAQ: /faqs/, the slug URL-encoded. */ export function faqPath(slug: string): string { return `/faqs/${encodeURIComponent(slug)}` } /** Items with a string question and answer; anything else is dropped. */ export function faqItems(content: unknown): FaqItem[] { if (!Array.isArray(content)) return [] return content.filter( (item): item is FaqItem => !!item && typeof item === 'object' && typeof (item as FaqItem).question === 'string' && typeof (item as FaqItem).answer === 'string', ) } export interface FaqClient { /** The FAQ with `slug`, or null when the API answers 404 (unknown, inactive or locale not enabled). */ getFaq(slug: string, locale?: string): Promise } export function createFaqClient(fetcher: FaqFetcher): FaqClient { return { async getFaq(slug, locale) { const query = locale ? { locale } : undefined let res: FaqEnvelope try { res = (await (query ? fetcher(faqPath(slug), query) : fetcher(faqPath(slug)))) as FaqEnvelope } catch (err) { if (isNotFound(err)) return null throw err } const faq = res?.data if (!faq || typeof faq !== 'object') return null return { ...faq, description: typeof faq.description === 'string' ? faq.description : null, content: faqItems(faq.content) } }, } }