{"version":3,"file":"errors-C4d7Nfd2.mjs","names":[],"sources":["../src/core/errors.ts"],"sourcesContent":["import { stripVTControlCharacters } from \"node:util\";\n\nconst ERROR_MESSAGE_UNSAFE = /[\\p{Cc}\\p{Cf}\\p{Cs}\\p{Zl}\\p{Zp}]/gu;\n\nexport function validateMaxResults(maxResults: number | undefined): void {\n  if (maxResults !== undefined && (!Number.isInteger(maxResults) || maxResults < 1)) {\n    throw new TypeError(\"maxResults must be a positive integer\");\n  }\n}\n\n/** Base error for all web operations. */\nexport class WebError extends Error {\n  constructor(message: string, options?: Readonly<ErrorOptions>) {\n    super(message, options);\n    this.name = \"WebError\";\n  }\n}\n\n/** HTTP failure with status code, URL, and response body. */\nexport class HTTPError extends WebError {\n  readonly statusCode: number;\n  readonly url: string;\n  readonly body: string;\n\n  constructor(statusCode: number, url: string, body: string, options?: Readonly<ErrorOptions>) {\n    super(formatHTTPErrorMessage(statusCode, url, body), options);\n    this.name = \"HTTPError\";\n    this.statusCode = statusCode;\n    this.url = url;\n    this.body = body;\n  }\n\n  isNotFound(): boolean {\n    return this.statusCode === 404;\n  }\n\n  isRateLimit(): boolean {\n    return this.statusCode === 429;\n  }\n\n  isServerError(): boolean {\n    return this.statusCode >= 500;\n  }\n}\n\n/** Provider credit exhaustion, retaining the original HTTP status and response. */\nexport class PaymentError extends HTTPError {\n  constructor(statusCode: number, url: string, body: string) {\n    super(statusCode, url, body);\n    this.name = \"PaymentError\";\n  }\n}\n\nfunction formatHTTPErrorMessage(statusCode: number, url: string, body: string): string {\n  const header = `HTTP ${statusCode}: ${url}`;\n  const safeBody = stripVTControlCharacters(body)\n    .replaceAll(ERROR_MESSAGE_UNSAFE, \" \")\n    .replaceAll(/\\s+/g, \" \")\n    .trim();\n  return safeBody.length > 0 ? `${header}: ${safeBody}` : header;\n}\n\n/** Thrown when a provider rejects the API key (HTTP 401). */\nexport class AuthError extends WebError {\n  readonly provider: string;\n\n  constructor(message: string, provider: string) {\n    super(message);\n    this.name = \"AuthError\";\n    this.provider = provider;\n  }\n}\n\n/** Thrown on HTTP 429. Check {@link retryAfter} for seconds until retry. */\nexport class RateLimitError extends WebError {\n  readonly retryAfter: number;\n\n  constructor(retryAfter: number) {\n    super(`Rate limited. Retry after ${retryAfter}s`);\n    this.name = \"RateLimitError\";\n    this.retryAfter = retryAfter;\n  }\n}\n\n/** Thrown when {@link create} is called with an unregistered provider name. */\nexport class UnknownProviderError extends WebError {\n  readonly provider: string;\n\n  constructor(provider: string) {\n    super(`Unknown provider: ${provider}`);\n    this.name = \"UnknownProviderError\";\n    this.provider = provider;\n  }\n}\n\n/** Thrown when the search query is empty or whitespace-only. */\nexport class EmptyQueryError extends WebError {\n  constructor() {\n    super(\"Search query cannot be empty\");\n    this.name = \"EmptyQueryError\";\n  }\n}\n\n/** Thrown when a search continuation is malformed or belongs to another request. */\nexport class InvalidSearchContinuationError extends WebError {\n  constructor() {\n    super(\"Invalid search continuation token\");\n    this.name = \"InvalidSearchContinuationError\";\n  }\n}\n\n/** Thrown when the reverse image search URL is empty or whitespace-only. */\nexport class EmptyImageUrlError extends WebError {\n  constructor() {\n    super(\"Image URL cannot be empty\");\n    this.name = \"EmptyImageUrlError\";\n  }\n}\n\n/** Thrown when reverse image search receives a non-HTTP URL. */\nexport class InvalidImageUrlError extends WebError {\n  constructor() {\n    super(\"Image URL must be an absolute HTTP or HTTPS URL\");\n    this.name = \"InvalidImageUrlError\";\n  }\n}\n\n/** Thrown when the read URL is empty or whitespace-only. */\nexport class EmptyUrlError extends WebError {\n  constructor() {\n    super(\"Read URL cannot be empty\");\n    this.name = \"EmptyUrlError\";\n  }\n}\n\n/** Thrown when a read continuation token is malformed or belongs to another request. */\nexport class InvalidReadContinuationError extends WebError {\n  constructor() {\n    super(\"Invalid read continuation token\");\n    this.name = \"InvalidReadContinuationError\";\n  }\n}\n\n/** Thrown when the page changed before a continued read. */\nexport class StaleReadContinuationError extends WebError {\n  constructor() {\n    super(\"Read content changed since the continuation token was issued\");\n    this.name = \"StaleReadContinuationError\";\n  }\n}\n\nexport class InvalidProviderUrlError extends WebError {\n  readonly provider: string;\n\n  constructor(provider: string) {\n    super(`Invalid base URL for provider \"${provider}\": expected an absolute http or https URL`);\n    this.name = \"InvalidProviderUrlError\";\n    this.provider = provider;\n  }\n}\n\n/** Thrown when a provider does not implement the search capability. */\nexport class SearchNotSupportedError extends WebError {\n  readonly provider: string;\n\n  constructor(provider: string) {\n    super(`Provider does not support search: ${provider}`);\n    this.name = \"SearchNotSupportedError\";\n    this.provider = provider;\n  }\n}\n\n/** Thrown when a provider does not implement reverse image search. */\nexport class ImageSearchNotSupportedError extends WebError {\n  readonly provider: string;\n\n  constructor(provider: string) {\n    super(`Provider does not support reverse image search: ${provider}`);\n    this.name = \"ImageSearchNotSupportedError\";\n    this.provider = provider;\n  }\n}\n\n/** Thrown when a provider does not implement the read capability. */\nexport class ReadNotSupportedError extends WebError {\n  readonly provider: string;\n\n  constructor(provider: string) {\n    super(`Provider does not support read: ${provider}`);\n    this.name = \"ReadNotSupportedError\";\n    this.provider = provider;\n  }\n}\n\n/** Thrown when no provider can be selected from env or registry. */\nexport class NoProviderConfiguredError extends WebError {\n  constructor() {\n    super(\"No web search provider configured. Set an API key env var or register a provider.\");\n    this.name = \"NoProviderConfiguredError\";\n  }\n}\n\n/** Thrown when providers are configured but none are currently reachable. */\nexport class NoProviderAvailableError extends WebError {\n  readonly providers: readonly string[];\n\n  constructor(providers: readonly string[]) {\n    const providerList = providers.length > 0 ? providers.join(\", \") : \"unknown\";\n    super(`No configured web search provider is currently reachable: ${providerList}`);\n    this.name = \"NoProviderAvailableError\";\n    this.providers = providers;\n  }\n}\n\n/** Thrown when a date filter string is not valid ISO 8601 or the range is reversed. */\nexport class InvalidDateFilterError extends WebError {\n  readonly field: string;\n  readonly value: string;\n  readonly reason: string;\n\n  constructor(field: string, value: string, reason: string) {\n    super(`Invalid date filter ${field}=\"${value}\": ${reason}`);\n    this.name = \"InvalidDateFilterError\";\n    this.field = field;\n    this.value = value;\n    this.reason = reason;\n  }\n}\n\nconst ISO_DATE_RE = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$/;\nconst HAS_OFFSET_RE = /Z|[+-]\\d{2}:\\d{2}$/;\n\nexport function validateDateFilters(startPublishedDate?: string, endPublishedDate?: string): void {\n  validateDateFilter(\"startPublishedDate\", startPublishedDate);\n  validateDateFilter(\"endPublishedDate\", endPublishedDate);\n  validateDateOrder(startPublishedDate, endPublishedDate);\n}\n\nfunction validateDateFilter(\n  field: \"startPublishedDate\" | \"endPublishedDate\",\n  value?: string,\n): void {\n  if (value === undefined) return;\n  if (!ISO_DATE_RE.test(value)) {\n    throw new InvalidDateFilterError(\n      field,\n      value,\n      'must be ISO 8601 (e.g. \"2024-01-01\" or \"2024-01-01T00:00:00Z\")',\n    );\n  }\n  if (value.includes(\"T\") && !HAS_OFFSET_RE.test(value)) {\n    throw new InvalidDateFilterError(field, value, \"datetime must include Z or ±HH:mm offset\");\n  }\n  if (Number.isNaN(Date.parse(value))) {\n    throw new InvalidDateFilterError(field, value, \"not a valid date\");\n  }\n  validateCalendarDate(field, value);\n}\n\nfunction validateCalendarDate(\n  field: \"startPublishedDate\" | \"endPublishedDate\",\n  value: string,\n): void {\n  const [year, month, day] = value.split(\"T\")[0].split(\"-\").map(Number);\n  const probe = new Date(Date.UTC(year, month - 1, day));\n  if (\n    probe.getUTCFullYear() !== year ||\n    probe.getUTCMonth() + 1 !== month ||\n    probe.getUTCDate() !== day\n  ) {\n    throw new InvalidDateFilterError(field, value, \"not a valid calendar date\");\n  }\n}\n\nfunction validateDateOrder(start?: string, end?: string): void {\n  if (start === undefined || end === undefined || Date.parse(start) <= Date.parse(end)) return;\n  throw new InvalidDateFilterError(\n    \"startPublishedDate\",\n    start,\n    `start date is after end date \"${end}\"`,\n  );\n}\n\n/**\n * Convert any caught error into a typed {@link WebError} subclass.\n * Preserves payment errors; otherwise maps 401 to AuthError and 429 to RateLimitError.\n * @param {*} error - Caught value.\n * @param {string} provider - Provider that raised the error.\n * @returns {WebError} Normalized web error.\n */\nexport function normalizeError(error: unknown, provider?: string): WebError {\n  if (error instanceof PaymentError) return error;\n  if (error instanceof HTTPError && error.statusCode === 401) {\n    return new AuthError(\n      `Authentication failed: ${error.body || \"Invalid or missing API key\"}`,\n      provider || \"unknown\",\n    );\n  }\n\n  if (error instanceof WebError) {\n    return error;\n  }\n\n  if (isFetchLikeError(error)) return normalizeFetchLikeError(error, provider);\n\n  if (error instanceof Error) {\n    return new WebError(error.message);\n  }\n\n  return new WebError(String(error));\n}\n\ntype FetchLikeError = {\n  readonly status: number;\n  readonly message: string;\n  readonly response?: { readonly headers?: { readonly get: (key: string) => string | null } };\n};\n\nfunction isFetchLikeError(error: unknown): error is FetchLikeError {\n  return (\n    typeof error === \"object\" &&\n    error !== null &&\n    \"status\" in error &&\n    typeof error.status === \"number\" &&\n    \"message\" in error &&\n    typeof error.message === \"string\"\n  );\n}\n\nfunction normalizeFetchLikeError(error: FetchLikeError, provider?: string): WebError {\n  const message = error.message || `HTTP ${error.status}`;\n  switch (error.status) {\n    case 401:\n      return new AuthError(`Authentication failed: ${message}`, provider || \"unknown\");\n    case 404:\n      return new HTTPError(404, \"\", message);\n    case 429:\n      return new RateLimitError(parseRetryAfter(error.response?.headers?.get(\"Retry-After\")));\n    default:\n      return error.status >= 500 ? new HTTPError(error.status, \"\", message) : new WebError(message);\n  }\n}\n\nexport const DEFAULT_RETRY_AFTER = 60;\n\nexport function parseRetryAfter(header: string | null | undefined): number {\n  if (header === null || header === undefined) {\n    return DEFAULT_RETRY_AFTER;\n  }\n\n  const trimmed = header.trim();\n  if (!/^\\d+$/.test(trimmed)) {\n    return DEFAULT_RETRY_AFTER;\n  }\n\n  const parsed = Number.parseInt(trimmed, 10);\n  if (!Number.isFinite(parsed)) {\n    return DEFAULT_RETRY_AFTER;\n  }\n\n  return parsed;\n}\n"],"mappings":"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"}