( headers?: Record<string, string | string[] | undefined> )
| 212 | export const MAX_PAGINATION_PAGES = 1000; |
| 213 | |
| 214 | export function getNextPageUrlFromLinkHeader( |
| 215 | headers?: Record<string, string | string[] | undefined> |
| 216 | ): string | null { |
| 217 | if (!headers) { |
| 218 | return null; |
| 219 | } |
| 220 | |
| 221 | const linkHeader = headers.link ?? headers.Link; |
| 222 | if (!linkHeader) { |
| 223 | return null; |
| 224 | } |
| 225 | |
| 226 | const normalizedLinkHeader = Array.isArray(linkHeader) |
| 227 | ? linkHeader.join(',') |
| 228 | : linkHeader; |
| 229 | |
| 230 | // Split into individual link-values and find the one with rel="next" |
| 231 | // RFC 8288 allows rel to appear anywhere among the parameters |
| 232 | const linkValues = normalizedLinkHeader.split(/,(?=\s*<)/); |
| 233 | for (const linkValue of linkValues) { |
| 234 | const urlMatch = linkValue.match(/<([^>]+)>/); |
| 235 | if (!urlMatch) continue; |
| 236 | |
| 237 | const params = linkValue.slice(urlMatch[0].length); |
| 238 | // Use word boundary to match "next" as a standalone relation type |
| 239 | // RFC 8288 allows space-separated relation types like rel="next prev" |
| 240 | if (/;\s*rel="?[^"]*\bnext\b/i.test(params)) { |
| 241 | return urlMatch[1]; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | return null; |
| 246 | } |
| 247 | |
| 248 | export function validatePaginationUrl( |
| 249 | url: string, |
no outgoing calls
no test coverage detected