{"version":3,"file":"index.cjs","names":["DEFAULT_MAX_REQUESTS_PER_SECOND","DEFAULT_TIMEOUT_MS"],"sources":["../src/bulk/service.ts","../src/errors/index.ts","../src/http/fetch-json.ts","../src/discovery/fetch-submissions.ts","../src/discovery/normalization.ts","../src/company/service.ts","../src/discovery/deduplication.ts","../src/discovery/index-parser.ts","../src/discovery/index-service.ts","../src/discovery/types.ts","../src/discovery/pagination.ts","../src/discovery/service.ts","../src/download/hasher.ts","../src/download/service.ts","../src/exhibits/deduplication.ts","../src/exhibits/filters/contract.ts","../src/exhibits/normalization.ts","../src/exhibits/parsing.ts","../src/exhibits/service.ts","../src/http/error-mapper.ts","../src/http/limiter.ts","../src/http/retry.ts","../src/http/runtime.ts","../src/http/timeout.ts","../src/http/client.ts","../src/search/service.ts","../src/xbrl/service.ts","../src/client.ts"],"sourcesContent":["// BulkDataService — download SEC bulk data archives\n\nimport type { SecHttpClient } from \"@/http\"\n\nexport type BulkDownloadResult = {\n  bytes: Uint8Array\n  sizeBytes: number\n  mimeType?: string\n  source: \"submissions\" | \"companyfacts\"\n}\n\nconst SUBMISSIONS_URL = \"https://www.sec.gov/Archives/edgar/daily-index/bulkdata/submissions.zip\"\nconst COMPANYFACTS_URL = \"https://www.sec.gov/Archives/edgar/daily-index/xbrl/companyfacts.zip\"\n\nexport class BulkDataService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  async downloadSubmissionsBulk(): Promise<BulkDownloadResult> {\n    return this.downloadBulk(SUBMISSIONS_URL, \"submissions\", \"downloadSubmissionsBulk\")\n  }\n\n  async downloadCompanyFactsBulk(): Promise<BulkDownloadResult> {\n    return this.downloadBulk(COMPANYFACTS_URL, \"companyfacts\", \"downloadCompanyFactsBulk\")\n  }\n\n  private async downloadBulk(\n    url: string,\n    source: BulkDownloadResult[\"source\"],\n    operation: string,\n  ): Promise<BulkDownloadResult> {\n    const response = await this.httpClient.request(url, {\n      context: { operation, endpointClass: \"bulk-data\" },\n    })\n\n    const buffer = await response.arrayBuffer()\n    const bytes = new Uint8Array(buffer)\n    const contentType = response.headers.get(\"Content-Type\")\n    const mimeType = contentType?.split(\";\")[0]?.trim()\n\n    return {\n      bytes,\n      sizeBytes: bytes.byteLength,\n      mimeType,\n      source,\n    }\n  }\n}\n","// Typed error taxonomy with retryability hints\n\nexport type EdgarErrorCode =\n  | \"CONFIGURATION_ERROR\"\n  | \"VALIDATION_ERROR\"\n  | \"TRANSPORT_ERROR\"\n  | \"RATE_LIMITED\"\n  | \"TIMEOUT\"\n  | \"NOT_FOUND\"\n  | \"PARSE_ERROR\"\n  | \"UNKNOWN_ERROR\"\n\nexport class EdgarError extends Error {\n  readonly code: EdgarErrorCode\n  readonly retryable: boolean\n  readonly metadata?: Record<string, unknown>\n\n  constructor(\n    message: string,\n    code: EdgarErrorCode,\n    retryable: boolean,\n    options?: { cause?: unknown; metadata?: Record<string, unknown> },\n  ) {\n    super(message, { cause: options?.cause })\n    this.name = \"EdgarError\"\n    this.code = code\n    this.retryable = retryable\n    this.metadata = options?.metadata\n  }\n}\n\nexport class ConfigurationError extends EdgarError {\n  constructor(message: string, options?: { cause?: unknown; metadata?: Record<string, unknown> }) {\n    super(message, \"CONFIGURATION_ERROR\", false, options)\n    this.name = \"ConfigurationError\"\n  }\n}\n\nexport class ValidationError extends EdgarError {\n  constructor(message: string, options?: { cause?: unknown; metadata?: Record<string, unknown> }) {\n    super(message, \"VALIDATION_ERROR\", false, options)\n    this.name = \"ValidationError\"\n  }\n}\n\nexport class TransportError extends EdgarError {\n  constructor(\n    message: string,\n    retryable: boolean,\n    options?: { cause?: unknown; metadata?: Record<string, unknown> },\n  ) {\n    super(message, \"TRANSPORT_ERROR\", retryable, options)\n    this.name = \"TransportError\"\n  }\n}\n\nexport class RateLimitedError extends EdgarError {\n  constructor(message: string, options?: { cause?: unknown; metadata?: Record<string, unknown> }) {\n    super(message, \"RATE_LIMITED\", true, options)\n    this.name = \"RateLimitedError\"\n  }\n}\n\nexport class TimeoutError extends EdgarError {\n  constructor(message: string, options?: { cause?: unknown; metadata?: Record<string, unknown> }) {\n    super(message, \"TIMEOUT\", true, options)\n    this.name = \"TimeoutError\"\n  }\n}\n\nexport class NotFoundError extends EdgarError {\n  constructor(message: string, options?: { cause?: unknown; metadata?: Record<string, unknown> }) {\n    super(message, \"NOT_FOUND\", false, options)\n    this.name = \"NotFoundError\"\n  }\n}\n\nexport class ParseError extends EdgarError {\n  constructor(message: string, options?: { cause?: unknown; metadata?: Record<string, unknown> }) {\n    super(message, \"PARSE_ERROR\", false, options)\n    this.name = \"ParseError\"\n  }\n}\n","// Shared utility for fetching JSON from SEC endpoints with ParseError wrapping\n\nimport { ParseError } from \"@/errors\"\nimport type { SecHttpClient } from \"./client\"\n\n/**\n * Fetch a URL via SecHttpClient, parse JSON, and wrap parse failures in ParseError.\n */\nexport async function fetchJson<T>(\n  url: string,\n  httpClient: SecHttpClient,\n  context: { readonly operation: string; readonly endpointClass: string },\n): Promise<T> {\n  const response = await httpClient.request(url, { context })\n\n  try {\n    return (await response.json()) as T\n  } catch (error) {\n    throw new ParseError(`Failed to parse JSON from ${url}`, {\n      metadata: { url },\n      cause: error,\n    })\n  }\n}\n","// Shared utility for fetching SEC Submissions API response\n\nimport type { SecHttpClient } from \"@/http\"\nimport { fetchJson } from \"@/http/fetch-json\"\nimport type { SubmissionsResponse } from \"./types\"\n\n/**\n * Fetch the SEC Submissions API response for a given CIK.\n * Expects a pre-normalized 10-digit CIK.\n */\nexport async function fetchSubmissionsResponse(\n  normalizedCik: string,\n  httpClient: SecHttpClient,\n  context: { readonly operation: string; readonly endpointClass: string },\n): Promise<SubmissionsResponse> {\n  const url = `https://data.sec.gov/submissions/CIK${normalizedCik}.json`\n  return fetchJson<SubmissionsResponse>(url, httpClient, context)\n}\n","// Pure normalization functions for SEC EDGAR data\n\nimport { ValidationError } from \"@/errors\"\n\n/**\n * Normalize CIK to canonical 10-digit zero-padded format.\n *\n * Handles both padded (\"0000320193\") and unpadded (\"320193\") input.\n * Validates numeric-only and range constraints.\n *\n * @param input - CIK string (padded or unpadded)\n * @returns 10-digit zero-padded CIK\n * @throws ValidationError if input is invalid\n *\n * @example\n * normalizeCik(\"320193\") // \"0000320193\"\n * normalizeCik(\"0000320193\") // \"0000320193\"\n */\nexport function normalizeCik(input: string): string {\n  const trimmed = input.trim()\n\n  // Strip leading zeros to get numeric value\n  const numeric = trimmed.replace(/^0+/, \"\") || \"0\"\n\n  // Validate numeric-only\n  if (!/^\\d+$/.test(numeric)) {\n    throw new ValidationError(`Invalid CIK: must contain only digits`, {\n      metadata: { input },\n    })\n  }\n\n  // Validate range: max 10 digits (9,999,999,999)\n  if (Number(numeric) > 9_999_999_999) {\n    throw new ValidationError(`Invalid CIK: exceeds maximum 10-digit value`, {\n      metadata: { input },\n    })\n  }\n\n  // Zero-pad to 10 digits\n  return numeric.padStart(10, \"0\")\n}\n\n/**\n * Normalize accession number to canonical hyphenated format.\n *\n * Handles multiple input formats:\n * - \"0000000000-20-000000\" (already hyphenated)\n * - \"000000000020000000\" (compact)\n * - \"0000000000-20000000\" (partial hyphen)\n *\n * @param input - Accession number in any format\n * @returns Canonical format: ##########-##-######\n * @throws ValidationError if format is invalid\n *\n * @example\n * normalizeAccession(\"0001193125-20-123456\") // \"0001193125-20-123456\"\n * normalizeAccession(\"000119312520123456\") // \"0001193125-20-123456\"\n */\nexport function normalizeAccession(input: string): string {\n  // Remove all spaces and hyphens\n  const cleaned = input.replace(/[\\s-]/g, \"\")\n\n  // Validate: exactly 18 digits\n  if (!/^\\d{18}$/.test(cleaned)) {\n    throw new ValidationError(`Invalid accession number: must be 18 digits`, {\n      metadata: { input },\n    })\n  }\n\n  // Format as ##########-##-######\n  return `${cleaned.slice(0, 10)}-${cleaned.slice(10, 12)}-${cleaned.slice(12)}`\n}\n\n/**\n * Normalize form type to canonical uppercase format.\n *\n * Preserves slashes for amendments (e.g., \"10-K/A\").\n *\n * @param input - Form type string\n * @returns Uppercase, trimmed form type\n *\n * @example\n * normalizeFormType(\"10-k\") // \"10-K\"\n * normalizeFormType(\" 10-K/A \") // \"10-K/A\"\n */\nexport function normalizeFormType(input: string): string {\n  return input.trim().toUpperCase()\n}\n\n/**\n * Validate date string format.\n *\n * Ensures date is in YYYY-MM-DD ISO format and represents a valid calendar date.\n *\n * @param input - Date string to validate\n * @throws ValidationError if format or value is invalid\n *\n * @example\n * validateDate(\"2024-01-15\") // OK\n * validateDate(\"2024-02-30\") // throws ValidationError\n */\nexport function validateDate(input: string): void {\n  // Check format: YYYY-MM-DD\n  if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(input)) {\n    throw new ValidationError(`Invalid date format: expected YYYY-MM-DD`, {\n      metadata: { input },\n    })\n  }\n\n  // Parse and validate value - check if parsing changes the date (detects invalid dates)\n  const date = new Date(`${input}T00:00:00Z`)\n  if (Number.isNaN(date.getTime())) {\n    throw new ValidationError(`Invalid date value`, {\n      metadata: { input },\n    })\n  }\n\n  // Verify the date didn't roll over (e.g., Feb 30 → Mar 2)\n  const isoString = date.toISOString().slice(0, 10)\n  if (isoString !== input) {\n    throw new ValidationError(`Invalid date value`, {\n      metadata: { input },\n    })\n  }\n}\n","// CompanyService — company metadata and ticker lookup from SEC\n\nimport { fetchSubmissionsResponse } from \"@/discovery/fetch-submissions\"\nimport { normalizeCik } from \"@/discovery/normalization\"\nimport type { SecHttpClient } from \"@/http\"\nimport { fetchJson } from \"@/http/fetch-json\"\nimport type { CompanyInfo, CompanyTicker } from \"@/types\"\n\n/** SEC company_tickers_exchange.json response: { fields: string[], data: [cik, name, ticker, exchange][] } */\ntype TickerExchangeResponse = {\n  fields: string[]\n  data: [number, string, string, string][]\n}\n\nexport class CompanyService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  async getCompanyInfo(cik: string): Promise<CompanyInfo> {\n    const normalizedCik = normalizeCik(cik)\n\n    const submissions = await fetchSubmissionsResponse(normalizedCik, this.httpClient, {\n      operation: \"getCompanyInfo\",\n      endpointClass: \"submissions\",\n    })\n\n    return {\n      cik: normalizedCik,\n      name: submissions.name,\n      tickers: submissions.tickers ?? [],\n      exchanges: submissions.exchanges ?? [],\n      entityType: submissions.entityType,\n      sic: submissions.sic,\n      sicDescription: submissions.sicDescription,\n      stateOfIncorporation: submissions.stateOfIncorporation,\n      fiscalYearEnd: submissions.fiscalYearEnd,\n    }\n  }\n\n  async lookupCompany(query: string): Promise<CompanyTicker[]> {\n    const raw = await fetchJson<TickerExchangeResponse>(\n      \"https://www.sec.gov/files/company_tickers_exchange.json\",\n      this.httpClient,\n      { operation: \"lookupCompany\", endpointClass: \"files\" },\n    )\n\n    const queryUpper = query.toUpperCase().trim()\n    const queryLower = query.toLowerCase().trim()\n\n    const tickerMatches: CompanyTicker[] = []\n    const nameMatches: CompanyTicker[] = []\n\n    for (const [cikNum, name, ticker, exchange] of raw.data) {\n      if (ticker.toUpperCase() === queryUpper) {\n        tickerMatches.push({ cik: normalizeCik(String(cikNum)), ticker, name, exchange })\n      } else if (name.toLowerCase().includes(queryLower)) {\n        nameMatches.push({ cik: normalizeCik(String(cikNum)), ticker, name, exchange })\n      }\n    }\n\n    return [...tickerMatches, ...nameMatches]\n  }\n}\n","// Deduplication and stable sort for filing discovery\n\nimport type { FilingRef } from \"@/types\"\n\n/**\n * Deduplicate and stable-sort filing references.\n *\n * Deduplication uses identity key: `{cik}:{accessionNo}`\n * - Retains first occurrence of each unique filing\n * - Assumes inputs are already normalized (CIK padded, accession hyphenated)\n *\n * Stable sort order:\n * 1. Primary: filingDate ascending (lexicographic for ISO 8601)\n * 2. Secondary: accessionNo ascending (lexicographic)\n * 3. Preserves input order for ties\n *\n * @param filings - Array of filing references (may contain duplicates)\n * @returns Deduplicated, sorted array\n *\n * @example\n * const filings = [\n *   { cik: \"0000320193\", accessionNo: \"0001193125-20-123456\", ... },\n *   { cik: \"0000320193\", accessionNo: \"0001193125-20-123456\", ... }, // duplicate\n * ]\n * const result = dedupeAndSort(filings)\n * // result.length === 1\n */\nexport function dedupeAndSort(filings: FilingRef[]): FilingRef[] {\n  // Build identity map: \"{cik}:{accessionNo}\" → first occurrence\n  const identityMap = new Map<string, FilingRef>()\n\n  for (const filing of filings) {\n    const identity = `${filing.cik}:${filing.accessionNo}`\n\n    // Retain first occurrence only\n    if (!identityMap.has(identity)) {\n      identityMap.set(identity, filing)\n    }\n    // If duplicate, skip (or emit telemetry if available)\n  }\n\n  // Convert map values to array\n  const deduplicated = Array.from(identityMap.values())\n\n  // Stable sort: filingDate asc, then accessionNo asc\n  return deduplicated.sort((a, b) => {\n    // Primary sort: filingDate ascending\n    const dateOrder = a.filingDate.localeCompare(b.filingDate)\n    if (dateOrder !== 0) return dateOrder\n\n    // Secondary sort: accessionNo ascending\n    return a.accessionNo.localeCompare(b.accessionNo)\n  })\n}\n","// Parse SEC EDGAR master.idx index files (pipe-delimited)\n\nimport { normalizeCik, normalizeFormType } from \"./normalization\"\n\nexport type IndexEntry = {\n  cik: string\n  companyName: string\n  formType: string\n  filingDate: string\n  filename: string\n}\n\n/**\n * Parse a SEC master.idx file into structured entries.\n *\n * Format: CIK|Company Name|Form Type|Date Filed|Filename\n * First line is a header, second line is dashes. Both are skipped.\n * Malformed rows are silently skipped.\n */\nexport function parseIndexFile(content: string): IndexEntry[] {\n  const lines = content.split(\"\\n\")\n  const entries: IndexEntry[] = []\n\n  for (const line of lines) {\n    const trimmed = line.trim()\n    if (!trimmed || trimmed.startsWith(\"-\") || trimmed.startsWith(\"CIK|\")) {\n      continue\n    }\n\n    const parts = trimmed.split(\"|\")\n    if (parts.length < 5) {\n      continue\n    }\n\n    const rawCik = parts[0] ?? \"\"\n    const companyName = parts[1] ?? \"\"\n    const formType = parts[2] ?? \"\"\n    const filingDate = parts[3] ?? \"\"\n    const filename = parts[4] ?? \"\"\n\n    // Skip rows where CIK is not numeric\n    if (!/^\\d+$/.test(rawCik.trim())) {\n      continue\n    }\n\n    entries.push({\n      cik: normalizeCik(rawCik.trim()),\n      companyName: companyName.trim(),\n      formType: normalizeFormType(formType.trim()),\n      filingDate: filingDate.trim(),\n      filename: filename.trim(),\n    })\n  }\n\n  return entries\n}\n","// IndexService — bulk filing discovery via SEC quarterly/daily index files\n\nimport type { SecHttpClient } from \"@/http\"\nimport type { FilingRef } from \"@/types\"\nimport { dedupeAndSort } from \"./deduplication\"\nimport { parseIndexFile } from \"./index-parser\"\nimport { normalizeAccession, normalizeFormType } from \"./normalization\"\n\nconst ACCESSION_RE = /(\\d{10}-\\d{2}-\\d{6})|(\\d{18})/\n\nexport type IndexDiscoveryInput = {\n  from: string\n  to: string\n  formTypes?: string[]\n}\n\nexport class IndexService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  async discoverByIndex(input: IndexDiscoveryInput): Promise<FilingRef[]> {\n    const quarters = getQuartersInRange(input.from, input.to)\n    const formTypeSet = input.formTypes ? new Set(input.formTypes.map(normalizeFormType)) : null\n\n    const allFilings: FilingRef[] = []\n\n    for (const { year, quarter } of quarters) {\n      const url = `https://www.sec.gov/Archives/edgar/full-index/${year}/QTR${quarter}/master.idx`\n\n      const response = await this.httpClient.request(url, {\n        context: { operation: \"discoverByIndex\", endpointClass: \"full-index\" },\n      })\n\n      const content = await response.text()\n      const entries = parseIndexFile(content)\n\n      for (const entry of entries) {\n        if (entry.filingDate < input.from || entry.filingDate > input.to) {\n          continue\n        }\n\n        if (formTypeSet && !formTypeSet.has(entry.formType)) {\n          continue\n        }\n\n        const accessionMatch = entry.filename.match(ACCESSION_RE)\n        if (!accessionMatch) {\n          continue\n        }\n\n        const rawAccession = accessionMatch[1] ?? accessionMatch[2] ?? \"\"\n        const accessionNo = normalizeAccession(rawAccession)\n        const accessionNoCompact = accessionNo.replace(/-/g, \"\")\n        const filingUrl = `https://www.sec.gov/cgi-bin/viewer?action=view&cik=${entry.cik}&accession_number=${accessionNoCompact}&xbrl_type=v`\n\n        allFilings.push({\n          cik: entry.cik,\n          accessionNo,\n          formType: entry.formType,\n          filingDate: entry.filingDate,\n          filingUrl,\n        })\n      }\n    }\n\n    return dedupeAndSort(allFilings)\n  }\n}\n\ntype Quarter = { year: number; quarter: number }\n\nfunction getQuartersInRange(from: string, to: string): Quarter[] {\n  const startYear = Number.parseInt(from.slice(0, 4), 10)\n  const startMonth = Number.parseInt(from.slice(5, 7), 10)\n  const endYear = Number.parseInt(to.slice(0, 4), 10)\n  const endMonth = Number.parseInt(to.slice(5, 7), 10)\n\n  const startQ = Math.ceil(startMonth / 3)\n  const endQ = Math.ceil(endMonth / 3)\n\n  const quarters: Quarter[] = []\n\n  for (let year = startYear; year <= endYear; year++) {\n    const qStart = year === startYear ? startQ : 1\n    const qEnd = year === endYear ? endQ : 4\n\n    for (let q = qStart; q <= qEnd; q++) {\n      quarters.push({ year, quarter: q })\n    }\n  }\n\n  return quarters\n}\n","// Internal discovery types — SEC Submissions API response structures\n\n/**\n * Filing record from SEC Submissions API\n */\nexport type FilingRecord = {\n  /** Accession number (hyphenated format: ##########-##-######) */\n  accessionNumber: string\n  /** Filing date (YYYY-MM-DD) */\n  filingDate: string\n  /** Report date (YYYY-MM-DD) */\n  reportDate: string\n  /** Acceptance timestamp (ISO 8601) */\n  acceptanceDateTime: string\n  /** Securities Act variant (\"33\", \"34\", \"40\") */\n  act: string\n  /** Form type (e.g., \"10-K\", \"8-K/A\") */\n  form: string\n  /** File number */\n  fileNumber: string\n  /** Primary document filename */\n  primaryDocument: string\n  /** Primary document description */\n  primaryDocDescription?: string\n  /** File size in bytes */\n  size: number\n  /** Whether filing is XBRL (0 or 1) */\n  isXBRL: number\n  /** Whether filing is inline XBRL (0 or 1) */\n  isInlineXBRL: number\n}\n\n/**\n * Paginated file reference in filings.files array\n */\nexport type PaginatedFileRef = {\n  /** Relative path to paginated JSON file (e.g., \"submissions/CIK0000320193-submissions-001.json\") */\n  name: string\n  /** Number of filings in this paginated file */\n  filingCount: number\n  /** Starting filing date (YYYY-MM-DD) */\n  filingFrom?: string\n  /** Ending filing date (YYYY-MM-DD) */\n  filingTo?: string\n}\n\n/**\n * Response from SEC Submissions API (data.sec.gov/submissions/CIK##########.json)\n */\nexport type SubmissionsResponse = {\n  /** CIK as string (may or may not be zero-padded) */\n  cik: string\n  /** Entity name */\n  name: string\n  /** Trading symbols */\n  tickers?: string[]\n  /** Entity type */\n  entityType?: string\n  /** SIC code */\n  sic?: string\n  /** SIC description */\n  sicDescription?: string\n  /** Fiscal year end (MMDD format) */\n  fiscalYearEnd?: string\n  /** Stock exchanges */\n  exchanges?: string[]\n  /** State of incorporation */\n  stateOfIncorporation?: string\n  /** Filings collection */\n  filings: {\n    /** Most recent filings as parallel arrays (up to 1000 entries per array) */\n    recent: ParallelFilingArrays\n    /** References to paginated filing files (for CIKs with 1000+ filings) */\n    files: PaginatedFileRef[]\n  }\n}\n\n/**\n * SEC API parallel-array format for filing data.\n * The SEC Submissions API returns filings as an object with parallel arrays\n * (one array per field), not as an array of objects.\n */\nexport type ParallelFilingArrays = {\n  accessionNumber: string[]\n  filingDate: string[]\n  reportDate: string[]\n  acceptanceDateTime: string[]\n  act: string[]\n  form: string[]\n  fileNumber: string[]\n  primaryDocument: string[]\n  primaryDocDescription: string[]\n  size: number[]\n  isXBRL: number[]\n  isInlineXBRL: number[]\n}\n\n/**\n * Reconstruct FilingRecord[] from SEC parallel-array format.\n */\nexport function recordsFromParallelArrays(arrays: ParallelFilingArrays): FilingRecord[] {\n  const len = arrays.accessionNumber?.length ?? 0\n  const records: FilingRecord[] = []\n  for (let i = 0; i < len; i++) {\n    records.push({\n      accessionNumber: arrays.accessionNumber[i] ?? \"\",\n      filingDate: arrays.filingDate[i] ?? \"\",\n      reportDate: arrays.reportDate[i] ?? \"\",\n      acceptanceDateTime: arrays.acceptanceDateTime[i] ?? \"\",\n      act: arrays.act[i] ?? \"\",\n      form: arrays.form[i] ?? \"\",\n      fileNumber: arrays.fileNumber[i] ?? \"\",\n      primaryDocument: arrays.primaryDocument[i] ?? \"\",\n      primaryDocDescription: arrays.primaryDocDescription[i],\n      size: arrays.size[i] ?? 0,\n      isXBRL: arrays.isXBRL[i] ?? 0,\n      isInlineXBRL: arrays.isInlineXBRL[i] ?? 0,\n    })\n  }\n  return records\n}\n","// SEC Submissions API pagination — recursive fetching for high-volume CIKs\n\nimport { ParseError } from \"@/errors\"\nimport type { SecHttpClient } from \"@/http\"\nimport { fetchSubmissionsResponse } from \"./fetch-submissions\"\nimport { normalizeCik } from \"./normalization\"\nimport type { FilingRecord, ParallelFilingArrays } from \"./types\"\nimport { recordsFromParallelArrays } from \"./types\"\n\n/**\n * Fetch all filings for a CIK from SEC Submissions API with pagination.\n *\n * The SEC Submissions API returns the most recent 1000 filings in the `recent` array.\n * For CIKs with more than 1000 filings, additional filings are available via\n * the `files` array, which contains references to paginated JSON files.\n *\n * This function:\n * 1. Normalizes the CIK to 10-digit zero-padded format\n * 2. Fetches the primary submissions endpoint (data.sec.gov/submissions/CIK##########.json)\n * 3. Collects filings from the `recent` array\n * 4. Iterates through the `files` array to fetch paginated filing data\n * 5. Returns all filings as a single array\n *\n * @param cik - Central Index Key (padded or unpadded)\n * @param httpClient - SecHttpClient instance (for rate limiting and retry)\n * @returns Promise resolving to complete array of filing records\n * @throws ValidationError if CIK format is invalid\n * @throws TransportError if HTTP request fails (after retries)\n * @throws ParseError if response JSON is malformed\n *\n * @example\n * const filings = await fetchAllFilings(\"320193\", httpClient)\n * console.log(`Total filings: ${filings.length}`)\n */\nexport async function fetchAllFilings(\n  cik: string,\n  httpClient: SecHttpClient,\n  context?: { readonly operation: string; readonly endpointClass: string },\n): Promise<FilingRecord[]> {\n  const normalizedCik = normalizeCik(cik)\n\n  const submissions = await fetchSubmissionsResponse(\n    normalizedCik,\n    httpClient,\n    context ?? { operation: \"discoverFilings\", endpointClass: \"submissions\" },\n  )\n\n  // Collect recent filings (first 1000 or fewer)\n  const allFilings: FilingRecord[] = recordsFromParallelArrays(submissions.filings.recent)\n\n  // Iterate through paginated files\n  const paginatedFiles = submissions.filings.files ?? []\n\n  for (const file of paginatedFiles) {\n    const fileUrl = `https://data.sec.gov/submissions/${file.name}`\n\n    const paginatedResponse = await httpClient.request(fileUrl, { context })\n\n    let paginatedData: Record<string, unknown>\n    try {\n      paginatedData = (await paginatedResponse.json()) as Record<string, unknown>\n    } catch (error) {\n      throw new ParseError(`Failed to parse paginated filings JSON from ${fileUrl}`, {\n        metadata: { url: fileUrl, fileName: file.name },\n        cause: error,\n      })\n    }\n\n    const paginatedArrays = paginatedData as unknown as ParallelFilingArrays\n    if (paginatedArrays.accessionNumber && Array.isArray(paginatedArrays.accessionNumber)) {\n      allFilings.push(...recordsFromParallelArrays(paginatedArrays))\n    }\n  }\n\n  return allFilings\n}\n","// DiscoveryService — orchestrates filing discovery with normalization and deduplication\n\nimport { ValidationError } from \"@/errors\"\nimport type { SecHttpClient } from \"@/http\"\nimport type { DiscoverFilingsInput, FilingRef } from \"@/types\"\nimport { dedupeAndSort } from \"./deduplication\"\nimport { IndexService } from \"./index-service\"\nimport { normalizeAccession, normalizeCik, normalizeFormType, validateDate } from \"./normalization\"\nimport { fetchAllFilings } from \"./pagination\"\n\nconst DEFAULT_FORM_TYPES = [\n  \"8-K\",\n  \"10-K\",\n  \"10-Q\",\n  \"20-F\",\n  \"S-1\",\n  \"8-K/A\",\n  \"10-K/A\",\n  \"10-Q/A\",\n  \"20-F/A\",\n  \"S-1/A\",\n]\n\nexport class DiscoveryService {\n  private readonly indexService: IndexService\n\n  constructor(private readonly httpClient: SecHttpClient) {\n    this.indexService = new IndexService(httpClient)\n  }\n\n  async discoverFilings(input: DiscoverFilingsInput): Promise<FilingRef[]> {\n    validateDate(input.from)\n    validateDate(input.to)\n\n    if (input.from > input.to) {\n      throw new ValidationError(\"Date range invalid: 'from' must be <= 'to'\", {\n        metadata: { from: input.from, to: input.to },\n      })\n    }\n\n    const formTypes = input.formTypes ?? DEFAULT_FORM_TYPES\n    const normalizedFormTypes = formTypes.map((form) => normalizeFormType(form))\n\n    // CIK-less discovery uses index files\n    if (!input.cik) {\n      return this.indexService.discoverByIndex({\n        from: input.from,\n        to: input.to,\n        formTypes: normalizedFormTypes,\n      })\n    }\n\n    // CIK-scoped discovery uses Submissions API\n    const cik = normalizeCik(input.cik)\n    const rawFilings = await fetchAllFilings(cik, this.httpClient, {\n      operation: \"discoverFilings\",\n      endpointClass: \"submissions\",\n    })\n\n    const dateFiltered = rawFilings.filter((filing) => {\n      return filing.filingDate >= input.from && filing.filingDate <= input.to\n    })\n\n    const formTypeSet = new Set(normalizedFormTypes)\n    const formFiltered = dateFiltered.filter((filing) => {\n      return formTypeSet.has(normalizeFormType(filing.form))\n    })\n\n    const normalizedFilings = formFiltered.map((filing) => {\n      const filingAccession = normalizeAccession(filing.accessionNumber)\n      const accessionNoCompact = filingAccession.replace(/-/g, \"\")\n      const filingUrl = `https://www.sec.gov/cgi-bin/viewer?action=view&cik=${cik}&accession_number=${accessionNoCompact}&xbrl_type=v`\n\n      return {\n        cik,\n        accessionNo: filingAccession,\n        formType: normalizeFormType(filing.form),\n        filingDate: filing.filingDate,\n        filingUrl,\n      }\n    })\n\n    return dedupeAndSort(normalizedFilings)\n  }\n}\n","// SHA-256 digest computation using Web Crypto API\n// Reference: NIST FIPS 180-4 (SHA-256 specification)\n\n// Type for the Web Crypto subtle interface (avoids @types/node dependency).\ntype SubtleCrypto = {\n  digest(algorithm: string, data: Uint8Array | ArrayBuffer): Promise<ArrayBuffer>\n}\n\n/**\n * Lazily resolve crypto.subtle across Node.js and Bun runtimes.\n *\n * Strategy:\n * 1. Try globalThis.crypto.subtle (Bun, Node 19+, browsers)\n * 2. Fall back to node:crypto webcrypto (Node 18+, vitest environments)\n */\nlet _subtle: SubtleCrypto | undefined\n\nasync function getSubtle(): Promise<SubtleCrypto> {\n  if (_subtle) return _subtle\n\n  // Try globalThis first (available in Bun, Node 19+, browsers)\n  const g = globalThis as unknown as { crypto?: { subtle?: SubtleCrypto } }\n  if (g.crypto?.subtle) {\n    _subtle = g.crypto.subtle\n    return _subtle\n  }\n\n  // Fall back to node:crypto for Node 18 / vitest environments where\n  // globalThis.crypto may not be defined\n  try {\n    // @ts-expect-error -- no @types/node; module exists at runtime on Node 18+\n    const nodeCrypto = await import(\"node:crypto\")\n    const wc = (nodeCrypto as unknown as { webcrypto?: { subtle?: SubtleCrypto } }).webcrypto\n    if (wc?.subtle) {\n      _subtle = wc.subtle\n      return _subtle\n    }\n  } catch {\n    // not in a Node.js environment\n  }\n\n  throw new Error(\"Web Crypto API not available in this runtime\")\n}\n\n/**\n * Compute SHA-256 hex digest of binary data.\n *\n * Uses the W3C Web Crypto API (crypto.subtle.digest) for cryptographic hashing.\n * Available natively in Node.js 18+ and Bun without polyfills.\n *\n * The digest is returned as a lowercase hexadecimal string (64 characters).\n *\n * @param data - Binary data to hash\n * @returns Promise resolving to lowercase hex digest (64 chars)\n *\n * @example\n * ```ts\n * const bytes = new TextEncoder().encode(\"hello world\")\n * const hash = await computeSha256Hex(bytes)\n * // => \"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9\"\n * ```\n */\nexport async function computeSha256Hex(data: Uint8Array): Promise<string> {\n  const subtle = await getSubtle()\n  const hashBuffer = await subtle.digest(\"SHA-256\", data)\n\n  // Convert ArrayBuffer to lowercase hex string\n  const hashArray = Array.from(new Uint8Array(hashBuffer))\n  const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\")\n\n  return hashHex\n}\n","// DownloadService — orchestrates exhibit download with integrity verification\n\nimport type { SecHttpClient } from \"@/http\"\nimport type { DownloadedExhibit, ExhibitRef } from \"@/types\"\nimport { computeSha256Hex } from \"./hasher\"\n\n/**\n * Download service for raw exhibit acquisition with SHA-256 integrity verification.\n *\n * Orchestration flow:\n * 1. Fetch exhibit binary content via SecHttpClient\n * 2. Extract optional MIME type from Content-Type header\n * 3. Convert response to Uint8Array\n * 4. Compute SHA-256 digest for integrity verification\n * 5. Return DownloadedExhibit with complete metadata\n *\n * The service always uses actual bytes.length for size reporting (not Content-Length header).\n * MIME type is optional and extracted from Content-Type header when available.\n */\nexport class DownloadService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  /**\n   * Download exhibit and return raw bytes with metadata.\n   *\n   * @param exhibit - Exhibit reference to download\n   * @returns Promise resolving to DownloadedExhibit with bytes, metadata, and SHA-256 digest\n   */\n  async downloadExhibit(exhibit: ExhibitRef): Promise<DownloadedExhibit> {\n    // 1. Fetch exhibit URL via SecHttpClient\n    const response = await this.httpClient.request(exhibit.exhibitUrl, {\n      context: { operation: \"downloadExhibit\", endpointClass: \"archive\" },\n    })\n\n    // 2. Extract optional MIME type from Content-Type header\n    const contentType = response.headers.get(\"Content-Type\")\n    const mimeType = contentType?.split(\";\")[0]?.trim() ?? undefined\n\n    // 3. Read binary response and convert to Uint8Array\n    const buffer = await response.arrayBuffer()\n    const bytes = new Uint8Array(buffer)\n\n    // 4. Compute SHA-256 digest\n    const sha256 = await computeSha256Hex(bytes)\n\n    // 5. Return DownloadedExhibit with all metadata\n    return {\n      exhibit,\n      bytes,\n      sizeBytes: bytes.length,\n      mimeType,\n      sha256,\n    }\n  }\n}\n","// Deduplication and stable sort for exhibit enumeration\n\nimport type { ExhibitRef } from \"@/types\"\n\n/**\n * Deduplicate and stable-sort exhibit references.\n *\n * Deduplication uses filing-local identity key: `{accessionNo}:{sequence}`\n * - Retains first occurrence of each unique exhibit\n * - Assumes inputs are already normalized (accession hyphenated, type uppercase)\n * - Different from Phase 2 deduplication: filing-local (accessionNo:sequence)\n *   vs global (cik:accessionNo)\n *\n * Stable sort order:\n * 1. Primary: sequence NUMERIC ascending (10 comes after 2, not before)\n * 2. Secondary: filename STRING ascending (lexicographic)\n * 3. Preserves input order for ties\n *\n * CRITICAL: Sequence comparison is NUMERIC, not lexicographic.\n * - Lexicographic: \"10\" < \"2\" (wrong)\n * - Numeric: 10 > 2 (correct)\n *\n * @param exhibits - Array of exhibit references (may contain duplicates)\n * @returns Deduplicated, sorted array\n *\n * @example\n * const exhibits = [\n *   { accessionNo: \"0001193125-20-123456\", sequence: \"10\", filename: \"ex10-1.htm\", ... },\n *   { accessionNo: \"0001193125-20-123456\", sequence: \"2\", filename: \"ex10-2.htm\", ... },\n * ]\n * const result = dedupeAndSortExhibits(exhibits)\n * // result[0].sequence === \"2\" (not \"10\" — numeric sort)\n * // result[1].sequence === \"10\"\n */\nexport function dedupeAndSortExhibits(exhibits: ExhibitRef[]): ExhibitRef[] {\n  // Build identity map: \"{accessionNo}:{sequence}\" → first occurrence\n  const identityMap = new Map<string, ExhibitRef>()\n\n  for (const exhibit of exhibits) {\n    const identity = `${exhibit.accessionNo}:${exhibit.sequence}`\n\n    // Retain first occurrence only\n    if (!identityMap.has(identity)) {\n      identityMap.set(identity, exhibit)\n    }\n    // If duplicate, skip (filing-local deduplication)\n  }\n\n  // Convert map values to array\n  const deduplicated = Array.from(identityMap.values())\n\n  // Stable sort: sequence NUMERIC ascending, then filename STRING ascending\n  return deduplicated.sort((a, b) => {\n    // Primary sort: sequence NUMERIC ascending\n    const seqA = Number(a.sequence)\n    const seqB = Number(b.sequence)\n    if (seqA !== seqB) return seqA - seqB\n\n    // Secondary sort: filename STRING ascending\n    return a.filename.localeCompare(b.filename)\n  })\n}\n","// Contract exhibit (EX-10*) filtering\n\n/**\n * Determines if a normalized exhibit type is a material contract (EX-10*).\n *\n * Matches all EX-10 variants including:\n * - Base: EX-10\n * - Dotted: EX-10.1, EX-10.2, EX-10.01, EX-10.123\n * - Lettered: EX-10A, EX-10B, EX-10Z\n *\n * The input must be a normalized exhibit type (uppercase, hyphenated).\n * This function performs pattern matching only — normalization happens upstream.\n *\n * Pattern: /^EX-10(\\.\\d+|[A-Z])?$/\n * - ^EX-10 — Must start with \"EX-10\"\n * - (\\.\\d+|[A-Z])? — Optional: dot + digits OR single letter\n * - $ — Must end (rejects EX-10.1.2, EX-10AB)\n *\n * @param normalizedType - Normalized exhibit type (uppercase, hyphenated)\n * @returns true if exhibit is a material contract, false otherwise\n *\n * @example\n * isContractExhibit(\"EX-10\")     // true\n * isContractExhibit(\"EX-10.1\")   // true\n * isContractExhibit(\"EX-10A\")    // true\n * isContractExhibit(\"EX-21\")     // false\n * isContractExhibit(\"EX-99\")     // false\n * isContractExhibit(\"EX10\")      // false (no hyphen)\n */\nexport function isContractExhibit(normalizedType: string): boolean {\n  return /^EX-10(\\.\\d+|[A-Z])?$/.test(normalizedType)\n}\n","// Pure normalization functions for exhibit fields\n\nimport { ValidationError } from \"@/errors\"\n\n/**\n * Normalizes exhibit sequence number.\n *\n * Validates that the input is a numeric string. Preserves leading zeros\n * for identity uniqueness (e.g., \"001\" stays \"001\" to differentiate from \"1\"\n * in filing index tables where both may exist).\n *\n * @param input - Exhibit sequence number (may have whitespace or leading zeros)\n * @returns Trimmed numeric string with leading zeros preserved\n * @throws ValidationError if input is not a valid numeric string\n *\n * @example\n * normalizeSequence(\"1\") // \"1\"\n * normalizeSequence(\"001\") // \"001\"\n * normalizeSequence(\"  2  \") // \"2\"\n */\nexport function normalizeSequence(input: string): string {\n  const trimmed = input.trim()\n\n  // Validate numeric string only\n  if (!/^\\d+$/.test(trimmed)) {\n    throw new ValidationError(\"Invalid exhibit sequence: must be numeric\", {\n      metadata: { input },\n    })\n  }\n\n  return trimmed\n}\n\n/**\n * Normalizes exhibit type to canonical hyphenated format.\n *\n * Handles all SEC exhibit type separator variants:\n * - Underscore: EX_10.1 → EX-10.1\n * - Slash: EX/10.1 → EX-10.1\n * - Hyphen: EX-10.1 → EX-10.1 (already canonical)\n *\n * Supports dotted forms (EX-10.1) and letter suffixes (EX-10A).\n *\n * @param input - Exhibit type in any separator format\n * @returns Canonical uppercase hyphenated format (e.g., \"EX-10.1\")\n * @throws ValidationError if normalized type doesn't match expected pattern\n *\n * @example\n * normalizeExhibitType(\"ex-10.1\") // \"EX-10.1\"\n * normalizeExhibitType(\"EX_10.1\") // \"EX-10.1\"\n * normalizeExhibitType(\"EX/10.1\") // \"EX-10.1\"\n * normalizeExhibitType(\"EX-10A\") // \"EX-10A\"\n */\nexport function normalizeExhibitType(input: string): string {\n  const trimmed = input.trim()\n\n  // Convert to uppercase\n  const upper = trimmed.toUpperCase()\n\n  // Normalize separators: replace _ and / with hyphen\n  const normalized = upper.replace(/[_/]/g, \"-\")\n\n  // Validate pattern: EX-## or EX-##.## or EX-##A (letter suffix)\n  // Pattern allows single letter suffix OR dotted decimal, but not both simultaneously\n  if (!/^EX-\\d+(\\.\\d+|[A-Z])?$/.test(normalized)) {\n    throw new ValidationError(\n      \"Invalid exhibit type: must match pattern EX-##, EX-##.##, or EX-##A\",\n      {\n        metadata: { input, normalized },\n      },\n    )\n  }\n\n  return normalized\n}\n\n/**\n * Normalizes exhibit description.\n *\n * Converts empty or whitespace-only strings to undefined.\n * Trims whitespace from non-empty descriptions.\n *\n * @param input - Optional exhibit description\n * @returns Trimmed description or undefined if empty\n *\n * @example\n * normalizeDescription(\"Employment Agreement\") // \"Employment Agreement\"\n * normalizeDescription(\"  Whitespace  \") // \"Whitespace\"\n * normalizeDescription(\"\") // undefined\n * normalizeDescription(\"   \") // undefined\n * normalizeDescription(undefined) // undefined\n */\nexport function normalizeDescription(input?: string): string | undefined {\n  if (input === undefined) {\n    return undefined\n  }\n\n  const trimmed = input.trim()\n\n  return trimmed === \"\" ? undefined : trimmed\n}\n","// Filing index HTML parsing with custom table extraction (Node 18+ compatible)\n\nimport { ParseError } from \"@/errors\"\nimport type { RawExhibit } from \"./types\"\n\n/**\n * Parse exhibit metadata from SEC filing index HTML.\n *\n * Uses custom string-based HTML parsing (NO DOMParser) for Node 18+ compatibility.\n * Extracts exhibit sequence, type, description, and filename from HTML table structure.\n *\n * **Parsing strategy:**\n * 1. Extract <table> block using regex\n * 2. Split into <tr> rows\n * 3. Extract <td> cells from each row\n * 4. Clean cell content (strip tags, decode entities, trim whitespace)\n * 5. Map cells to RawExhibit fields based on position\n * 6. Skip header rows (rows containing <th> tags)\n *\n * **Expected EDGAR table structure (example):**\n * ```html\n * <table class=\"tableFile\">\n *   <tr><th>Seq</th><th>Description</th><th>Document</th><th>Type</th><th>Size</th></tr>\n *   <tr>\n *     <td>1</td>\n *     <td>EMPLOYMENT AGREEMENT</td>\n *     <td><a href=\"/Archives/edgar/data/320193/000032019320000001/exhibit10-1.htm\">exhibit10-1.htm</a></td>\n *     <td>EX-10.1</td>\n *     <td>12345</td>\n *   </tr>\n * </table>\n * ```\n *\n * **Cell mapping (0-indexed):**\n * - Cell 0: sequence\n * - Cell 1: description\n * - Cell 2: filename (extract from <a href=\"...\"> if present, else text content)\n * - Cell 3: type\n * - Cell 4+: ignored (size, date, etc.)\n *\n * @param htmlContent - Raw HTML content from filing index\n * @returns Array of raw exhibit metadata (empty array if no exhibits found in table)\n * @throws ParseError if no table found or table structure is invalid\n *\n * @example\n * const html = await client.fetchFilingIndex(filing)\n * const exhibits = parseExhibitTableFromHtml(html)\n * // [{ sequence: \"1\", type: \"EX-10.1\", description: \"...\", filename: \"...\" }]\n */\nexport function parseExhibitTableFromHtml(htmlContent: string): RawExhibit[] {\n  // Extract table block\n  const tableMatch = htmlContent.match(/<table[\\s\\S]*?<\\/table>/i)\n  if (!tableMatch) {\n    throw new ParseError(\"No table found in filing index HTML\", {\n      metadata: { htmlLength: htmlContent.length },\n    })\n  }\n\n  const tableHtml = tableMatch[0]\n\n  // Extract all rows\n  const rowMatches = tableHtml.match(/<tr[\\s\\S]*?<\\/tr>/gi)\n  if (!rowMatches || rowMatches.length === 0) {\n    throw new ParseError(\"No rows found in table\", {\n      metadata: { tableLength: tableHtml.length },\n    })\n  }\n\n  const exhibits: RawExhibit[] = []\n\n  for (const rowHtml of rowMatches) {\n    // Skip header rows (contain <th> tags)\n    if (/<th[\\s\\S]*?<\\/th>/i.test(rowHtml)) {\n      continue\n    }\n\n    // Extract cells\n    const cellMatches = rowHtml.match(/<td[\\s\\S]*?<\\/td>/gi)\n    if (!cellMatches || cellMatches.length < 4) {\n      // Skip rows with insufficient cells (may be malformed or separator rows)\n      continue\n    }\n\n    // Clean cell content and map to fields\n    const cells = cellMatches.map((cell) => cleanCellContent(cell))\n\n    const sequence = cells[0]\n    const description = cells[1]\n    const filenameCell = cells[2]\n    const type = cells[3]\n\n    // Extract filename from <a href=\"...\"> if present, else use cleaned text\n    const filename = extractFilename(filenameCell, cellMatches[2])\n\n    // Basic validation: sequence and type must be non-empty\n    if (!sequence || !type) {\n      continue\n    }\n\n    exhibits.push({\n      sequence,\n      filename,\n      type,\n      description: description || undefined,\n    })\n  }\n\n  return exhibits\n}\n\n/**\n * Clean HTML cell content: strip tags, decode entities, trim whitespace.\n */\nfunction cleanCellContent(cellHtml: string): string {\n  // Strip all HTML tags\n  let text = cellHtml.replace(/<[^>]+>/g, \"\")\n\n  // Decode common HTML entities\n  text = text\n    .replace(/&amp;/g, \"&\")\n    .replace(/&lt;/g, \"<\")\n    .replace(/&gt;/g, \">\")\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&nbsp;/g, \" \")\n\n  // Trim whitespace\n  return text.trim()\n}\n\n/**\n * Extract filename from cell HTML.\n *\n * Prefers href attribute from <a> tag, falls back to cleaned text content.\n */\nfunction extractFilename(cleanedText: string, rawCellHtml: string): string {\n  // Try to extract href from <a href=\"...\">\n  const hrefMatch = rawCellHtml.match(/<a[^>]*href=\"([^\"]+)\"/i)\n  if (hrefMatch) {\n    const href = hrefMatch[1]\n    // Extract filename from path (e.g., \"/Archives/.../ exhibit10-1.htm\" → \"exhibit10-1.htm\")\n    const pathParts = href.split(\"/\")\n    return pathParts[pathParts.length - 1] || cleanedText\n  }\n\n  // Fall back to cleaned text content\n  return cleanedText\n}\n","// ExhibitService — orchestrates exhibit enumeration with normalization and filtering\n\nimport type { SecHttpClient } from \"@/http\"\nimport type { ExhibitRef, FilingRef } from \"@/types\"\nimport { dedupeAndSortExhibits } from \"./deduplication\"\nimport { isContractExhibit } from \"./filters/contract\"\nimport { normalizeDescription, normalizeExhibitType, normalizeSequence } from \"./normalization\"\nimport { parseExhibitTableFromHtml } from \"./parsing\"\n\n/**\n * ExhibitService orchestrates exhibit enumeration flow:\n *\n * 1. Build filing index URL from FilingRef (CIK + accession)\n * 2. Fetch index HTML from SEC archive\n * 3. Parse exhibits from HTML table structure\n * 4. Normalize exhibit fields (sequence, type, description)\n * 5. Build ExhibitRef objects with full exhibit URLs\n * 6. Deduplicate and stable-sort\n *\n * All HTTP requests route through SecHttpClient for rate limiting,\n * retry, and timeout handling.\n */\nexport class ExhibitService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  /**\n   * Build filing index URL for SEC archive.\n   *\n   * URL pattern: https://www.sec.gov/Archives/edgar/data/{cik}/{accessionCompact}/{accessionNo}-index.html\n   *\n   * The SEC filing index page (with exhibit metadata table) uses the accession\n   * number as a filename prefix, NOT plain \"index.html\" (which returns a\n   * directory listing without exhibit type/sequence information).\n   *\n   * @param cik - Normalized 10-digit CIK (from FilingRef)\n   * @param accessionNo - Canonical hyphenated accession (from FilingRef)\n   * @returns Complete filing index URL\n   *\n   * @example\n   * buildFilingIndexUrl(\"0000320193\", \"0000320193-25-000008\")\n   * // \"https://www.sec.gov/Archives/edgar/data/0000320193/000032019325000008/0000320193-25-000008-index.html\"\n   */\n  private buildFilingIndexUrl(cik: string, accessionNo: string): string {\n    const accessionCompact = accessionNo.replace(/-/g, \"\")\n    return `https://www.sec.gov/Archives/edgar/data/${cik}/${accessionCompact}/${accessionNo}-index.html`\n  }\n\n  /**\n   * Build exhibit URL for SEC archive.\n   *\n   * URL pattern: https://www.sec.gov/Archives/edgar/data/{cik}/{accessionCompact}/{filename}\n   *\n   * @param cik - Normalized 10-digit CIK\n   * @param accessionNo - Canonical hyphenated accession\n   * @param filename - Exhibit filename from filing index table\n   * @returns Complete exhibit URL\n   *\n   * @example\n   * buildExhibitUrl(\"0000320193\", \"0001193125-20-123456\", \"ex10-1.htm\")\n   * // \"https://www.sec.gov/Archives/edgar/data/0000320193/000119312520123456/ex10-1.htm\"\n   */\n  private buildExhibitUrl(cik: string, accessionNo: string, filename: string): string {\n    const accessionCompact = accessionNo.replace(/-/g, \"\")\n    return `https://www.sec.gov/Archives/edgar/data/${cik}/${accessionCompact}/${filename}`\n  }\n\n  /**\n   * List all exhibits for a filing.\n   *\n   * Orchestration flow:\n   * 1. Fetch filing index HTML from SEC archive\n   * 2. Parse exhibit table from HTML\n   * 3. Normalize fields (sequence, type, description)\n   * 4. Build ExhibitRef objects with full URLs\n   * 5. Deduplicate and stable-sort\n   *\n   * @param filing - Filing reference from discovery\n   * @returns Promise resolving to normalized, deduplicated, sorted ExhibitRef array\n   * @throws TransportError if HTTP request fails (after retries)\n   * @throws ParseError if HTML table structure is invalid\n   * @throws ValidationError if exhibit fields fail normalization\n   *\n   * @example\n   * const filing = await client.discoverFilings({ cik: \"320193\", from: \"2024-01-01\", to: \"2024-12-31\" })\n   * const exhibits = await service.listExhibits(filing[0])\n   * // [{ accessionNo: \"...\", sequence: \"1\", type: \"EX-10.1\", ... }]\n   */\n  async listExhibits(filing: FilingRef): Promise<ExhibitRef[]> {\n    // 1. Build filing index URL\n    const indexUrl = this.buildFilingIndexUrl(filing.cik, filing.accessionNo)\n\n    // 2. Fetch index HTML\n    const response = await this.httpClient.request(indexUrl, {\n      context: { operation: \"listExhibits\", endpointClass: \"archive\" },\n    })\n    const htmlContent = await response.text()\n\n    // 3. Parse exhibits from HTML table\n    const rawExhibits = parseExhibitTableFromHtml(htmlContent)\n\n    // 4. Filter to exhibit rows only, normalize, and build ExhibitRef objects\n    // The SEC filing index table includes ALL documents (primary doc, graphics, XBRL, etc.)\n    // Only rows whose type normalizes to a valid exhibit pattern (EX-##) are included.\n    const normalized: ExhibitRef[] = []\n    for (const raw of rawExhibits) {\n      // Skip non-exhibit types (10-Q, GRAPHIC, EX-101.SCH, etc.)\n      let type: string\n      try {\n        type = normalizeExhibitType(raw.type)\n      } catch {\n        continue\n      }\n      normalized.push({\n        accessionNo: filing.accessionNo,\n        sequence: normalizeSequence(raw.sequence),\n        type,\n        description: normalizeDescription(raw.description),\n        filename: raw.filename,\n        exhibitUrl: this.buildExhibitUrl(filing.cik, filing.accessionNo, raw.filename),\n      })\n    }\n\n    // 5. Deduplicate and sort\n    return dedupeAndSortExhibits(normalized)\n  }\n\n  /**\n   * List only material contract exhibits (EX-10*) for a filing.\n   *\n   * Delegates to listExhibits() then filters by contract pattern.\n   *\n   * @param filing - Filing reference from discovery\n   * @returns Promise resolving to filtered ExhibitRef array (only EX-10* exhibits)\n   * @throws TransportError if HTTP request fails (after retries)\n   * @throws ParseError if HTML table structure is invalid\n   * @throws ValidationError if exhibit fields fail normalization\n   *\n   * @example\n   * const filing = await client.discoverFilings({ cik: \"320193\", from: \"2024-01-01\", to: \"2024-12-31\" })\n   * const contracts = await service.listContractExhibits(filing[0])\n   * // [{ accessionNo: \"...\", sequence: \"1\", type: \"EX-10.1\", ... }] (only EX-10*)\n   */\n  async listContractExhibits(filing: FilingRef): Promise<ExhibitRef[]> {\n    // 1. Get all exhibits\n    const allExhibits = await this.listExhibits(filing)\n\n    // 2. Filter to contracts only\n    return allExhibits.filter((e) => isContractExhibit(e.type))\n  }\n}\n","// HTTP status code to typed error classification\n// Maps SEC EDGAR responses to retryable/non-retryable errors\n\nimport type { EdgarError } from \"@/errors\"\nimport { NotFoundError, RateLimitedError, TimeoutError, TransportError } from \"@/errors\"\n\n/**\n * Classify HTTP response error by status code\n *\n * Maps HTTP status codes to typed errors with correct retryability flags.\n * This function should only be called for non-2xx responses.\n *\n * Retryability rules:\n * - 5xx server errors: retryable (transient failures)\n * - 429 rate limited: retryable (external throttling)\n * - 408 request timeout: retryable (server timeout, not client)\n * - 404 not found: non-retryable (permanent condition)\n * - Other 4xx client errors: non-retryable (malformed request)\n *\n * @param statusCode - HTTP response status code\n * @param url - Request URL for error metadata\n * @returns Typed EdgarError with appropriate retryability flag\n *\n * @example\n * classifyResponseError(503, url) // → TransportError(retryable=true)\n * classifyResponseError(404, url) // → NotFoundError(retryable=false)\n * classifyResponseError(429, url) // → RateLimitedError(retryable=true)\n */\nexport function classifyResponseError(statusCode: number, url: string): EdgarError {\n  const metadata = { statusCode, url }\n\n  // 5xx Server Errors: retryable (transient failures)\n  if (statusCode >= 500 && statusCode <= 599) {\n    return new TransportError(`HTTP ${statusCode} from ${url}`, true, { metadata })\n  }\n\n  // 429 Too Many Requests: always retryable (external rate limiting)\n  if (statusCode === 429) {\n    return new RateLimitedError(`HTTP 429 Too Many Requests from ${url}`, { metadata })\n  }\n\n  // 408 Request Timeout: retryable (server timeout, not client-side timeout)\n  if (statusCode === 408) {\n    return new TimeoutError(`HTTP 408 Request Timeout from ${url}`, { metadata })\n  }\n\n  // 404 Not Found: non-retryable (filing/exhibit does not exist)\n  if (statusCode === 404) {\n    return new NotFoundError(`HTTP 404 Not Found: ${url}`, { metadata })\n  }\n\n  // Other 4xx Client Errors: non-retryable (malformed request, auth, etc.)\n  if (statusCode >= 400 && statusCode <= 499) {\n    return new TransportError(`HTTP ${statusCode} from ${url}`, false, { metadata })\n  }\n\n  // All other status codes: non-retryable (unexpected status)\n  return new TransportError(`Unexpected HTTP ${statusCode} from ${url}`, false, {\n    metadata,\n  })\n}\n","// Token bucket rate limiter for SEC EDGAR compliance\n\n// setTimeout is available globally in Node 18+ and Bun\ndeclare const setTimeout: (callback: () => void, ms: number) => unknown\n\n/**\n * Token bucket rate limiter with configurable refill rate.\n * Enforces steady-state rate limiting with controlled burst on startup.\n *\n * Implementation follows SEC compliance requirements:\n * - Default: 8 requests/second\n * - Bounded: 1-10 requests/second\n * - Capacity equals refill rate (prevents burst abuse)\n * - Refill happens on every acquire() call (no background timer)\n * - Sequential processing via promise chain ensures fairness\n */\nexport class TokenBucket {\n  private tokens: number\n  private lastRefillTime: number\n  private readonly refillRatePerMs: number\n  private readonly capacity: number\n  private lastAcquire: Promise<void> = Promise.resolve()\n\n  /**\n   * Create a new token bucket rate limiter.\n   *\n   * @param requestsPerSecond - Rate limit in requests per second (1-10)\n   * @throws Error if rate is outside SEC compliance bounds\n   */\n  constructor(requestsPerSecond: number) {\n    // Enforce SEC compliance bounds\n    if (requestsPerSecond < 1 || requestsPerSecond > 10) {\n      throw new Error(\"Rate must be 1-10 requests/second\")\n    }\n\n    // Capacity equals rate to prevent burst escape\n    this.capacity = requestsPerSecond\n\n    // Start full to allow one burst on startup\n    this.tokens = requestsPerSecond\n\n    // Convert rate to tokens per millisecond for precise timing\n    this.refillRatePerMs = requestsPerSecond / 1000\n\n    // Track last refill for elapsed time calculation\n    this.lastRefillTime = Date.now()\n  }\n\n  /**\n   * Acquire tokens from the bucket.\n   * If tokens available: returns immediately.\n   * If insufficient tokens: waits until tokens refill.\n   *\n   * @param count - Number of tokens to acquire (default: 1)\n   * @returns Promise that resolves when tokens are acquired\n   */\n  async acquire(count: number = 1): Promise<void> {\n    // Chain this acquire after the previous one to ensure sequential processing\n    const myAcquire = this.lastAcquire.then(async () => {\n      const now = Date.now()\n      const elapsedMs = now - this.lastRefillTime\n\n      // Refill tokens based on elapsed time\n      this.tokens = Math.min(this.capacity, this.tokens + elapsedMs * this.refillRatePerMs)\n      this.lastRefillTime = now\n\n      // Tokens available: acquire immediately\n      if (this.tokens >= count) {\n        this.tokens -= count\n        return\n      }\n\n      // Tokens insufficient: calculate wait time for the full deficit\n      const deficit = count - this.tokens\n      const waitMs = deficit / this.refillRatePerMs\n\n      // Deduct all available tokens (partial payment toward the request)\n      this.tokens = 0\n\n      // Update lastRefillTime to account for the wait period\n      // After waiting, we'll have exactly the tokens we need\n      this.lastRefillTime = now + waitMs\n\n      return new Promise<void>((resolve) => {\n        setTimeout(resolve, waitMs)\n      })\n    })\n\n    this.lastAcquire = myAcquire\n    return myAcquire\n  }\n}\n","// Exponential backoff with full jitter for retry policy\n// AWS best practice: uniformly distributed delays prevent thundering herd\n\nimport type { RetryOptions } from \"@/types\"\n\n/**\n * Calculate exponential backoff delay with full jitter\n *\n * Formula: random(0, min(maxDelayMs, baseDelayMs * 2^attempt))\n *\n * Full jitter ensures uniformly distributed delays across concurrent clients,\n * preventing synchronized retry spikes (\"thundering herd\" problem).\n *\n * @param attempt - Zero-indexed retry attempt (0 = first retry)\n * @param policy - Retry policy configuration\n * @returns Delay in milliseconds (integer)\n * @throws Error if attempt is out of bounds\n *\n * @example\n * // Default policy: baseDelayMs=250, maxDelayMs=4000, maxAttempts=3\n * calculateBackoffMs(0, defaultPolicy) // → random(0, 250)\n * calculateBackoffMs(1, defaultPolicy) // → random(0, 500)\n * calculateBackoffMs(2, defaultPolicy) // → random(0, 1000)\n */\nexport function calculateBackoffMs(attempt: number, policy: RetryOptions): number {\n  // Validate attempt bounds\n  if (attempt < 0) {\n    throw new Error(`Invalid attempt: ${attempt} (must be >= 0)`)\n  }\n\n  if (attempt >= policy.maxAttempts) {\n    throw new Error(`Invalid attempt: ${attempt} (must be < maxAttempts=${policy.maxAttempts})`)\n  }\n\n  // Calculate exponential cap: baseDelayMs * 2^attempt\n  const exponentialCap = policy.baseDelayMs * 2 ** attempt\n\n  // Apply max delay ceiling\n  const maxJitter = Math.min(policy.maxDelayMs, exponentialCap)\n\n  // Full jitter: uniformly distributed in [0, maxJitter)\n  return Math.floor(Math.random() * maxJitter)\n}\n","declare const process: { versions: { bun?: string; node: string } }\n\n/**\n * Detect runtime environment (Node.js or Bun).\n * Cached at module load time for performance.\n */\nconst cachedRuntime: \"node\" | \"bun\" = process.versions.bun ? \"bun\" : \"node\"\n\nexport function getRuntime(): \"node\" | \"bun\" {\n  return cachedRuntime\n}\n","// Timeout and abort signal composition for SEC HTTP client\n\nimport { TimeoutError, TransportError } from \"@/errors\"\n\n// Web-standard APIs available globally in Node 18+ and Bun\ndeclare const setTimeout: (callback: () => void, ms: number) => unknown\ndeclare const fetch: (url: string, init?: { signal?: AbortSignal }) => Promise<Response>\ndeclare class AbortController {\n  signal: AbortSignal\n  abort(reason?: unknown): void\n}\ndeclare class AbortSignal {\n  static timeout(ms: number): AbortSignal\n  readonly aborted: boolean\n  readonly reason: unknown\n  addEventListener(event: string, listener: () => void): void\n}\ndeclare class Response {\n  readonly status: number\n}\n\n/**\n * Combine multiple AbortSignals into a single signal.\n * Polyfill for AbortSignal.any() (available natively in Node 22+).\n *\n * @param signals - Array of AbortSignals to combine\n * @returns Combined AbortSignal that fires when any input signal fires\n */\nexport function combineSignals(signals: AbortSignal[]): AbortSignal {\n  const controller = new AbortController()\n\n  for (const signal of signals) {\n    // If already aborted, abort immediately with that reason\n    if (signal.aborted) {\n      controller.abort(signal.reason)\n      return controller.signal\n    }\n\n    // Listen for abort events\n    signal.addEventListener(\"abort\", () => {\n      if (!controller.signal.aborted) {\n        controller.abort(signal.reason)\n      }\n    })\n  }\n\n  return controller.signal\n}\n\n/**\n * Fetch with timeout and optional caller abort signal.\n * Differentiates between library timeout and caller abort for proper error classification.\n *\n * @param url - URL to fetch\n * @param timeoutMs - Timeout in milliseconds\n * @param userSignal - Optional caller-provided AbortSignal\n * @returns Promise resolving to Response\n * @throws TimeoutError if library timeout fires\n * @throws TransportError if caller aborts\n * @throws Original error for other fetch failures\n */\nexport async function fetchWithTimeoutAndAbort(\n  url: string,\n  timeoutMs: number,\n  userSignal?: AbortSignal,\n): Promise<Response> {\n  // Create internal timeout signal\n  const timeoutSignal = AbortSignal.timeout(timeoutMs)\n\n  // Compose with user signal if provided\n  let composedSignal: AbortSignal = timeoutSignal\n  if (userSignal) {\n    composedSignal = combineSignals([userSignal, timeoutSignal])\n  }\n\n  try {\n    const response = await fetch(url, { signal: composedSignal })\n    return response\n  } catch (error) {\n    // Differentiate timeout from user abort\n    if (timeoutSignal.aborted && !userSignal?.aborted) {\n      throw new TimeoutError(`Request timeout after ${timeoutMs}ms`, {\n        metadata: { url, timeoutMs, attempt: 1 },\n      })\n    }\n\n    if (userSignal?.aborted) {\n      throw new TransportError(\"Request cancelled by caller\", false, {\n        metadata: { url, cancelled: true },\n      })\n    }\n\n    // Re-throw original error for other failures\n    throw error\n  }\n}\n","// SecHttpClient — orchestrates rate limiting, timeout, retry, error classification, and telemetry\n\nimport type { EdgarError } from \"@/errors\"\nimport { ConfigurationError, TimeoutError, TransportError } from \"@/errors\"\nimport type { EdgarClientOptions, FetchFn, FetchResponse } from \"@/types\"\nimport { classifyResponseError } from \"./error-mapper\"\nimport { TokenBucket } from \"./limiter\"\nimport { calculateBackoffMs } from \"./retry\"\nimport { getRuntime } from \"./runtime\"\nimport { combineSignals } from \"./timeout\"\n\n// Web-standard APIs available globally in Node 18+ and Bun\ndeclare const setTimeout: (callback: () => void, ms: number) => unknown\ndeclare const fetch: (\n  url: string,\n  init?: { signal?: AbortSignal; headers?: Headers },\n) => Promise<FetchResponse>\ndeclare class Headers {\n  constructor(init?: Record<string, string>)\n  has(name: string): boolean\n  set(name: string, value: string): void\n  get(name: string): string | null\n}\ndeclare class AbortSignal {\n  static timeout(ms: number): AbortSignal\n  readonly aborted: boolean\n  readonly reason: unknown\n  addEventListener(event: string, listener: () => void): void\n}\n\ntype RequestContext = {\n  readonly operation: string\n  readonly endpointClass: string\n}\n\ntype HttpRequestInit = {\n  method?: string\n  headers?: Record<string, string> | Headers\n  body?: unknown\n  signal?: AbortSignal\n  context?: RequestContext\n}\n\nconst DEFAULT_MAX_REQUESTS_PER_SECOND = 8\nconst DEFAULT_TIMEOUT_MS = 10_000\nconst DEFAULT_RETRIES = {\n  maxAttempts: 3,\n  baseDelayMs: 250,\n  maxDelayMs: 4_000,\n}\n\n/**\n * SEC HTTP client with rate limiting, timeout, retry, and telemetry.\n *\n * Orchestrates all five transport concerns:\n * 1. Rate limiting: TokenBucket enforces SEC compliance (default 8 req/s)\n * 2. Timeout: AbortSignal-based timeout with user signal composition\n * 3. Retry: Exponential backoff with full jitter on retryable errors\n * 4. Error classification: HTTP status → typed error with retryability flags\n * 5. Telemetry: Optional hooks for observability\n *\n * All requests flow through the request() method in this sequence:\n * - Validate user-agent header\n * - Retry loop (up to maxAttempts):\n *   - Acquire rate limiter token\n *   - Fire onRequestStart telemetry\n *   - Fetch with timeout\n *   - Fire onRequestEnd telemetry\n *   - Check response.ok\n *   - If error and retryable: backoff + retry\n *   - If error and non-retryable: throw immediately\n */\nexport class SecHttpClient {\n  private readonly limiter: TokenBucket\n  private readonly userAgent: string\n  private readonly timeoutMs: number\n  private readonly retries: Required<NonNullable<EdgarClientOptions[\"retries\"]>>\n  private readonly telemetry?: EdgarClientOptions[\"telemetry\"]\n  private readonly fetchFn: FetchFn\n\n  constructor(options: EdgarClientOptions) {\n    // Validate user-agent (SEC compliance requirement)\n    if (!options.userAgent || options.userAgent.trim().length === 0) {\n      throw new ConfigurationError(\"userAgent is required and must be non-empty\")\n    }\n\n    this.userAgent = options.userAgent.trim()\n\n    // Initialize rate limiter\n    const maxRequestsPerSecond = options.maxRequestsPerSecond ?? DEFAULT_MAX_REQUESTS_PER_SECOND\n    if (maxRequestsPerSecond < 1 || maxRequestsPerSecond > 10) {\n      throw new ConfigurationError(`maxRequestsPerSecond must be 1-10, got ${maxRequestsPerSecond}`)\n    }\n    this.limiter = new TokenBucket(maxRequestsPerSecond)\n\n    // Store timeout configuration\n    this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n    if (this.timeoutMs <= 0) {\n      throw new ConfigurationError(`timeoutMs must be > 0, got ${this.timeoutMs}`)\n    }\n\n    // Store retry configuration\n    this.retries = { ...DEFAULT_RETRIES, ...options.retries }\n    if (this.retries.maxAttempts < 1) {\n      throw new ConfigurationError(\n        `retries.maxAttempts must be >= 1, got ${this.retries.maxAttempts}`,\n      )\n    }\n\n    // Store optional telemetry hooks\n    this.telemetry = options.telemetry\n\n    // Store custom fetch or fall back to global\n    this.fetchFn = options.fetch ?? (fetch as FetchFn)\n  }\n\n  /**\n   * Execute HTTP request with rate limiting, timeout, retry, and telemetry.\n   *\n   * @param url - URL to fetch\n   * @param init - Fetch options (headers, method, signal, context, etc.)\n   * @returns Promise resolving to Response (caller handles response.ok check and body parsing)\n   * @throws EdgarError on retryable errors after exhausting retries, or non-retryable errors immediately\n   */\n  async request(url: string, init?: HttpRequestInit): Promise<FetchResponse> {\n    // Ensure User-Agent header is set (SEC compliance)\n    const headers = new Headers(\n      init?.headers instanceof Headers ? {} : (init?.headers as Record<string, string>),\n    )\n    if (!headers.has(\"User-Agent\")) {\n      headers.set(\"User-Agent\", this.userAgent)\n    }\n\n    const method = init?.method ?? \"GET\"\n    const userSignal = init?.signal\n\n    // Extract context (operation, endpointClass) from request options\n    const operation = init?.context?.operation ?? \"unknown\"\n    const endpointClass = init?.context?.endpointClass ?? \"unknown\"\n\n    // Get runtime environment\n    const runtime = getRuntime()\n\n    // Retry loop\n    let attempt = 0\n    while (attempt < this.retries.maxAttempts) {\n      // Generate unique requestId for each HTTP attempt\n      const requestId = (\n        globalThis as unknown as { crypto: { randomUUID: () => string } }\n      ).crypto.randomUUID()\n\n      try {\n        // 1. Acquire rate limiter token\n        await this.limiter.acquire(1)\n\n        // 2. Fire telemetry: onRequestStart\n        this.telemetry?.onRequestStart?.({\n          url,\n          method,\n          timestamp: Date.now(),\n          requestId,\n          operation,\n          endpointClass,\n          runtime,\n        })\n\n        // 3. Fetch with timeout and abort composition\n        const startTime = Date.now()\n\n        // Create internal timeout signal\n        const timeoutSignal = AbortSignal.timeout(this.timeoutMs)\n\n        // Compose with user signal if provided\n        const composedSignal = userSignal\n          ? combineSignals([userSignal, timeoutSignal])\n          : timeoutSignal\n\n        let response: FetchResponse\n        try {\n          response = await this.fetchFn(url, { signal: composedSignal, headers })\n        } catch (error) {\n          // Differentiate timeout from user abort\n          if (timeoutSignal.aborted && !userSignal?.aborted) {\n            throw new TimeoutError(`Request timeout after ${this.timeoutMs}ms`, {\n              metadata: { url, timeoutMs: this.timeoutMs, attempt: attempt + 1 },\n            })\n          }\n\n          if (userSignal?.aborted) {\n            throw new TransportError(\"Request cancelled by caller\", false, {\n              metadata: { url, cancelled: true },\n            })\n          }\n\n          // Re-throw original error for other failures\n          throw error\n        }\n\n        const durationMs = Date.now() - startTime\n\n        // 4. Fire telemetry: onRequestEnd\n        this.telemetry?.onRequestEnd?.({\n          url,\n          method,\n          statusCode: response.status,\n          durationMs,\n          timestamp: Date.now(),\n          requestId,\n          operation,\n          endpointClass,\n          runtime,\n        })\n\n        // 5. Check response.ok\n        if (!response.ok) {\n          const error = classifyResponseError(response.status, url)\n          throw error\n        }\n\n        // Success: return response\n        return response\n      } catch (error) {\n        // Classify error\n        const typedError: EdgarError =\n          error instanceof Error && \"retryable\" in error && typeof error.retryable === \"boolean\"\n            ? (error as EdgarError)\n            : new TransportError(error instanceof Error ? error.message : String(error), true, {\n                cause: error,\n              })\n\n        // If non-retryable or last attempt: throw immediately\n        if (!typedError.retryable || attempt === this.retries.maxAttempts - 1) {\n          throw typedError\n        }\n\n        // Retryable error: calculate backoff and retry\n        const delayMs = calculateBackoffMs(attempt, this.retries)\n\n        // Fire telemetry: onRetry\n        this.telemetry?.onRetry?.({\n          url,\n          attempt: attempt + 1,\n          maxAttempts: this.retries.maxAttempts,\n          delayMs,\n          error: typedError.code,\n          timestamp: Date.now(),\n          requestId,\n          operation,\n          endpointClass,\n          runtime,\n        })\n\n        // Wait for backoff delay\n        await new Promise<void>((resolve) => {\n          setTimeout(resolve, delayMs)\n        })\n\n        // Increment attempt counter and retry\n        attempt++\n      }\n    }\n\n    // Should never reach here (loop always returns or throws)\n    throw new TransportError(\"Exhausted retry attempts\", false)\n  }\n}\n","// SearchService — wrap SEC EFTS full-text search (unofficial/undocumented API)\n\nimport type { SecHttpClient } from \"@/http\"\nimport { fetchJson } from \"@/http/fetch-json\"\n\nexport type SearchQuery = {\n  q: string\n  formTypes?: string[]\n  from?: string\n  to?: string\n  entity?: string\n  start?: number\n}\n\nexport type SearchResult = {\n  total: number\n  hits: SearchHit[]\n}\n\nexport type SearchHit = {\n  id: string\n  entityName: string\n  fileNumber?: string\n  formType: string\n  fileDate: string\n  fileDescription?: string\n  periodOfReport?: string\n  score: number\n}\n\ntype EftsResponse = {\n  hits: {\n    total: { value: number }\n    hits: Array<{\n      _id: string\n      _score: number\n      _source: {\n        display_names?: string[]\n        file_num?: string[]\n        form?: string\n        root_forms?: string[]\n        file_date?: string\n        file_description?: string\n        period_ending?: string\n        adsh?: string\n      }\n    }>\n  }\n}\n\nexport class SearchService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  async searchFilings(query: SearchQuery): Promise<SearchResult> {\n    const url = this.buildUrl(query)\n\n    const data = await fetchJson<EftsResponse>(url, this.httpClient, {\n      operation: \"searchFilings\",\n      endpointClass: \"efts\",\n    })\n\n    return {\n      total: data.hits.total.value,\n      hits: data.hits.hits.map((hit) => ({\n        id: hit._id,\n        entityName: hit._source.display_names?.[0] ?? \"\",\n        fileNumber: hit._source.file_num?.[0],\n        formType: hit._source.form ?? hit._source.root_forms?.[0] ?? \"\",\n        fileDate: hit._source.file_date ?? \"\",\n        fileDescription: hit._source.file_description,\n        periodOfReport: hit._source.period_ending,\n        score: hit._score,\n      })),\n    }\n  }\n\n  private buildUrl(query: SearchQuery): string {\n    const parts: string[] = [`q=${encodeURIComponent(query.q)}`]\n\n    if (query.formTypes?.length) {\n      parts.push(`forms=${encodeURIComponent(query.formTypes.join(\",\"))}`)\n    }\n\n    if (query.from || query.to) {\n      parts.push(\"dateRange=custom\")\n      if (query.from) parts.push(`startdt=${encodeURIComponent(query.from)}`)\n      if (query.to) parts.push(`enddt=${encodeURIComponent(query.to)}`)\n    }\n\n    if (query.entity) {\n      parts.push(`entity=${encodeURIComponent(query.entity)}`)\n    }\n\n    if (query.start !== undefined) {\n      parts.push(`from=${query.start}`)\n    }\n\n    return `https://efts.sec.gov/LATEST/search-index?${parts.join(\"&\")}`\n  }\n}\n","// XbrlService — typed access to SEC XBRL REST APIs (Layer 1)\n\nimport { normalizeCik } from \"@/discovery/normalization\"\nimport type { SecHttpClient } from \"@/http\"\nimport { fetchJson } from \"@/http/fetch-json\"\n\nexport type CompanyFacts = {\n  cik: number\n  entityName: string\n  facts: Record<string, Record<string, FactEntry>>\n}\n\nexport type FactEntry = {\n  label: string\n  description?: string\n  units: Record<string, FactValue[]>\n}\n\nexport type FactValue = {\n  val: number\n  accn: string\n  fy?: number\n  fp?: string\n  form?: string\n  filed?: string\n  start?: string\n  end?: string\n}\n\nexport type CompanyConcept = {\n  cik: number\n  taxonomy: string\n  tag: string\n  label?: string\n  entityName?: string\n  units: Record<string, FactValue[]>\n}\n\nexport type Frame = {\n  taxonomy: string\n  tag: string\n  ccp?: string\n  uom?: string\n  label?: string\n  pts?: number\n  data: FrameEntry[]\n}\n\nexport type FrameEntry = {\n  accn: string\n  cik: number\n  entityName: string\n  val: number\n  start?: string\n  end?: string\n}\n\nexport class XbrlService {\n  constructor(private readonly httpClient: SecHttpClient) {}\n\n  async getCompanyFacts(cik: string): Promise<CompanyFacts> {\n    const normalizedCik = normalizeCik(cik)\n    return fetchJson<CompanyFacts>(\n      `https://data.sec.gov/api/xbrl/companyfacts/CIK${normalizedCik}.json`,\n      this.httpClient,\n      { operation: \"getCompanyFacts\", endpointClass: \"xbrl\" },\n    )\n  }\n\n  async getCompanyConcept(cik: string, taxonomy: string, tag: string): Promise<CompanyConcept> {\n    const normalizedCik = normalizeCik(cik)\n    return fetchJson<CompanyConcept>(\n      `https://data.sec.gov/api/xbrl/companyconcept/CIK${normalizedCik}/${taxonomy}/${tag}.json`,\n      this.httpClient,\n      { operation: \"getCompanyConcept\", endpointClass: \"xbrl\" },\n    )\n  }\n\n  async getFrame(taxonomy: string, tag: string, unit: string, period: string): Promise<Frame> {\n    return fetchJson<Frame>(\n      `https://data.sec.gov/api/xbrl/frames/${taxonomy}/${tag}/${unit}/${period}.json`,\n      this.httpClient,\n      { operation: \"getFrame\", endpointClass: \"xbrl\" },\n    )\n  }\n}\n","import type { BulkDownloadResult } from \"@/bulk\"\nimport { BulkDataService } from \"@/bulk\"\nimport { CompanyService } from \"@/company\"\nimport { DiscoveryService } from \"@/discovery\"\nimport { DownloadService } from \"@/download\"\nimport { ConfigurationError } from \"@/errors\"\nimport { ExhibitService } from \"@/exhibits\"\nimport { SecHttpClient } from \"@/http\"\nimport type { SearchQuery, SearchResult } from \"@/search\"\nimport { SearchService } from \"@/search\"\nimport type {\n  CompanyInfo,\n  CompanyTicker,\n  DiscoverFilingsInput,\n  DownloadedExhibit,\n  EdgarClientOptions,\n  ExhibitRef,\n  FilingRef,\n  RetryOptions,\n} from \"@/types\"\nimport type { CompanyConcept, CompanyFacts, Frame } from \"@/xbrl\"\nimport { XbrlService } from \"@/xbrl\"\n\nconst DEFAULT_MAX_REQUESTS_PER_SECOND = 8\nconst DEFAULT_TIMEOUT_MS = 10_000\nconst DEFAULT_RETRY: RetryOptions = {\n  maxAttempts: 3,\n  baseDelayMs: 250,\n  maxDelayMs: 4_000,\n}\n\nexport class EdgarClient {\n  private readonly options: Required<\n    Pick<EdgarClientOptions, \"userAgent\" | \"maxRequestsPerSecond\" | \"timeoutMs\">\n  > & { retries: RetryOptions; telemetry: EdgarClientOptions[\"telemetry\"]; fetch?: EdgarClientOptions[\"fetch\"] }\n  private readonly httpClient: SecHttpClient\n  private readonly bulkDataService: BulkDataService\n  private readonly searchService: SearchService\n  private readonly xbrlService: XbrlService\n  private readonly companyService: CompanyService\n  private readonly discoveryService: DiscoveryService\n  private readonly exhibitService: ExhibitService\n  private readonly downloadService: DownloadService\n\n  constructor(options: EdgarClientOptions) {\n    if (!options.userAgent || options.userAgent.trim().length === 0) {\n      throw new ConfigurationError(\"userAgent is required and must be non-empty\")\n    }\n\n    this.options = {\n      userAgent: options.userAgent.trim(),\n      maxRequestsPerSecond: options.maxRequestsPerSecond ?? DEFAULT_MAX_REQUESTS_PER_SECOND,\n      timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n      retries: { ...DEFAULT_RETRY, ...options.retries },\n      telemetry: options.telemetry,\n      fetch: options.fetch,\n    }\n\n    // Initialize HTTP client and services\n    this.httpClient = new SecHttpClient(this.options)\n    this.bulkDataService = new BulkDataService(this.httpClient)\n    this.companyService = new CompanyService(this.httpClient)\n    this.searchService = new SearchService(this.httpClient)\n    this.xbrlService = new XbrlService(this.httpClient)\n    this.discoveryService = new DiscoveryService(this.httpClient)\n    this.exhibitService = new ExhibitService(this.httpClient)\n    this.downloadService = new DownloadService(this.httpClient)\n  }\n\n  async getCompanyInfo(cik: string): Promise<CompanyInfo> {\n    return this.companyService.getCompanyInfo(cik)\n  }\n\n  async lookupCompany(query: string): Promise<CompanyTicker[]> {\n    return this.companyService.lookupCompany(query)\n  }\n\n  async discoverFilings(input: DiscoverFilingsInput): Promise<FilingRef[]> {\n    return this.discoveryService.discoverFilings(input)\n  }\n\n  async listExhibits(filing: FilingRef): Promise<ExhibitRef[]> {\n    return this.exhibitService.listExhibits(filing)\n  }\n\n  async listContractExhibits(filing: FilingRef): Promise<ExhibitRef[]> {\n    return this.exhibitService.listContractExhibits(filing)\n  }\n\n  async downloadExhibit(exhibit: ExhibitRef): Promise<DownloadedExhibit> {\n    return this.downloadService.downloadExhibit(exhibit)\n  }\n\n  async downloadSubmissionsBulk(): Promise<BulkDownloadResult> {\n    return this.bulkDataService.downloadSubmissionsBulk()\n  }\n\n  async downloadCompanyFactsBulk(): Promise<BulkDownloadResult> {\n    return this.bulkDataService.downloadCompanyFactsBulk()\n  }\n\n  async getCompanyFacts(cik: string): Promise<CompanyFacts> {\n    return this.xbrlService.getCompanyFacts(cik)\n  }\n\n  async getCompanyConcept(cik: string, taxonomy: string, tag: string): Promise<CompanyConcept> {\n    return this.xbrlService.getCompanyConcept(cik, taxonomy, tag)\n  }\n\n  async getFrame(taxonomy: string, tag: string, unit: string, period: string): Promise<Frame> {\n    return this.xbrlService.getFrame(taxonomy, tag, unit, period)\n  }\n\n  async searchFilings(query: SearchQuery): Promise<SearchResult> {\n    return this.searchService.searchFilings(query)\n  }\n}\n"],"mappings":"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"}