// The layer's pagination and HTML-sitemap module options normalised for // the runtime config (lib/seo-module.ts) and their readers in app and // server code. Pure, so `node --test` drives it. export interface SeoPaginationOption { form: 'query' | 'path' param: string segments: Record } /** * The HTML sitemap pages by locale, or false for none. overCatchAll: serve * the layer's pages ahead of an app catch-all page that covers the path * (default false: the catch-all keeps the path, L4 WR-04). */ export type SeoHtmlSitemapOption = { paths: Record; overCatchAll: boolean } | false /** The pagination option with its defaults, as the runtime config carries it. */ export function paginationOption(value: unknown): SeoPaginationOption { const v = value !== null && typeof value === 'object' ? (value as Record) : {} const segments: Record = {} const raw = v.segments !== null && typeof v.segments === 'object' ? (v.segments as Record) : {} for (const [locale, segment] of Object.entries(raw)) if (typeof segment === 'string' && /^[\p{L}\p{N}_-]+$/u.test(segment)) segments[locale] = segment return { form: v.form === 'path' ? 'path' : 'query', param: typeof v.param === 'string' && /^[A-Za-z0-9_-]+$/.test(v.param) ? v.param : 'page', segments, } } /** The HTML sitemap option: false, or the paths (each '/' plus at least one character) and overCatchAll. */ export function htmlSitemapOption(value: unknown): SeoHtmlSitemapOption { if (value === false) return false const v = value !== null && typeof value === 'object' ? (value as Record) : {} const paths: Record = {} const raw = v.paths !== null && typeof v.paths === 'object' ? (v.paths as Record) : {} for (const [locale, path] of Object.entries(raw)) { if (typeof path === 'string' && /^\/[^?#\s]+$/.test(path) && !path.includes(':') && !path.includes('*')) paths[locale] = path.replace(/\/+$/, '') } return { paths, overCatchAll: v.overCatchAll === true } }