{"version":3,"sources":["../src/core/errors.ts","../src/data-model/primitives.ts","../src/http/cookie-jar.ts","../src/http/cf-challenge.ts","../src/http/browser/safe-close.ts","../src/http/browser/client.ts","../src/index.ts","../src/utils/logger.ts","../src/core/downloader.ts","../src/core/registry.ts","../src/core/events.ts","../src/http/client.ts","../src/utils/url.ts","../src/http/user-agents.ts","../src/utils/sleep.ts","../src/http/rate-limiter.ts","../src/http/retry.ts","../src/http/index.ts","../src/http/tiered.ts","../src/http/browser/loader.ts","../src/http/browser/pool.ts","../src/data-model/parse.ts","../src/data-model/schema.ts","../src/pipeline/fetch-metadata.ts","../src/pipeline/fetch-toc.ts","../src/pipeline/walk-toc.ts","../src/storage/cache-store.ts","../src/storage/_fs.ts","../src/storage/http-cache.ts","../src/core/download-run.ts","../src/core/debouncer.ts","../src/core/run-ledger.ts","../src/pipeline/fetch-chapters.ts","../src/pipeline/normalize.ts","../src/storage/resume-state.ts","../src/storage/resume-token.ts","../src/storage/resume-storage.ts","../src/storage/resume-store.ts","../src/storage/chapter-store.ts","../src/adapters/base.ts","../src/utils/html.ts","../src/adapters/truyenfull/index.ts","../src/adapters/truyenfull/parser.ts","../src/adapters/shared/anchors.ts","../src/adapters/shared/header.ts","../src/adapters/shared/cover.ts","../src/adapters/shared/content.ts","../src/adapters/truyenfull/selectors.ts","../src/adapters/shared/paginate.ts","../src/adapters/metruyenchu/index.ts","../src/adapters/metruyenchu/parser.ts","../src/adapters/metruyenchu/selectors.ts","../src/adapters/wikicv/index.ts","../src/adapters/wikicv/parser.ts","../src/adapters/wikicv/selectors.ts","../src/adapters/tangthuvien/index.ts","../src/core/default.ts"],"sourcesContent":["import type { Url } from \"../data-model/primitives.js\";\r\nimport type { ChapterRef } from \"../data-model/novel.js\";\r\n\r\n/**\r\n * Base class for every error this library raises or returns. Each subclass\r\n * carries a stable, machine-readable `code` (see {@link DownloadErrorCode}).\r\n * The result/event error channels are typed as {@link KnownDownloadError}, so\r\n * `switch (error.code)` narrows to the concrete subclass and exposes its typed\r\n * fields. Custom subclasses (open extension point) use `instanceof DownloadError`.\r\n */\r\nexport abstract class DownloadError extends Error {\r\n  abstract readonly code: string;\r\n  override readonly cause?: unknown;\r\n  constructor(message: string, options?: { cause?: unknown }) {\r\n    super(message);\r\n    this.cause = options?.cause;\r\n    this.name = this.constructor.name;\r\n  }\r\n}\r\n\r\n/**\r\n * No registered adapter can handle the given URL (`code: \"ADAPTER_NOT_FOUND\"`).\r\n * `download()` returns this in its `{ status: \"error\" }` envelope;\r\n * `fetchMetadata()` throws it.\r\n */\r\nexport class AdapterNotFoundError extends DownloadError {\r\n  readonly code = \"ADAPTER_NOT_FOUND\" as const;\r\n  constructor(\r\n    public readonly url: string,\r\n    options?: { cause?: unknown },\r\n  ) {\r\n    super(`No adapter for URL: ${url}`, options);\r\n  }\r\n}\r\n\r\n/** A non-retryable HTTP error response (`code: \"HTTP_ERROR\"`); carries `status` and `url`. */\r\nexport class HttpError extends DownloadError {\r\n  readonly code = \"HTTP_ERROR\" as const;\r\n  constructor(\r\n    public readonly status: number,\r\n    public readonly url: Url,\r\n    message?: string,\r\n    options?: { cause?: unknown },\r\n  ) {\r\n    super(message ?? `HTTP ${status} for ${url}`, options);\r\n  }\r\n}\r\n\r\n/** The host signalled rate limiting, e.g. HTTP 429 (`code: \"RATE_LIMITED\"`); `retryAfterMs` if known. */\r\nexport class RateLimitedError extends DownloadError {\r\n  readonly code = \"RATE_LIMITED\" as const;\r\n  constructor(\r\n    public readonly url: Url,\r\n    public readonly retryAfterMs?: number,\r\n    options?: { cause?: unknown },\r\n  ) {\r\n    super(`Rate limited at ${url}`, options);\r\n  }\r\n}\r\n\r\n/**\r\n * Malformed input or an unparseable/invalid response (`code: \"PARSE_ERROR\"`) —\r\n * e.g. a bad URL, an out-of-range `chapterRange` bound, or adapter output that\r\n * fails schema validation. Carries optional `url` and `snippet` for debugging.\r\n */\r\nexport class ParseError extends DownloadError {\r\n  readonly code = \"PARSE_ERROR\" as const;\r\n  readonly url: Url | undefined;\r\n  readonly snippet: string | undefined;\r\n  readonly path: string | undefined;\r\n  constructor(message: string, options?: { cause?: unknown; url?: Url; snippet?: string; path?: string }) {\r\n    super(message, options);\r\n    this.url = options?.url;\r\n    this.snippet = options?.snippet;\r\n    this.path = options?.path;\r\n  }\r\n}\r\n\r\n/**\r\n * A single chapter could not be fetched (`code: \"CHAPTER_FETCH_FAILED\"`);\r\n * carries the failing `ref`. Surfaces in {@link DownloadPartial}'s `failures`.\r\n */\r\nexport class ChapterFetchError extends DownloadError {\r\n  readonly code = \"CHAPTER_FETCH_FAILED\" as const;\r\n  constructor(\r\n    public readonly ref: ChapterRef,\r\n    options?: { cause?: unknown },\r\n  ) {\r\n    super(`Chapter ${ref.index} failed: ${ref.url}`, options);\r\n  }\r\n}\r\n\r\n/** A request exceeded its timeout (`code: \"TIMEOUT\"`). */\r\nexport class TimeoutError extends DownloadError {\r\n  readonly code = \"TIMEOUT\" as const;\r\n  constructor(\r\n    public readonly url: Url,\r\n    options?: { cause?: unknown },\r\n  ) {\r\n    super(`Timeout for ${url}`, options);\r\n  }\r\n}\r\n\r\n/**\r\n * The operation was aborted via an `AbortSignal` (`code: \"CANCELLED\"`).\r\n * `download()` **throws** this rather than returning an error envelope.\r\n */\r\nexport class CancelledError extends DownloadError {\r\n  readonly code = \"CANCELLED\" as const;\r\n  constructor(options?: { cause?: unknown }) {\r\n    super(\"Cancelled\", options);\r\n  }\r\n}\r\n\r\n/**\r\n * A browser-tier request was required but neither `patchright` nor `playwright`\r\n * is installed (`code: \"BROWSER_MODULE_NOT_INSTALLED\"`). The message lists the\r\n * install commands and the `http-only` opt-out.\r\n */\r\nexport class BrowserModuleNotInstalledError extends DownloadError {\r\n  readonly code = \"BROWSER_MODULE_NOT_INSTALLED\" as const;\r\n  constructor(options?: { cause?: unknown }) {\r\n    super(\r\n      \"No headless browser module installed. Install one of:\\n\" +\r\n        \"  pnpm add patchright   (recommended for Cloudflare-fronted sites)\\n\" +\r\n        \"  pnpm add playwright   (works for non-protected pages)\\n\" +\r\n        'Or pass transport: \"http-only\" to disable the browser tier.',\r\n      options,\r\n    );\r\n  }\r\n}\r\n\r\n/**\r\n * Suggested remedy on a {@link ChallengeUnresolvedError}:\r\n * `\"manual-solve\"` (launch a headed browser and solve once) or\r\n * `\"transport-config\"` (raise the navigation timeout / use a stronger transport).\r\n */\r\nexport type ChallengeUnresolvedHint = \"manual-solve\" | \"transport-config\";\r\n\r\n/**\r\n * A Cloudflare challenge could not be cleared within the navigation timeout\r\n * (`code: \"CHALLENGE_UNRESOLVED\"`). `hint` indicates how to recover; the message\r\n * spells out the concrete fix.\r\n */\r\nexport class ChallengeUnresolvedError extends DownloadError {\r\n  readonly code = \"CHALLENGE_UNRESOLVED\" as const;\r\n  constructor(\r\n    public readonly url: Url,\r\n    public readonly hint: ChallengeUnresolvedHint,\r\n    options?: { cause?: unknown },\r\n  ) {\r\n    const tail =\r\n      hint === \"manual-solve\"\r\n        ? 'Try transport: { mode: \"auto\", browserOptions: { headed: true } } and solve the checkbox in the launched window once.'\r\n        : \"Increase transport.browserOptions.navigationTimeoutMs or switch to a stronger transport (e.g. a Browserless/ZenRows custom HttpClient).\";\r\n    super(`Cloudflare challenge could not be resolved within the navigation timeout for ${url}. ${tail}`, options);\r\n  }\r\n}\r\n\r\n/** Stable, machine-readable code for every built-in {@link DownloadError}. */\r\nexport type DownloadErrorCode = KnownDownloadError[\"code\"];\r\n\r\n/**\r\n * Discriminated union of every built-in error. The result/event error channels\r\n * use this so `switch (error.code)` narrows to the concrete class (e.g. reaching\r\n * {@link HttpError.status}). A custom-adapter error that is not one of these is\r\n * normalized to {@link ParseError} in the returned envelope (its original is on\r\n * `.cause`); use `instanceof DownloadError` as the catch-all.\r\n */\r\nexport type KnownDownloadError =\r\n  | AdapterNotFoundError\r\n  | HttpError\r\n  | RateLimitedError\r\n  | ParseError\r\n  | ChapterFetchError\r\n  | TimeoutError\r\n  | CancelledError\r\n  | BrowserModuleNotInstalledError\r\n  | ChallengeUnresolvedError;\r\n\r\n/**\r\n * Coerce any thrown value into a {@link KnownDownloadError} without a cast.\r\n * Built-in errors pass through with full type; everything else (including an\r\n * exotic custom `DownloadError` subclass) is wrapped in a {@link ParseError}\r\n * with the original preserved on `cause`.\r\n */\r\nexport function normalizeToKnownError(err: unknown): KnownDownloadError {\r\n  if (err instanceof AdapterNotFoundError) return err;\r\n  if (err instanceof HttpError) return err;\r\n  if (err instanceof RateLimitedError) return err;\r\n  if (err instanceof ParseError) return err;\r\n  if (err instanceof ChapterFetchError) return err;\r\n  if (err instanceof TimeoutError) return err;\r\n  if (err instanceof CancelledError) return err;\r\n  if (err instanceof BrowserModuleNotInstalledError) return err;\r\n  if (err instanceof ChallengeUnresolvedError) return err;\r\n  if (err instanceof DownloadError) return new ParseError(err.message, { cause: err });\r\n  return new ParseError(String(err), { cause: err });\r\n}\r\n\r\n/**\r\n * Like {@link normalizeToKnownError} but re-throws {@link CancelledError}\r\n * instead of returning it. Use at `download()` return sites where `CancelledError`\r\n * is always thrown, never placed in the error envelope.\r\n */\r\nexport function normalizeToEnvelopeError(err: unknown): Exclude<KnownDownloadError, CancelledError> {\r\n  if (err instanceof CancelledError) throw err;\r\n  const known = normalizeToKnownError(err);\r\n  if (known instanceof CancelledError) throw known;\r\n  return known;\r\n}\r\n","import { z } from \"zod\";\r\n\r\nexport const UrlSchema = z.string().url().brand<\"Url\">();\r\n/**\r\n * An absolute URL string, branded after validation so an unchecked string\r\n * cannot be passed where a real URL is required. Library output is already\r\n * branded; to brand untrusted input, use `parseUrl` from `data-model/parse.ts`.\r\n */\r\nexport type Url = z.infer<typeof UrlSchema>;\r\n/**\r\n * Brand a string as Url WITHOUT runtime validation. Use only when the caller\r\n * has already proved the value is a valid absolute URL (e.g. it came from\r\n * `new URL().toString()` or from a Zod-validated source). For untrusted input,\r\n * use `parseUrl` from `data-model/parse.ts` instead.\r\n */\r\nexport function unsafeBrandUrl(input: string): Url {\r\n  return input as Url;\r\n}\r\n\r\nexport const ChapterIndexSchema = z.number().int().nonnegative().brand<\"ChapterIndex\">();\r\n/**\r\n * A chapter's position in the table of contents, branded as a non-negative\r\n * integer. Indices are **0-based**: chapter 1 is index 0. Build one with\r\n * {@link chapterIndex}.\r\n */\r\nexport type ChapterIndex = z.infer<typeof ChapterIndexSchema>;\r\n/** Brand a number as ChapterIndex without validation. See `unsafeBrandUrl`. */\r\nexport function unsafeBrandChapterIndex(input: number): ChapterIndex {\r\n  return input as ChapterIndex;\r\n}\r\n/**\r\n * Build a validated ChapterIndex from a plain number. Throws if `input` is not\r\n * a non-negative integer. Used internally and by custom adapters when hand-\r\n * building a {@link ChapterRef}; `DownloadOptions.chapterRange` takes plain\r\n * numbers, so callers do not need this for ranges. Indices are 0-based.\r\n */\r\nexport function chapterIndex(input: number): ChapterIndex {\r\n  return ChapterIndexSchema.parse(input);\r\n}\r\n\r\nexport const AdapterIdSchema = z.string().min(1).brand<\"AdapterId\">();\r\n/** Stable identifier for a site adapter (e.g. `\"truyenfull\"`), branded as a non-empty string. */\r\nexport type AdapterId = z.infer<typeof AdapterIdSchema>;\r\n/** Brand a string as AdapterId without validation. See `unsafeBrandUrl`. */\r\nexport function unsafeBrandAdapterId(input: string): AdapterId {\r\n  return input as AdapterId;\r\n}\r\n\r\nexport const IsoDateSchema = z.string().datetime().brand<\"IsoDate\">();\r\n/** An ISO-8601 datetime string (e.g. `\"2026-06-17T09:30:00.000Z\"`), branded after validation. */\r\nexport type IsoDate = z.infer<typeof IsoDateSchema>;\r\n/** Brand a string as IsoDate without validation. See `unsafeBrandUrl`. */\r\nexport function unsafeBrandIsoDate(input: string): IsoDate {\r\n  return input as IsoDate;\r\n}\r\n\r\nexport const ResumeTokenSchema = z.string().min(1).brand<\"ResumeToken\">();\r\n/**\r\n * Opaque token returned on a partial download (see {@link DownloadPartial}).\r\n * Pass it back via `DownloadOptions.resume` to continue where the run left off.\r\n * Branded as a non-empty string.\r\n */\r\nexport type ResumeToken = z.infer<typeof ResumeTokenSchema>;\r\n/** Brand a string as ResumeToken without validation. See `unsafeBrandUrl`. */\r\nexport function unsafeBrandResumeToken(input: string): ResumeToken {\r\n  return input as ResumeToken;\r\n}\r\n","export interface Cookie {\r\n  readonly name: string;\r\n  readonly value: string;\r\n}\r\n\r\nexport interface CookieJarReadResult {\r\n  readonly cookieHeader: string | null;\r\n  readonly ua: string | null;\r\n}\r\n\r\ninterface Entry {\r\n  readonly cookies: readonly Cookie[];\r\n  readonly ua: string;\r\n}\r\n\r\nconst CF_NAME_PATTERN = /^(cf_clearance|__cf_bm|cf_chl)/;\r\n\r\nexport function isCfCookie(name: string): boolean {\r\n  return CF_NAME_PATTERN.test(name);\r\n}\r\n\r\nexport class CookieJar {\r\n  readonly #byHost = new Map<string, Entry>();\r\n\r\n  put(host: string, cookies: readonly Cookie[], ua: string): void {\r\n    // The UA pin is needed for HTTP-branch session continuity even when CF\r\n    // hasn't issued cookies yet — dropping the entry on `filtered.length === 0`\r\n    // would let the rotating UA client churn UAs until the first challenge.\r\n    const filtered = cookies.filter((c) => isCfCookie(c.name));\r\n    const key = host.toLowerCase();\r\n    const existing = this.#byHost.get(key);\r\n    const nextCookies = filtered.length > 0 ? filtered : (existing?.cookies ?? []);\r\n    this.#byHost.set(key, { cookies: nextCookies, ua });\r\n  }\r\n\r\n  read(host: string): CookieJarReadResult {\r\n    const entry = this.#byHost.get(host.toLowerCase());\r\n    if (!entry) return { cookieHeader: null, ua: null };\r\n    const cookieHeader =\r\n      entry.cookies.length > 0\r\n        ? entry.cookies.map((c) => `${c.name}=${c.value}`).join(\"; \")\r\n        : null;\r\n    return { cookieHeader, ua: entry.ua };\r\n  }\r\n}\r\n","import type { HttpResponse } from \"./types.js\";\r\n\r\n// Raw regex-alternation fragment, not a plain string: callers splice it into a\r\n// `new RegExp()`. Phrases must stay metacharacter-free or the built regex shifts.\r\nexport const CF_TITLE_PHRASES = \"Just a moment|Chờ một chút\";\r\nconst CF_TITLE_REGEX = new RegExp(`<title>(?:${CF_TITLE_PHRASES})\\\\b`, \"i\");\r\nconst CF_BODY_MARKER = /cf-browser-verification|cdn-cgi\\/challenge-platform/;\r\n// Cloudflare serves challenge pages on 403 (block), 503 (\"Under Attack\"),\r\n// 429 (rate-gate), and occasionally 200 (Turnstile interstitial). The body /\r\n// header markers below still gate the decision; status alone is not enough.\r\nconst CF_CANDIDATE_STATUSES = new Set([200, 403, 429, 503]);\r\n\r\nexport function isCloudflareChallenge(res: HttpResponse): boolean {\r\n  if (!CF_CANDIDATE_STATUSES.has(res.status)) return false;\r\n  if (res.headers[\"cf-mitigated\"] === \"challenge\") return true;\r\n  if (CF_TITLE_REGEX.test(res.body)) return true;\r\n  if (CF_BODY_MARKER.test(res.body)) return true;\r\n  return false;\r\n}\r\n","import type { Logger } from \"../../data-model/ports.js\";\r\n\r\ninterface Closeable {\r\n  close(): Promise<void>;\r\n}\r\n\r\nexport async function safeClosePage(page: Closeable, logger?: Logger): Promise<void> {\r\n  try {\r\n    await page.close();\r\n  } catch (err) {\r\n    logger?.debug({ err }, \"page close failed (non-fatal)\");\r\n  }\r\n}\r\n\r\nexport async function safeCloseContext(ctx: Closeable, logger?: Logger): Promise<void> {\r\n  try {\r\n    await ctx.close();\r\n  } catch (err) {\r\n    logger?.debug({ err }, \"context close failed (non-fatal)\");\r\n  }\r\n}\r\n","// src/http/browser/client.ts\r\nimport type { ZodType } from \"zod\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport { unsafeBrandUrl } from \"../../data-model/primitives.js\";\r\nimport type { HttpClient, HttpRequestOptions, HttpResponse } from \"../types.js\";\r\nimport type {\r\n  BrowserClient,\r\n  BrowserPage,\r\n  NavigateOptions,\r\n  RuntimeContext,\r\n  RuntimePage,\r\n  RuntimeResponse,\r\n  WaitForResponseJsonOpts,\r\n  WaitForResponseTextOpts,\r\n} from \"./types.js\";\r\nimport type { BrowserPool } from \"./pool.js\";\r\nimport { safeClosePage } from \"./safe-close.js\";\r\nimport { ChallengeUnresolvedError, CancelledError } from \"../../core/errors.js\";\r\nimport { isCfCookie } from \"../cookie-jar.js\";\r\nimport { CF_TITLE_PHRASES } from \"../cf-challenge.js\";\r\n\r\ntype WaitOpts =\r\n  | {\r\n      readonly timeoutMs?: number;\r\n      readonly signal?: AbortSignal;\r\n      readonly parse: \"text\";\r\n      readonly skipBuffer?: boolean;\r\n    }\r\n  | {\r\n      readonly timeoutMs?: number;\r\n      readonly signal?: AbortSignal;\r\n      readonly parse: \"json\";\r\n      readonly schema: ZodType<unknown>;\r\n      readonly skipBuffer?: boolean;\r\n    };\r\n\r\ninterface BufferedResponse {\r\n  url: string;\r\n  status: number;\r\n  headers: Record<string, string>;\r\n  raw: RuntimeResponse;\r\n}\r\n\r\n// Cap how many responses we retain between `navigate()` and `waitForResponse()`.\r\n// Each entry holds a strong ref to a Playwright Response (with its body buffer),\r\n// so an uncapped buffer leaks memory for the page's lifetime. 20 is ample for the\r\n// fast-path case (target XHR fired during goto/before listener attach).\r\nconst RESPONSE_BUFFER_LIMIT = 20;\r\n\r\nconst CHALLENGE_TITLE = new RegExp(`^(?:${CF_TITLE_PHRASES})`, \"i\");\r\n\r\nexport class BrowserHttpClient implements HttpClient {\r\n  readonly #pool: BrowserPool;\r\n  readonly #navTimeoutMs: number;\r\n\r\n  constructor(opts: { pool: BrowserPool; navigationTimeoutMs?: number }) {\r\n    this.#pool = opts.pool;\r\n    this.#navTimeoutMs = opts.navigationTimeoutMs ?? 30_000;\r\n  }\r\n\r\n  async get(url: Url, opts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    const handle = await this.#pool.acquireContext();\r\n    try {\r\n      const page = await handle.context.newPage();\r\n      try {\r\n        const response = await page.goto(String(url), {\r\n          timeout: this.#navTimeoutMs,\r\n          waitUntil: \"domcontentloaded\",\r\n          ...(opts?.signal ? { signal: opts.signal } : {}),\r\n        });\r\n        const title = await page.title();\r\n        if (CHALLENGE_TITLE.test(title)) {\r\n          throw new ChallengeUnresolvedError(url, \"manual-solve\");\r\n        }\r\n        const body = await page.content();\r\n        const status = response?.status() ?? 0;\r\n        const headers = response?.headers() ?? {};\r\n        return { status, headers, body, url };\r\n      } finally {\r\n        await safeClosePage(page);\r\n      }\r\n    } catch (err) {\r\n      if (opts?.signal?.aborted) {\r\n        throw new CancelledError({ cause: err ?? opts.signal.reason });\r\n      }\r\n      throw err;\r\n    } finally {\r\n      await handle.release();\r\n    }\r\n  }\r\n}\r\n\r\nexport class BrowserClientImpl implements BrowserClient {\r\n  readonly #context: RuntimeContext;\r\n  readonly #navTimeoutMs: number;\r\n\r\n  constructor(opts: { context: RuntimeContext; navigationTimeoutMs?: number }) {\r\n    this.#context = opts.context;\r\n    this.#navTimeoutMs = opts.navigationTimeoutMs ?? 30_000;\r\n  }\r\n\r\n  async navigate(\r\n    url: Url,\r\n    signal: AbortSignal,\r\n    opts: NavigateOptions = {},\r\n  ): Promise<BrowserPage> {\r\n    const page: RuntimePage = await this.#context.newPage();\r\n    const buffer: BufferedResponse[] = [];\r\n    const onResponse = (r: RuntimeResponse): void => {\r\n      buffer.push({ url: r.url(), status: r.status(), headers: r.headers(), raw: r });\r\n      if (buffer.length > RESPONSE_BUFFER_LIMIT) buffer.shift();\r\n    };\r\n    page.on(\"response\", onResponse);\r\n\r\n    if (opts.initScript !== undefined) {\r\n      try {\r\n        await page.addInitScript(opts.initScript);\r\n      } catch (err) {\r\n        page.off(\"response\", onResponse);\r\n        await safeClosePage(page);\r\n        throw err;\r\n      }\r\n    }\r\n\r\n    let navError: unknown;\r\n    try {\r\n      await page.goto(String(url), {\r\n        timeout: this.#navTimeoutMs,\r\n        waitUntil: \"domcontentloaded\",\r\n        ...(signal ? { signal } : {}),\r\n      });\r\n    } catch (err) {\r\n      navError = err;\r\n    }\r\n\r\n    function waitForResponse(\r\n      pattern: RegExp,\r\n      waitOpts: WaitForResponseTextOpts,\r\n    ): Promise<{ url: Url; status: number; body: string }>;\r\n    function waitForResponse<T>(\r\n      pattern: RegExp,\r\n      waitOpts: WaitForResponseJsonOpts<T>,\r\n    ): Promise<{ url: Url; status: number; body: T }>;\r\n    async function waitForResponse(\r\n      pattern: RegExp,\r\n      waitOpts: WaitOpts,\r\n    ): Promise<{ url: Url; status: number; body: unknown }> {\r\n      if (navError !== undefined) throw navError;\r\n      const timeoutMs = waitOpts.timeoutMs ?? 30_000;\r\n      const innerSignal = waitOpts.signal;\r\n      const validate = async (raw: RuntimeResponse): Promise<unknown> => {\r\n        if (waitOpts.parse === \"text\") return raw.text();\r\n        const json = await raw.json();\r\n        return waitOpts.schema.parse(json);\r\n      };\r\n\r\n      const existing = waitOpts.skipBuffer\r\n        ? undefined\r\n        : buffer.find((r) => pattern.test(r.url));\r\n      if (existing) {\r\n        return {\r\n          // Playwright Response.url() always returns an absolute URL\r\n          url: unsafeBrandUrl(existing.url),\r\n          status: existing.status,\r\n          body: await validate(existing.raw),\r\n        };\r\n      }\r\n\r\n      return new Promise<{ url: Url; status: number; body: unknown }>((resolve, reject) => {\r\n        function listener(r: RuntimeResponse): void {\r\n          if (!pattern.test(r.url())) return;\r\n          cleanup();\r\n          validate(r)\r\n            .then((body) =>\r\n              // Playwright Response.url() always returns an absolute URL\r\n              resolve({ url: unsafeBrandUrl(r.url()), status: r.status(), body }),\r\n            )\r\n            .catch(reject);\r\n        }\r\n\r\n        function onAbort(): void {\r\n          cleanup();\r\n          reject(new CancelledError({ cause: innerSignal?.reason ?? signal.reason }));\r\n        }\r\n\r\n        function cleanup(): void {\r\n          clearTimeout(timer);\r\n          page.off(\"response\", listener);\r\n          innerSignal?.removeEventListener(\"abort\", onAbort);\r\n          signal.removeEventListener(\"abort\", onAbort);\r\n        }\r\n\r\n        const timer = setTimeout(() => {\r\n          cleanup();\r\n          reject(new Error(`Timeout waiting for response matching ${pattern}`));\r\n        }, timeoutMs);\r\n\r\n        if (innerSignal?.aborted) { onAbort(); return; }\r\n        if (signal.aborted) { onAbort(); return; }\r\n        innerSignal?.addEventListener(\"abort\", onAbort, { once: true });\r\n        signal.addEventListener(\"abort\", onAbort, { once: true });\r\n        page.on(\"response\", listener);\r\n      });\r\n    }\r\n\r\n    return {\r\n      waitForResponse,\r\n\r\n      async content(): Promise<string> {\r\n        if (navError !== undefined) throw navError;\r\n        return page.content();\r\n      },\r\n\r\n      async evaluate(script: string): Promise<unknown> {\r\n        if (navError !== undefined) throw navError;\r\n        return page.evaluate(script);\r\n      },\r\n\r\n      async close(): Promise<void> {\r\n        page.off(\"response\", onResponse);\r\n        await safeClosePage(page);\r\n      },\r\n    };\r\n  }\r\n}\r\n\r\n/** Snapshot a context's CF cookies for jar replay. */\r\nexport async function captureCfCookies(\r\n  context: RuntimeContext,\r\n  origin: string,\r\n): Promise<{ cookies: ReadonlyArray<{ name: string; value: string }>; ua: string | null }> {\r\n  const all = await context.cookies([origin]);\r\n  const cookies = all\r\n    .filter((c) => isCfCookie(c.name))\r\n    .map((c) => ({ name: c.name, value: c.value }));\r\n  // UA is set per-context at newContext({userAgent}) time; the pool callsite\r\n  // owns this. captureCfCookies returns ua: null and the orchestrator pins the UA elsewhere.\r\n  return { cookies, ua: null };\r\n}\r\n","// Branded primitives\r\nexport type {\r\n  Url,\r\n  ChapterIndex,\r\n  AdapterId,\r\n  IsoDate,\r\n  ResumeToken,\r\n} from \"./data-model/primitives.js\";\r\n\r\n// Novel data types\r\nexport type {\r\n  NovelStatus,\r\n  NovelMetadata,\r\n  ChapterRef,\r\n  Chapter,\r\n  NovelData,\r\n} from \"./data-model/novel.js\";\r\n\r\n// Result + failure types\r\nexport type {\r\n  ChapterFailure,\r\n  DownloadResult,\r\n  DownloadSuccess,\r\n  DownloadPartial,\r\n  DownloadFailed,\r\n} from \"./data-model/result.js\";\r\n\r\n// Options\r\nexport type {\r\n  DownloadOptions,\r\n  RateLimitOptions,\r\n  RetryOptions,\r\n  CacheOptions,\r\n  ResumeOptions,\r\n  TransportMode,\r\n  TransportConfig,\r\n  TransportBrowserOptions,\r\n} from \"./data-model/options.js\";\r\n\r\n// Events\r\nexport type { DownloadEvent } from \"./data-model/events.js\";\r\n\r\n// Logger\r\nexport type { Logger, LogLevel } from \"./utils/logger.js\";\r\nexport { createLogger } from \"./utils/logger.js\";\r\n\r\n// Errors (value exports — classes)\r\nexport {\r\n  DownloadError,\r\n  AdapterNotFoundError,\r\n  HttpError,\r\n  RateLimitedError,\r\n  ParseError,\r\n  ChapterFetchError,\r\n  TimeoutError,\r\n  CancelledError,\r\n  BrowserModuleNotInstalledError,\r\n  ChallengeUnresolvedError,\r\n} from \"./core/errors.js\";\r\nexport type { ChallengeUnresolvedHint, DownloadErrorCode, KnownDownloadError } from \"./core/errors.js\";\r\n\r\n// Classes\r\nexport { Downloader } from \"./core/downloader.js\";\r\n\r\n// Default singleton + factory for custom-configured instances\r\nexport { downloader, createDownloader, builtinAdapters } from \"./core/default.js\";\r\nexport type { CreateDownloaderOptions } from \"./core/default.js\";\r\n\r\n// Adapter contract\r\nexport type { SiteAdapter, AdapterContext } from \"./adapters/base.js\";\r\nexport { BaseAdapter } from \"./adapters/base.js\";\r\n\r\n// HTTP transport contract (for custom transport plugins)\r\nexport type { HttpClient } from \"./http/types.js\";\r\n\r\n// Shared adapter parsing helpers (for custom adapters)\r\nexport { parseNovelHeader } from \"./adapters/shared/header.js\";\r\nexport type { NovelHeaderSelectors, NovelHeader } from \"./adapters/shared/header.js\";\r\n","import pino, { type Logger as PinoLogger } from \"pino\";\r\nimport type { Logger, LogLevel } from \"../data-model/ports.js\";\r\n\r\nexport type { Logger, LogLevel } from \"../data-model/ports.js\";\r\n\r\nfunction adapt(p: PinoLogger): Logger {\r\n  return {\r\n    trace: (a: object | string, b?: string) => (typeof a === \"string\" ? p.trace(a) : p.trace(a, b)),\r\n    debug: (a: object | string, b?: string) => (typeof a === \"string\" ? p.debug(a) : p.debug(a, b)),\r\n    info: (a: object | string, b?: string) => (typeof a === \"string\" ? p.info(a) : p.info(a, b)),\r\n    warn: (a: object | string, b?: string) => (typeof a === \"string\" ? p.warn(a) : p.warn(a, b)),\r\n    error: (a: object | string, b?: string) => (typeof a === \"string\" ? p.error(a) : p.error(a, b)),\r\n    child: (bindings: object) => adapt(p.child(bindings)),\r\n  };\r\n}\r\n\r\n/**\r\n * Build a pino-backed {@link Logger} at the given level (default `\"silent\"`).\r\n * Pass it to `createDownloader({ logger })`, or use the `logLevel` shortcut on\r\n * `createDownloader` to skip constructing one.\r\n */\r\nexport function createLogger(opts: { level?: LogLevel } = {}): Logger {\r\n  return adapt(pino({ level: opts.level ?? \"silent\" }));\r\n}\r\n\r\n/** A {@link Logger} that discards everything — the default when no logger is configured. */\r\nexport const silentLogger: Logger = createLogger({ level: \"silent\" });\r\n","import { Agent, type Dispatcher } from \"undici\";\r\nimport type { SiteAdapter, AdapterContext } from \"../adapters/base.js\";\r\nimport type { HttpClient, HttpEvent } from \"../http/types.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport type { NovelMetadata, ChapterRef } from \"../data-model/novel.js\";\r\nimport type { DownloadOptions, CacheOptions, TransportConfig, TransportMode, RateLimitOptions, RetryOptions } from \"../data-model/options.js\";\r\nimport type { DownloadResult } from \"../data-model/result.js\";\r\nimport type { Url, AdapterId } from \"../data-model/primitives.js\";\r\nimport type { BrowserClient, BrowserModule } from \"../http/browser/types.js\";\r\nimport type { CookieCaptureResult } from \"../http/tiered.js\";\r\nimport { AdapterRegistry } from \"./registry.js\";\r\nimport { EventBus } from \"./events.js\";\r\nimport { buildHttpClient, normalizeTransport } from \"../http/index.js\";\r\nimport { CookieJar } from \"../http/cookie-jar.js\";\r\nimport { BrowserPool } from \"../http/browser/pool.js\";\r\nimport { BrowserClientImpl } from \"../http/browser/client.js\";\r\nimport { safeClosePage } from \"../http/browser/safe-close.js\";\r\nimport { createLogger, silentLogger, type LogLevel } from \"../utils/logger.js\";\r\nimport type { DownloadEvent } from \"../data-model/events.js\";\r\nimport { parseUrl } from \"../data-model/parse.js\";\r\nimport { fetchMetadataStage } from \"../pipeline/fetch-metadata.js\";\r\nimport { fetchTocStage } from \"../pipeline/fetch-toc.js\";\r\nimport type { FetchTocRange } from \"../pipeline/fetch-toc.js\";\r\nimport { ParseError, normalizeToEnvelopeError } from \"./errors.js\";\r\nimport { FilesystemCacheStore } from \"../storage/cache-store.js\";\r\nimport { HttpCacheClient } from \"../storage/http-cache.js\";\r\nimport { DownloadRun, normalizeChapterRange } from \"./download-run.js\";\r\nimport { resolveResumeStorage } from \"../storage/resume-storage.js\";\r\n\r\nconst DEFAULT_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\r\nconst DEFAULT_CONCURRENCY = 4;\r\n\r\n/**\r\n * Orchestrates a download: resolves a site adapter from the URL, fetches\r\n * metadata + the table of contents, then fetches chapters concurrently with\r\n * shared rate-limiting, retries, optional caching, and resumable state.\r\n *\r\n * Most callers use the shared `downloader` singleton or `createDownloader()`\r\n * rather than constructing this directly. An instance owns a per-host transport\r\n * (and may hold a browser pool), so call {@link Downloader.dispose} when done.\r\n */\r\nexport class Downloader {\r\n  readonly #registry: AdapterRegistry;\r\n  readonly #http: HttpClient;\r\n  readonly #logger: Logger;\r\n  readonly #transport: TransportConfig;\r\n  readonly #cookieJar: CookieJar;\r\n  readonly #browserPool: BrowserPool | undefined;\r\n  readonly #httpDispatcher: Dispatcher | undefined;\r\n  readonly #transportListeners = new Set<(e: DownloadEvent) => void>();\r\n\r\n  /**\r\n   * Build a downloader from a set of adapters. `rateLimit`, `retry`, and\r\n   * `transport` configure the shared per-host transport (set once here, not\r\n   * per call). Provide `http` to replace the entire transport stack with a\r\n   * custom {@link HttpClient}. Prefer `createDownloader()` for the built-in\r\n   * adapters; use this constructor for a custom adapter set or transport.\r\n   */\r\n  constructor(opts: {\r\n    adapters: readonly SiteAdapter[];\r\n    http?: HttpClient;\r\n    logger?: Logger;\r\n    transport?: TransportConfig | TransportMode;\r\n    rateLimit?: RateLimitOptions;\r\n    retry?: RetryOptions;\r\n    /** @internal — test seam for browser module loading */\r\n    browserModuleLoader?: () => Promise<BrowserModule>;\r\n    /** @internal — test seam: replaces the real UndiciHttpClient leaf */\r\n    undiciOverride?: HttpClient;\r\n    logLevel?: LogLevel;\r\n  }) {\r\n    this.#registry = new AdapterRegistry(opts.adapters);\r\n    this.#logger =\r\n      opts.logger ??\r\n      (opts.logLevel !== undefined ? createLogger({ level: opts.logLevel }) : silentLogger);\r\n    this.#transport = normalizeTransport(opts.transport);\r\n    this.#cookieJar = new CookieJar();\r\n\r\n    if (this.#transport.mode !== \"http-only\") {\r\n      const browserOpts = this.#transport.browserOptions;\r\n      this.#browserPool = new BrowserPool({\r\n        logger: this.#logger,\r\n        ...(opts.browserModuleLoader !== undefined ? { moduleLoader: opts.browserModuleLoader } : {}),\r\n        ...(browserOpts?.idleTimeoutMs !== undefined ? { idleTimeoutMs: browserOpts.idleTimeoutMs } : {}),\r\n        ...(browserOpts?.headed !== undefined ? { headed: browserOpts.headed } : {}),\r\n        ...(browserOpts?.executablePath !== undefined ? { executablePath: browserOpts.executablePath } : {}),\r\n      });\r\n    }\r\n\r\n    const pool = this.#browserPool;\r\n\r\n    const browserBranchFactory = pool !== undefined ? this.#buildBrowserBranchFactory(pool) : undefined;\r\n\r\n    const onCookieCapture: ((host: string) => Promise<CookieCaptureResult>) | undefined = pool !== undefined\r\n      ? async (host): Promise<CookieCaptureResult> => {\r\n          const handle = await pool.acquireContext();\r\n          try {\r\n            const { captureCfCookies } = await import(\"../http/browser/client.js\");\r\n            return await captureCfCookies(handle.context, `https://${host}`);\r\n          } finally {\r\n            await handle.release();\r\n          }\r\n        }\r\n      : undefined;\r\n\r\n    // Own the leaf dispatcher so requests never route through the process-global\r\n    // one (immune to another dependency's undici version) and its sockets are\r\n    // released in dispose(). Skipped when the whole transport is provided.\r\n    this.#httpDispatcher =\r\n      opts.http === undefined && opts.undiciOverride === undefined ? new Agent() : undefined;\r\n\r\n    this.#http = opts.http ?? buildHttpClient({\r\n      logger: this.#logger,\r\n      transport: this.#transport,\r\n      cookieJar: this.#cookieJar,\r\n      ...(this.#httpDispatcher !== undefined ? { dispatcher: this.#httpDispatcher } : {}),\r\n      ...(opts.rateLimit !== undefined ? { rateLimit: opts.rateLimit } : {}),\r\n      ...(opts.retry !== undefined ? { retry: opts.retry } : {}),\r\n      onEvent: (e: HttpEvent): void => {\r\n        if (e.type === \"rate-limit:wait\") {\r\n          const out: DownloadEvent = { type: \"rate-limit:wait\", host: e.host, waitMs: e.waitMs };\r\n          for (const listener of this.#transportListeners) listener(out);\r\n        } else if (e.type === \"http:retry\") {\r\n          const out: DownloadEvent = {\r\n            type: \"http:retry\",\r\n            url: e.url,\r\n            attempt: e.attempt,\r\n            waitMs: e.waitMs,\r\n            ...(e.status !== undefined ? { status: e.status } : {}),\r\n          };\r\n          for (const listener of this.#transportListeners) listener(out);\r\n        }\r\n      },\r\n      ...(opts.undiciOverride !== undefined ? { undiciOverride: opts.undiciOverride } : {}),\r\n      preferredTransport: (url) => {\r\n        try {\r\n          const adapter = this.#registry.resolve(String(url));\r\n          return adapter.preferredTransport === \"browser\" ? \"browser\" : \"http\";\r\n        } catch {\r\n          return \"http\";\r\n        }\r\n      },\r\n      ...(browserBranchFactory !== undefined ? { browserBranchFactory } : {}),\r\n      ...(onCookieCapture !== undefined ? { onCookieCapture } : {}),\r\n    });\r\n  }\r\n\r\n  /** Release held resources (closes the browser pool and HTTP dispatcher, if any). Safe to call once when done. */\r\n  async dispose(): Promise<void> {\r\n    const pool = this.#browserPool;\r\n    if (pool) await pool.dispose();\r\n    if (this.#httpDispatcher) await this.#httpDispatcher.close();\r\n  }\r\n\r\n  /** True when a built-in/registered adapter can handle this URL. */\r\n  canHandle(url: string): boolean {\r\n    return this.#registry.tryResolve(url) !== null;\r\n  }\r\n\r\n  /** Plain, serializable list of the sites this instance can download. */\r\n  supportedSites(): readonly { id: AdapterId; displayName: string; hostnames: readonly string[] }[] {\r\n    return this.#registry.list().map((a) => ({\r\n      id: a.id,\r\n      displayName: a.displayName,\r\n      hostnames: a.hostnames,\r\n    }));\r\n  }\r\n\r\n  /** @internal — for tests only. Returns a ready ctx and a release fn. */\r\n  async _ctxFor(\r\n    adapter: SiteAdapter,\r\n    signal: AbortSignal,\r\n  ): Promise<{ ctx: AdapterContext; release: () => Promise<void> }> {\r\n    const handle = await this.#maybeBuildBrowserClient(adapter);\r\n    const ctx: AdapterContext = {\r\n      http: this.#http,\r\n      logger: this.#logger,\r\n      signal,\r\n      ...(handle !== null ? { browser: handle.client } : {}),\r\n    };\r\n    return {\r\n      ctx,\r\n      release: handle !== null ? handle.release : async () => {},\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Low-level single-stage metadata fetch. Unlike `download()`, this returns a\r\n   * bare value and therefore **throws** on failure (`AdapterNotFoundError`,\r\n   * `ParseError`, or any `DownloadError`). Use `download()` if you want the\r\n   * `{ status: \"error\" }` envelope instead of exceptions.\r\n   */\r\n  async fetchMetadata(url: string, options?: DownloadOptions): Promise<NovelMetadata> {\r\n    const adapter: SiteAdapter = options?.adapter ?? this.#registry.resolve(url);\r\n    const validated = parseUrl(url);\r\n    const bus = new EventBus(options?.onEvent, this.#logger);\r\n    const transportListener = (e: DownloadEvent): void => bus.emit(e);\r\n    const browserClient = await this.#maybeBuildBrowserClient(adapter);\r\n    const ctx: AdapterContext = {\r\n      http: this.#http,\r\n      logger: this.#logger,\r\n      signal: options?.signal ?? new AbortController().signal,\r\n      ...(browserClient !== null ? { browser: browserClient.client } : {}),\r\n    };\r\n    try {\r\n      this.#transportListeners.add(transportListener);\r\n      return await fetchMetadataStage(adapter, validated, ctx, bus);\r\n    } finally {\r\n      this.#transportListeners.delete(transportListener);\r\n      if (browserClient !== null) await browserClient.release();\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Low-level table-of-contents fetch: returns the chapter list (refs only —\r\n   * `index`, `title`, `url`) **without downloading any chapter bodies**,\r\n   * honoring `options.chapterRange`. This is the cheap \"list-only\" counterpart\r\n   * to `download()`. Like {@link fetchMetadata} it returns a bare value and\r\n   * therefore **throws** on failure (`AdapterNotFoundError`, `ParseError`, or\r\n   * any `DownloadError`); use `download()` for the full novel with bodies.\r\n   */\r\n  async fetchChapterList(url: string, options?: DownloadOptions): Promise<readonly ChapterRef[]> {\r\n    const adapter: SiteAdapter = options?.adapter ?? this.#registry.resolve(url);\r\n    const validated = parseUrl(url);\r\n    const range = normalizeChapterRange(options?.chapterRange);\r\n    const bus = new EventBus(options?.onEvent, this.#logger);\r\n    const transportListener = (e: DownloadEvent): void => bus.emit(e);\r\n    const browserClient = await this.#maybeBuildBrowserClient(adapter);\r\n    const ctx: AdapterContext = {\r\n      http: this.#http,\r\n      logger: this.#logger,\r\n      signal: options?.signal ?? new AbortController().signal,\r\n      ...(browserClient !== null ? { browser: browserClient.client } : {}),\r\n    };\r\n    const refs: ChapterRef[] = [];\r\n    try {\r\n      this.#transportListeners.add(transportListener);\r\n      await fetchTocStage(adapter, validated, ctx, bus, (ref) => {\r\n        refs.push(ref);\r\n      }, range);\r\n      return refs;\r\n    } finally {\r\n      this.#transportListeners.delete(transportListener);\r\n      if (browserClient !== null) await browserClient.release();\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Download a novel end to end. Returns a {@link DownloadResult} envelope —\r\n   * `\"success\"`, `\"partial\"` (some chapters failed; carries a `resumeToken`\r\n   * when `options.resume` is set), or `\"error\"` (including an unsupported or\r\n   * malformed URL). The only thrown error is `CancelledError`, when\r\n   * `options.signal` aborts. Use `options.onEvent` for progress.\r\n   */\r\n  async download(url: string, options?: DownloadOptions): Promise<DownloadResult> {\r\n    let adapter: SiteAdapter;\r\n    let validated: Url;\r\n    let normalizedRange: FetchTocRange | undefined;\r\n    try {\r\n      validated = parseUrl(url);\r\n      adapter = options?.adapter ?? this.#registry.resolve(url);\r\n      normalizedRange = normalizeChapterRange(options?.chapterRange);\r\n    } catch (err) {\r\n      return {\r\n        status: \"error\",\r\n        error: normalizeToEnvelopeError(err),\r\n      };\r\n    }\r\n    const bus = new EventBus(options?.onEvent, this.#logger);\r\n    const transportListener = (e: DownloadEvent): void => bus.emit(e);\r\n    const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;\r\n    const signal = options?.signal ?? new AbortController().signal;\r\n\r\n    const browserClient = await this.#maybeBuildBrowserClient(adapter);\r\n    const httpForDownload = this.#wrapHttpForDownload(options?.cache, bus);\r\n    const ctx: AdapterContext = {\r\n      http: httpForDownload,\r\n      logger: this.#logger,\r\n      signal,\r\n      ...(browserClient !== null ? { browser: browserClient.client } : {}),\r\n    };\r\n\r\n    try {\r\n      this.#transportListeners.add(transportListener);\r\n      const resumeStorage = resolveResumeStorage(options?.resume, validated, this.#logger);\r\n      const run = new DownloadRun({\r\n        adapter,\r\n        validated,\r\n        normalizedRange,\r\n        resumeStorage,\r\n        concurrency,\r\n        signal,\r\n        ctx,\r\n        bus,\r\n        logger: this.#logger,\r\n      });\r\n      return await run.execute();\r\n    } finally {\r\n      this.#transportListeners.delete(transportListener);\r\n      if (browserClient !== null) await browserClient.release();\r\n    }\r\n  }\r\n\r\n  #buildBrowserBranchFactory(pool: BrowserPool): () => HttpClient {\r\n    return () => ({\r\n      async get(\r\n        url: Url,\r\n        reqOpts?: import(\"../http/types.js\").HttpRequestOptions,\r\n      ): Promise<import(\"../http/types.js\").HttpResponse> {\r\n        const handle = await pool.acquireContext();\r\n        try {\r\n          const page = await handle.context.newPage();\r\n          try {\r\n            const response = await page.goto(String(url), {\r\n              timeout: 30_000,\r\n              waitUntil: \"domcontentloaded\",\r\n              ...(reqOpts?.signal ? { signal: reqOpts.signal } : {}),\r\n            });\r\n            const body = await page.content();\r\n            const status = response?.status() ?? 200;\r\n            const headers = response?.headers() ?? {};\r\n            return { status, headers, body, url };\r\n          } finally {\r\n            await safeClosePage(page);\r\n          }\r\n        } finally {\r\n          await handle.release();\r\n        }\r\n      },\r\n    });\r\n  }\r\n\r\n  async #maybeBuildBrowserClient(\r\n    adapter: SiteAdapter,\r\n  ): Promise<{ client: BrowserClient; release: () => Promise<void> } | null> {\r\n    const pool = this.#browserPool;\r\n    if (!pool) {\r\n      if (adapter.preferredTransport === \"browser\" && this.#transport.mode === \"http-only\") {\r\n        throw new ParseError(\r\n          `Adapter ${adapter.id} requires the browser tier; current transport mode is \"http-only\".`,\r\n        );\r\n      }\r\n      return null;\r\n    }\r\n    if (adapter.preferredTransport !== \"browser\" && this.#transport.mode !== \"browser-required\") {\r\n      return null;\r\n    }\r\n    const handle = await pool.acquireContext();\r\n    const navTimeout = this.#transport.browserOptions?.navigationTimeoutMs;\r\n    const client = new BrowserClientImpl({\r\n      context: handle.context,\r\n      ...(navTimeout !== undefined ? { navigationTimeoutMs: navTimeout } : {}),\r\n    });\r\n    return { client, release: handle.release };\r\n  }\r\n\r\n  #wrapHttpForDownload(cache: CacheOptions | undefined, bus: EventBus): HttpClient {\r\n    if (!cache) return this.#http;\r\n    const store = new FilesystemCacheStore(cache.dir);\r\n    const onEvent = (e: HttpEvent): void => {\r\n      if (e.type === \"cache:hit\") bus.emit({ type: \"cache:hit\", url: e.url });\r\n    };\r\n    return new HttpCacheClient(this.#http, store, {\r\n      maxAgeMs: cache.maxAgeMs ?? DEFAULT_CACHE_MAX_AGE_MS,\r\n      onEvent,\r\n    });\r\n  }\r\n}\r\n","import type { SiteAdapter } from \"../adapters/base.js\";\r\nimport { AdapterNotFoundError } from \"./errors.js\";\r\n\r\nexport class AdapterRegistry {\r\n  readonly #adapters: readonly SiteAdapter[];\r\n\r\n  constructor(adapters: readonly SiteAdapter[]) {\r\n    if (adapters.length === 0) {\r\n      throw new Error(\"Downloader requires at least one adapter\");\r\n    }\r\n    const seen = new Set<string>();\r\n    for (const a of adapters) {\r\n      if (seen.has(a.id)) {\r\n        throw new Error(`Duplicate adapter id: ${a.id}`);\r\n      }\r\n      seen.add(a.id);\r\n    }\r\n    this.#adapters = adapters;\r\n  }\r\n\r\n  /**\r\n   * Return the first adapter whose `canHandle(url)` is true. **First-match\r\n   * wins**: when multiple adapters claim the same URL, registration order\r\n   * decides. Built-in adapters are registered before user-supplied ones, so a\r\n   * custom adapter that overlaps a built-in host must be placed first.\r\n   * Throws {@link AdapterNotFoundError} when none match.\r\n   */\r\n  resolve(url: string): SiteAdapter {\r\n    for (const a of this.#adapters) {\r\n      if (a.canHandle(url)) return a;\r\n    }\r\n    throw new AdapterNotFoundError(url);\r\n  }\r\n\r\n  tryResolve(url: string): SiteAdapter | null {\r\n    for (const a of this.#adapters) {\r\n      if (a.canHandle(url)) return a;\r\n    }\r\n    return null;\r\n  }\r\n\r\n  list(): readonly SiteAdapter[] {\r\n    return this.#adapters;\r\n  }\r\n}\r\n","import type { DownloadEvent } from \"../data-model/events.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\n\r\nconst PROGRESS_EMIT_INTERVAL_MS = 1000;\r\n\r\n/**\r\n * Per-download event throttle + dispatch. NOTE: stateful (progress/toc\r\n * throttle); construct a fresh EventBus per download() call — never reuse.\r\n */\r\nexport class EventBus {\r\n  readonly #listener: ((e: DownloadEvent) => void) | undefined;\r\n  readonly #logger: Logger | undefined;\r\n  #lastProgressMs = 0;\r\n  #lastTocDiscovered = -1;\r\n\r\n  constructor(listener?: (e: DownloadEvent) => void, logger?: Logger) {\r\n    this.#listener = listener;\r\n    this.#logger = logger;\r\n  }\r\n\r\n  emit(event: DownloadEvent): void {\r\n    if (!this.#listener) return;\r\n\r\n    if (event.type === \"progress\") {\r\n      const now = Date.now();\r\n      // A zero-chapter download never emits a `progress` event — #fetchChapter\r\n      // only emits on a chapter success. (isFinal here just bypasses the 1s\r\n      // throttle for the terminal emit; it is not what suppresses zero-total.)\r\n      const isFinal = event.total > 0 && event.completed >= event.total;\r\n      if (!isFinal && now - this.#lastProgressMs < PROGRESS_EMIT_INTERVAL_MS) return;\r\n      this.#lastProgressMs = now;\r\n    }\r\n\r\n    if (event.type === \"toc:progress\") {\r\n      if (event.discovered === this.#lastTocDiscovered) return;\r\n      this.#lastTocDiscovered = event.discovered;\r\n    }\r\n\r\n    try {\r\n      this.#listener(event);\r\n    } catch (err) {\r\n      this.#logger?.warn({ err, eventType: event.type }, \"event listener threw; download continues\");\r\n    }\r\n  }\r\n}\r\n","import { Agent, request, errors as undiciErrors, type Dispatcher } from \"undici\";\r\nimport type { HttpClient, HttpRequestOptions, HttpResponse } from \"./types.js\";\r\nimport { CancelledError, HttpError, TimeoutError } from \"../core/errors.js\";\r\nimport { unsafeBrandUrl, type Url } from \"../data-model/primitives.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\n\r\nconst DEFAULT_TIMEOUT_MS = 30_000;\r\nconst MAX_REDIRECTS = 5;\r\nconst REDIRECT_STATUSES: ReadonlySet<number> = new Set([301, 302, 303, 307, 308]);\r\n\r\n/**\r\n * Leaf HTTP client. Dispatches through a dispatcher it controls — never the\r\n * process-global one. undici copies in a process share the global dispatcher\r\n * via a `Symbol.for(...)` registry, so a v7 copy claimed by an unrelated\r\n * dependency would otherwise reject this v6 client's `maxRedirections` option.\r\n */\r\nexport class UndiciHttpClient implements HttpClient {\r\n  readonly #logger: Logger;\r\n  readonly #dispatcher: Dispatcher;\r\n  readonly #ownsDispatcher: boolean;\r\n\r\n  constructor(opts: { logger: Logger; dispatcher?: Dispatcher }) {\r\n    this.#logger = opts.logger;\r\n    this.#ownsDispatcher = opts.dispatcher === undefined;\r\n    this.#dispatcher = opts.dispatcher ?? new Agent();\r\n  }\r\n\r\n  async get(url: Url, opts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n    const half = Math.max(1, Math.floor(timeoutMs / 2));\r\n    let res: Awaited<ReturnType<typeof request>> | undefined;\r\n    try {\r\n      // Follow redirects manually rather than via `maxRedirections`: that\r\n      // option was removed in undici v7, and a manual loop lets us report the\r\n      // true post-redirect URL on `HttpResponse.url`.\r\n      let currentUrl = url;\r\n      for (let hop = 0; ; hop++) {\r\n        res = await request(currentUrl, {\r\n          method: \"GET\",\r\n          dispatcher: this.#dispatcher,\r\n          ...(opts?.headers !== undefined ? { headers: opts.headers } : {}),\r\n          ...(opts?.signal !== undefined ? { signal: opts.signal } : {}),\r\n          headersTimeout: half,\r\n          bodyTimeout: half,\r\n        });\r\n        const next = hop < MAX_REDIRECTS ? redirectTarget(res.statusCode, res.headers, currentUrl) : undefined;\r\n        if (next === undefined) break;\r\n        await res.body.dump();\r\n        currentUrl = next;\r\n      }\r\n      const body = await res.body.text();\r\n      const headers = normalizeHeaders(res.headers);\r\n      return {\r\n        status: res.statusCode,\r\n        headers,\r\n        body,\r\n        url: currentUrl,\r\n      };\r\n    } catch (cause) {\r\n      // Release the socket back to the pool promptly if the body was started but\r\n      // not fully consumed (e.g. a mid-read failure). dump() is a no-op if already drained.\r\n      if (res !== undefined) await res.body.dump().catch(() => {});\r\n      if (isAbortError(cause, opts?.signal)) {\r\n        throw new CancelledError({ cause });\r\n      }\r\n      if (\r\n        cause instanceof undiciErrors.HeadersTimeoutError ||\r\n        cause instanceof undiciErrors.BodyTimeoutError\r\n      ) {\r\n        throw new TimeoutError(url, { cause });\r\n      }\r\n      const detail = cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);\r\n      this.#logger.warn({ url, cause }, \"undici network error\");\r\n      throw new HttpError(0, url, `Network error (${detail})`, { cause });\r\n    }\r\n  }\r\n\r\n  /** Closes the dispatcher this client created. A no-op when one was injected — the caller owns that. */\r\n  async dispose(): Promise<void> {\r\n    if (this.#ownsDispatcher) await this.#dispatcher.close();\r\n  }\r\n}\r\n\r\n/**\r\n * Resolves the next URL to fetch for a redirect response, or `undefined` when\r\n * the response is not a followable redirect (non-3xx, missing/unparseable\r\n * `Location`). A relative `Location` is resolved against the current URL.\r\n */\r\nfunction redirectTarget(\r\n  status: number,\r\n  headers: Record<string, string | string[] | undefined>,\r\n  currentUrl: Url,\r\n): Url | undefined {\r\n  if (!REDIRECT_STATUSES.has(status)) return undefined;\r\n  const raw = headers[\"location\"];\r\n  const location = Array.isArray(raw) ? raw[0] : raw;\r\n  if (location === undefined || location === \"\") return undefined;\r\n  try {\r\n    return unsafeBrandUrl(new URL(location, currentUrl).href);\r\n  } catch {\r\n    return undefined;\r\n  }\r\n}\r\n\r\nfunction normalizeHeaders(\r\n  h: Record<string, string | string[] | undefined>,\r\n): Readonly<Record<string, string>> {\r\n  const out: Record<string, string> = {};\r\n  for (const [k, v] of Object.entries(h)) {\r\n    if (v == null) continue;\r\n    out[k.toLowerCase()] = Array.isArray(v) ? v.join(\", \") : String(v);\r\n  }\r\n  return out;\r\n}\r\n\r\nfunction isAbortError(err: unknown, signal: AbortSignal | undefined): boolean {\r\n  if (signal?.aborted) return true;\r\n  if (err && typeof err === \"object\" && \"name\" in err) {\r\n    return err.name === \"AbortError\";\r\n  }\r\n  return false;\r\n}\r\n","import { type Url, unsafeBrandUrl } from \"../data-model/primitives.js\";\r\n\r\nexport function hostnameOf(url: Url): string {\r\n  return new URL(url).hostname.toLowerCase();\r\n}\r\n\r\nexport function matchesAny(url: Url, hostnames: readonly string[]): boolean {\r\n  const host = hostnameOf(url);\r\n  return hostnames.some((h) => h.toLowerCase() === host);\r\n}\r\n\r\nexport function resolveUrl(base: Url, relative: string): Url {\r\n  return unsafeBrandUrl(new URL(relative, base).toString());\r\n}\r\n\r\nexport function mapHostname(url: Url, hostMap: Readonly<Record<string, string>>): Url {\r\n  const u = new URL(url);\r\n  const mapped = hostMap[u.hostname.toLowerCase()];\r\n  if (mapped === undefined) return url;\r\n  u.hostname = mapped;\r\n  return unsafeBrandUrl(u.toString());\r\n}\r\n","import type { HttpClient, HttpRequestOptions, HttpResponse } from \"./types.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport type { CookieJar } from \"./cookie-jar.js\";\r\nimport { hostnameOf } from \"../utils/url.js\";\r\n\r\nexport const DEFAULT_UA_POOL: readonly string[] = Object.freeze([\r\n  \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\",\r\n  \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0\",\r\n  \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0\",\r\n  \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\",\r\n  \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15\",\r\n  \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1; rv:124.0) Gecko/20100101 Firefox/124.0\",\r\n]);\r\n\r\nexport class UserAgents {\r\n  readonly #pool: readonly string[];\r\n  #idx = 0;\r\n  constructor(pool: readonly string[] = DEFAULT_UA_POOL) {\r\n    if (pool.length === 0) {\r\n      throw new Error(\"UserAgents: pool must be non-empty\");\r\n    }\r\n    this.#pool = pool;\r\n  }\r\n  next(): string {\r\n    const idx = this.#idx % this.#pool.length;\r\n    this.#idx++;\r\n    const ua = this.#pool[idx];\r\n    if (ua === undefined) {\r\n      throw new Error(\"UserAgents: invariant violated — pool became empty\");\r\n    }\r\n    return ua;\r\n  }\r\n}\r\n\r\nexport class UserAgentRotatingHttpClient implements HttpClient {\r\n  constructor(\r\n    private readonly inner: HttpClient,\r\n    private readonly uas: UserAgents,\r\n    private readonly defaults?: Readonly<Record<string, string>>,\r\n    private readonly cookieJar?: CookieJar,\r\n  ) {}\r\n\r\n  async get(url: Url, opts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    const host = hostnameOf(url);\r\n    const jarRead = this.cookieJar?.read(host);\r\n    const callerHasCookie = Object.keys(opts?.headers ?? {}).some(\r\n      (k) => k.toLowerCase() === \"cookie\",\r\n    );\r\n    const userAgent = jarRead?.ua ?? this.uas.next();\r\n\r\n    const headers: Record<string, string> = {\r\n      \"User-Agent\": userAgent,\r\n      Accept: \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\r\n      \"Accept-Language\": \"vi,en;q=0.8\",\r\n      ...this.defaults,\r\n      ...opts?.headers,\r\n    };\r\n\r\n    if (jarRead?.cookieHeader && !callerHasCookie) {\r\n      headers[\"Cookie\"] = jarRead.cookieHeader;\r\n    }\r\n\r\n    return this.inner.get(url, { ...opts, headers });\r\n  }\r\n}\r\n","import { CancelledError } from \"../core/errors.js\";\r\n\r\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\r\n  if (signal?.aborted) throw new CancelledError({ cause: signal.reason });\r\n}\r\n\r\nexport async function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\r\n  return new Promise((resolve, reject) => {\r\n    if (signal?.aborted) {\r\n      reject(new CancelledError({ cause: signal.reason }));\r\n      return;\r\n    }\r\n    const timer = setTimeout(() => {\r\n      signal?.removeEventListener(\"abort\", onAbort);\r\n      resolve();\r\n    }, ms);\r\n    const onAbort = (): void => {\r\n      clearTimeout(timer);\r\n      reject(new CancelledError({ cause: signal?.reason }));\r\n    };\r\n    signal?.addEventListener(\"abort\", onAbort, { once: true });\r\n  });\r\n}\r\n","import type { HttpClient, HttpEvent, HttpRequestOptions, HttpResponse } from \"./types.js\";\r\nimport type { RateLimitOptions } from \"../data-model/options.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport { hostnameOf } from \"../utils/url.js\";\r\nimport { sleep, throwIfAborted } from \"../utils/sleep.js\";\r\n\r\nexport const DEFAULT_RATE_LIMIT = {\r\n  requestsPerSecond: 2,\r\n  burst: 2,\r\n} as const satisfies Required<RateLimitOptions>;\r\n\r\nconst RATE_LIMIT_WAIT_EVENT_THRESHOLD_MS = 50;\r\n\r\ninterface Bucket {\r\n  tokens: number;\r\n  lastRefill: number;\r\n}\r\n\r\nexport class RateLimiter {\r\n  readonly #opts: { requestsPerSecond: number; burst: number };\r\n  readonly #buckets = new Map<string, Bucket>();\r\n  readonly #pauses = new Map<string, number>();\r\n\r\n  constructor(opts: RateLimitOptions = DEFAULT_RATE_LIMIT) {\r\n    // A request costs one token, so a burst below 1 can never fill enough to\r\n    // issue one. Default it up (a sub-1 rps like 0.5 is a natural polite rate)\r\n    // and reject an explicit sub-1 burst rather than hang forever on acquire().\r\n    const burst = opts.burst ?? Math.max(1, opts.requestsPerSecond);\r\n    if (burst < 1) {\r\n      throw new RangeError(`RateLimiter: burst must be >= 1 (got ${burst})`);\r\n    }\r\n    this.#opts = {\r\n      requestsPerSecond: opts.requestsPerSecond,\r\n      burst,\r\n    };\r\n  }\r\n\r\n  async acquire(host: string, signal?: AbortSignal): Promise<void> {\r\n    while (true) {\r\n      throwIfAborted(signal);\r\n\r\n      const pauseUntil = this.#pauses.get(host) ?? 0;\r\n      const pauseRemaining = pauseUntil - Date.now();\r\n      if (pauseRemaining > 0) {\r\n        await sleep(pauseRemaining, signal);\r\n        continue;\r\n      }\r\n\r\n      const bucket = this.#getBucket(host);\r\n      this.#refill(bucket);\r\n      if (bucket.tokens >= 1) {\r\n        bucket.tokens -= 1;\r\n        return;\r\n      }\r\n\r\n      const waitMs = Math.ceil(1000 / this.#opts.requestsPerSecond);\r\n      await sleep(waitMs, signal);\r\n    }\r\n  }\r\n\r\n  pauseHost(host: string, ms: number): void {\r\n    this.#pauses.set(host, Date.now() + ms);\r\n  }\r\n\r\n  #getBucket(host: string): Bucket {\r\n    let b = this.#buckets.get(host);\r\n    if (!b) {\r\n      b = { tokens: this.#opts.burst, lastRefill: Date.now() };\r\n      this.#buckets.set(host, b);\r\n    }\r\n    return b;\r\n  }\r\n\r\n  #refill(bucket: Bucket): void {\r\n    const now = Date.now();\r\n    const elapsedMs = now - bucket.lastRefill;\r\n    if (elapsedMs <= 0) return;\r\n    const refill = (elapsedMs / 1000) * this.#opts.requestsPerSecond;\r\n    bucket.tokens = Math.min(this.#opts.burst, bucket.tokens + refill);\r\n    bucket.lastRefill = now;\r\n  }\r\n}\r\n\r\nexport class RateLimitedHttpClient implements HttpClient {\r\n  constructor(\r\n    private readonly inner: HttpClient,\r\n    private readonly limiter: RateLimiter,\r\n    private readonly onEvent?: (e: HttpEvent) => void,\r\n  ) {}\r\n\r\n  async get(url: Url, opts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    const host = hostnameOf(url);\r\n    const start = Date.now();\r\n    await this.limiter.acquire(host, opts?.signal);\r\n    const waited = Date.now() - start;\r\n    if (waited > RATE_LIMIT_WAIT_EVENT_THRESHOLD_MS) {\r\n      this.onEvent?.({ type: \"rate-limit:wait\", host, waitMs: waited });\r\n    }\r\n    return this.inner.get(url, opts);\r\n  }\r\n}\r\n","import type { HttpClient, HttpEvent, HttpRequestOptions, HttpResponse } from \"./types.js\";\r\nimport type { RateLimiter } from \"./rate-limiter.js\";\r\nimport type { RetryOptions } from \"../data-model/options.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport { CancelledError, HttpError, ParseError, RateLimitedError } from \"../core/errors.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport { hostnameOf } from \"../utils/url.js\";\r\nimport { sleep } from \"../utils/sleep.js\";\r\n\r\nexport const DEFAULT_RETRY = {\r\n  retries: 3,\r\n  backoff: \"exponential\",\r\n  minDelayMs: 500,\r\n  maxDelayMs: 30_000,\r\n  retryOn: [408, 429, 500, 502, 503, 504, 524],\r\n} as const satisfies Required<RetryOptions>;\r\n\r\nexport class RetryingHttpClient implements HttpClient {\r\n  constructor(\r\n    private readonly inner: HttpClient,\r\n    private readonly opts: Required<RetryOptions>,\r\n    private readonly logger: Logger,\r\n    private readonly rateLimiter?: RateLimiter,\r\n    private readonly onEvent?: (e: HttpEvent) => void,\r\n  ) {}\r\n\r\n  async get(url: Url, reqOpts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    let attempt = 0;\r\n    while (true) {\r\n      try {\r\n        const res = await this.inner.get(url, reqOpts);\r\n        if (this.opts.retryOn.includes(res.status)) {\r\n          if (attempt >= this.opts.retries) {\r\n            if (res.status === 429) {\r\n              throw new RateLimitedError(url, parseRetryAfter(res));\r\n            }\r\n            throw new HttpError(res.status, url);\r\n          }\r\n          const wait = this.#computeBackoff(res, attempt);\r\n          if (res.status === 429 && this.rateLimiter) {\r\n            this.rateLimiter.pauseHost(hostnameOf(url), wait);\r\n          }\r\n          this.logger.debug({ url, status: res.status, attempt, wait }, \"http retry\");\r\n          this.onEvent?.({\r\n            type: \"http:retry\",\r\n            url,\r\n            attempt,\r\n            waitMs: wait,\r\n            status: res.status,\r\n          });\r\n          await sleep(wait, reqOpts?.signal);\r\n          attempt++;\r\n          continue;\r\n        }\r\n        return res;\r\n      } catch (err) {\r\n        if (\r\n          err instanceof CancelledError ||\r\n          err instanceof ParseError ||\r\n          err instanceof RateLimitedError ||\r\n          (err instanceof HttpError && err.status > 0)\r\n        ) {\r\n          throw err;\r\n        }\r\n        if (attempt >= this.opts.retries) throw err;\r\n        const wait = this.#computeBackoff(null, attempt);\r\n        this.logger.debug(\r\n          { url, errMessage: err instanceof Error ? err.message : String(err), attempt, wait },\r\n          \"http retry (error)\",\r\n        );\r\n        this.onEvent?.({ type: \"http:retry\", url, attempt, waitMs: wait });\r\n        await sleep(wait, reqOpts?.signal);\r\n        attempt++;\r\n      }\r\n    }\r\n  }\r\n\r\n  #computeBackoff(res: HttpResponse | null, attempt: number): number {\r\n    if (res) {\r\n      const ra = parseRetryAfter(res);\r\n      if (ra != null) return clamp(ra, this.opts.minDelayMs, this.opts.maxDelayMs);\r\n    }\r\n    const base = this.opts.minDelayMs * Math.pow(2, attempt);\r\n    const jitter = Math.floor(Math.random() * (base * 0.5));\r\n    return clamp(base + jitter, this.opts.minDelayMs, this.opts.maxDelayMs);\r\n  }\r\n}\r\n\r\nfunction parseRetryAfter(res: HttpResponse): number | undefined {\r\n  const raw = res.headers[\"retry-after\"];\r\n  if (!raw) return undefined;\r\n  const asInt = Number.parseInt(raw, 10);\r\n  if (Number.isFinite(asInt) && String(asInt) === raw.trim()) {\r\n    return asInt * 1000;\r\n  }\r\n  const asDate = Date.parse(raw);\r\n  if (Number.isFinite(asDate)) {\r\n    return Math.max(0, asDate - Date.now());\r\n  }\r\n  return undefined;\r\n}\r\n\r\nfunction clamp(n: number, lo: number, hi: number): number {\r\n  return Math.max(lo, Math.min(hi, n));\r\n}\r\n","import type { Dispatcher } from \"undici\";\r\nimport type { HttpClient, HttpEvent } from \"./types.js\";\r\nimport type { RetryOptions, RateLimitOptions, TransportConfig, TransportMode } from \"../data-model/options.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport { silentLogger } from \"../utils/logger.js\";\r\nimport { UndiciHttpClient } from \"./client.js\";\r\nimport { UserAgents, UserAgentRotatingHttpClient } from \"./user-agents.js\";\r\nimport { RateLimiter, RateLimitedHttpClient, DEFAULT_RATE_LIMIT } from \"./rate-limiter.js\";\r\nimport { RetryingHttpClient, DEFAULT_RETRY } from \"./retry.js\";\r\nimport { CookieJar } from \"./cookie-jar.js\";\r\nimport { TieredHttpClient } from \"./tiered.js\";\r\nimport type { CookieCaptureResult } from \"./tiered.js\";\r\n\r\nexport interface HttpStackOptions {\r\n  readonly retry?: RetryOptions;\r\n  readonly rateLimit?: RateLimitOptions;\r\n  readonly rateLimiter?: RateLimiter;\r\n  readonly userAgents?: UserAgents;\r\n  readonly logger?: Logger;\r\n  readonly defaultHeaders?: Readonly<Record<string, string>>;\r\n  readonly onEvent?: (e: HttpEvent) => void;\r\n  /** Transport configuration. Defaults to `{ mode: \"auto\" }`. */\r\n  readonly transport?: TransportConfig;\r\n  /** Cookie jar shared between the HTTP branch and the browser branch. */\r\n  readonly cookieJar?: CookieJar;\r\n  /**\r\n   * Returns the preferred transport for a given URL.\r\n   * Defaults to always returning `\"http\"`.\r\n   */\r\n  readonly preferredTransport?: (url: Url) => \"http\" | \"browser\";\r\n  /**\r\n   * Factory called once to create the browser-side `HttpClient`.\r\n   * When omitted, a lazy fallback is used that throws\r\n   * `BrowserModuleNotInstalledError` on first `get()` call.\r\n   */\r\n  readonly browserBranchFactory?: () => HttpClient;\r\n  /**\r\n   * Called after each successful browser-branch request to capture\r\n   * Cloudflare cookies + the user-agent string the browser used.\r\n   * Defaults to a no-op that returns empty cookies.\r\n   */\r\n  readonly onCookieCapture?: (host: string) => Promise<CookieCaptureResult>;\r\n  /**\r\n   * Test seam: replaces the real `UndiciHttpClient` with a fake.\r\n   * Do not use in production code.\r\n   */\r\n  readonly undiciOverride?: HttpClient;\r\n  /**\r\n   * undici dispatcher for the leaf HTTP client. Pass one so its lifecycle is\r\n   * owned by the caller (closed on `dispose()`) and requests never route\r\n   * through the process-global dispatcher. When omitted, the leaf creates and\r\n   * owns its own `Agent`.\r\n   */\r\n  readonly dispatcher?: Dispatcher;\r\n}\r\n\r\nexport function normalizeTransport(t: TransportConfig | TransportMode | undefined): TransportConfig {\r\n  if (t === undefined) return { mode: \"auto\" };\r\n  if (typeof t === \"string\") return { mode: t };\r\n  return t;\r\n}\r\n\r\nexport function buildHttpClient(opts: HttpStackOptions = {}): HttpClient {\r\n  const logger = opts.logger ?? silentLogger;\r\n  const cookieJar = opts.cookieJar ?? new CookieJar();\r\n\r\n  // Leaf HTTP client (real undici or test override)\r\n  const leaf: HttpClient =\r\n    opts.undiciOverride ??\r\n    new UndiciHttpClient({\r\n      logger,\r\n      ...(opts.dispatcher !== undefined ? { dispatcher: opts.dispatcher } : {}),\r\n    });\r\n  const limiter = opts.rateLimiter ?? new RateLimiter(opts.rateLimit ?? DEFAULT_RATE_LIMIT);\r\n\r\n  // Retry wraps rate-limit wraps UA-rotation: every retry attempt re-enters\r\n  // the rate limiter and picks a fresh user-agent before hitting the leaf.\r\n  const withUserAgent = new UserAgentRotatingHttpClient(\r\n    leaf,\r\n    opts.userAgents ?? new UserAgents(),\r\n    opts.defaultHeaders,\r\n    cookieJar,\r\n  );\r\n  const withRateLimit = new RateLimitedHttpClient(withUserAgent, limiter, opts.onEvent);\r\n  const httpBranch: HttpClient = new RetryingHttpClient(\r\n    withRateLimit,\r\n    normalizeRetry(opts.retry),\r\n    logger,\r\n    limiter,\r\n    opts.onEvent,\r\n  );\r\n\r\n  const { mode } = normalizeTransport(opts.transport);\r\n\r\n  if (mode === \"http-only\") {\r\n    return httpBranch;\r\n  }\r\n\r\n  // Build the browser branch\r\n  const browserBranch: HttpClient = opts.browserBranchFactory != null\r\n    ? opts.browserBranchFactory()\r\n    : {\r\n        async get() {\r\n          // Lazy import so the error class is not loaded at module init time.\r\n          const { BrowserModuleNotInstalledError } = await import(\"../core/errors.js\");\r\n          throw new BrowserModuleNotInstalledError();\r\n        },\r\n      };\r\n\r\n  return new TieredHttpClient({\r\n    httpBranch,\r\n    browserBranch,\r\n    cookieJar,\r\n    mode,\r\n    preferredTransport: opts.preferredTransport ?? (() => \"http\"),\r\n    onCookieCapture: opts.onCookieCapture ?? (async () => ({ cookies: [], ua: null })),\r\n    logger,\r\n  });\r\n}\r\n\r\nfunction normalizeRetry(opts?: RetryOptions): Required<RetryOptions> {\r\n  return {\r\n    retries: opts?.retries ?? DEFAULT_RETRY.retries,\r\n    backoff: opts?.backoff ?? DEFAULT_RETRY.backoff,\r\n    minDelayMs: opts?.minDelayMs ?? DEFAULT_RETRY.minDelayMs,\r\n    maxDelayMs: opts?.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,\r\n    retryOn: opts?.retryOn ?? DEFAULT_RETRY.retryOn,\r\n  };\r\n}\r\n\r\nexport type { HttpClient, HttpRequestOptions, HttpResponse, HttpEvent } from \"./types.js\";\r\nexport { RateLimiter, RateLimitedHttpClient, DEFAULT_RATE_LIMIT } from \"./rate-limiter.js\";\r\nexport { RetryingHttpClient, DEFAULT_RETRY } from \"./retry.js\";\r\nexport { UserAgents, UserAgentRotatingHttpClient, DEFAULT_UA_POOL } from \"./user-agents.js\";\r\nexport { UndiciHttpClient } from \"./client.js\";\r\nexport { CookieJar } from \"./cookie-jar.js\";\r\nexport { TieredHttpClient } from \"./tiered.js\";\r\nexport type { CookieCaptureResult } from \"./tiered.js\";\r\n","import type { HttpClient, HttpRequestOptions, HttpResponse } from \"./types.js\";\r\nimport type { CookieJar, Cookie } from \"./cookie-jar.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport type { TransportMode } from \"../data-model/options.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport { isCloudflareChallenge } from \"./cf-challenge.js\";\r\nimport { hostnameOf } from \"../utils/url.js\";\r\nimport { DEFAULT_UA_POOL } from \"./user-agents.js\";\r\n\r\nconst FALLBACK_UA = DEFAULT_UA_POOL[0] ?? \"Mozilla/5.0\";\r\n\r\nexport interface CookieCaptureResult {\r\n  readonly cookies: readonly Cookie[];\r\n  readonly ua: string | null;\r\n}\r\n\r\nexport interface TieredHttpClientOptions {\r\n  readonly httpBranch: HttpClient;\r\n  readonly browserBranch: HttpClient;\r\n  readonly cookieJar: CookieJar;\r\n  readonly mode: TransportMode;\r\n  /** Returns the preferred transport for the adapter handling this URL. */\r\n  readonly preferredTransport: (url: Url) => \"http\" | \"browser\";\r\n  /** Called after each successful browser-branch call to snapshot CF cookies + UA. */\r\n  readonly onCookieCapture: (host: string) => Promise<CookieCaptureResult>;\r\n  readonly logger: Logger;\r\n}\r\n\r\nexport class TieredHttpClient implements HttpClient {\r\n  readonly #httpBranch: HttpClient;\r\n  readonly #browserBranch: HttpClient;\r\n  readonly #jar: CookieJar;\r\n  readonly #mode: TransportMode;\r\n  readonly #preferred: (url: Url) => \"http\" | \"browser\";\r\n  readonly #onCapture: (host: string) => Promise<CookieCaptureResult>;\r\n  readonly #logger: Logger;\r\n\r\n  constructor(opts: TieredHttpClientOptions) {\r\n    this.#httpBranch = opts.httpBranch;\r\n    this.#browserBranch = opts.browserBranch;\r\n    this.#jar = opts.cookieJar;\r\n    this.#mode = opts.mode;\r\n    this.#preferred = opts.preferredTransport;\r\n    this.#onCapture = opts.onCookieCapture;\r\n    this.#logger = opts.logger;\r\n  }\r\n\r\n  async get(url: Url, opts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    if (this.#mode === \"browser-required\") {\r\n      return this.#callBrowser(url, opts);\r\n    }\r\n    if (this.#mode === \"http-only\") {\r\n      return this.#httpBranch.get(url, opts);\r\n    }\r\n    // auto\r\n    if (this.#preferred(url) === \"browser\") {\r\n      return this.#callBrowser(url, opts);\r\n    }\r\n    const res = await this.#httpBranch.get(url, opts);\r\n    if (isCloudflareChallenge(res)) {\r\n      this.#logger.info({ url, status: res.status }, \"transport: escalating to browser tier\");\r\n      return this.#callBrowser(url, opts);\r\n    }\r\n    return res;\r\n  }\r\n\r\n  async #callBrowser(url: Url, opts: HttpRequestOptions | undefined): Promise<HttpResponse> {\r\n    const res = await this.#browserBranch.get(url, opts);\r\n    try {\r\n      const host = hostnameOf(url);\r\n      const captured = await this.#onCapture(host);\r\n      this.#jar.put(host, captured.cookies, captured.ua ?? FALLBACK_UA);\r\n    } catch (err) {\r\n      this.#logger.warn({ err }, \"transport: cookie capture failed (non-fatal)\");\r\n    }\r\n    return res;\r\n  }\r\n}\r\n","import { BrowserModuleNotInstalledError } from \"../../core/errors.js\";\r\nimport type { BrowserModule, ChromiumNamespace } from \"./types.js\";\r\n\r\ninterface MaybeModule {\r\n  readonly chromium?: ChromiumNamespace;\r\n}\r\n\r\nasync function tryImport(name: \"patchright\" | \"playwright\"): Promise<BrowserModule | null> {\r\n  try {\r\n    const mod = (await import(/* @vite-ignore */ name)) as MaybeModule;\r\n    if (!mod.chromium) return null;\r\n    return { name, chromium: mod.chromium };\r\n  } catch {\r\n    return null;\r\n  }\r\n}\r\n\r\nexport async function loadBrowserModule(): Promise<BrowserModule> {\r\n  const p = await tryImport(\"patchright\");\r\n  if (p) return p;\r\n  const w = await tryImport(\"playwright\");\r\n  if (w) return w;\r\n  throw new BrowserModuleNotInstalledError();\r\n}\r\n","import type { BrowserModule, RuntimeBrowser, RuntimeContext } from \"./types.js\";\r\nimport type { Logger } from \"../../data-model/ports.js\";\r\nimport { loadBrowserModule } from \"./loader.js\";\r\nimport { safeCloseContext } from \"./safe-close.js\";\r\n\r\nexport interface BrowserPoolOptions {\r\n  readonly moduleLoader?: () => Promise<BrowserModule>;\r\n  readonly idleTimeoutMs?: number;\r\n  readonly headed?: boolean;\r\n  readonly executablePath?: string;\r\n  readonly logger?: Logger;\r\n}\r\n\r\nexport interface ContextHandle {\r\n  readonly context: RuntimeContext;\r\n  readonly release: () => Promise<void>;\r\n}\r\n\r\nexport class BrowserPool {\r\n  readonly #loader: () => Promise<BrowserModule>;\r\n  readonly #idleMs: number;\r\n  readonly #headed: boolean;\r\n  readonly #execPath: string | undefined;\r\n  readonly #logger: Logger | undefined;\r\n  #module: BrowserModule | null = null;\r\n  #browser: RuntimeBrowser | null = null;\r\n  #active = new Set<RuntimeContext>();\r\n  #idleTimer: NodeJS.Timeout | null = null;\r\n  #launching: Promise<RuntimeBrowser> | null = null;\r\n  #disposed: boolean = false;\r\n\r\n  constructor(opts: BrowserPoolOptions = {}) {\r\n    this.#loader = opts.moduleLoader ?? loadBrowserModule;\r\n    this.#idleMs = opts.idleTimeoutMs ?? 60_000;\r\n    this.#headed = opts.headed ?? false;\r\n    this.#execPath = opts.executablePath;\r\n    this.#logger = opts.logger;\r\n  }\r\n\r\n  async acquireContext(): Promise<ContextHandle> {\r\n    if (this.#disposed) {\r\n      throw new Error(\"BrowserPool: cannot acquire context after dispose()\");\r\n    }\r\n    if (this.#idleTimer) {\r\n      clearTimeout(this.#idleTimer);\r\n      this.#idleTimer = null;\r\n    }\r\n    const browser = await this.#getBrowser();\r\n    const context = await browser.newContext();\r\n    if (this.#disposed) {\r\n      await safeCloseContext(context);\r\n      throw new Error(\"BrowserPool: disposed during context creation\");\r\n    }\r\n    this.#active.add(context);\r\n    return {\r\n      context,\r\n      release: async () => {\r\n        if (!this.#active.has(context)) return;\r\n        this.#active.delete(context);\r\n        await safeCloseContext(context);\r\n        this.#armIdleTimer();\r\n      },\r\n    };\r\n  }\r\n\r\n  async dispose(): Promise<void> {\r\n    this.#disposed = true;\r\n    if (this.#idleTimer) {\r\n      clearTimeout(this.#idleTimer);\r\n      this.#idleTimer = null;\r\n    }\r\n    if (this.#launching) {\r\n      await this.#launching.catch(() => {});\r\n      this.#launching = null;\r\n    }\r\n    for (const ctx of this.#active) {\r\n      await safeCloseContext(ctx);\r\n    }\r\n    this.#active.clear();\r\n    if (this.#browser) {\r\n      try { await this.#browser.close(); } catch (err) { this.#logger?.debug({ err }, \"browser close failed (non-fatal)\"); }\r\n      this.#browser = null;\r\n    }\r\n    this.#module = null;\r\n    this.#launching = null;\r\n    // dispose is one-way; #disposed = true makes later acquireContext() fail fast.\r\n  }\r\n\r\n  async #getBrowser(): Promise<RuntimeBrowser> {\r\n    if (this.#browser?.isConnected()) return this.#browser;\r\n    if (this.#launching) return this.#launching;\r\n    const launching = (async () => {\r\n      this.#module ??= await this.#loader();\r\n      if (this.#disposed) throw new Error(\"BrowserPool disposed during launch\");\r\n      const launchOpts: { headless: boolean; executablePath?: string } = { headless: !this.#headed };\r\n      if (this.#execPath) launchOpts.executablePath = this.#execPath;\r\n      const b = await this.#module.chromium.launch(launchOpts);\r\n      if (this.#disposed) {\r\n        await b.close().catch(() => {});\r\n        throw new Error(\"BrowserPool disposed during launch\");\r\n      }\r\n      this.#browser = b;\r\n      return b;\r\n    })();\r\n    this.#launching = launching;\r\n    void launching\r\n      .finally(() => {\r\n        if (this.#launching === launching) this.#launching = null;\r\n      })\r\n      .catch(() => {});\r\n    return launching;\r\n  }\r\n\r\n  #armIdleTimer(): void {\r\n    if (this.#active.size > 0) return;\r\n    if (this.#idleTimer) clearTimeout(this.#idleTimer);\r\n    const timer = setTimeout(() => {\r\n      this.#idleTimer = null;\r\n      void this.#closeIdleBrowser();\r\n    }, this.#idleMs);\r\n    this.#idleTimer = timer;\r\n    timer.unref();\r\n  }\r\n\r\n  async #closeIdleBrowser(): Promise<void> {\r\n    if (this.#active.size > 0) return;\r\n    if (!this.#browser) return;\r\n    const b = this.#browser;\r\n    this.#browser = null;\r\n    try { await b.close(); } catch (err) { this.#logger?.debug({ err }, \"idle browser close failed (non-fatal)\"); }\r\n  }\r\n}\r\n","import { ParseError } from \"../core/errors.js\";\r\nimport {\r\n  UrlSchema,\r\n  ChapterIndexSchema,\r\n  AdapterIdSchema,\r\n  IsoDateSchema,\r\n  ResumeTokenSchema,\r\n  type Url,\r\n  type ChapterIndex,\r\n  type AdapterId,\r\n  type IsoDate,\r\n  type ResumeToken,\r\n} from \"./primitives.js\";\r\n\r\nfunction preview(input: unknown): string {\r\n  return String(input).slice(0, 80);\r\n}\r\n\r\nexport function parseUrl(input: unknown): Url {\r\n  const result = UrlSchema.safeParse(input);\r\n  if (!result.success) {\r\n    throw new ParseError(`Invalid URL: ${preview(input)}`, { cause: result.error });\r\n  }\r\n  return result.data;\r\n}\r\n\r\nexport function parseChapterIndex(input: unknown): ChapterIndex {\r\n  const result = ChapterIndexSchema.safeParse(input);\r\n  if (!result.success) {\r\n    throw new ParseError(`Invalid ChapterIndex: ${preview(input)}`, { cause: result.error });\r\n  }\r\n  return result.data;\r\n}\r\n\r\nexport function parseAdapterId(input: unknown): AdapterId {\r\n  const result = AdapterIdSchema.safeParse(input);\r\n  if (!result.success) {\r\n    throw new ParseError(`Invalid AdapterId: ${preview(input)}`, { cause: result.error });\r\n  }\r\n  return result.data;\r\n}\r\n\r\nexport function parseIsoDate(input: unknown): IsoDate {\r\n  const result = IsoDateSchema.safeParse(input);\r\n  if (!result.success) {\r\n    throw new ParseError(`Invalid IsoDate: ${preview(input)}`, { cause: result.error });\r\n  }\r\n  return result.data;\r\n}\r\n\r\nexport function parseResumeToken(input: unknown): ResumeToken {\r\n  const result = ResumeTokenSchema.safeParse(input);\r\n  if (!result.success) {\r\n    throw new ParseError(`Invalid ResumeToken: ${preview(input)}`, { cause: result.error });\r\n  }\r\n  return result.data;\r\n}\r\n","import { z } from \"zod\";\r\nimport { UrlSchema, ChapterIndexSchema, AdapterIdSchema } from \"./primitives.js\";\r\n\r\nexport const NovelStatusSchema = z.enum([\"ongoing\", \"completed\", \"hiatus\", \"unknown\"]);\r\n\r\nexport const NovelMetadataSchema = z\r\n  .object({\r\n    sourceUrl: UrlSchema,\r\n    sourceSite: AdapterIdSchema,\r\n    title: z.string().min(1),\r\n    author: z.string(),\r\n    description: z.string(),\r\n    coverUrl: UrlSchema.optional(),\r\n    genres: z.array(z.string()).readonly(),\r\n    status: NovelStatusSchema,\r\n    totalChapters: z.number().int().nonnegative().optional(),\r\n    fetchedAt: z.coerce.date(),\r\n  })\r\n  .readonly();\r\n\r\nexport const ChapterRefSchema = z\r\n  .object({\r\n    index: ChapterIndexSchema,\r\n    title: z.string(),\r\n    url: UrlSchema,\r\n    volume: z.string().optional(),\r\n  })\r\n  .readonly();\r\n\r\nexport const ChapterSchema = z\r\n  .object({\r\n    index: ChapterIndexSchema,\r\n    title: z.string(),\r\n    url: UrlSchema,\r\n    volume: z.string().optional(),\r\n    content: z.string(),\r\n    fetchedAt: z.coerce.date(),\r\n    wordCount: z.number().int().nonnegative(),\r\n  })\r\n  .readonly();\r\n\r\nexport const NovelDataSchema = z\r\n  .object({\r\n    metadata: NovelMetadataSchema,\r\n    chapters: z.array(ChapterSchema).readonly(),\r\n  })\r\n  .readonly();\r\n","import type { SiteAdapter, AdapterContext } from \"../adapters/base.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport type { NovelMetadata } from \"../data-model/novel.js\";\r\nimport type { EventBus } from \"../core/events.js\";\r\nimport { NovelMetadataSchema } from \"../data-model/schema.js\";\r\nimport { DownloadError, ParseError } from \"../core/errors.js\";\r\n\r\nexport async function fetchMetadataStage(\r\n  adapter: SiteAdapter,\r\n  url: Url,\r\n  ctx: AdapterContext,\r\n  bus: EventBus,\r\n): Promise<NovelMetadata> {\r\n  let raw: NovelMetadata;\r\n  try {\r\n    raw = await adapter.fetchMetadata(url, ctx);\r\n  } catch (cause) {\r\n    if (cause instanceof DownloadError) throw cause;\r\n    throw new ParseError(`fetchMetadata failed for ${url}`, { cause, url });\r\n  }\r\n  const parsed = NovelMetadataSchema.safeParse(raw);\r\n  if (!parsed.success) {\r\n    throw new ParseError(`Invalid NovelMetadata from adapter`, { cause: parsed.error, url });\r\n  }\r\n  bus.emit({ type: \"metadata:fetched\", metadata: parsed.data });\r\n  return parsed.data;\r\n}\r\n","import type { SiteAdapter, AdapterContext } from \"../adapters/base.js\";\r\nimport type { Url, ChapterIndex } from \"../data-model/primitives.js\";\r\nimport type { ChapterRef } from \"../data-model/novel.js\";\r\nimport type { EventBus } from \"../core/events.js\";\r\nimport { ChapterRefSchema } from \"../data-model/schema.js\";\r\nimport { ParseError } from \"../core/errors.js\";\r\nimport { walkToc } from \"./walk-toc.js\";\r\n\r\nexport interface FetchTocRange {\r\n  readonly from?: ChapterIndex;\r\n  readonly to?: ChapterIndex;\r\n}\r\n\r\n/**\r\n * Validates each raw ChapterRef from the adapter, then forwards in-range refs to\r\n * `onRef` via {@link walkToc}. Returns the count of forwarded (in-range) refs.\r\n * See walkToc for the ascending-index requirement.\r\n */\r\nexport async function fetchTocStage(\r\n  adapter: SiteAdapter,\r\n  url: Url,\r\n  ctx: AdapterContext,\r\n  bus: EventBus,\r\n  onRef: (ref: ChapterRef) => Promise<void> | void,\r\n  range?: FetchTocRange,\r\n): Promise<number> {\r\n  async function* validated(): AsyncIterable<ChapterRef> {\r\n    for await (const raw of adapter.fetchChapterList(url, ctx)) {\r\n      const parsed = ChapterRefSchema.safeParse(raw);\r\n      if (!parsed.success) {\r\n        throw new ParseError(`Invalid ChapterRef from adapter`, { cause: parsed.error, url });\r\n      }\r\n      yield parsed.data;\r\n    }\r\n  }\r\n  return walkToc(validated(), {\r\n    ...(range !== undefined ? { range } : {}),\r\n    bus,\r\n    signal: ctx.signal,\r\n    onRef,\r\n  });\r\n}\r\n","import type { ChapterRef } from \"../data-model/novel.js\";\r\nimport type { ChapterIndex } from \"../data-model/primitives.js\";\r\nimport type { EventBus } from \"../core/events.js\";\r\nimport { CancelledError } from \"../core/errors.js\";\r\n\r\nexport const TOC_PROGRESS_EMIT_EVERY = 50;\r\n\r\nexport interface WalkTocRange {\r\n  readonly from?: ChapterIndex;\r\n  readonly to?: ChapterIndex;\r\n}\r\n\r\nexport interface WalkTocOptions {\r\n  readonly range?: WalkTocRange;\r\n  readonly bus: EventBus;\r\n  readonly signal: AbortSignal;\r\n  readonly onRef: (ref: ChapterRef) => Promise<void> | void;\r\n}\r\n\r\n/**\r\n * Walks a ChapterRef source, applying the chapter-range window and progress\r\n * throttle once. The source MUST yield refs in ascending `index` order — once a\r\n * ref past `range.to` is seen, iteration short-circuits (no later ref can be in\r\n * range). Returns the count of in-range refs forwarded to `onRef`.\r\n */\r\nexport async function walkToc(\r\n  source: AsyncIterable<ChapterRef>,\r\n  opts: WalkTocOptions,\r\n): Promise<number> {\r\n  const from = opts.range?.from;\r\n  const to = opts.range?.to;\r\n  let count = 0;\r\n  if (opts.signal.aborted) throw new CancelledError({ cause: opts.signal.reason });\r\n  for await (const ref of source) {\r\n    if (opts.signal.aborted) throw new CancelledError({ cause: opts.signal.reason });\r\n    if (to !== undefined && ref.index > to) break;\r\n    if (from !== undefined && ref.index < from) continue;\r\n    await opts.onRef(ref);\r\n    count++;\r\n    if (count % TOC_PROGRESS_EMIT_EVERY === 0) opts.bus.emit({ type: \"toc:progress\", discovered: count });\r\n  }\r\n  opts.bus.emit({ type: \"toc:progress\", discovered: count });\r\n  opts.bus.emit({ type: \"toc:complete\", total: count });\r\n  return count;\r\n}\r\n","import { unlink } from \"node:fs/promises\";\r\nimport { join, resolve } from \"node:path\";\r\nimport { z } from \"zod\";\r\nimport { UrlSchema, type Url } from \"../data-model/primitives.js\";\r\nimport { atomicWriteJson, isEnoent, readJsonWithSchema } from \"./_fs.js\";\r\n\r\nexport interface CachedResponse {\r\n  readonly status: number;\r\n  readonly headers: Readonly<Record<string, string>>;\r\n  readonly body: string;\r\n  readonly url: Url;\r\n  readonly fetchedAt: Date;\r\n  readonly etag?: string;\r\n  readonly lastModified?: string;\r\n}\r\n\r\nexport interface CacheStore {\r\n  get(key: string): Promise<CachedResponse | null>;\r\n  set(key: string, value: CachedResponse): Promise<void>;\r\n  delete(key: string): Promise<void>;\r\n}\r\n\r\nexport class MemoryCacheStore implements CacheStore {\r\n  readonly #map = new Map<string, CachedResponse>();\r\n  async get(key: string): Promise<CachedResponse | null> {\r\n    return this.#map.get(key) ?? null;\r\n  }\r\n  async set(key: string, value: CachedResponse): Promise<void> {\r\n    this.#map.set(key, value);\r\n  }\r\n  async delete(key: string): Promise<void> {\r\n    this.#map.delete(key);\r\n  }\r\n}\r\n\r\nconst SerializedEntrySchema = z.object({\r\n  v: z.literal(1),\r\n  status: z.number().int(),\r\n  headers: z.record(z.string()),\r\n  body: z.string(),\r\n  url: UrlSchema,\r\n  fetchedAt: z.string().datetime(),\r\n  etag: z.string().optional(),\r\n  lastModified: z.string().optional(),\r\n});\r\n\r\ntype SerializedEntry = z.infer<typeof SerializedEntrySchema>;\r\n\r\nexport class FilesystemCacheStore implements CacheStore {\r\n  readonly #dir: string;\r\n\r\n  constructor(dir: string) {\r\n    this.#dir = resolve(dir);\r\n  }\r\n\r\n  async get(key: string): Promise<CachedResponse | null> {\r\n    const raw = await readJsonWithSchema(this.#pathFor(key), SerializedEntrySchema);\r\n    if (raw === null) return null;\r\n    return {\r\n      status: raw.status,\r\n      headers: raw.headers,\r\n      body: raw.body,\r\n      url: raw.url,\r\n      fetchedAt: new Date(raw.fetchedAt),\r\n      ...(raw.etag !== undefined ? { etag: raw.etag } : {}),\r\n      ...(raw.lastModified !== undefined ? { lastModified: raw.lastModified } : {}),\r\n    };\r\n  }\r\n\r\n  async set(key: string, value: CachedResponse): Promise<void> {\r\n    const serialized: SerializedEntry = {\r\n      v: 1,\r\n      status: value.status,\r\n      headers: { ...value.headers },\r\n      body: value.body,\r\n      url: value.url,\r\n      fetchedAt: value.fetchedAt.toISOString(),\r\n      ...(value.etag !== undefined ? { etag: value.etag } : {}),\r\n      ...(value.lastModified !== undefined ? { lastModified: value.lastModified } : {}),\r\n    };\r\n    await atomicWriteJson(this.#pathFor(key), serialized);\r\n  }\r\n\r\n  async delete(key: string): Promise<void> {\r\n    try {\r\n      await unlink(this.#pathFor(key));\r\n    } catch (err) {\r\n      if (!isEnoent(err)) throw err;\r\n    }\r\n  }\r\n\r\n  #pathFor(key: string): string {\r\n    if (!/^[A-Za-z0-9_-]+$/.test(key)) {\r\n      throw new Error(`FilesystemCacheStore: unsafe cache key ${JSON.stringify(key)}`);\r\n    }\r\n    return join(this.#dir, `${key}.json`);\r\n  }\r\n}\r\n","import type { z } from \"zod\";\r\nimport { createHash, randomBytes } from \"node:crypto\";\r\nimport { mkdir, readFile, rename, unlink, writeFile } from \"node:fs/promises\";\r\nimport { homedir } from \"node:os\";\r\nimport { dirname, join } from \"node:path\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\n\r\nexport function sha1(input: string): string {\r\n  return createHash(\"sha1\").update(input).digest(\"hex\");\r\n}\r\n\r\nexport async function ensureDir(path: string): Promise<void> {\r\n  await mkdir(path, { recursive: true });\r\n}\r\n\r\nexport async function atomicWriteJson(path: string, value: unknown): Promise<void> {\r\n  await ensureDir(dirname(path));\r\n  const tmp = `${path}.${process.pid}.${Date.now()}.${randomBytes(6).toString(\"hex\")}.tmp`;\r\n  const body = JSON.stringify(value);\r\n  await writeFile(tmp, body, \"utf8\");\r\n  try {\r\n    await rename(tmp, path);\r\n  } catch (err) {\r\n    try {\r\n      await unlink(tmp);\r\n    } catch {\r\n      // ignore cleanup failure\r\n    }\r\n    throw err;\r\n  }\r\n}\r\n\r\nexport async function readJsonWithSchema<S extends z.ZodTypeAny>(\r\n  path: string,\r\n  schema: S,\r\n  opts: { logger?: Logger } = {},\r\n): Promise<z.infer<S> | null> {\r\n  let text: string;\r\n  try {\r\n    text = await readFile(path, \"utf8\");\r\n  } catch (err) {\r\n    if (isEnoent(err)) return null;\r\n    throw err;\r\n  }\r\n  let raw: unknown;\r\n  try {\r\n    raw = JSON.parse(text);\r\n  } catch (err) {\r\n    opts.logger?.warn({ path, err }, \"cache file is not valid JSON; treating as miss\");\r\n    return null;\r\n  }\r\n  const parsed = schema.safeParse(raw);\r\n  if (!parsed.success) {\r\n    opts.logger?.warn({ path, issues: parsed.error.issues }, \"cache file failed schema; treating as miss\");\r\n    return null;\r\n  }\r\n  return parsed.data;\r\n}\r\n\r\nexport function userCacheDir(): string {\r\n  if (process.platform === \"win32\") {\r\n    return process.env.LOCALAPPDATA ?? join(homedir(), \"AppData\", \"Local\");\r\n  }\r\n  if (process.platform === \"darwin\") {\r\n    return join(homedir(), \"Library\", \"Caches\");\r\n  }\r\n  return process.env.XDG_CACHE_HOME ?? join(homedir(), \".cache\");\r\n}\r\n\r\nexport function isEnoent(err: unknown): boolean {\r\n  return typeof err === \"object\" && err !== null && \"code\" in err && err.code === \"ENOENT\";\r\n}\r\n","import type { HttpClient, HttpEvent, HttpRequestOptions, HttpResponse } from \"../http/types.js\";\r\nimport type { CacheStore, CachedResponse } from \"./cache-store.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport { sha1 } from \"./_fs.js\";\r\n\r\nexport interface HttpCacheClientOptions {\r\n  readonly maxAgeMs: number;\r\n  readonly onEvent?: (e: HttpEvent) => void;\r\n}\r\n\r\nexport class HttpCacheClient implements HttpClient {\r\n  readonly #inner: HttpClient;\r\n  readonly #store: CacheStore;\r\n  readonly #opts: HttpCacheClientOptions;\r\n\r\n  constructor(inner: HttpClient, store: CacheStore, opts: HttpCacheClientOptions) {\r\n    this.#inner = inner;\r\n    this.#store = store;\r\n    this.#opts = opts;\r\n  }\r\n\r\n  // NOTE: Concurrent get() calls for the same URL on a cold cache will\r\n  // double-fetch (no in-flight Promise coalescing). Acceptable in v1 because\r\n  // every URL the downloader requests is unique; revisit if we add prefetching.\r\n  async get(url: Url, reqOpts?: HttpRequestOptions): Promise<HttpResponse> {\r\n    const key = sha1(\"GET\\n\" + url);\r\n    const entry = await this.#store.get(key);\r\n    const now = Date.now();\r\n\r\n    if (entry && now - entry.fetchedAt.getTime() < this.#opts.maxAgeMs) {\r\n      this.#opts.onEvent?.({ type: \"cache:hit\", url });\r\n      return toResponse(entry);\r\n    }\r\n\r\n    if (entry && (entry.etag !== undefined || entry.lastModified !== undefined)) {\r\n      const headers: Record<string, string> = { ...(reqOpts?.headers ?? {}) };\r\n      if (entry.etag !== undefined) headers[\"if-none-match\"] = entry.etag;\r\n      if (entry.lastModified !== undefined) headers[\"if-modified-since\"] = entry.lastModified;\r\n      const res = await this.#inner.get(url, { ...reqOpts, headers });\r\n      if (res.status === 304) {\r\n        const refreshed: CachedResponse = { ...entry, fetchedAt: new Date(now) };\r\n        await this.#store.set(key, refreshed);\r\n        this.#opts.onEvent?.({ type: \"cache:hit\", url });\r\n        return toResponse(refreshed);\r\n      }\r\n      if (isCacheable(res)) {\r\n        await this.#store.set(key, fromResponse(res, now));\r\n      }\r\n      return res;\r\n    }\r\n\r\n    if (entry) {\r\n      // Stale entry without validators is unusable; drop it before refetching.\r\n      await this.#store.delete(key);\r\n    }\r\n\r\n    const res = await this.#inner.get(url, reqOpts);\r\n    if (isCacheable(res)) {\r\n      await this.#store.set(key, fromResponse(res, now));\r\n    }\r\n    return res;\r\n  }\r\n}\r\n\r\nfunction isCacheable(res: HttpResponse): boolean {\r\n  return res.status >= 200 && res.status < 300;\r\n}\r\n\r\nfunction fromResponse(res: HttpResponse, fetchedAtMs: number): CachedResponse {\r\n  const etag = res.headers[\"etag\"];\r\n  const lastModified = res.headers[\"last-modified\"];\r\n  return {\r\n    status: res.status,\r\n    headers: { ...res.headers },\r\n    body: res.body,\r\n    url: res.url,\r\n    fetchedAt: new Date(fetchedAtMs),\r\n    ...(etag !== undefined ? { etag } : {}),\r\n    ...(lastModified !== undefined ? { lastModified } : {}),\r\n  };\r\n}\r\n\r\nfunction toResponse(entry: CachedResponse): HttpResponse {\r\n  return {\r\n    status: entry.status,\r\n    headers: { ...entry.headers },\r\n    body: entry.body,\r\n    url: entry.url,\r\n  };\r\n}\r\n","import PQueue from \"p-queue\";\r\nimport type { SiteAdapter, AdapterContext } from \"../adapters/base.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport type { NovelMetadata, ChapterRef, NovelData } from \"../data-model/novel.js\";\r\nimport type { DownloadResult } from \"../data-model/result.js\";\r\nimport type { ChapterIndex, Url } from \"../data-model/primitives.js\";\r\nimport { EventBus } from \"./events.js\";\r\nimport { Debouncer } from \"./debouncer.js\";\r\nimport { RunLedger } from \"./run-ledger.js\";\r\nimport { parseChapterIndex } from \"../data-model/parse.js\";\r\nimport { fetchMetadataStage } from \"../pipeline/fetch-metadata.js\";\r\nimport { fetchTocStage } from \"../pipeline/fetch-toc.js\";\r\nimport type { FetchTocRange } from \"../pipeline/fetch-toc.js\";\r\nimport { walkToc } from \"../pipeline/walk-toc.js\";\r\nimport { fetchChapterTask } from \"../pipeline/fetch-chapters.js\";\r\nimport { assembleNovelData } from \"../pipeline/normalize.js\";\r\nimport { CancelledError, ChapterFetchError, normalizeToEnvelopeError } from \"./errors.js\";\r\nimport type { KnownDownloadError } from \"./errors.js\";\r\nimport type { ResumeState } from \"../storage/resume-state.js\";\r\nimport { nowIso } from \"../storage/resume-state.js\";\r\nimport { encodeResumeToken } from \"../storage/resume-token.js\";\r\nimport { ResumeStorage } from \"../storage/resume-storage.js\";\r\n\r\nconst QUEUE_BACKPRESSURE_RATIO = 4;\r\nconst STATE_PERSIST_DEBOUNCE_MS = 500;\r\n\r\nexport interface DownloadRunArgs {\r\n  readonly adapter: SiteAdapter;\r\n  readonly validated: Url;\r\n  readonly normalizedRange: FetchTocRange | undefined;\r\n  readonly resumeStorage: ResumeStorage | null;\r\n  readonly concurrency: number;\r\n  readonly signal: AbortSignal;\r\n  readonly ctx: AdapterContext;\r\n  readonly bus: EventBus;\r\n  readonly logger: Logger;\r\n}\r\n\r\nexport class DownloadRun {\r\n  readonly #adapter: SiteAdapter;\r\n  readonly #validated: Url;\r\n  readonly #normalizedRange: FetchTocRange | undefined;\r\n  readonly #resumeStorage: ResumeStorage | null;\r\n  readonly #concurrency: number;\r\n  readonly #signal: AbortSignal;\r\n  readonly #ctx: AdapterContext;\r\n  readonly #bus: EventBus;\r\n  readonly #logger: Logger;\r\n\r\n  #state: ResumeState | null = null;\r\n  #ledger: RunLedger = RunLedger.seed(null, []);\r\n  readonly #queue: PQueue;\r\n  readonly #stateDebouncer: Debouncer;\r\n\r\n  constructor(args: DownloadRunArgs) {\r\n    this.#adapter = args.adapter;\r\n    this.#validated = args.validated;\r\n    this.#normalizedRange = args.normalizedRange;\r\n    this.#resumeStorage = args.resumeStorage;\r\n    this.#concurrency = args.concurrency;\r\n    this.#signal = args.signal;\r\n    this.#ctx = args.ctx;\r\n    this.#bus = args.bus;\r\n    this.#logger = args.logger;\r\n    this.#queue = new PQueue({ concurrency: this.#concurrency });\r\n    this.#stateDebouncer = new Debouncer(\r\n      (): Promise<void> => this.#persistState(),\r\n      STATE_PERSIST_DEBOUNCE_MS,\r\n      { logger: this.#logger },\r\n    );\r\n  }\r\n\r\n  async execute(): Promise<DownloadResult> {\r\n    const initErr = await this.#initResume();\r\n    if (initErr) return initErr;\r\n\r\n    const resolved = await this.#resolveMetadata();\r\n    if (\"error\" in resolved) return resolved.error;\r\n    const meta = resolved.meta;\r\n\r\n    const ctxWithMeta: AdapterContext = { ...this.#ctx, novelMetadata: meta };\r\n    const onAbort = (): void => {\r\n      this.#queue.clear();\r\n    };\r\n    this.#signal.addEventListener(\"abort\", onAbort, { once: true });\r\n    try {\r\n      const stage2Error = await this.#runTocStage(ctxWithMeta);\r\n      await this.#drainQueueOrAbort();\r\n      await this.#flushQuietly();\r\n      return this.#finalize(meta, stage2Error);\r\n    } finally {\r\n      this.#signal.removeEventListener(\"abort\", onAbort);\r\n    }\r\n  }\r\n\r\n  async #initResume(): Promise<DownloadResult | null> {\r\n    const resumeCfg = this.#resumeStorage;\r\n    if (!resumeCfg) return null;\r\n    try {\r\n      const { state, priorChapters } = await resumeCfg.loadFor(this.#validated, this.#adapter.id);\r\n      this.#state = state;\r\n      this.#ledger = RunLedger.seed(state, priorChapters);\r\n    } catch (err) {\r\n      return { status: \"error\", error: normalizeToEnvelopeError(err) };\r\n    }\r\n    return null;\r\n  }\r\n\r\n  #schedulePersist(): void {\r\n    if (this.#state) this.#stateDebouncer.schedule();\r\n  }\r\n\r\n  async #persistState(): Promise<void> {\r\n    const resumeCfg = this.#resumeStorage;\r\n    if (!resumeCfg || !this.#state) return;\r\n    const next: ResumeState = { ...this.#ledger.applyTo(this.#state), updatedAt: nowIso() };\r\n    this.#state = next;\r\n    await resumeCfg.resume.save(resumeCfg.stateFile, next);\r\n  }\r\n\r\n  async #resolveMetadata(): Promise<{ readonly meta: NovelMetadata } | { readonly error: DownloadResult }> {\r\n    try {\r\n      let meta: NovelMetadata;\r\n      if (this.#state?.metadata) {\r\n        meta = this.#state.metadata;\r\n      } else {\r\n        meta = await fetchMetadataStage(this.#adapter, this.#validated, this.#ctx, this.#bus);\r\n        if (this.#state) {\r\n          this.#state = { ...this.#state, metadata: meta, updatedAt: nowIso() };\r\n          await this.#persistState();\r\n        }\r\n      }\r\n      return { meta };\r\n    } catch (err) {\r\n      return { error: { status: \"error\", error: normalizeToEnvelopeError(err) } };\r\n    }\r\n  }\r\n\r\n  async #enqueueRef(ref: ChapterRef, ctxWithMeta: AdapterContext): Promise<void> {\r\n    if (this.#signal.aborted) return;\r\n    if (!this.#ledger.markEnqueued(ref.index)) return;\r\n    const backpressureLimit = this.#concurrency * QUEUE_BACKPRESSURE_RATIO;\r\n    while (this.#queue.size >= backpressureLimit) {\r\n      await this.#queue.onSizeLessThan(backpressureLimit);\r\n      if (this.#signal.aborted) throw new CancelledError({ cause: this.#signal.reason });\r\n    }\r\n    void this.#queue.add((): Promise<void> => this.#fetchChapter(ref, ctxWithMeta));\r\n  }\r\n\r\n  async #fetchChapter(ref: ChapterRef, ctxWithMeta: AdapterContext): Promise<void> {\r\n    if (this.#signal.aborted) return;\r\n    try {\r\n      const chapter = await fetchChapterTask(this.#adapter, ref, ctxWithMeta, this.#bus);\r\n      if (this.#signal.aborted) return;\r\n      // Persist before recording in-memory success — if the chapter store\r\n      // throws (ENOSPC, EACCES, atomic-rename loss), the catch below records\r\n      // a clean failure instead of a chapter that's listed in both\r\n      // successes and failures.\r\n      if (this.#resumeStorage) await this.#resumeStorage.chapters.save(chapter);\r\n      this.#ledger.recordSuccess(chapter);\r\n      this.#schedulePersist();\r\n      this.#bus.emit({ type: \"chapter:success\", chapter });\r\n      this.#bus.emit({\r\n        type: \"progress\",\r\n        completed: this.#ledger.completedCount,\r\n        total: ctxWithMeta.novelMetadata?.totalChapters ?? this.#ledger.enqueuedCount,\r\n      });\r\n    } catch (cause) {\r\n      if (cause instanceof CancelledError) return;\r\n      const wrapped =\r\n        cause instanceof ChapterFetchError ? cause : new ChapterFetchError(ref, { cause });\r\n      this.#ledger.recordFailure(ref, wrapped);\r\n      this.#schedulePersist();\r\n      this.#bus.emit({ type: \"chapter:failed\", ref, error: wrapped });\r\n    }\r\n  }\r\n\r\n  async #runTocStage(ctxWithMeta: AdapterContext): Promise<Exclude<KnownDownloadError, CancelledError> | undefined> {\r\n    let stage2Error: Exclude<KnownDownloadError, CancelledError> | undefined;\r\n    try {\r\n      if (this.#state?.toc) {\r\n        const toc = this.#state.toc;\r\n        async function* fromState(): AsyncIterable<ChapterRef> {\r\n          for (const ref of toc) yield ref;\r\n        }\r\n        await walkToc(fromState(), {\r\n          ...(this.#normalizedRange !== undefined ? { range: this.#normalizedRange } : {}),\r\n          bus: this.#bus,\r\n          signal: this.#signal,\r\n          onRef: (ref) => this.#enqueueRef(ref, ctxWithMeta),\r\n        });\r\n      } else {\r\n        const tocBuffer: ChapterRef[] = [];\r\n        await fetchTocStage(\r\n          this.#adapter,\r\n          this.#validated,\r\n          ctxWithMeta,\r\n          this.#bus,\r\n          async (ref) => {\r\n            tocBuffer.push(ref);\r\n            await this.#enqueueRef(ref, ctxWithMeta);\r\n          },\r\n          this.#normalizedRange,\r\n        );\r\n        if (this.#state) {\r\n          this.#state = { ...this.#state, toc: tocBuffer };\r\n          await this.#persistState();\r\n        }\r\n      }\r\n      await this.#retryFailedRecords(ctxWithMeta);\r\n    } catch (err) {\r\n      if (err instanceof CancelledError) {\r\n        this.#queue.clear();\r\n        await this.#stateDebouncer.cancel();\r\n        throw err;\r\n      }\r\n      stage2Error = normalizeToEnvelopeError(err);\r\n    }\r\n    return stage2Error;\r\n  }\r\n\r\n  async #retryFailedRecords(ctxWithMeta: AdapterContext): Promise<void> {\r\n    if (!this.#state) return;\r\n    // Build an index-keyed lookup so the failed-records retry loop is O(N+F)\r\n    // instead of O(N*F) — significant for large novels with many failures.\r\n    const tocByIndex = new Map<ChapterIndex, ChapterRef>(\r\n      (this.#state.toc ?? []).map((r) => [r.index, r]),\r\n    );\r\n    for (const f of this.#ledger.failedRecords()) {\r\n      const ref = tocByIndex.get(f.index);\r\n      if (ref) {\r\n        await this.#enqueueRef(ref, ctxWithMeta);\r\n      } else {\r\n        // Failed record points to an index not present in the current TOC\r\n        // (e.g. the source novel removed a chapter between runs). Drop it\r\n        // from state.failed so it doesn't linger forever.\r\n        this.#logger.warn(\r\n          `Resume: dropping orphan failed record for index ${String(f.index)} (no matching TOC ref)`,\r\n        );\r\n        this.#ledger.dropOrphanFailure(f.index);\r\n        this.#schedulePersist();\r\n      }\r\n    }\r\n  }\r\n\r\n  async #drainQueueOrAbort(): Promise<void> {\r\n    let onAbortResolve: (() => void) | undefined;\r\n    const abortPromise = new Promise<void>((resolve) => {\r\n      if (this.#signal.aborted) {\r\n        resolve();\r\n        return;\r\n      }\r\n      onAbortResolve = (): void => resolve();\r\n      this.#signal.addEventListener(\"abort\", onAbortResolve, { once: true });\r\n    });\r\n    await Promise.race([this.#queue.onIdle(), abortPromise]);\r\n    if (onAbortResolve) this.#signal.removeEventListener(\"abort\", onAbortResolve);\r\n\r\n    if (this.#signal.aborted) {\r\n      await this.#stateDebouncer.cancel();\r\n      throw new CancelledError({ cause: this.#signal.reason });\r\n    }\r\n  }\r\n\r\n  async #flushQuietly(): Promise<void> {\r\n    try {\r\n      await this.#stateDebouncer.flush();\r\n    } catch (err) {\r\n      // Resume-state persistence is best-effort: the chapters are already in\r\n      // `data` and on disk, and state self-heals on the next resume via\r\n      // chapter-file reconciliation. Never sink a finished download over it.\r\n      this.#logger.warn({ err, url: this.#validated }, \"resume state persist failed (non-fatal)\");\r\n    }\r\n  }\r\n\r\n  #finalize(\r\n    metadata: NovelMetadata,\r\n    stage2Error: Exclude<KnownDownloadError, CancelledError> | undefined,\r\n  ): DownloadResult {\r\n    if (stage2Error) return { status: \"error\", error: stage2Error };\r\n\r\n    let data: NovelData;\r\n    try {\r\n      data = assembleNovelData(metadata, this.#ledger.successes());\r\n    } catch (err) {\r\n      return {\r\n        status: \"error\",\r\n        error: normalizeToEnvelopeError(err),\r\n      };\r\n    }\r\n\r\n    const failures = this.#ledger.failures();\r\n    if (failures.length > 0) {\r\n      if (this.#resumeStorage) {\r\n        const token = encodeResumeToken(this.#resumeStorage.stateFile, this.#validated);\r\n        return { status: \"partial\", resumable: true, data, failures, resumeToken: token };\r\n      }\r\n      return { status: \"partial\", resumable: false, data, failures };\r\n    }\r\n    return { status: \"success\", data };\r\n  }\r\n}\r\n\r\nexport function normalizeChapterRange(\r\n  range: { from?: number; to?: number } | undefined,\r\n): FetchTocRange | undefined {\r\n  if (range === undefined) return undefined;\r\n  const out: { from?: ChapterIndex; to?: ChapterIndex } = {};\r\n  if (range.from !== undefined) out.from = parseChapterIndex(range.from);\r\n  if (range.to !== undefined) out.to = parseChapterIndex(range.to);\r\n  return out;\r\n}\r\n","import type { Logger } from \"../utils/logger.js\";\r\n\r\n/**\r\n * Debounces an async persist. Errors are observable two ways: logged at warn\r\n * immediately, AND retained so the next flush()/cancel() re-throws (recovery\r\n * contract — the caller learns of a failed persist at the next sync point).\r\n */\r\nexport class Debouncer {\r\n  #timer: NodeJS.Timeout | null = null;\r\n  #pending = false;\r\n  #disabled = false;\r\n  #lastError: unknown = null;\r\n  readonly #logger: Logger | undefined;\r\n\r\n  constructor(\r\n    private readonly fn: () => Promise<void>,\r\n    private readonly ms: number,\r\n    opts: { logger?: Logger } = {},\r\n  ) {\r\n    this.#logger = opts.logger;\r\n  }\r\n\r\n  schedule(): void {\r\n    if (this.#disabled) return;\r\n    this.#pending = true;\r\n    if (this.#timer !== null) return;\r\n    this.#timer = setTimeout(() => {\r\n      this.#timer = null;\r\n      if (!this.#pending) return;\r\n      this.#pending = false;\r\n      // Capture rejections so the next flush()/cancel() can surface them\r\n      // to the caller. Without this, a failed disk write (e.g. ENOSPC)\r\n      // would silently disappear and the download would keep running as\r\n      // if state were durable.\r\n      void this.fn().catch((err: unknown) => {\r\n        this.#lastError = err;\r\n        this.#logger?.warn({ err }, \"debounced persist failed; will re-throw at next flush\");\r\n      });\r\n    }, this.ms);\r\n  }\r\n\r\n  async flush(): Promise<void> {\r\n    if (this.#timer !== null) {\r\n      clearTimeout(this.#timer);\r\n      this.#timer = null;\r\n    }\r\n    if (this.#pending) {\r\n      this.#pending = false;\r\n      try {\r\n        await this.fn();\r\n      } catch (err: unknown) {\r\n        this.#lastError = err;\r\n      }\r\n    }\r\n    if (this.#lastError !== null) {\r\n      const err = this.#lastError;\r\n      this.#lastError = null;\r\n      throw err;\r\n    }\r\n  }\r\n\r\n  // Drain any pending work and refuse future schedule() calls.\r\n  // Used on cancellation so in-flight tasks completing after the\r\n  // download function returns can't reschedule a stale state-file write.\r\n  async cancel(): Promise<void> {\r\n    this.#disabled = true;\r\n    await this.flush();\r\n  }\r\n}\r\n","import type { Chapter, ChapterRef } from \"../data-model/novel.js\";\r\nimport type { ChapterIndex } from \"../data-model/primitives.js\";\r\nimport type { ChapterFailure } from \"../data-model/result.js\";\r\nimport type { ChapterFetchError } from \"./errors.js\";\r\nimport type { ResumeState, ResumeFailureRecord } from \"../storage/resume-state.js\";\r\n\r\n/**\r\n * In-memory chapter bookkeeping for one download run: enqueue dedup, the\r\n * success/failure ledger, and the single projection onto the persisted\r\n * ResumeState shape. Pure state — no I/O, no events, no timers; DownloadRun\r\n * owns persistence and the event bus.\r\n */\r\nexport class RunLedger {\r\n  readonly #successes: Chapter[] = [];\r\n  readonly #failures: ChapterFailure[] = [];\r\n  readonly #completedSet = new Set<ChapterIndex>();\r\n  readonly #completedList: ChapterIndex[] = [];\r\n  readonly #failedMap = new Map<ChapterIndex, ResumeFailureRecord>();\r\n  readonly #enqueuedSet = new Set<ChapterIndex>();\r\n\r\n  private constructor() {}\r\n\r\n  static seed(state: ResumeState | null, priorChapters: readonly Chapter[]): RunLedger {\r\n    const ledger = new RunLedger();\r\n    ledger.#successes.push(...priorChapters);\r\n    for (const idx of state?.completed ?? []) ledger.#markCompleted(idx);\r\n    for (const chapter of priorChapters) ledger.#markCompleted(chapter.index);\r\n    for (const f of state?.failed ?? []) {\r\n      if (!ledger.#completedSet.has(f.index)) ledger.#failedMap.set(f.index, f);\r\n    }\r\n    return ledger;\r\n  }\r\n\r\n  #markCompleted(index: ChapterIndex): void {\r\n    if (this.#completedSet.has(index)) return;\r\n    this.#completedSet.add(index);\r\n    this.#completedList.push(index);\r\n  }\r\n\r\n  /** Check-and-set enqueue dedup: false if the index is completed or already enqueued. */\r\n  markEnqueued(index: ChapterIndex): boolean {\r\n    if (this.#completedSet.has(index) || this.#enqueuedSet.has(index)) return false;\r\n    this.#enqueuedSet.add(index);\r\n    return true;\r\n  }\r\n\r\n  recordSuccess(chapter: Chapter): void {\r\n    this.#successes.push(chapter);\r\n    this.#markCompleted(chapter.index);\r\n    this.#failedMap.delete(chapter.index);\r\n  }\r\n\r\n  recordFailure(ref: ChapterRef, error: ChapterFetchError): void {\r\n    this.#failures.push({ ref, error, attempts: 1 });\r\n    this.#failedMap.set(ref.index, {\r\n      index: ref.index,\r\n      lastError: { code: error.code, message: error.message },\r\n    });\r\n  }\r\n\r\n  dropOrphanFailure(index: ChapterIndex): void {\r\n    this.#failedMap.delete(index);\r\n  }\r\n\r\n  /** The ONLY projection to the persisted shape; nothing else writes state.completed/failed. */\r\n  applyTo(state: ResumeState): ResumeState {\r\n    return {\r\n      ...state,\r\n      completed: [...this.#completedList].sort((a, b) => Number(a) - Number(b)),\r\n      failed: [...this.#failedMap.values()],\r\n    };\r\n  }\r\n\r\n  successes(): readonly Chapter[] {\r\n    return this.#successes;\r\n  }\r\n\r\n  failures(): readonly ChapterFailure[] {\r\n    return this.#failures;\r\n  }\r\n\r\n  /** Snapshot — safe to mutate the ledger while iterating the result. */\r\n  failedRecords(): readonly ResumeFailureRecord[] {\r\n    return [...this.#failedMap.values()];\r\n  }\r\n\r\n  /** Fetched-chapter count (including prior-run chapters) — drives the progress event. */\r\n  get completedCount(): number {\r\n    return this.#successes.length;\r\n  }\r\n\r\n  get enqueuedCount(): number {\r\n    return this.#enqueuedSet.size;\r\n  }\r\n}\r\n","import type { SiteAdapter, AdapterContext } from \"../adapters/base.js\";\r\nimport type { ChapterRef, Chapter } from \"../data-model/novel.js\";\r\nimport type { EventBus } from \"../core/events.js\";\r\nimport { ChapterSchema } from \"../data-model/schema.js\";\r\nimport { DownloadError, ParseError } from \"../core/errors.js\";\r\n\r\nexport async function fetchChapterTask(\r\n  adapter: SiteAdapter,\r\n  ref: ChapterRef,\r\n  ctx: AdapterContext,\r\n  bus: EventBus,\r\n): Promise<Chapter> {\r\n  bus.emit({ type: \"chapter:start\", ref });\r\n  let raw: Chapter;\r\n  try {\r\n    raw = await adapter.fetchChapter(ref, ctx);\r\n  } catch (cause) {\r\n    if (cause instanceof DownloadError) throw cause;\r\n    throw new ParseError(`fetchChapter failed for ${ref.url}`, { cause, url: ref.url });\r\n  }\r\n  const parsed = ChapterSchema.safeParse(raw);\r\n  if (!parsed.success) {\r\n    throw new ParseError(`Invalid Chapter from adapter`, { cause: parsed.error, url: ref.url });\r\n  }\r\n  return parsed.data;\r\n}\r\n","import type { Chapter, NovelMetadata, NovelData } from \"../data-model/novel.js\";\r\nimport { NovelDataSchema } from \"../data-model/schema.js\";\r\n\r\nexport function assembleNovelData(\r\n  metadata: NovelMetadata,\r\n  results: readonly Chapter[],\r\n): NovelData {\r\n  // Sort by original ChapterRef.index. Do NOT renumber: a partial result\r\n  // (some chapters permanently failed) must preserve gaps so the index in\r\n  // each Chapter still matches its source position.\r\n  const sorted = [...results].sort((a, b) => a.index - b.index);\r\n  // Never backfill totalChapters from sorted.length: in bounded chapterRange\r\n  // mode the slice can be far smaller than the source TOC.\r\n  return NovelDataSchema.parse({ metadata, chapters: sorted });\r\n}\r\n","import { z } from \"zod\";\r\nimport {\r\n  AdapterIdSchema,\r\n  ChapterIndexSchema,\r\n  IsoDateSchema,\r\n  UrlSchema,\r\n  unsafeBrandIsoDate,\r\n} from \"../data-model/primitives.js\";\r\nimport type { Url } from \"../data-model/primitives.js\";\r\nimport { NovelMetadataSchema, ChapterRefSchema } from \"../data-model/schema.js\";\r\n\r\nexport const ResumeFailureRecordSchema = z\r\n  .object({\r\n    index: ChapterIndexSchema,\r\n    lastError: z.object({ code: z.string(), message: z.string() }).readonly(),\r\n  })\r\n  .readonly();\r\n\r\nexport type ResumeFailureRecord = z.infer<typeof ResumeFailureRecordSchema>;\r\n\r\nexport const ResumeStateSchema = z\r\n  .object({\r\n    schemaVersion: z.literal(1),\r\n    url: UrlSchema,\r\n    adapterId: AdapterIdSchema,\r\n    metadata: NovelMetadataSchema.nullable(),\r\n    toc: z.array(ChapterRefSchema).readonly().nullable(),\r\n    completed: z.array(ChapterIndexSchema).readonly(),\r\n    failed: z.array(ResumeFailureRecordSchema).readonly(),\r\n    startedAt: IsoDateSchema,\r\n    updatedAt: IsoDateSchema,\r\n  })\r\n  .readonly();\r\n\r\nexport type ResumeState = z.infer<typeof ResumeStateSchema>;\r\n\r\nexport function nowIso(): ResumeState[\"startedAt\"] {\r\n  return unsafeBrandIsoDate(new Date().toISOString());\r\n}\r\n\r\nexport function freshState(url: Url, adapterId: ResumeState[\"adapterId\"]): ResumeState {\r\n  const now = nowIso();\r\n  return {\r\n    schemaVersion: 1,\r\n    url,\r\n    adapterId,\r\n    metadata: null,\r\n    toc: null,\r\n    completed: [],\r\n    failed: [],\r\n    startedAt: now,\r\n    updatedAt: now,\r\n  };\r\n}\r\n","import { join, relative, resolve, isAbsolute } from \"node:path\";\r\nimport { z } from \"zod\";\r\nimport { ParseError } from \"../core/errors.js\";\r\nimport { unsafeBrandResumeToken, type ResumeToken, type Url } from \"../data-model/primitives.js\";\r\nimport { sha1, userCacheDir } from \"./_fs.js\";\r\n\r\nconst TokenPayloadSchema = z.object({\r\n  v: z.literal(1),\r\n  stateFile: z.string().min(1),\r\n  urlHash: z.string().min(1),\r\n});\r\n\r\ntype TokenPayload = z.infer<typeof TokenPayloadSchema>;\r\n\r\nexport function encodeResumeToken(stateFile: string, url: Url): ResumeToken {\r\n  const payload: TokenPayload = { v: 1, stateFile, urlHash: sha1(url) };\r\n  return unsafeBrandResumeToken(Buffer.from(JSON.stringify(payload)).toString(\"base64url\"));\r\n}\r\n\r\nexport function decodeResumeToken(token: ResumeToken): {\r\n  readonly stateFile: string;\r\n  readonly urlHash: string;\r\n} {\r\n  let raw: unknown;\r\n  try {\r\n    const text = Buffer.from(token, \"base64url\").toString(\"utf8\");\r\n    raw = JSON.parse(text);\r\n  } catch (cause) {\r\n    throw new ParseError(\"Invalid resume token: not base64-encoded JSON\", { cause });\r\n  }\r\n  const parsed = TokenPayloadSchema.safeParse(raw);\r\n  if (!parsed.success) {\r\n    throw new ParseError(\"Invalid resume token: unsupported payload\", { cause: parsed.error });\r\n  }\r\n  return { stateFile: parsed.data.stateFile, urlHash: parsed.data.urlHash };\r\n}\r\n\r\nfunction assertWithinCacheDir(stateFile: string): void {\r\n  const root = resolve(join(userCacheDir(), \"webnovel-downloader\"));\r\n  const rel = relative(root, resolve(stateFile));\r\n  // Reject the root itself (rel === \"\") as well as any escape: stateFile must be\r\n  // a file *inside* the cache dir, never the directory.\r\n  if (rel === \"\" || rel.startsWith(\"..\") || isAbsolute(rel)) {\r\n    throw new ParseError(\"Resume token state path escapes the cache directory\");\r\n  }\r\n}\r\n\r\nexport function verifyResumeToken(token: ResumeToken, url: Url): { readonly stateFile: string } {\r\n  const { stateFile, urlHash } = decodeResumeToken(token);\r\n  if (urlHash !== sha1(url)) {\r\n    throw new ParseError(\"Resume token does not match URL\");\r\n  }\r\n  // Containment is enforced on the token branch only: a token's stateFile is\r\n  // attacker-influenceable, but an explicit `{ stateFile }` resume option is\r\n  // caller-controlled and intentionally not checked here.\r\n  assertWithinCacheDir(stateFile);\r\n  return { stateFile };\r\n}\r\n","import { join } from \"node:path\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport type { Url, ResumeToken, AdapterId, ChapterIndex } from \"../data-model/primitives.js\";\r\nimport type { ResumeOptions } from \"../data-model/options.js\";\r\nimport type { Chapter } from \"../data-model/novel.js\";\r\nimport { ParseError } from \"../core/errors.js\";\r\nimport { freshState, type ResumeState } from \"./resume-state.js\";\r\nimport { sha1, userCacheDir } from \"./_fs.js\";\r\nimport { FilesystemResumeStore, MemoryResumeStore, type ResumeStore } from \"./resume-store.js\";\r\nimport { FilesystemChapterStore, MemoryChapterStore, type ChapterStore } from \"./chapter-store.js\";\r\nimport { verifyResumeToken } from \"./resume-token.js\";\r\n\r\n/**\r\n * Owns resume persistence: the state-file path plus the `${stateFile}.chapters`\r\n * directory convention, and the three resume-option entry points. The cache\r\n * store is intentionally NOT here — it is keyed by the caller's cache dir and\r\n * wired separately in Downloader.\r\n */\r\nexport class ResumeStorage {\r\n  readonly resume: ResumeStore;\r\n  readonly chapters: ChapterStore;\r\n  readonly stateFile: string;\r\n\r\n  private constructor(resume: ResumeStore, chapters: ChapterStore, stateFile: string) {\r\n    this.resume = resume;\r\n    this.chapters = chapters;\r\n    this.stateFile = stateFile;\r\n  }\r\n\r\n  static forDefault(url: Url, logger: Logger): ResumeStorage {\r\n    const stateFile = join(userCacheDir(), \"webnovel-downloader\", \"state\", `${sha1(url)}.json`);\r\n    return ResumeStorage.#filesystem(stateFile, logger);\r\n  }\r\n\r\n  static forStateFile(stateFile: string, logger: Logger): ResumeStorage {\r\n    return ResumeStorage.#filesystem(stateFile, logger);\r\n  }\r\n\r\n  static forToken(token: ResumeToken, url: Url, logger: Logger): ResumeStorage {\r\n    const { stateFile } = verifyResumeToken(token, url);\r\n    return ResumeStorage.#filesystem(stateFile, logger);\r\n  }\r\n\r\n  static memory(stateFile: string): ResumeStorage {\r\n    return new ResumeStorage(new MemoryResumeStore(), new MemoryChapterStore(), stateFile);\r\n  }\r\n\r\n  static #filesystem(stateFile: string, logger: Logger): ResumeStorage {\r\n    return new ResumeStorage(\r\n      new FilesystemResumeStore(),\r\n      new FilesystemChapterStore(`${stateFile}.chapters`, { logger }),\r\n      stateFile,\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Loads state for a run against (url, adapterId) and applies the reconcile\r\n   * invariant: a chapter file on disk proves the fetch completed, so its index\r\n   * wins over a stale `state.completed` (they diverge when run 1's in-flight\r\n   * tasks write chapters after abort but before the debounced state save).\r\n   */\r\n  async loadFor(\r\n    url: Url,\r\n    adapterId: AdapterId,\r\n  ): Promise<{ state: ResumeState; priorChapters: readonly Chapter[] }> {\r\n    let state = await this.resume.load(this.stateFile);\r\n    if (state) {\r\n      if (state.url !== url) {\r\n        throw new ParseError(\"Stale resume state: URL mismatch\", { url, path: this.stateFile });\r\n      }\r\n      if (state.adapterId !== adapterId) {\r\n        throw new ParseError(\"Stale resume state: adapter mismatch\", { url, path: this.stateFile });\r\n      }\r\n    } else {\r\n      state = freshState(url, adapterId);\r\n    }\r\n    const priorChapters = await this.chapters.loadAll();\r\n    if (priorChapters.length > 0) {\r\n      const merged = new Set<ChapterIndex>(state.completed);\r\n      for (const c of priorChapters) merged.add(c.index);\r\n      state = { ...state, completed: [...merged].sort((a, b) => Number(a) - Number(b)) };\r\n    }\r\n    return { state, priorChapters };\r\n  }\r\n}\r\n\r\nexport function resolveResumeStorage(\r\n  opt: ResumeOptions | undefined,\r\n  url: Url,\r\n  logger: Logger,\r\n): ResumeStorage | null {\r\n  if (opt === undefined) return null;\r\n  if (opt === true) return ResumeStorage.forDefault(url, logger);\r\n  if (\"stateFile\" in opt) return ResumeStorage.forStateFile(opt.stateFile, logger);\r\n  if (\"token\" in opt) return ResumeStorage.forToken(opt.token, url, logger);\r\n  return null;\r\n}\r\n","import { readFile, unlink } from \"node:fs/promises\";\r\nimport { resolve } from \"node:path\";\r\nimport { ParseError } from \"../core/errors.js\";\r\nimport { atomicWriteJson, isEnoent } from \"./_fs.js\";\r\nimport { ResumeStateSchema, type ResumeState } from \"./resume-state.js\";\r\n\r\nexport interface ResumeStore {\r\n  load(path: string): Promise<ResumeState | null>;\r\n  save(path: string, state: ResumeState): Promise<void>;\r\n  delete(path: string): Promise<void>;\r\n}\r\n\r\nexport class MemoryResumeStore implements ResumeStore {\r\n  readonly #map = new Map<string, ResumeState>();\r\n  async load(path: string): Promise<ResumeState | null> {\r\n    return this.#map.get(resolve(path)) ?? null;\r\n  }\r\n  async save(path: string, state: ResumeState): Promise<void> {\r\n    this.#map.set(resolve(path), state);\r\n  }\r\n  async delete(path: string): Promise<void> {\r\n    this.#map.delete(resolve(path));\r\n  }\r\n}\r\n\r\nexport class FilesystemResumeStore implements ResumeStore {\r\n  async load(path: string): Promise<ResumeState | null> {\r\n    let text: string;\r\n    try {\r\n      text = await readFile(path, \"utf8\");\r\n    } catch (err) {\r\n      if (isEnoent(err)) return null;\r\n      throw err;\r\n    }\r\n    let raw: unknown;\r\n    try {\r\n      raw = JSON.parse(text);\r\n    } catch (cause) {\r\n      throw new ParseError(\"Resume state file is corrupt; delete it to start over\", { cause, path });\r\n    }\r\n    const parsed = ResumeStateSchema.safeParse(raw);\r\n    if (!parsed.success) {\r\n      throw new ParseError(\"Resume state file is corrupt; delete it to start over\", {\r\n        cause: parsed.error,\r\n        path,\r\n      });\r\n    }\r\n    return parsed.data;\r\n  }\r\n\r\n  async save(path: string, state: ResumeState): Promise<void> {\r\n    await atomicWriteJson(path, state);\r\n  }\r\n\r\n  async delete(path: string): Promise<void> {\r\n    try {\r\n      await unlink(path);\r\n    } catch (err) {\r\n      if (!isEnoent(err)) throw err;\r\n    }\r\n  }\r\n}\r\n","import { readdir, readFile } from \"node:fs/promises\";\r\nimport { join, resolve } from \"node:path\";\r\nimport { atomicWriteJson, isEnoent } from \"./_fs.js\";\r\nimport { ChapterSchema } from \"../data-model/schema.js\";\r\nimport type { Chapter } from \"../data-model/novel.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport { silentLogger } from \"../utils/logger.js\";\r\n\r\nexport interface ChapterStore {\r\n  save(chapter: Chapter): Promise<void>;\r\n  /** Returns chapters in ascending index order. */\r\n  loadAll(): Promise<readonly Chapter[]>;\r\n}\r\n\r\nexport class MemoryChapterStore implements ChapterStore {\r\n  readonly #map = new Map<number, Chapter>();\r\n  async save(chapter: Chapter): Promise<void> {\r\n    this.#map.set(Number(chapter.index), chapter);\r\n  }\r\n  async loadAll(): Promise<readonly Chapter[]> {\r\n    return [...this.#map.values()].sort((a, b) => Number(a.index) - Number(b.index));\r\n  }\r\n}\r\n\r\nexport class FilesystemChapterStore implements ChapterStore {\r\n  readonly #logger: Logger;\r\n  readonly #dir: string;\r\n\r\n  constructor(dir: string, opts: { logger?: Logger } = {}) {\r\n    this.#dir = resolve(dir);\r\n    this.#logger = opts.logger ?? silentLogger;\r\n  }\r\n\r\n  async save(chapter: Chapter): Promise<void> {\r\n    await atomicWriteJson(join(this.#dir, `${Number(chapter.index)}.json`), chapter);\r\n  }\r\n\r\n  async loadAll(): Promise<readonly Chapter[]> {\r\n    let entries: string[];\r\n    try {\r\n      entries = await readdir(this.#dir);\r\n    } catch (err) {\r\n      if (isEnoent(err)) return [];\r\n      throw err;\r\n    }\r\n    const chapters: Chapter[] = [];\r\n    for (const name of entries) {\r\n      if (!/^\\d+\\.json$/.test(name)) continue;\r\n      const path = join(this.#dir, name);\r\n      // Resume's source-of-truth contract (see downloader.ts comment in the\r\n      // resume-load branch) requires that one corrupt or half-written file\r\n      // doesn't poison the entire reuse set. Skip+log instead of throwing.\r\n      let text: string;\r\n      try {\r\n        text = await readFile(path, \"utf8\");\r\n      } catch (err) {\r\n        this.#logger.warn({ path, err }, \"chapter-store: failed to read file, skipping\");\r\n        continue;\r\n      }\r\n      let raw: unknown;\r\n      try {\r\n        raw = JSON.parse(text);\r\n      } catch (err) {\r\n        this.#logger.warn({ path, err }, \"chapter-store: invalid JSON, skipping\");\r\n        continue;\r\n      }\r\n      const parsed = ChapterSchema.safeParse(raw);\r\n      if (!parsed.success) {\r\n        this.#logger.warn(\r\n          { path, issues: parsed.error.issues },\r\n          \"chapter-store: schema mismatch, skipping\",\r\n        );\r\n        continue;\r\n      }\r\n      chapters.push(parsed.data);\r\n    }\r\n    chapters.sort((a, b) => Number(a.index) - Number(b.index));\r\n    return chapters;\r\n  }\r\n}\r\n","import type { CheerioAPI } from \"cheerio\";\r\nimport type { Url, AdapterId } from \"../data-model/primitives.js\";\r\nimport type { NovelMetadata, ChapterRef, Chapter } from \"../data-model/novel.js\";\r\nimport type { AdapterContext as FwdAdapterContext } from \"../data-model/fwd.js\";\r\nimport type { BrowserClient } from \"../http/browser/types.js\";\r\nimport { DownloadError, ParseError } from \"../core/errors.js\";\r\nimport { parseUrl } from \"../data-model/parse.js\";\r\nimport { loadHtml } from \"../utils/html.js\";\r\nimport { matchesAny } from \"../utils/url.js\";\r\n\r\n/**\r\n * Runtime services handed to every adapter method: the {@link HttpClient}, a\r\n * logger, the abort signal, an optional browser client (present only for\r\n * browser-tier adapters), and — during chapter fetches — the already-fetched\r\n * novel metadata.\r\n */\r\nexport interface AdapterContext extends FwdAdapterContext {\r\n  readonly browser?: BrowserClient;\r\n}\r\n\r\n/**\r\n * The contract every site adapter implements. Register custom adapters via\r\n * `createDownloader({ adapters })` or the `Downloader` constructor. Extending\r\n * {@link BaseAdapter} covers `canHandle` and the HTML/JSON helpers for you.\r\n * See `docs/adapter-spec.md`.\r\n */\r\nexport interface SiteAdapter {\r\n  /** Stable adapter id, also stamped onto `NovelMetadata.sourceSite`. */\r\n  readonly id: AdapterId;\r\n  /** Human-readable site name. */\r\n  readonly displayName: string;\r\n  /** Hostnames this adapter claims (used by the default `canHandle`). */\r\n  readonly hostnames: readonly string[];\r\n  /**\r\n   * Optional transport hint. Adapters that require a real browser (e.g. for\r\n   * XHR-rendered TOCs or Cloudflare-protected pages) set this to `\"browser\"`.\r\n   * Omitting the field (or setting it to `\"http\"`) selects the default HTTP\r\n   * transport.\r\n   */\r\n  readonly preferredTransport?: \"http\" | \"browser\";\r\n  /** Whether this adapter handles `url` (typically a hostname match). */\r\n  canHandle(url: string): boolean;\r\n  /** Fetch novel-level metadata. Throw a {@link DownloadError} subclass on failure. */\r\n  fetchMetadata(url: Url, ctx: AdapterContext): Promise<NovelMetadata>;\r\n  /**\r\n   * Yield chapter references for the whole table of contents. Refs MUST be\r\n   * yielded in ascending `index` order — the pipeline short-circuits a\r\n   * `chapterRange` once it passes the upper bound and relies on this ordering.\r\n   */\r\n  fetchChapterList(url: Url, ctx: AdapterContext): AsyncIterable<ChapterRef>;\r\n  /** Fetch and normalize one chapter body. */\r\n  fetchChapter(ref: ChapterRef, ctx: AdapterContext): Promise<Chapter>;\r\n}\r\n\r\n/**\r\n * Convenience base for adapters: implements `canHandle` (hostname match) and\r\n * provides protected `loadHtml` / `fetchJson` helpers. Extend it and implement\r\n * the three `fetch*` methods.\r\n */\r\nexport abstract class BaseAdapter implements SiteAdapter {\r\n  abstract readonly id: AdapterId;\r\n  abstract readonly displayName: string;\r\n  abstract readonly hostnames: readonly string[];\r\n  readonly preferredTransport?: \"http\" | \"browser\";\r\n  abstract fetchMetadata(url: Url, ctx: AdapterContext): Promise<NovelMetadata>;\r\n  abstract fetchChapterList(url: Url, ctx: AdapterContext): AsyncIterable<ChapterRef>;\r\n  abstract fetchChapter(ref: ChapterRef, ctx: AdapterContext): Promise<Chapter>;\r\n\r\n  canHandle(url: string): boolean {\r\n    try {\r\n      return matchesAny(parseUrl(url), this.hostnames);\r\n    } catch {\r\n      return false;\r\n    }\r\n  }\r\n\r\n  protected async loadHtml(url: Url, ctx: AdapterContext): Promise<CheerioAPI> {\r\n    try {\r\n      const res = await ctx.http.get(url, { signal: ctx.signal });\r\n      return loadHtml(res.body);\r\n    } catch (cause) {\r\n      // Pass DownloadError subclasses through; wrap others.\r\n      if (cause instanceof DownloadError) throw cause;\r\n      throw new ParseError(`Failed to load ${url}`, { cause, url });\r\n    }\r\n  }\r\n\r\n  protected async fetchJson(url: Url, ctx: AdapterContext): Promise<unknown> {\r\n    let res: Awaited<ReturnType<typeof ctx.http.get>>;\r\n    try {\r\n      res = await ctx.http.get(url, { signal: ctx.signal });\r\n    } catch (cause) {\r\n      if (cause instanceof DownloadError) throw cause;\r\n      throw new ParseError(`Failed to fetch JSON at ${url}`, { cause, url });\r\n    }\r\n    try {\r\n      return JSON.parse(res.body);\r\n    } catch (cause) {\r\n      throw new ParseError(`Invalid JSON at ${url}`, { cause, url });\r\n    }\r\n  }\r\n}\r\n","import { type CheerioAPI, load as cheerioLoad } from \"cheerio\";\r\nimport { convert as htmlToTextConvert } from \"html-to-text\";\r\n\r\nexport function loadHtml(html: string): CheerioAPI {\r\n  return cheerioLoad(html);\r\n}\r\n\r\n// Sentinel chars that survive html-to-text's text extraction.\r\n// PARA marks a 2+ consecutive <br> sequence (paragraph break);\r\n// LINE marks a single <br> (joined with a space inside a paragraph).\r\nconst PARA = \"\";\r\nconst LINE = \"\";\r\n\r\nexport function normalizeText(html: string): string {\r\n  // Convert <br> runs to sentinel markers BEFORE parsing. 2+ consecutive <br>s\r\n  // → paragraph break; single <br> → soft line break joined with a space.\r\n  // html-to-text's default <br> formatter emits \\n, which we'd then have to\r\n  // disambiguate from real paragraph breaks; sentinels sidestep that.\r\n  const preprocessed = html\r\n    .replace(/(?:<br\\s*\\/?>\\s*){2,}/gi, PARA)\r\n    .replace(/<br\\s*\\/?>/gi, LINE);\r\n\r\n  const text = htmlToTextConvert(preprocessed, {\r\n    wordwrap: false,\r\n    selectors: [\r\n      { selector: \"a\", options: { ignoreHref: true } },\r\n      { selector: \"img\", format: \"skip\" },\r\n      { selector: \"script\", format: \"skip\" },\r\n      { selector: \"style\", format: \"skip\" },\r\n      { selector: \"h1\", options: { uppercase: false } },\r\n      { selector: \"h2\", options: { uppercase: false } },\r\n      { selector: \"h3\", options: { uppercase: false } },\r\n      { selector: \"h4\", options: { uppercase: false } },\r\n      { selector: \"h5\", options: { uppercase: false } },\r\n      { selector: \"h6\", options: { uppercase: false } },\r\n    ],\r\n  });\r\n\r\n  // Replace sentinels, then split on any run of 2+ newlines (whether from\r\n  // html-to-text's block separators or our PARA marker), collapse intra-block\r\n  // whitespace, and rejoin with a uniform paragraph separator.\r\n  return text\r\n    .replace(new RegExp(LINE, \"g\"), \" \")\r\n    .replace(new RegExp(PARA, \"g\"), \"\\n\\n\")\r\n    .split(/\\n{2,}/)\r\n    .map((p) => p.replace(/\\s+/g, \" \").trim())\r\n    .filter(Boolean)\r\n    .join(\"\\n\\n\")\r\n    .trim();\r\n}\r\n\r\nexport function pickText($: CheerioAPI, selectors: readonly string[]): string {\r\n  for (const sel of selectors) {\r\n    const t = $(sel).first().text().trim();\r\n    if (t) return t;\r\n  }\r\n  return \"\";\r\n}\r\n\r\nexport function wordCount(text: string): number {\r\n  return text.split(/\\s+/).filter(Boolean).length;\r\n}\r\n\r\ninterface TurndownInstance {\r\n  turndown(html: string): string;\r\n}\r\n\r\ninterface TurndownConstructor {\r\n  new (opts?: { headingStyle?: string; bulletListMarker?: string }): TurndownInstance;\r\n}\r\n\r\nfunction isTurndownConstructor(v: unknown): v is TurndownConstructor {\r\n  return typeof v === \"function\";\r\n}\r\n\r\nexport async function htmlToMarkdown(html: string): Promise<string> {\r\n  let mod: { default?: unknown };\r\n  try {\r\n    mod = await import(\"turndown\");\r\n  } catch (cause) {\r\n    throw new Error(\r\n      \"htmlToMarkdown requires the 'turndown' peer dependency. Install it: pnpm add turndown\",\r\n      { cause },\r\n    );\r\n  }\r\n  if (!isTurndownConstructor(mod.default)) {\r\n    throw new Error(\r\n      \"htmlToMarkdown: 'turndown' module loaded but its default export is not a constructor; this likely indicates a peer-dep version mismatch.\",\r\n    );\r\n  }\r\n  const td = new mod.default({ headingStyle: \"atx\", bulletListMarker: \"-\" });\r\n  return td.turndown(html);\r\n}\r\n","import { BaseAdapter } from \"../base.js\";\r\nimport type { AdapterContext } from \"../base.js\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport type { NovelMetadata, ChapterRef, Chapter } from \"../../data-model/novel.js\";\r\nimport { unsafeBrandAdapterId, unsafeBrandChapterIndex, unsafeBrandUrl } from \"../../data-model/primitives.js\";\r\nimport type { CheerioAPI } from \"cheerio\";\r\nimport { parseNovelPage, parseTocPage, parseChapterPage } from \"./parser.js\";\r\nimport { mapHostname } from \"../../utils/url.js\";\r\nimport { paginateToc, type TocWalkPage } from \"../shared/paginate.js\";\r\n\r\nexport class TruyenFullAdapter extends BaseAdapter {\r\n  readonly id = unsafeBrandAdapterId(\"truyenfull\");\r\n  readonly displayName = \"TruyenFull\";\r\n  readonly hostnames = [\r\n    \"truyenfull.today\",\r\n    \"www.truyenfull.today\",\r\n    \"truyenfull.vision\",\r\n    \"www.truyenfull.vision\",\r\n    \"truyenfull.vn\",\r\n    \"www.truyenfull.vn\",\r\n  ];\r\n\r\n  async fetchMetadata(url: Url, ctx: AdapterContext): Promise<NovelMetadata> {\r\n    const target = this.#normalize(url);\r\n    const $page1 = await this.loadHtml(target, ctx);\r\n    const meta = parseNovelPage($page1, target);\r\n    const totalChapters = await this.#computeTotalChapters($page1, target, ctx);\r\n    return totalChapters !== undefined ? { ...meta, totalChapters } : meta;\r\n  }\r\n\r\n  // The novel page IS page 1 of the TOC. Page sizes on truyenfull are constant\r\n  // for pages 1..(N-1), so total = (lastPage-1) * page1Count + lastPageCount.\r\n  // For single-page novels we can answer from page 1 alone; for multi-page we\r\n  // pay one extra HTTP for the last page.\r\n  async #computeTotalChapters(\r\n    $page1: CheerioAPI,\r\n    novelUrl: Url,\r\n    ctx: AdapterContext,\r\n  ): Promise<number | undefined> {\r\n    const { refs: page1Refs, totalPages, maxLinkedPage } = parseTocPage($page1, unsafeBrandChapterIndex(0), novelUrl);\r\n    const page1Count = page1Refs.length;\r\n    if (page1Count === 0) return undefined;\r\n    const lastPage = totalPages ?? maxLinkedPage ?? 1;\r\n    if (lastPage <= 1) return page1Count;\r\n    const base = stripTrailingSlash(this.#normalize(novelUrl));\r\n    const lastPageUrl = unsafeBrandUrl(`${base}/trang-${lastPage}/`);\r\n    const $last = await this.loadHtml(lastPageUrl, ctx);\r\n    const { refs: lastRefs } = parseTocPage($last, unsafeBrandChapterIndex(0), lastPageUrl);\r\n    if (lastRefs.length === 0) return undefined;\r\n    return (lastPage - 1) * page1Count + lastRefs.length;\r\n  }\r\n\r\n  async *fetchChapterList(url: Url, ctx: AdapterContext): AsyncIterable<ChapterRef> {\r\n    const base = stripTrailingSlash(this.#normalize(url));\r\n    let knownTotalPages: number | undefined;\r\n    yield* paginateToc({\r\n      ctx,\r\n      fetchPage: async (page, startIndex): Promise<TocWalkPage | null> => {\r\n        const pageUrl = page === 1 ? unsafeBrandUrl(`${base}/`) : unsafeBrandUrl(`${base}/trang-${page}/`);\r\n        const $ = await this.loadHtml(pageUrl, ctx);\r\n        const { refs, isLastPage, totalPages } = parseTocPage($, startIndex, pageUrl);\r\n        if (page === 1 && totalPages !== undefined) knownTotalPages = totalPages;\r\n        return {\r\n          refs,\r\n          isLast: isLastPage || (knownTotalPages !== undefined && page >= knownTotalPages),\r\n        };\r\n      },\r\n    });\r\n  }\r\n\r\n  async fetchChapter(ref: ChapterRef, ctx: AdapterContext): Promise<Chapter> {\r\n    const target = this.#normalize(ref.url);\r\n    const targetRef = target === ref.url ? ref : { ...ref, url: target };\r\n    const $ = await this.loadHtml(target, ctx);\r\n    return parseChapterPage($, targetRef);\r\n  }\r\n\r\n  #normalize(url: Url): Url {\r\n    return mapHostname(url, {\r\n      \"truyenfull.vn\": \"truyenfull.today\",\r\n      \"www.truyenfull.vn\": \"truyenfull.today\",\r\n      \"truyenfull.vision\": \"truyenfull.today\",\r\n      \"www.truyenfull.vision\": \"truyenfull.today\",\r\n      \"www.truyenfull.today\": \"truyenfull.today\",\r\n    });\r\n  }\r\n}\r\n\r\nfunction stripTrailingSlash(u: Url): string {\r\n  const s = String(u);\r\n  return s.endsWith(\"/\") ? s.slice(0, -1) : s;\r\n}\r\n","import type { CheerioAPI } from \"cheerio\";\r\nimport type { Chapter, ChapterRef, NovelMetadata } from \"../../data-model/novel.js\";\r\nimport type { ChapterIndex, Url } from \"../../data-model/primitives.js\";\r\nimport { unsafeBrandAdapterId } from \"../../data-model/primitives.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\nimport { normalizeText, wordCount } from \"../../utils/html.js\";\r\nimport { refsFromAnchors } from \"../shared/anchors.js\";\r\nimport { parseNovelHeader, buildNovelMetadata } from \"../shared/header.js\";\r\nimport { stripLeadingTitle, assertContentNonEmpty } from \"../shared/content.js\";\r\nimport { TRUYENFULL_SELECTORS as S } from \"./selectors.js\";\r\n\r\nexport function parseNovelPage($: CheerioAPI, sourceUrl: Url): NovelMetadata {\r\n  const header = parseNovelHeader($, S.metadata, sourceUrl);\r\n\r\n  let status: NovelMetadata[\"status\"] = \"unknown\";\r\n  if ($(S.metadata.statusFull).length > 0) status = \"completed\";\r\n  else if ($(S.metadata.statusOngoing).length > 0) status = \"ongoing\";\r\n\r\n  return buildNovelMetadata(header, {\r\n    sourceUrl,\r\n    sourceSite: unsafeBrandAdapterId(\"truyenfull\"),\r\n    status,\r\n  });\r\n}\r\n\r\nexport interface TocPageResult {\r\n  readonly refs: ChapterRef[];\r\n  readonly isLastPage: boolean;\r\n  readonly totalPages?: number;\r\n  // Largest page number reachable via numeric pagination anchors. Useful for\r\n  // 2-3 page novels where the \"Cuối\" link is suppressed by the site so\r\n  // `totalPages` is undefined.\r\n  readonly maxLinkedPage?: number;\r\n}\r\n\r\nexport function parseTocPage(\r\n  $: CheerioAPI,\r\n  startIndex: ChapterIndex,\r\n  baseUrl: Url,\r\n): TocPageResult {\r\n  const refs = refsFromAnchors($, { selector: S.toc.listItem, startIndex, baseUrl });\r\n\r\n  let totalPages: number | undefined;\r\n  let maxLinkedPage = 0;\r\n  $(S.toc.paginationLinks).each((_, el) => {\r\n    const text = $(el).text().replace(/\\s+/g, \" \").trim();\r\n    const href = $(el).attr(\"href\") ?? \"\";\r\n    const m = /\\/trang-(\\d+)\\//.exec(href);\r\n    const linkedPage = m && m[1] ? Number.parseInt(m[1], 10) : 0;\r\n    if (linkedPage > maxLinkedPage) maxLinkedPage = linkedPage;\r\n    if (text.startsWith(\"Cuối\") && linkedPage > 0) {\r\n      totalPages = linkedPage;\r\n    }\r\n  });\r\n\r\n  const activeText = $(S.toc.paginationActive).first().text().replace(/\\s+/g, \" \").trim();\r\n  const activePageMatch = /^(\\d+)/.exec(activeText);\r\n  const activePage =\r\n    activePageMatch && activePageMatch[1] ? Number.parseInt(activePageMatch[1], 10) : undefined;\r\n\r\n  // Don't infer last-page from a hardcoded items-per-page count — TruyenFull\r\n  // has changed page sizes before and a transient short response would silently\r\n  // truncate the novel. Two structural signals:\r\n  // 1. \"Cuối\" link present + active >= totalPages (works on multi-page novels)\r\n  // 2. Active page exists and no pagination anchor points beyond it (works on\r\n  //    small novels where \"Cuối\" is absent — page 3 of 3 only links back to 2).\r\n  const isLastPage =\r\n    (totalPages !== undefined && activePage !== undefined && activePage >= totalPages) ||\r\n    (activePage !== undefined && maxLinkedPage > 0 && maxLinkedPage < activePage) ||\r\n    (activePage === 1 && maxLinkedPage === 0 && totalPages === undefined);\r\n\r\n  const result: TocPageResult = { refs, isLastPage };\r\n  return {\r\n    ...result,\r\n    ...(totalPages !== undefined ? { totalPages } : {}),\r\n    ...(maxLinkedPage > 0 ? { maxLinkedPage } : {}),\r\n  };\r\n}\r\n\r\nexport function parseChapterPage($: CheerioAPI, ref: ChapterRef): Chapter {\r\n  const body = $(S.chapter.body);\r\n  if (body.length === 0) {\r\n    throw new ParseError(\"Missing #chapter-c on chapter page\", { url: ref.url });\r\n  }\r\n  // Dedup uses ref.title: truyenfull renders the chapter title OUTSIDE #chapter-c,\r\n  // so there is no in-page title element to read. LATENT RISK (unverified) — if a\r\n  // title block is ever injected INSIDE #chapter-c with text differing from\r\n  // ref.title, the first paragraph would duplicate it. See\r\n  // docs/surveys/2026-06-20-review-deferred-verification.md\r\n  body.find(S.chapter.adStrip).remove();\r\n  body.find(\"script, style\").remove();\r\n  const inner = body.html() ?? \"\";\r\n  let content = normalizeText(inner);\r\n  content = stripLeadingTitle(content, ref.title);\r\n  assertContentNonEmpty(content, ref.url);\r\n  return {\r\n    ...ref,\r\n    content,\r\n    fetchedAt: new Date(),\r\n    wordCount: wordCount(content),\r\n  };\r\n}\r\n","import type { CheerioAPI } from \"cheerio\";\r\nimport type { ChapterRef } from \"../../data-model/novel.js\";\r\nimport type { ChapterIndex, Url } from \"../../data-model/primitives.js\";\r\nimport { unsafeBrandChapterIndex } from \"../../data-model/primitives.js\";\r\nimport { resolveUrl } from \"../../utils/url.js\";\r\n\r\nexport interface AnchorRefsOptions {\r\n  readonly selector: string;\r\n  readonly startIndex: ChapterIndex;\r\n  readonly baseUrl: Url;\r\n  /**\r\n   * When true (default), use the anchor's `title` attribute, falling back to\r\n   * its text. When false, use the anchor text only.\r\n   */\r\n  readonly preferTitleAttr?: boolean;\r\n}\r\n\r\n/**\r\n * Extract `ChapterRef`s from a list of anchors.\r\n *\r\n * INVARIANT: the returned indices are exactly `startIndex … startIndex+n-1`\r\n * where `n === result.length`. The counter advances ONLY on push; anchors with\r\n * no `href` or an empty title are skipped without consuming an index. This is\r\n * what lets callers safely advance the next page by `startIndex + refs.length`\r\n * without gaps or collisions.\r\n */\r\nexport function refsFromAnchors($: CheerioAPI, opts: AnchorRefsOptions): ChapterRef[] {\r\n  const { selector, startIndex, baseUrl, preferTitleAttr = true } = opts;\r\n  const refs: ChapterRef[] = [];\r\n  let count = 0;\r\n  $(selector).each((_, el) => {\r\n    const a = $(el);\r\n    const href = a.attr(\"href\");\r\n    if (!href) return;\r\n    const text = a.text().replace(/\\s+/g, \" \").trim();\r\n    const titleAttr = a.attr(\"title\")?.trim();\r\n    const title = preferTitleAttr ? titleAttr || text : text;\r\n    if (!title) return;\r\n    refs.push({\r\n      index: unsafeBrandChapterIndex(startIndex + count),\r\n      title,\r\n      url: resolveUrl(baseUrl, href),\r\n    });\r\n    count++;\r\n  });\r\n  return refs;\r\n}\r\n","import type { CheerioAPI } from \"cheerio\";\r\nimport type { Url, AdapterId } from \"../../data-model/primitives.js\";\r\nimport type { NovelMetadata, NovelStatus } from \"../../data-model/novel.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\nimport { extractCoverUrl } from \"./cover.js\";\r\n\r\nexport interface NovelHeaderSelectors {\r\n  readonly title: string;\r\n  readonly author: string;\r\n  /** Single selector, or ordered fallbacks — the first non-empty match wins. */\r\n  readonly description: string | readonly string[];\r\n  /** Anchor selector iterated with `.each()`; results are deduped. */\r\n  readonly genres: string;\r\n  readonly cover: string;\r\n}\r\n\r\nexport interface NovelHeader {\r\n  readonly title: string;\r\n  readonly author: string;\r\n  readonly description: string;\r\n  readonly genres: readonly string[];\r\n  readonly coverUrl?: Url;\r\n}\r\n\r\n/** Parse the fields shared verbatim across the truyenfull and metruyenchu novel pages. */\r\nexport function parseNovelHeader(\r\n  $: CheerioAPI,\r\n  selectors: NovelHeaderSelectors,\r\n  sourceUrl: Url,\r\n): NovelHeader {\r\n  const title = $(selectors.title).first().text().trim();\r\n  if (!title) throw new ParseError(\"Missing title on novel page\", { url: sourceUrl });\r\n\r\n  const author = $(selectors.author).first().text().trim();\r\n\r\n  const descSelectors =\r\n    typeof selectors.description === \"string\" ? [selectors.description] : selectors.description;\r\n  let description = \"\";\r\n  for (const sel of descSelectors) {\r\n    const t = $(sel).first().text().trim();\r\n    if (t) {\r\n      description = t;\r\n      break;\r\n    }\r\n  }\r\n  description = description.replace(/\\s+/g, \" \").trim();\r\n\r\n  const genres: string[] = [];\r\n  $(selectors.genres).each((_, el) => {\r\n    const t = $(el).text().trim();\r\n    if (t && !genres.includes(t)) genres.push(t);\r\n  });\r\n\r\n  const coverUrl = extractCoverUrl($, selectors.cover, sourceUrl);\r\n\r\n  return {\r\n    title,\r\n    author,\r\n    description,\r\n    genres,\r\n    ...(coverUrl !== undefined ? { coverUrl } : {}),\r\n  };\r\n}\r\n\r\nexport interface NovelEnvelope {\r\n  readonly sourceUrl: Url;\r\n  readonly sourceSite: AdapterId;\r\n  readonly status: NovelStatus;\r\n  readonly totalChapters?: number | undefined;\r\n}\r\n\r\n/**\r\n * Assembles the NovelMetadata envelope: stamps fetchedAt (the single\r\n * non-determinism seam in parsing) and owns the omit-when-undefined spreads.\r\n */\r\nexport function buildNovelMetadata(fields: NovelHeader, envelope: NovelEnvelope): NovelMetadata {\r\n  return {\r\n    title: fields.title,\r\n    author: fields.author,\r\n    description: fields.description,\r\n    genres: fields.genres,\r\n    sourceUrl: envelope.sourceUrl,\r\n    sourceSite: envelope.sourceSite,\r\n    status: envelope.status,\r\n    fetchedAt: new Date(),\r\n    ...(fields.coverUrl !== undefined ? { coverUrl: fields.coverUrl } : {}),\r\n    ...(envelope.totalChapters !== undefined ? { totalChapters: envelope.totalChapters } : {}),\r\n  };\r\n}\r\n","import type { CheerioAPI } from \"cheerio\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport { resolveUrl } from \"../../utils/url.js\";\r\n\r\n/** Resolve a cover image URL from `selector`, trying `src` then `data-src`. */\r\nexport function extractCoverUrl($: CheerioAPI, selector: string, baseUrl: Url): Url | undefined {\r\n  const el = $(selector).first();\r\n  const raw = el.attr(\"src\") ?? el.attr(\"data-src\");\r\n  return raw ? resolveUrl(baseUrl, raw) : undefined;\r\n}\r\n","import type { Url } from \"../../data-model/primitives.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\n\r\n/**\r\n * Drop a leading paragraph block that duplicates the chapter title. Blocks are\r\n * the `\\n\\n`-separated paragraphs produced by `normalizeText`. The comparison is\r\n * whitespace-normalized; a non-matching first block is left in place. Always\r\n * returns a re-joined, trimmed string.\r\n */\r\nexport function stripLeadingTitle(content: string, title: string): string {\r\n  const blocks = content.split(\"\\n\\n\");\r\n  const normalizedTitle = title.replace(/\\s+/g, \" \").trim();\r\n  if (normalizedTitle.length > 0 && blocks[0]?.replace(/\\s+/g, \" \").trim() === normalizedTitle) {\r\n    blocks.shift();\r\n  }\r\n  return blocks.join(\"\\n\\n\").trim();\r\n}\r\n\r\n/** Throw {@link ParseError} when a chapter body is empty after normalization. */\r\nexport function assertContentNonEmpty(content: string, url: Url): void {\r\n  if (content.length === 0) {\r\n    throw new ParseError(\"Empty chapter body after normalization\", { url });\r\n  }\r\n}\r\n","export const TRUYENFULL_SELECTORS = {\r\n  metadata: {\r\n    title: \"h3.title\",\r\n    author: \".info-holder a[itemprop='author']\",\r\n    description: \".desc-text\",\r\n    cover: \".info-holder .book img\",\r\n    genres: \".info-holder a[itemprop='genre']\",\r\n    statusFull: \".info-holder .text-success\",\r\n    statusOngoing: \".info-holder .text-primary\",\r\n  },\r\n  toc: {\r\n    listItem: \".list-chapter li a\",\r\n    paginationLinks: \"ul.pagination li a\",\r\n    paginationActive: \"ul.pagination li.active\",\r\n  },\r\n  chapter: {\r\n    body: \"#chapter-c\",\r\n    adStrip: '#chapter-c [id^=\"ads-\"]',\r\n  },\r\n} as const;\r\n","import type { AdapterContext } from \"../base.js\";\r\nimport type { ChapterRef } from \"../../data-model/novel.js\";\r\nimport type { ChapterIndex } from \"../../data-model/primitives.js\";\r\nimport { unsafeBrandChapterIndex } from \"../../data-model/primitives.js\";\r\nimport { CancelledError } from \"../../core/errors.js\";\r\n\r\nexport interface TocWalkPage {\r\n  readonly refs: readonly ChapterRef[];\r\n  /** Stop after yielding this page's refs. Omitted = keep walking. */\r\n  readonly isLast?: boolean;\r\n}\r\n\r\n/**\r\n * Owns the cross-page invariants every paginated TOC walk shares: checks\r\n * ctx.signal before each page (throws CancelledError), yields refs in order,\r\n * advances startIndex by refs.length between pages, and stops on fetchPage\r\n * returning null, an empty refs array, or isLast. Pages are numbered from 1.\r\n */\r\nexport async function* paginateToc(opts: {\r\n  readonly ctx: AdapterContext;\r\n  readonly fetchPage: (page: number, startIndex: ChapterIndex) => Promise<TocWalkPage | null>;\r\n}): AsyncIterable<ChapterRef> {\r\n  let index = unsafeBrandChapterIndex(0);\r\n  for (let page = 1; ; page++) {\r\n    if (opts.ctx.signal.aborted) throw new CancelledError({ cause: opts.ctx.signal.reason });\r\n    const result = await opts.fetchPage(page, index);\r\n    if (result === null || result.refs.length === 0) return;\r\n    for (const ref of result.refs) yield ref;\r\n    index = unsafeBrandChapterIndex(index + result.refs.length);\r\n    if (result.isLast) return;\r\n  }\r\n}\r\n","import { z } from \"zod\";\r\nimport { BaseAdapter } from \"../base.js\";\r\nimport type { AdapterContext } from \"../base.js\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport type { NovelMetadata, ChapterRef, Chapter } from \"../../data-model/novel.js\";\r\nimport { unsafeBrandAdapterId, unsafeBrandUrl } from \"../../data-model/primitives.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\nimport {\r\n  parseNovelPage,\r\n  parseChapterPage,\r\n  parseTocPage1,\r\n  parseTocJsonPage,\r\n  extractBookId,\r\n  extractLastPageNumber,\r\n} from \"./parser.js\";\r\nimport { METRUYENCHU_SELECTORS } from \"./selectors.js\";\r\nimport { paginateToc, type TocWalkPage } from \"../shared/paginate.js\";\r\n\r\nconst tocJsonEnvelopeSchema = z.object({ data: z.string() });\r\n\r\nexport class MeTruyenChuComVnAdapter extends BaseAdapter {\r\n  readonly id = unsafeBrandAdapterId(\"metruyenchu-com-vn\");\r\n  readonly displayName = \"MeTruyenChu.com.vn\";\r\n  readonly hostnames = [\"metruyenchu.com.vn\", \"www.metruyenchu.com.vn\"];\r\n\r\n  async fetchMetadata(url: Url, ctx: AdapterContext): Promise<NovelMetadata> {\r\n    const $ = await this.loadHtml(url, ctx);\r\n    return parseNovelPage($, url);\r\n  }\r\n\r\n  async *fetchChapterList(url: Url, ctx: AdapterContext): AsyncIterable<ChapterRef> {\r\n    const $ = await this.loadHtml(url, ctx);\r\n    const bid = extractBookId($);\r\n    if (!bid) {\r\n      throw new ParseError(\"Missing book id on novel page\", { url });\r\n    }\r\n    const lastPage = extractLastPageNumber($);\r\n    if (lastPage === 1 && $(METRUYENCHU_SELECTORS.metadata.pagingPageFn).length > 0) {\r\n      ctx.logger.warn(\r\n        { url },\r\n        \"metruyenchu: pagination controls present but last-page parse fell back to 1 — onclick format may have drifted (survey needed)\",\r\n      );\r\n    }\r\n    const apiOrigin = new URL(url).origin;\r\n\r\n    yield* paginateToc({\r\n      ctx,\r\n      fetchPage: async (page, startIndex): Promise<TocWalkPage | null> => {\r\n        if (page === 1) {\r\n          return { refs: parseTocPage1($, startIndex, url), isLast: lastPage <= 1 };\r\n        }\r\n        const apiUrl = unsafeBrandUrl(`${apiOrigin}/get/listchap/${bid}?page=${page}`);\r\n        const raw = await this.fetchJson(apiUrl, ctx);\r\n        const parsed = tocJsonEnvelopeSchema.safeParse(raw);\r\n        if (!parsed.success) {\r\n          throw new ParseError(`Invalid TOC JSON envelope at ${apiUrl}`, {\r\n            cause: parsed.error,\r\n            url: apiUrl,\r\n          });\r\n        }\r\n        const fragment = parsed.data.data;\r\n        if (!fragment) {\r\n          ctx.logger.warn({ apiUrl, page }, \"Empty TOC JSON data; ending walk early\");\r\n          return null;\r\n        }\r\n        const refs = parseTocJsonPage(fragment, startIndex, url);\r\n        if (refs.length === 0) {\r\n          ctx.logger.warn({ apiUrl, page }, \"Zero anchors in TOC JSON page; ending walk early\");\r\n          return null;\r\n        }\r\n        return { refs, isLast: page >= lastPage };\r\n      },\r\n    });\r\n  }\r\n\r\n  async fetchChapter(ref: ChapterRef, ctx: AdapterContext): Promise<Chapter> {\r\n    const $ = await this.loadHtml(ref.url, ctx);\r\n    return parseChapterPage($, ref);\r\n  }\r\n}\r\n","import type { CheerioAPI } from \"cheerio\";\r\nimport type { NovelMetadata, ChapterRef, Chapter } from \"../../data-model/novel.js\";\r\nimport type { Url, ChapterIndex } from \"../../data-model/primitives.js\";\r\nimport { unsafeBrandAdapterId } from \"../../data-model/primitives.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\nimport { loadHtml, normalizeText, wordCount } from \"../../utils/html.js\";\r\nimport { METRUYENCHU_SELECTORS as S } from \"./selectors.js\";\r\nimport { refsFromAnchors } from \"../shared/anchors.js\";\r\nimport { parseNovelHeader, buildNovelMetadata } from \"../shared/header.js\";\r\nimport { stripLeadingTitle, assertContentNonEmpty } from \"../shared/content.js\";\r\n\r\nconst SOURCE_SITE = unsafeBrandAdapterId(\"metruyenchu-com-vn\");\r\n\r\nexport function parseNovelPage($: CheerioAPI, sourceUrl: Url): NovelMetadata {\r\n  const header = parseNovelHeader($, S.metadata, sourceUrl);\r\n\r\n  let status: NovelMetadata[\"status\"] = \"unknown\";\r\n  if ($(S.metadata.statusFull).length > 0) status = \"completed\";\r\n  else if (S.metadata.statusOngoing.some((sel) => $(sel).length > 0)) status = \"ongoing\";\r\n\r\n  return buildNovelMetadata(header, {\r\n    sourceUrl,\r\n    sourceSite: SOURCE_SITE,\r\n    status,\r\n    totalChapters: extractTotalChapters($),\r\n  });\r\n}\r\n\r\n// Matches the \"Số chương\" badge inside the book-info-text list:\r\n//   <li><b>Số chương :</b> 2330</li>\r\n// The site is the source of truth for the novel's TOC size; reading it here\r\n// keeps fetched/total decoupled so quick-mode runs that fetch only N chapters\r\n// still report the source's full chapter count, not N.\r\nconst TOTAL_CHAPTERS_LABEL_REGEX = /^\\s*Số\\s*chương\\s*:?\\s*$/i;\r\nconst TOTAL_CHAPTERS_VALUE_REGEX = /(\\d[\\d.,]*)/;\r\n\r\nfunction extractTotalChapters($: CheerioAPI): number | undefined {\r\n  let result: number | undefined;\r\n  $(S.metadata.infoListItem).each((_, el) => {\r\n    if (result !== undefined) return;\r\n    const li = $(el);\r\n    const labelEl = li.find(\"b, strong\").first();\r\n    if (labelEl.length === 0) return;\r\n    const labelText = labelEl.text().replace(/\\s+/g, \" \").trim();\r\n    if (!TOTAL_CHAPTERS_LABEL_REGEX.test(labelText)) return;\r\n    // Read the value from the li text nodes excluding the <b>/<strong> label.\r\n    // Avoids fragile slice math when the site adds whitespace or stray text\r\n    // nodes around the label.\r\n    const clone = li.clone();\r\n    clone.find(\"b, strong\").remove();\r\n    const valueText = clone.text().replace(/\\s+/g, \" \").trim();\r\n    const m = TOTAL_CHAPTERS_VALUE_REGEX.exec(valueText);\r\n    if (!m || !m[1]) return;\r\n    const digits = m[1].replace(/[.,]/g, \"\");\r\n    const n = Number.parseInt(digits, 10);\r\n    if (Number.isFinite(n) && n > 0) result = n;\r\n  });\r\n  return result;\r\n}\r\n\r\nexport function parseTocPage1(\r\n  $: CheerioAPI,\r\n  startIndex: ChapterIndex,\r\n  baseUrl: Url,\r\n): ChapterRef[] {\r\n  return refsFromAnchors($, { selector: S.toc.listItem, startIndex, baseUrl });\r\n}\r\n\r\nexport function parseTocJsonPage(\r\n  htmlFragment: string,\r\n  startIndex: ChapterIndex,\r\n  baseUrl: Url,\r\n): ChapterRef[] {\r\n  const $ = loadHtml(htmlFragment);\r\n  return refsFromAnchors($, { selector: S.toc.jsonListItem, startIndex, baseUrl });\r\n}\r\n\r\nexport function extractBookId($: CheerioAPI): string | undefined {\r\n  const v = $(S.metadata.bookIdInput).first().attr(\"value\")?.trim();\r\n  return v && v.length > 0 ? v : undefined;\r\n}\r\n\r\nexport function extractLastPageNumber($: CheerioAPI): number {\r\n  let max = 0;\r\n  $(S.metadata.pagingPageFn).each((_, el) => {\r\n    const onclick = $(el).attr(\"onclick\") ?? \"\";\r\n    const m = /page\\(\\s*\\d+\\s*,\\s*(\\d+)\\s*\\)/.exec(onclick);\r\n    if (m && m[1]) {\r\n      const n = Number.parseInt(m[1], 10);\r\n      if (n > max) max = n;\r\n    }\r\n  });\r\n  return max > 0 ? max : 1;\r\n}\r\n\r\nexport function parseChapterPage($: CheerioAPI, ref: ChapterRef): Chapter {\r\n  const body = $(S.chapter.body);\r\n  if (body.length === 0) {\r\n    throw new ParseError(\"Missing #vungdoc on chapter page\", { url: ref.url });\r\n  }\r\n\r\n  // Capture in-page title before stripping nav containers (which may include the title element).\r\n  const inPageTitle = $(S.chapter.title).first().text().replace(/\\s+/g, \" \").trim();\r\n\r\n  for (const sel of S.chapter.stripNavSelectors) body.find(sel).remove();\r\n  body.find(\"script, style\").remove();\r\n  body.find(\"a\").each((_, el) => {\r\n    const text = $(el).text().replace(/\\s+/g, \" \").trim();\r\n    if (S.chapter.navTextRegex.test(text)) $(el).remove();\r\n  });\r\n\r\n  const inner = body.html() ?? \"\";\r\n  let content = normalizeText(inner);\r\n\r\n  content = stripLeadingTitle(content, inPageTitle);\r\n\r\n  assertContentNonEmpty(content, ref.url);\r\n\r\n  const title = inPageTitle.length > 0 ? inPageTitle : ref.title;\r\n\r\n  return {\r\n    ...ref,\r\n    title,\r\n    content,\r\n    fetchedAt: new Date(),\r\n    wordCount: wordCount(content),\r\n  };\r\n}\r\n","export const METRUYENCHU_SELECTORS = {\r\n  metadata: {\r\n    title: \"h1[itemprop='name']\",\r\n    author: \"[itemprop='author']\",\r\n    description: [\"div.book-info-text [itemprop='description']\", \"div.book-info-text .desc\"],\r\n    cover: \"[itemprop='image']\",\r\n    genres: \".li--genres a[href*='/the-loai/']\",\r\n    statusFull: \".label-status.label-full\",\r\n    statusOngoing: [\".label-status.label-hot\", \".label-status.label-ongoing\"],\r\n    bookIdInput: \"input[name='bid']\",\r\n    pagingPageFn: \"[onclick^='page(']\",\r\n    infoListItem: \"div.book-info-text ul li\",\r\n  },\r\n  toc: {\r\n    listItem: \"#chapter-list ul li a\",\r\n    jsonListItem: \"ul:not(ul ul) > li > a\",\r\n  },\r\n  chapter: {\r\n    title: \".chapter-title h2.current-chapter\",\r\n    body: \"#vungdoc\",\r\n    stripNavSelectors: [\r\n      \".chapter-nav\",\r\n      \".chapter-nav-top\",\r\n      \".chapter-nav-bottom\",\r\n      \".chapter_wrap\",\r\n    ],\r\n    navTextRegex: /^(Chương trước|Chương tiếp|Tải Ebook|《|》)/i,\r\n  },\r\n} as const;\r\n","import { BaseAdapter } from \"../base.js\";\r\nimport type { AdapterContext } from \"../base.js\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport type { NovelMetadata, ChapterRef, Chapter } from \"../../data-model/novel.js\";\r\nimport { unsafeBrandAdapterId } from \"../../data-model/primitives.js\";\r\nimport { mapHostname } from \"../../utils/url.js\";\r\nimport type { BrowserPage } from \"../../http/browser/types.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\nimport {\r\n  parseNovelPage,\r\n  parseChapterPage,\r\n  parseBookIndexHtml,\r\n  parsePaginationProbe,\r\n} from \"./parser.js\";\r\n\r\n// Walking a TOC longer than one /book/index response requires triggering\r\n// further loadBookIndex calls. Patchright runs `page.evaluate()` in an\r\n// isolated world where the page's globals (loadBookIndex, jQuery, …) are\r\n// not visible, so we don't call loadBookIndex directly. Instead we use the\r\n// pagination DOM as the source of truth and the page's own click delegate\r\n// as our trigger:\r\n//\r\n//   1. The page renders pagination controls under `.pagination` with\r\n//      `<a data-action=\"loadBookIndex\" data-start=\"...\" data-size=\"...\">`\r\n//      links. The \"last page\" button (`i.fa-angle-double-right`) carries\r\n//      the start index of the final window — total range is therefore\r\n//      known after the first XHR.\r\n//   2. To trigger window N we synthesise an `<a>` element with the same\r\n//      data-attributes, append it, and `.click()` it from the isolated\r\n//      world. The page's main-world delegate (bound to\r\n//      `[data-action=\"loadBookIndex\"]`) picks the event up via DOM\r\n//      bubbling and fires the XHR with a fresh signature.\r\n//\r\n// No magic 500/501 constant; no first-URL clamp guard — the last window's\r\n// `data-start` exposes the exact stop boundary.\r\nconst WIKICV_TOC_RESPONSE_TIMEOUT_MS = 30_000;\r\nconst WIKICV_INDEX_PATTERN = /\\/book\\/index\\?[^#]*\\bbookId=/;\r\nconst WIKICV_PROBE_PAGINATION_SCRIPT = `JSON.stringify((() => {\r\n  const last = document.querySelector('.pagination .fa-angle-double-right')?.closest('a');\r\n  if (!last) return null;\r\n  const lastStart = Number(last.getAttribute('data-start'));\r\n  const pageSize = Number(last.getAttribute('data-size'));\r\n  if (!Number.isInteger(lastStart) || lastStart <= 0) return null;\r\n  if (!Number.isInteger(pageSize) || pageSize <= 0) return null;\r\n  return { lastStart, pageSize };\r\n})())`;\r\n\r\nexport class WikicvAdapter extends BaseAdapter {\r\n  readonly id = unsafeBrandAdapterId(\"wikicv\");\r\n  readonly displayName = \"WikiCV (DichTiengHoa)\";\r\n  readonly hostnames = [\r\n    \"wikicv.net\",\r\n    \"www.wikicv.net\",\r\n    \"truyenwikidich.net\",\r\n    \"www.truyenwikidich.net\",\r\n  ];\r\n  override readonly preferredTransport = \"browser\" as const;\r\n\r\n  async fetchMetadata(url: Url, ctx: AdapterContext): Promise<NovelMetadata> {\r\n    const target = this.#normalize(url);\r\n    const $ = await this.loadHtml(target, ctx);\r\n    const meta = parseNovelPage($, target);\r\n    if (meta.status === \"unknown\") {\r\n      ctx.logger.warn(\r\n        { url: target },\r\n        \"wikicv: status resolved to 'unknown' — positional selector div:nth-of-type(4) may have drifted (survey needed)\",\r\n      );\r\n    }\r\n    return meta;\r\n  }\r\n\r\n  async *fetchChapterList(url: Url, ctx: AdapterContext): AsyncIterable<ChapterRef> {\r\n    if (!ctx.browser) {\r\n      throw new ParseError(\r\n        \"wikicv requires the browser transport tier; ensure transport mode is 'auto' or 'browser-required' and patchright/playwright is installed\",\r\n        { url },\r\n      );\r\n    }\r\n    const target = this.#normalize(url);\r\n    const page = await ctx.browser.navigate(target, ctx.signal);\r\n    try {\r\n      // Window 0 fires automatically on page load; just await it.\r\n      const first = await page.waitForResponse(WIKICV_INDEX_PATTERN, {\r\n        timeoutMs: WIKICV_TOC_RESPONSE_TIMEOUT_MS,\r\n        signal: ctx.signal,\r\n        parse: \"text\",\r\n      });\r\n      const firstRefs = parseBookIndexHtml(first.body, target, 0, { logger: ctx.logger });\r\n      if (firstRefs.length === 0) return;\r\n      for (const ref of firstRefs) yield ref;\r\n\r\n      const range = await this.#readPaginationRange(page);\r\n      if (!range) return;\r\n\r\n      for (let start = range.pageSize; start <= range.lastStart; start += range.pageSize) {\r\n        const responsePromise = page.waitForResponse(WIKICV_INDEX_PATTERN, {\r\n          timeoutMs: WIKICV_TOC_RESPONSE_TIMEOUT_MS,\r\n          signal: ctx.signal,\r\n          parse: \"text\",\r\n          skipBuffer: true,\r\n        });\r\n        await page.evaluate(this.#triggerLoadScript(start, range.pageSize));\r\n        const captured = await responsePromise;\r\n        const windowRefs = parseBookIndexHtml(captured.body, target, start, { logger: ctx.logger });\r\n        if (windowRefs.length === 0) return;\r\n        for (const ref of windowRefs) yield ref;\r\n      }\r\n    } finally {\r\n      await page.close();\r\n    }\r\n  }\r\n\r\n  async #readPaginationRange(\r\n    page: BrowserPage,\r\n  ): Promise<{ lastStart: number; pageSize: number } | null> {\r\n    const raw = await page.evaluate(WIKICV_PROBE_PAGINATION_SCRIPT);\r\n    return parsePaginationProbe(raw);\r\n  }\r\n\r\n  // Synthesise the same anchor the page renders for pagination links and\r\n  // click it. The page's main-world delegate handler — bound on a parent\r\n  // element to `[data-action=\"loadBookIndex\"]` — picks the bubbled click\r\n  // event up and fires the signed XHR for this window.\r\n  #triggerLoadScript(start: number, pageSize: number): string {\r\n    return `(() => {\r\n  const a = document.createElement('a');\r\n  a.setAttribute('data-action', 'loadBookIndex');\r\n  a.setAttribute('data-start', ${JSON.stringify(String(start))});\r\n  a.setAttribute('data-size', ${JSON.stringify(String(pageSize))});\r\n  a.setAttribute('href', '#!');\r\n  a.style.display = 'none';\r\n  document.body.appendChild(a);\r\n  a.click();\r\n  a.remove();\r\n})()`;\r\n  }\r\n\r\n  async fetchChapter(ref: ChapterRef, ctx: AdapterContext): Promise<Chapter> {\r\n    const $ = await this.loadHtml(ref.url, ctx);\r\n    return parseChapterPage($, ref);\r\n  }\r\n\r\n  #normalize(url: Url): Url {\r\n    return mapHostname(url, {\r\n      \"truyenwikidich.net\": \"wikicv.net\",\r\n      \"www.truyenwikidich.net\": \"wikicv.net\",\r\n      \"www.wikicv.net\": \"wikicv.net\",\r\n    });\r\n  }\r\n}\r\n","import { z } from \"zod\";\r\nimport type { CheerioAPI } from \"cheerio\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport { unsafeBrandAdapterId, unsafeBrandChapterIndex } from \"../../data-model/primitives.js\";\r\nimport type { NovelMetadata, NovelStatus, ChapterRef, Chapter } from \"../../data-model/novel.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\nimport { loadHtml, normalizeText, wordCount } from \"../../utils/html.js\";\r\nimport type { Logger } from \"../../utils/logger.js\";\r\nimport { refsFromAnchors } from \"../shared/anchors.js\";\r\nimport { extractCoverUrl } from \"../shared/cover.js\";\r\nimport { assertContentNonEmpty } from \"../shared/content.js\";\r\nimport { buildNovelMetadata } from \"../shared/header.js\";\r\nimport { WIKICV_SELECTORS as S } from \"./selectors.js\";\r\n\r\nconst SOURCE_SITE = unsafeBrandAdapterId(\"wikicv\");\r\n\r\nexport function parseNovelPage($: CheerioAPI, sourceUrl: Url): NovelMetadata {\r\n  const title = $(S.metadata.title).first().text().trim();\r\n  if (!title) throw new ParseError(\"Missing title on novel page\", { url: sourceUrl });\r\n\r\n  const author = $(S.metadata.author).first().text().trim();\r\n\r\n  const descRaw = $(S.metadata.description).first().text().trim();\r\n  // Strip leading \"Văn án\" header paragraph (appears as first child <p>Văn án</p>)\r\n  // and any surrounding whitespace.\r\n  const description = descRaw.replace(/^V[aă]n\\s*án\\s*/i, \"\").trim();\r\n\r\n  const coverUrl = extractCoverUrl($, S.metadata.cover, sourceUrl);\r\n\r\n  const descBlock = $(S.metadata.genresContainer).first().text();\r\n  const genres = parseGenres(descBlock);\r\n\r\n  const statusText = $(S.metadata.statusAnchor).first().text().trim();\r\n  const status: NovelStatus =\r\n    /^Hoàn\\s*thành/i.test(statusText)\r\n      ? \"completed\"\r\n      : /^Đang\\s*ra/i.test(statusText)\r\n        ? \"ongoing\"\r\n        : \"unknown\";\r\n\r\n  return buildNovelMetadata(\r\n    {\r\n      title,\r\n      author,\r\n      description,\r\n      genres,\r\n      ...(coverUrl !== undefined ? { coverUrl } : {}),\r\n    },\r\n    {\r\n      sourceUrl,\r\n      sourceSite: SOURCE_SITE,\r\n      status,\r\n      totalChapters: extractTotalChapters($),\r\n    },\r\n  );\r\n}\r\n\r\n// Read the \"Mới nhất:\" anchor: its slug carries the latest chapter index, and\r\n// because wikicv chapter indices are dense and 1-based, the latest index is\r\n// the source TOC size. Falls back to the visible \"Chương N\" text when the\r\n// slug shape changes. Returns undefined if neither yields a positive integer.\r\n//\r\n// RISK (unverified): for an ongoing novel with deleted/renumbered chapters, or\r\n// when \"Mới nhất\" points at a side-story (/chuong-100-phien-ngoai-...), this can\r\n// mis-report totalChapters with no detection path. Cross-check against a\r\n// browser-tier TOC walk at survey time — see\r\n// docs/surveys/2026-06-20-review-deferred-verification.md.\r\nconst CHUONG_SLUG_REGEX = /\\/chuong-(\\d+)-/i;\r\nconst CHUONG_TEXT_REGEX = /Chương\\s*(\\d+)/i;\r\n\r\nfunction extractTotalChapters($: CheerioAPI): number | undefined {\r\n  let best: number | undefined;\r\n  $(S.metadata.latestChapterAnchor).each((_, el) => {\r\n    const a = $(el);\r\n    const href = a.attr(\"href\") ?? \"\";\r\n    const text = a.text();\r\n    const m = CHUONG_SLUG_REGEX.exec(href) ?? CHUONG_TEXT_REGEX.exec(text);\r\n    if (!m?.[1]) return;\r\n    const n = Number.parseInt(m[1], 10);\r\n    if (Number.isFinite(n) && n > 0 && (best === undefined || n > best)) best = n;\r\n  });\r\n  return best;\r\n}\r\n\r\nfunction parseGenres(block: string): readonly string[] {\r\n  const m = /Thể\\s*loại\\s*:\\s*([\\s\\S]+?)(?:\\n\\s*\\n|$)/i.exec(block);\r\n  if (!m?.[1]) return [];\r\n  return m[1]\r\n    .split(\",\")\r\n    .map((s) => s.replace(/\\s+/g, \" \").trim())\r\n    // Drop description spillover: a real genre is short. When `.book-desc`\r\n    // renders on one line the lazy capture runs into the synopsis.\r\n    .filter((s) => s.length > 0 && s.length <= 50 && s.toLowerCase() !== \"thể loại\");\r\n}\r\n\r\nconst PaginationProbeSchema = z.object({\r\n  lastStart: z.number().int().positive(),\r\n  pageSize: z.number().int().positive(),\r\n});\r\n\r\n/** Parse the wikicv pagination probe payload. Returns null on any bad input. */\r\nexport function parsePaginationProbe(\r\n  raw: unknown,\r\n): { lastStart: number; pageSize: number } | null {\r\n  if (typeof raw !== \"string\") return null;\r\n  let json: unknown;\r\n  try {\r\n    json = JSON.parse(raw);\r\n  } catch {\r\n    return null;\r\n  }\r\n  const parsed = PaginationProbeSchema.safeParse(json);\r\n  return parsed.success\r\n    ? { lastStart: parsed.data.lastStart, pageSize: parsed.data.pageSize }\r\n    : null;\r\n}\r\n\r\nexport function parseChapterPage($: CheerioAPI, ref: ChapterRef): Chapter {\r\n  // The three p.book-title blocks (novel title, chapter label, author line)\r\n  // live in #bookContent — a sibling wrapper around #bookContentBody.\r\n  // Grab the chapter title from the second p.book-title before touching the DOM.\r\n  const titleEl = $(S.chapter.titleBlock).eq(1);\r\n  const title = titleEl.text().trim() || ref.title;\r\n\r\n  const body = $(S.chapter.bodyRoot).first();\r\n  if (body.length === 0) {\r\n    throw new ParseError(`Chapter body not found at ${ref.url}`, { url: ref.url });\r\n  }\r\n\r\n  // Strip residual UI / help / ads / comment blocks inside the body (defensive).\r\n  body\r\n    .find(\r\n      \".comment-title, .book-comment, [class*='comment'], [class*='ads'], .chapter-nav, .tooltip-link\",\r\n    )\r\n    .remove();\r\n\r\n  const content = normalizeText(body.html() ?? \"\");\r\n  assertContentNonEmpty(content, ref.url);\r\n\r\n  return {\r\n    index: ref.index,\r\n    url: ref.url,\r\n    title,\r\n    content,\r\n    wordCount: wordCount(content),\r\n    fetchedAt: new Date(),\r\n  };\r\n}\r\n\r\n/**\r\n * Parse the HTML response from /book/index?bookId=... — a fragment containing\r\n * <li class=\"chapter-name\"><a class=\"truncate\" href=\"...\">title</a></li> items.\r\n *\r\n * `startIndex` shifts the global index for windows beyond the first, so paged\r\n * walks (start=0, 501, 1002, …) yield a continuous sequence. `baseUrl` is the\r\n * URL of the page that produced the fragment — relative anchors resolve\r\n * against it so a `www.` host or a mirror domain in the user input is\r\n * preserved instead of collapsing to a hardcoded origin.\r\n */\r\nexport function parseBookIndexHtml(\r\n  html: string,\r\n  baseUrl: Url,\r\n  startIndex = 0,\r\n  opts: { logger?: Logger } = {},\r\n): readonly ChapterRef[] {\r\n  const $ = loadHtml(html);\r\n  const refs = refsFromAnchors($, {\r\n    selector: S.toc.chapterList,\r\n    startIndex: unsafeBrandChapterIndex(startIndex),\r\n    baseUrl,\r\n    preferTitleAttr: false,\r\n  });\r\n  if (refs.length === 0) {\r\n    opts.logger?.warn({ url: baseUrl, startIndex }, \"wikicv: empty book/index window — ending walk\");\r\n  }\r\n  return refs;\r\n}\r\n","export const WIKICV_SELECTORS = {\r\n  metadata: {\r\n    title: \".book-info .cover-info h2\",\r\n    author: \".book-info .cover-info a[href*='/tac-gia/']\",\r\n    description: \".book-desc-detail\",\r\n    cover: \".book-info .cover-wrapper img\",\r\n    genresContainer: \".book-desc\",\r\n    // FRAGILE positional selector (unverified): if wikicv reorders the cover-info\r\n    // divs, status silently becomes \"unknown\". Candidate fallback to validate next\r\n    // survey: a[href*='/trang-thai/']. See docs/surveys/2026-06-20-review-deferred-verification.md\r\n    statusAnchor: \".book-info .cover-info div:nth-of-type(4) a\",\r\n    // \"Mới nhất:\" line in the static novel HTML — anchor href points to the\r\n    // most-recent chapter, e.g. /chuong-265-... — and the anchor text reads\r\n    // \"Chương 265 ...\". For wikicv the chapter index is monotonically\r\n    // increasing from 1, so the latest index equals the source TOC size.\r\n    latestChapterAnchor: \".book-info a[href*='/chuong-']\",\r\n  },\r\n  toc: {\r\n    chapterList: \"li.chapter-name a\",\r\n  },\r\n  chapter: {\r\n    bodyRoot: \"#bookContentBody\",\r\n    titleBlock: \"p.book-title\",\r\n  },\r\n} as const;\r\n","import { BaseAdapter } from \"../base.js\";\r\nimport type { AdapterContext } from \"../base.js\";\r\nimport type { Url } from \"../../data-model/primitives.js\";\r\nimport type { NovelMetadata, ChapterRef, Chapter } from \"../../data-model/novel.js\";\r\nimport { unsafeBrandAdapterId } from \"../../data-model/primitives.js\";\r\nimport { ParseError } from \"../../core/errors.js\";\r\n\r\nconst SCAFFOLD_MESSAGE =\r\n  \"tangthuvien adapter is a scaffold; full implementation tracked as M5.1 follow-up. \" +\r\n  \"Site was unreachable at survey time (2026-05-03 Cloudflare interactive Turnstile). \" +\r\n  \"See docs/surveys/tangthuvien-2026-05-03.md.\";\r\n\r\nexport class TangThuVienAdapter extends BaseAdapter {\r\n  readonly id = unsafeBrandAdapterId(\"tangthuvien\");\r\n  readonly displayName = \"TangThuVien\";\r\n  readonly hostnames = [\"tangthuvien.net\", \"www.tangthuvien.net\"];\r\n  override readonly preferredTransport = \"browser\" as const;\r\n\r\n  override async fetchMetadata(url: Url, _ctx: AdapterContext): Promise<NovelMetadata> {\r\n    throw new ParseError(SCAFFOLD_MESSAGE, { url });\r\n  }\r\n\r\n  // eslint-disable-next-line require-yield\r\n  override async *fetchChapterList(url: Url, _ctx: AdapterContext): AsyncIterable<ChapterRef> {\r\n    throw new ParseError(SCAFFOLD_MESSAGE, { url });\r\n  }\r\n\r\n  override async fetchChapter(ref: ChapterRef, _ctx: AdapterContext): Promise<Chapter> {\r\n    throw new ParseError(SCAFFOLD_MESSAGE, { url: ref.url });\r\n  }\r\n}\r\n","import { Downloader } from \"./downloader.js\";\r\nimport { TruyenFullAdapter } from \"../adapters/truyenfull/index.js\";\r\nimport { MeTruyenChuComVnAdapter } from \"../adapters/metruyenchu/index.js\";\r\nimport { WikicvAdapter } from \"../adapters/wikicv/index.js\";\r\nimport { TangThuVienAdapter } from \"../adapters/tangthuvien/index.js\";\r\nimport type { SiteAdapter } from \"../adapters/base.js\";\r\nimport type { HttpClient } from \"../http/types.js\";\r\nimport type { Logger } from \"../utils/logger.js\";\r\nimport type { TransportConfig, TransportMode, RateLimitOptions, RetryOptions } from \"../data-model/options.js\";\r\nimport type { LogLevel } from \"../utils/logger.js\";\r\n\r\n/** Fresh instances of every built-in site adapter. */\r\nexport function builtinAdapters(): SiteAdapter[] {\r\n  return [\r\n    new TruyenFullAdapter(),\r\n    new MeTruyenChuComVnAdapter(),\r\n    new WikicvAdapter(),\r\n    new TangThuVienAdapter(),\r\n  ];\r\n}\r\n\r\n/** Options for {@link createDownloader}. All fields are optional. */\r\nexport interface CreateDownloaderOptions {\r\n  /** Transport mode/config. Default: `{ mode: \"auto\" }` (headless). */\r\n  readonly transport?: TransportConfig | TransportMode;\r\n  /** Replace the entire transport stack with a custom {@link HttpClient}. */\r\n  readonly http?: HttpClient;\r\n  readonly logger?: Logger;\r\n  /** Convenience: build a pino logger at this level when no `logger` is given. */\r\n  readonly logLevel?: LogLevel;\r\n  /** Adapters to register. Defaults to every built-in adapter. */\r\n  readonly adapters?: readonly SiteAdapter[];\r\n  readonly rateLimit?: RateLimitOptions;\r\n  readonly retry?: RetryOptions;\r\n}\r\n\r\n/**\r\n * Build a Downloader wired with the built-in site adapters. Unlike the shared\r\n * `downloader` singleton, this lets you set the transport (e.g. a headed\r\n * browser for a manual Cloudflare solve), a custom HttpClient, or a logger.\r\n * Call `dispose()` when done — the instance may hold a browser pool.\r\n */\r\nexport function createDownloader(opts: CreateDownloaderOptions = {}): Downloader {\r\n  return new Downloader({\r\n    adapters: opts.adapters ?? builtinAdapters(),\r\n    ...(opts.transport !== undefined ? { transport: opts.transport } : {}),\r\n    ...(opts.http !== undefined ? { http: opts.http } : {}),\r\n    ...(opts.logger !== undefined ? { logger: opts.logger } : {}),\r\n    ...(opts.logLevel !== undefined ? { logLevel: opts.logLevel } : {}),\r\n    ...(opts.rateLimit !== undefined ? { rateLimit: opts.rateLimit } : {}),\r\n    ...(opts.retry !== undefined ? { retry: opts.retry } : {}),\r\n  });\r\n}\r\n\r\n/**\r\n * Shared, process-wide downloader: every built-in adapter, default (auto)\r\n * transport. Holds a BrowserPool that is never disposed automatically — fine\r\n * for short-lived CLIs. Callers needing deterministic teardown (tests,\r\n * long-lived servers) should build their own via `createDownloader()` and call\r\n * `dispose()` when done.\r\n */\r\nexport const downloader = createDownloader();\r\n"],"mappings":"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","names":["resolve","pino","import_undici","undiciErrors","relative","resolve","BrowserModuleNotInstalledError","import_zod","import_promises","import_node_path","import_zod","res","import_zod","import_node_path","import_zod","PQueue","resolve","import_node_path","import_promises","import_node_path","import_promises","import_node_path","captureCfCookies","cheerioLoad","htmlToTextConvert","import_zod","parseNovelPage","parseChapterPage","parseNovelPage","parseChapterPage","import_zod","SOURCE_SITE","parseNovelPage","extractTotalChapters","parseChapterPage","parseNovelPage","parseChapterPage"]}