open-alex-wrapper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +437 -0
- package/dist/cli.cjs +1880 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +31 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +1848 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-CLRZTPFv.d.cts +1266 -0
- package/dist/client-CLRZTPFv.d.ts +1266 -0
- package/dist/index.cjs +1831 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +1750 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/export.ts","../src/cache.ts","../src/config.ts","../src/errors.ts","../src/budget.ts","../src/throttle.ts","../src/http.ts","../src/pagination.ts","../src/query.ts","../src/ids.ts","../src/entities.ts","../src/works.ts","../src/snapshot.ts","../src/planner.ts","../src/ngrams.ts","../src/client.ts","../src/cli.ts"],"sourcesContent":["/**\n * Stream any async-iterable of entities to a file, without buffering the whole\n * result set in memory. Formats: JSONL (NDJSON), JSON array, or CSV.\n */\n\nimport { createWriteStream } from 'node:fs'\nimport type { Writable } from 'node:stream'\n\nexport type ExportFormat = 'jsonl' | 'csv' | 'json'\n\nexport interface ExportOptions {\n format?: ExportFormat\n /** For CSV: flatten nested objects to dotted keys (default true). */\n flatten?: boolean\n}\n\nexport async function exportToFile<T>(\n source: AsyncIterable<T>,\n path: string,\n opts: ExportOptions = {},\n): Promise<{ path: string; count: number }> {\n const format = opts.format ?? inferFormat(path)\n const stream = createWriteStream(path, { encoding: 'utf8' })\n const write = makeWriter(stream)\n let count = 0\n\n try {\n if (format === 'jsonl') {\n for await (const item of source) {\n await write(JSON.stringify(item) + '\\n')\n count++\n }\n } else if (format === 'json') {\n await write('[')\n let first = true\n for await (const item of source) {\n await write((first ? '' : ',\\n') + JSON.stringify(item))\n first = false\n count++\n }\n await write(']\\n')\n } else {\n let headers: string[] | null = null\n for await (const item of source) {\n const row =\n opts.flatten === false\n ? (item as Record<string, unknown>)\n : flatten(item as Record<string, unknown>)\n if (!headers) {\n headers = Object.keys(row)\n await write(headers.map(csvCell).join(',') + '\\n')\n }\n await write(headers.map((h) => csvCell(row[h])).join(',') + '\\n')\n count++\n }\n }\n } finally {\n await endStream(stream)\n }\n\n return { path, count }\n}\n\nfunction makeWriter(stream: Writable): (s: string) => Promise<void> {\n return (s: string) =>\n new Promise<void>((resolve, reject) => {\n stream.write(s, (err) => {\n if (err) reject(err)\n })\n if (stream.writableNeedDrain) stream.once('drain', resolve)\n else resolve()\n })\n}\n\nfunction endStream(stream: Writable): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n stream.end((err?: Error | null) => (err ? reject(err) : resolve()))\n })\n}\n\nfunction inferFormat(path: string): ExportFormat {\n const p = path.toLowerCase()\n if (p.endsWith('.jsonl') || p.endsWith('.ndjson')) return 'jsonl'\n if (p.endsWith('.csv')) return 'csv'\n if (p.endsWith('.json')) return 'json'\n return 'jsonl'\n}\n\nexport function flatten(\n obj: Record<string, unknown>,\n prefix = '',\n out: Record<string, unknown> = {},\n): Record<string, unknown> {\n for (const [k, v] of Object.entries(obj)) {\n const key = prefix ? `${prefix}.${k}` : k\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n flatten(v as Record<string, unknown>, key, out)\n } else {\n out[key] = v\n }\n }\n return out\n}\n\nfunction csvCell(value: unknown): string {\n if (value === null || value === undefined) return ''\n const s = typeof value === 'object' ? JSON.stringify(value) : String(value)\n if (/[\",\\n\\r]/.test(s)) return `\"${s.replace(/\"/g, '\"\"')}\"`\n return s\n}\n","/**\n * Optional response caching.\n *\n * A tiny dependency-free LRU with per-entry TTL, plus a {@link CacheStore}\n * interface so callers can plug in Redis, a file cache, etc. Caching is\n * opt-in (`cache: true` or a custom store) and only ever applies to GETs.\n */\n\nexport interface CacheStore {\n get(key: string): unknown | undefined | Promise<unknown | undefined>\n set(key: string, value: unknown, ttlMs: number): void | Promise<void>\n delete?(key: string): void | Promise<void>\n clear?(): void | Promise<void>\n}\n\ninterface Entry {\n value: unknown\n expiresAt: number\n}\n\n/** In-memory LRU cache with TTL eviction. */\nexport class LRUCache implements CacheStore {\n private readonly map = new Map<string, Entry>()\n\n constructor(private readonly maxEntries = 1000) {}\n\n get(key: string): unknown | undefined {\n const entry = this.map.get(key)\n if (!entry) return undefined\n if (entry.expiresAt !== 0 && entry.expiresAt < Date.now()) {\n this.map.delete(key)\n return undefined\n }\n // Refresh recency: re-insert so it becomes the newest entry.\n this.map.delete(key)\n this.map.set(key, entry)\n return entry.value\n }\n\n set(key: string, value: unknown, ttlMs: number): void {\n if (this.map.has(key)) this.map.delete(key)\n this.map.set(key, { value, expiresAt: ttlMs > 0 ? Date.now() + ttlMs : 0 })\n while (this.map.size > this.maxEntries) {\n const oldest = this.map.keys().next().value\n if (oldest === undefined) break\n this.map.delete(oldest)\n }\n }\n\n delete(key: string): void {\n this.map.delete(key)\n }\n\n clear(): void {\n this.map.clear()\n }\n\n get size(): number {\n return this.map.size\n }\n}\n","/**\n * Client configuration and default resolution.\n */\n\nimport { LRUCache, type CacheStore } from './cache'\nimport type { ResolvedThrottle } from './throttle'\n\nexport interface ThrottleOptions {\n /** Target requests/second. Default 10. */\n maxRequestsPerSecond?: number\n /** Hard-stop (throws BudgetExceededError) once remaining USD drops below this. */\n minRemainingUsd?: number\n}\n\nexport type FetchLike = (\n url: string,\n init?: RequestInit,\n) => Promise<Response>\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void\n info(message: string, ...args: unknown[]): void\n warn(message: string, ...args: unknown[]): void\n error(message: string, ...args: unknown[]): void\n}\n\nexport interface RetryPolicy {\n /** Max retry attempts after the first try (default 5). */\n maxRetries: number\n /** Base backoff delay in ms (default 500). */\n baseDelayMs: number\n /** Ceiling on any single backoff delay (default 20_000). */\n maxDelayMs: number\n /** Status codes that trigger a retry (default 429, 500, 502, 503, 504). */\n retryOnStatus: number[]\n}\n\nexport interface OpenAlexConfig {\n /**\n * OpenAlex API key. Required by OpenAlex for all requests since Feb 2026.\n * Falls back to `process.env.OPENALEX_API_KEY`.\n */\n apiKey?: string\n /**\n * Contact email for the \"polite pool\". Sent as `mailto` and in User-Agent.\n * Falls back to `process.env.OPENALEX_MAILTO`.\n */\n mailto?: string\n /** API base URL. Default `https://api.openalex.org`. */\n baseUrl?: string\n /** Extra User-Agent identifier for your app. */\n userAgent?: string\n /** Per-request timeout in ms. Default 60_000. */\n timeoutMs?: number\n /** Retry/backoff behaviour. */\n retry?: Partial<RetryPolicy>\n /** Max concurrent in-flight requests. Default 8. */\n concurrency?: number\n /**\n * Soft daily budget in USD. When set, any call (or estimated extraction)\n * that would cross it throws `BudgetExceededError` *before* spending.\n */\n dailyBudgetUsd?: number\n /** Called when spend crosses 80% / 100% of `dailyBudgetUsd`. */\n onBudgetWarning?: (info: { spentUsd: number; limitUsd: number; ratio: number }) => void\n /** `true` for the built-in LRU, or a custom {@link CacheStore}. GET-only. */\n cache?: boolean | CacheStore\n /** Cache TTL in ms. Default 300_000 (5 min). */\n cacheTtlMs?: number\n /**\n * Budget-aware adaptive throttle. `true` = 10 req/s with auto-slowdown near\n * the daily budget; or fine-tune with {@link ThrottleOptions}.\n */\n throttle?: boolean | ThrottleOptions\n /** Inject a custom fetch (used for testing / proxies). Default global fetch. */\n fetch?: FetchLike\n /** Optional logger. Default: no-op. */\n logger?: Logger\n}\n\nexport interface ResolvedConfig {\n apiKey?: string\n mailto?: string\n baseUrl: string\n userAgent: string\n timeoutMs: number\n retry: RetryPolicy\n concurrency: number\n dailyBudgetUsd?: number\n onBudgetWarning?: (info: { spentUsd: number; limitUsd: number; ratio: number }) => void\n cache?: CacheStore\n cacheTtlMs: number\n throttle?: ResolvedThrottle\n fetch: FetchLike\n logger: Logger\n}\n\nconst NOOP_LOGGER: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n}\n\nconst DEFAULT_RETRY: RetryPolicy = {\n maxRetries: 5,\n baseDelayMs: 500,\n maxDelayMs: 20_000,\n retryOnStatus: [429, 500, 502, 503, 504],\n}\n\nconst PKG_VERSION = '0.1.0'\n\nexport function resolveConfig(config: OpenAlexConfig = {}): ResolvedConfig {\n const env = typeof process !== 'undefined' ? process.env : ({} as Record<string, string>)\n const apiKey = config.apiKey ?? env.OPENALEX_API_KEY\n const mailto = config.mailto ?? env.OPENALEX_MAILTO\n\n const globalFetch = typeof fetch !== 'undefined' ? (fetch as FetchLike) : undefined\n const fetchImpl = config.fetch ?? globalFetch\n if (!fetchImpl) {\n throw new Error(\n 'No fetch implementation available. Use Node 18+, or pass `fetch` in the config.',\n )\n }\n\n let cache: CacheStore | undefined\n if (config.cache === true) cache = new LRUCache()\n else if (config.cache && typeof config.cache === 'object') cache = config.cache\n\n let throttle: ResolvedThrottle | undefined\n if (config.throttle === true) throttle = { maxRequestsPerSecond: 10 }\n else if (config.throttle && typeof config.throttle === 'object') {\n throttle = {\n maxRequestsPerSecond: config.throttle.maxRequestsPerSecond ?? 10,\n minRemainingUsd: config.throttle.minRemainingUsd,\n }\n }\n\n const ua = [`open-alex-wrapper/${PKG_VERSION}`, config.userAgent, mailto ? `mailto:${mailto}` : undefined]\n .filter(Boolean)\n .join(' ')\n\n return {\n apiKey,\n mailto,\n baseUrl: (config.baseUrl ?? 'https://api.openalex.org').replace(/\\/+$/, ''),\n userAgent: ua,\n timeoutMs: config.timeoutMs ?? 60_000,\n retry: { ...DEFAULT_RETRY, ...config.retry },\n concurrency: Math.max(1, config.concurrency ?? 8),\n dailyBudgetUsd: config.dailyBudgetUsd,\n onBudgetWarning: config.onBudgetWarning,\n cache,\n cacheTtlMs: config.cacheTtlMs ?? 300_000,\n throttle,\n fetch: fetchImpl,\n logger: config.logger ?? NOOP_LOGGER,\n }\n}\n","/**\n * Error hierarchy for the OpenAlex wrapper.\n *\n * Every network/HTTP failure is normalized into one of these so callers can\n * `catch` a specific class (e.g. {@link RateLimitError}) or the base\n * {@link OpenAlexError}.\n */\n\nexport interface OpenAlexErrorContext {\n /** HTTP status code, when the failure came from a response. */\n status?: number\n /** The request URL (api_key redacted). */\n url?: string\n /** Raw response body, when available. */\n body?: unknown\n /** Underlying cause (e.g. a network `TypeError` or `AbortError`). */\n cause?: unknown\n}\n\n/** Base class for every error thrown by the SDK. */\nexport class OpenAlexError extends Error {\n readonly status?: number\n readonly url?: string\n readonly body?: unknown\n\n constructor(message: string, ctx: OpenAlexErrorContext = {}) {\n super(message, ctx.cause !== undefined ? { cause: ctx.cause } : undefined)\n this.name = new.target.name\n this.status = ctx.status\n this.url = ctx.url\n this.body = ctx.body\n // Maintain a proper prototype chain when compiled down to ES5-ish targets.\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/** 401/403 — missing, invalid, or insufficiently-privileged API key. */\nexport class AuthenticationError extends OpenAlexError {}\n\n/** 404 — the requested entity or endpoint does not exist. */\nexport class NotFoundError extends OpenAlexError {}\n\n/** 400/422 — malformed filter, bad parameter, or invalid cursor. */\nexport class ValidationError extends OpenAlexError {}\n\n/** 429 — rate limited. Inspect {@link RateLimitError.retryAfterMs}. */\nexport class RateLimitError extends OpenAlexError {\n readonly retryAfterMs?: number\n constructor(message: string, ctx: OpenAlexErrorContext & { retryAfterMs?: number } = {}) {\n super(message, ctx)\n this.retryAfterMs = ctx.retryAfterMs\n }\n}\n\n/** 5xx — OpenAlex server error (retried automatically before surfacing). */\nexport class ServerError extends OpenAlexError {}\n\n/** The request timed out before a response arrived. */\nexport class TimeoutError extends OpenAlexError {}\n\n/** A transport-level failure (DNS, connection reset, offline, …). */\nexport class NetworkError extends OpenAlexError {}\n\n/**\n * Thrown by the SDK — *not* by OpenAlex — when a request would push the\n * session past the configured `dailyBudgetUsd` soft cap.\n */\nexport class BudgetExceededError extends OpenAlexError {\n readonly spentUsd: number\n readonly limitUsd: number\n readonly wouldSpendUsd: number\n constructor(\n message: string,\n info: { spentUsd: number; limitUsd: number; wouldSpendUsd: number },\n ) {\n super(message)\n this.spentUsd = info.spentUsd\n this.limitUsd = info.limitUsd\n this.wouldSpendUsd = info.wouldSpendUsd\n }\n}\n\n/** Map an HTTP status code to the most specific error class. */\nexport function errorForStatus(\n status: number,\n message: string,\n ctx: OpenAlexErrorContext = {},\n): OpenAlexError {\n const full = { ...ctx, status }\n if (status === 401 || status === 403) return new AuthenticationError(message, full)\n if (status === 404) return new NotFoundError(message, full)\n if (status === 400 || status === 422) return new ValidationError(message, full)\n if (status === 429) return new RateLimitError(message, full)\n if (status >= 500) return new ServerError(message, full)\n return new OpenAlexError(message, full)\n}\n","/**\n * Cost & budget awareness.\n *\n * OpenAlex moved to usage-based pricing in Feb 2026. This module models that\n * pricing so the SDK can (a) *estimate the dollar cost of an extraction before\n * you run it* and (b) *track live, authoritative spend* from response headers.\n *\n * Prices (USD), per the OpenAlex pricing announcement:\n * - single entity lookup .......... $0 (unlimited)\n * - list / filter ................. $0.0001 / call (1 credit)\n * - search ........................ $0.001 / call\n * - PDF / XML full-text download .. $0.01 / call\n *\n * Free daily allowance: $1.00/day with an API key, $0.10/day without one.\n *\n * OpenAlex reports usage on every response via `x-ratelimit-*` headers\n * (confirmed against a live response):\n * x-ratelimit-cost-usd cost of this request\n * x-ratelimit-remaining-usd USD left in today's budget\n * x-ratelimit-limit-usd today's USD budget\n * x-ratelimit-credits-used credits this request cost\n * x-ratelimit-remaining credits left today\n * x-ratelimit-limit daily credit limit\n * x-ratelimit-prepaid-remaining-usd prepaid balance (beyond the free tier)\n * x-ratelimit-reset seconds until the budget resets\n * {@link UsageTracker} prefers these authoritative values and falls back to\n * the pricing model above when headers are absent.\n */\n\nimport { BudgetExceededError } from './errors'\n\nexport type RequestType = 'get' | 'list' | 'search' | 'download' | 'autocomplete'\n\n/** USD price per API call, keyed by request type (fallback when no headers). */\nexport const PRICING_USD: Record<RequestType, number> = {\n get: 0,\n list: 0.0001,\n search: 0.001,\n download: 0.01,\n autocomplete: 0.0001,\n}\n\nexport const FREE_DAILY_USD_WITH_KEY = 1.0\nexport const FREE_DAILY_USD_WITHOUT_KEY = 0.1\n\n/** Documented free-tier daily caps (informational). */\nexport const FREE_TIER_LIMITS = {\n singleLookupCalls: Infinity,\n listCalls: 10_000,\n listResults: 1_000_000,\n searchCalls: 1_000,\n searchResults: 100_000,\n downloadCalls: 100,\n} as const\n\n/** Basic (page-number) paging can only reach the first 10,000 results. */\nexport const BASIC_PAGING_MAX_RESULTS = 10_000\n\n/** Estimated cost of a bulk extraction, produced *before* running it. */\nexport interface CostEstimate {\n requestType: RequestType\n /** `meta.count` — total results matching the query. */\n matchingResults: number\n /** How many of those you can actually page through with this strategy. */\n fetchableResults: number\n perPage: number\n /** Page-fetch calls needed to walk `fetchableResults`. */\n pageCalls: number\n /** USD for the page fetches. */\n pagesCostUsd: number\n /** The single cheap count probe already spent to produce this estimate. */\n probeCostUsd: number\n /** `pagesCostUsd + probeCostUsd`. */\n totalCostUsd: number\n /** True when basic paging clipped the result set at 10k. */\n cappedByBasicPaging: boolean\n note?: string\n}\n\nexport interface EstimateInput {\n count: number\n requestType: RequestType\n perPage?: number\n strategy?: 'basic' | 'cursor'\n}\n\n/** Pure cost math: given a result `count`, project the extraction cost. */\nexport function estimateExtractionCost(input: EstimateInput): CostEstimate {\n const perPage = clampPerPage(input.perPage ?? 200)\n const strategy = input.strategy ?? 'cursor'\n const price = PRICING_USD[input.requestType]\n\n const cappedByBasicPaging = strategy === 'basic' && input.count > BASIC_PAGING_MAX_RESULTS\n const fetchableResults =\n strategy === 'basic' ? Math.min(input.count, BASIC_PAGING_MAX_RESULTS) : input.count\n\n const pageCalls = fetchableResults === 0 ? 0 : Math.ceil(fetchableResults / perPage)\n const pagesCostUsd = round6(pageCalls * price)\n const probeCostUsd = round6(price)\n\n return {\n requestType: input.requestType,\n matchingResults: input.count,\n fetchableResults,\n perPage,\n pageCalls,\n pagesCostUsd,\n probeCostUsd,\n totalCostUsd: round6(pagesCostUsd + probeCostUsd),\n cappedByBasicPaging,\n note: cappedByBasicPaging\n ? `Basic paging reaches only the first ${BASIC_PAGING_MAX_RESULTS.toLocaleString()} results; use cursor paging (the default) for the full ${input.count.toLocaleString()}.`\n : undefined,\n }\n}\n\n/** OpenAlex accepts per_page in [1, 200]. */\nexport function clampPerPage(n: number): number {\n if (!Number.isFinite(n)) return 200\n return Math.max(1, Math.min(200, Math.floor(n)))\n}\n\nexport interface UsageByType {\n calls: number\n results: number\n estimatedCostUsd: number\n}\n\nexport interface UsageSnapshot {\n totalCalls: number\n /** Spend estimated from the pricing model. */\n estimatedSpentUsd: number\n /** Authoritative spend today (limit − remaining), from headers, if seen. */\n reportedSpentUsd: number | null\n /** Authoritative remaining budget today, from headers, if seen. */\n reportedRemainingUsd: number | null\n /** Prepaid balance beyond the free tier, from headers, if seen. */\n prepaidRemainingUsd: number | null\n /** Credits remaining today, from headers, if seen. */\n creditsRemaining: number | null\n /** Seconds until the daily budget resets, from headers, if seen. */\n resetSeconds: number | null\n /** Cost of the most recent request (USD), from headers, if seen. */\n lastRequestCostUsd: number | null\n dailyLimitUsd: number\n /** Best available spend = reported, else estimated. */\n spentUsd: number\n /** Best available remaining = reported, else limit − spent. */\n remainingUsd: number\n byType: Record<RequestType, UsageByType>\n hasApiKey: boolean\n}\n\ntype Headerish =\n | Headers\n | Map<string, string>\n | Record<string, string | string[] | undefined>\n | undefined\n\nexport interface ParsedUsageHeaders {\n costUsd?: number\n costRequiredUsd?: number\n remainingUsd?: number\n limitUsd?: number\n prepaidRemainingUsd?: number\n creditsUsed?: number\n creditsRemaining?: number\n creditsLimit?: number\n resetSeconds?: number\n raw: Record<string, string>\n}\n\n/** Extract OpenAlex `x-ratelimit-*` usage headers into structured numbers. */\nexport function parseUsageHeaders(headers: Headerish): ParsedUsageHeaders {\n const map = normalizeHeaders(headers)\n const raw: Record<string, string> = {}\n for (const [k, v] of map) {\n if (k.toLowerCase().startsWith('x-ratelimit')) raw[k.toLowerCase()] = v\n }\n const num = (key: string): number | undefined => {\n const v = map.get(key)\n if (v === undefined) return undefined\n const n = Number(String(v).replace(/[$,\\s]/g, ''))\n return Number.isFinite(n) ? n : undefined\n }\n return {\n costUsd: num('x-ratelimit-cost-usd'),\n costRequiredUsd: num('x-ratelimit-cost-required-usd'),\n remainingUsd: num('x-ratelimit-remaining-usd'),\n limitUsd: num('x-ratelimit-limit-usd'),\n prepaidRemainingUsd: num('x-ratelimit-prepaid-remaining-usd'),\n creditsUsed: num('x-ratelimit-credits-used'),\n creditsRemaining: num('x-ratelimit-remaining'),\n creditsLimit: num('x-ratelimit-limit'),\n resetSeconds: num('x-ratelimit-reset'),\n raw,\n }\n}\n\nfunction normalizeHeaders(headers: Headerish): Map<string, string> {\n const out = new Map<string, string>()\n if (!headers) return out\n if (headers instanceof Map) {\n for (const [k, v] of headers) out.set(k.toLowerCase(), String(v))\n return out\n }\n if (typeof (headers as Headers).forEach === 'function' && !Array.isArray(headers)) {\n ;(headers as Headers).forEach((v, k) => out.set(k.toLowerCase(), v))\n return out\n }\n for (const [k, v] of Object.entries(headers as Record<string, unknown>)) {\n if (v === undefined) continue\n out.set(k.toLowerCase(), Array.isArray(v) ? v.join(',') : String(v))\n }\n return out\n}\n\nfunction emptyByType(): Record<RequestType, UsageByType> {\n const zero = (): UsageByType => ({ calls: 0, results: 0, estimatedCostUsd: 0 })\n return { get: zero(), list: zero(), search: zero(), download: zero(), autocomplete: zero() }\n}\n\n/**\n * Accumulates spend across a client session.\n *\n * When OpenAlex sends `x-ratelimit-*` headers, the tracker reports authoritative\n * spend/remaining/limit straight from them; otherwise it falls back to the\n * pricing model.\n */\nexport class UsageTracker {\n private byType = emptyByType()\n private estimatedSpentUsd = 0\n private reportedRemainingUsd: number | null = null\n private reportedLimitUsd: number | null = null\n private prepaidRemainingUsd: number | null = null\n private creditsRemaining: number | null = null\n private resetSeconds: number | null = null\n private lastRequestCostUsd: number | null = null\n private lastRawUsageHeaders: Record<string, string> = {}\n\n constructor(\n private readonly hasApiKey: boolean,\n private readonly dailyBudgetUsd?: number,\n ) {}\n\n /** The daily allowance in effect: OpenAlex-reported, else soft cap, else free tier. */\n get dailyLimitUsd(): number {\n if (this.reportedLimitUsd !== null) return this.reportedLimitUsd\n if (this.dailyBudgetUsd !== undefined) return this.dailyBudgetUsd\n return this.hasApiKey ? FREE_DAILY_USD_WITH_KEY : FREE_DAILY_USD_WITHOUT_KEY\n }\n\n /** Authoritative spend today if headers seen (limit − remaining), else estimate. */\n get spentUsd(): number {\n if (this.reportedLimitUsd !== null && this.reportedRemainingUsd !== null) {\n return round6(this.reportedLimitUsd - this.reportedRemainingUsd)\n }\n return round6(this.estimatedSpentUsd)\n }\n\n get remainingUsd(): number {\n if (this.reportedRemainingUsd !== null) return this.reportedRemainingUsd\n return round6(Math.max(0, this.dailyLimitUsd - this.spentUsd))\n }\n\n /** Record one completed request. Called by the HTTP layer. */\n record(type: RequestType, opts: { results?: number; headers?: Headerish } = {}): void {\n const bucket = this.byType[type]\n bucket.calls += 1\n bucket.results += opts.results ?? 0\n const modelled = PRICING_USD[type]\n bucket.estimatedCostUsd = round6(bucket.estimatedCostUsd + modelled)\n this.estimatedSpentUsd = round6(this.estimatedSpentUsd + modelled)\n\n if (opts.headers) {\n const p = parseUsageHeaders(opts.headers)\n if (Object.keys(p.raw).length) this.lastRawUsageHeaders = p.raw\n if (p.remainingUsd !== undefined) this.reportedRemainingUsd = p.remainingUsd\n if (p.limitUsd !== undefined) this.reportedLimitUsd = p.limitUsd\n if (p.prepaidRemainingUsd !== undefined) this.prepaidRemainingUsd = p.prepaidRemainingUsd\n if (p.creditsRemaining !== undefined) this.creditsRemaining = p.creditsRemaining\n if (p.resetSeconds !== undefined) this.resetSeconds = p.resetSeconds\n if (p.costUsd !== undefined) this.lastRequestCostUsd = p.costUsd\n }\n }\n\n /**\n * Throw {@link BudgetExceededError} if spending `additionalUsd` more would\n * cross the soft cap. Only enforced when `dailyBudgetUsd` was configured.\n */\n assertWithinBudget(additionalUsd: number): void {\n if (this.dailyBudgetUsd === undefined) return\n const projected = this.spentUsd + additionalUsd\n if (projected > this.dailyBudgetUsd) {\n throw new BudgetExceededError(\n `This request would spend ~$${round6(projected).toFixed(\n 6,\n )}, over the configured daily budget of $${this.dailyBudgetUsd.toFixed(2)}.`,\n { spentUsd: this.spentUsd, limitUsd: this.dailyBudgetUsd, wouldSpendUsd: round6(additionalUsd) },\n )\n }\n }\n\n /** Immutable point-in-time view of usage. */\n snapshot(): UsageSnapshot {\n const reportedSpent =\n this.reportedLimitUsd !== null && this.reportedRemainingUsd !== null\n ? round6(this.reportedLimitUsd - this.reportedRemainingUsd)\n : null\n return {\n totalCalls: Object.values(this.byType).reduce((a, b) => a + b.calls, 0),\n estimatedSpentUsd: round6(this.estimatedSpentUsd),\n reportedSpentUsd: reportedSpent,\n reportedRemainingUsd: this.reportedRemainingUsd,\n prepaidRemainingUsd: this.prepaidRemainingUsd,\n creditsRemaining: this.creditsRemaining,\n resetSeconds: this.resetSeconds,\n lastRequestCostUsd: this.lastRequestCostUsd,\n dailyLimitUsd: this.dailyLimitUsd,\n spentUsd: this.spentUsd,\n remainingUsd: this.remainingUsd,\n byType: structuredCloneSafe(this.byType),\n hasApiKey: this.hasApiKey,\n }\n }\n\n /** The last set of raw `x-ratelimit-*` headers OpenAlex sent (for debugging). */\n get lastUsageHeaders(): Record<string, string> {\n return { ...this.lastRawUsageHeaders }\n }\n\n /** Human-readable multi-line budget report. */\n report(): string {\n const s = this.snapshot()\n const lines = [\n 'OpenAlex usage report',\n ` API key: ${s.hasApiKey ? 'yes' : 'no (anonymous)'}`,\n ` Total API calls: ${s.totalCalls}`,\n ` Spend today: $${s.spentUsd.toFixed(6)}${\n s.reportedSpentUsd !== null ? ' (reported by OpenAlex)' : ' (estimated)'\n }`,\n ` Daily limit: $${s.dailyLimitUsd.toFixed(4)}`,\n ` Remaining: $${s.remainingUsd.toFixed(6)}`,\n ]\n if (s.prepaidRemainingUsd !== null)\n lines.push(` Prepaid remaining: $${s.prepaidRemainingUsd.toFixed(4)}`)\n if (s.creditsRemaining !== null)\n lines.push(` Credits remaining: ${s.creditsRemaining}`)\n if (s.resetSeconds !== null)\n lines.push(` Budget resets in: ${formatDuration(s.resetSeconds)}`)\n lines.push(' By type:')\n for (const type of Object.keys(s.byType) as RequestType[]) {\n const b = s.byType[type]\n if (b.calls === 0) continue\n lines.push(\n ` ${type.padEnd(12)} ${String(b.calls).padStart(6)} calls $${b.estimatedCostUsd.toFixed(6)}`,\n )\n }\n return lines.join('\\n')\n }\n\n reset(): void {\n this.byType = emptyByType()\n this.estimatedSpentUsd = 0\n this.reportedRemainingUsd = null\n this.reportedLimitUsd = null\n this.prepaidRemainingUsd = null\n this.creditsRemaining = null\n this.resetSeconds = null\n this.lastRequestCostUsd = null\n this.lastRawUsageHeaders = {}\n }\n}\n\nfunction formatDuration(seconds: number): string {\n const s = Math.max(0, Math.floor(seconds))\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n if (h > 0) return `${h}h ${m}m`\n if (m > 0) return `${m}m ${s % 60}s`\n return `${s}s`\n}\n\nfunction round6(n: number): number {\n return Math.round(n * 1e6) / 1e6\n}\n\nfunction structuredCloneSafe<T>(v: T): T {\n return JSON.parse(JSON.stringify(v)) as T\n}\n","/**\n * Budget-aware adaptive throttle.\n *\n * Paces requests to a target rate (default 10/s) and *automatically slows down\n * as the daily budget runs low* — so a long extraction glides to the limit\n * instead of slamming into a 429/402. A `minRemainingUsd` floor hard-stops\n * before the budget is fully exhausted.\n *\n * The clock is injectable so pacing is deterministically testable.\n */\n\nimport { BudgetExceededError } from './errors'\n\nexport interface ResolvedThrottle {\n /** Target requests/second (spacing = 1000 / rps). */\n maxRequestsPerSecond: number\n /** Throw {@link BudgetExceededError} once remaining budget drops below this. */\n minRemainingUsd?: number\n}\n\nexport interface ThrottleState {\n remainingUsd: number\n dailyLimitUsd: number\n}\n\nexport interface Clock {\n now(): number\n sleep(ms: number): Promise<void>\n}\n\nexport const REAL_CLOCK: Clock = {\n now: () => Date.now(),\n sleep: (ms) => new Promise((r) => setTimeout(r, ms)),\n}\n\nexport class Throttle {\n private nextAllowedAt = 0\n\n constructor(\n private readonly opts: ResolvedThrottle,\n private readonly clock: Clock = REAL_CLOCK,\n ) {}\n\n /**\n * Wait until the next request is allowed to start. Enforces the target rate\n * and multiplies spacing when the remaining budget fraction is low.\n */\n async gate(state: ThrottleState): Promise<void> {\n if (this.opts.minRemainingUsd !== undefined && state.remainingUsd < this.opts.minRemainingUsd) {\n throw new BudgetExceededError(\n `Auto-throttle floor hit: remaining budget $${state.remainingUsd.toFixed(6)} is below ` +\n `the configured floor $${this.opts.minRemainingUsd.toFixed(6)}.`,\n {\n spentUsd: Math.max(0, state.dailyLimitUsd - state.remainingUsd),\n limitUsd: state.dailyLimitUsd,\n wouldSpendUsd: 0,\n },\n )\n }\n\n let spacing = this.opts.maxRequestsPerSecond > 0 ? 1000 / this.opts.maxRequestsPerSecond : 0\n const frac = state.dailyLimitUsd > 0 ? state.remainingUsd / state.dailyLimitUsd : 1\n if (frac < 0.1) spacing *= 4\n else if (frac < 0.25) spacing *= 2\n\n const now = this.clock.now()\n const wait = Math.max(0, this.nextAllowedAt - now)\n // Reserve this request's slot `spacing` ahead, so concurrent callers queue.\n this.nextAllowedAt = Math.max(now, this.nextAllowedAt) + spacing\n if (wait > 0) await this.clock.sleep(wait)\n }\n}\n","/**\n * Low-level HTTP transport.\n *\n * Responsibilities:\n * - build request URLs (api_key + mailto injection, safe encoding)\n * - bound concurrency with a {@link Semaphore}\n * - retry with exponential backoff + jitter, honouring `Retry-After`\n * - per-request timeout via `AbortController`\n * - optional GET response caching\n * - record spend/usage into the {@link UsageTracker}\n * - normalize failures into the typed error hierarchy\n */\n\nimport type { ResolvedConfig } from './config'\nimport {\n NetworkError,\n TimeoutError,\n errorForStatus,\n OpenAlexError,\n} from './errors'\nimport { PRICING_USD, UsageTracker, type RequestType } from './budget'\nimport { Throttle } from './throttle'\n\n/** A concurrency limiter that hands a held slot directly to the next waiter. */\nexport class Semaphore {\n private active = 0\n private readonly waiters: Array<() => void> = []\n constructor(private readonly max: number) {}\n\n async run<T>(fn: () => Promise<T>): Promise<T> {\n await this.acquire()\n try {\n return await fn()\n } finally {\n this.release()\n }\n }\n\n private acquire(): Promise<void> {\n if (this.active < this.max) {\n this.active += 1\n return Promise.resolve()\n }\n return new Promise<void>((resolve) => this.waiters.push(resolve))\n }\n\n private release(): void {\n const next = this.waiters.shift()\n if (next) next()\n else this.active -= 1\n }\n}\n\nexport interface RequestOptions {\n /** Path after the base URL, e.g. `works` or `works/W123`. Not encoded by caller. */\n path: string\n /** Already-serialized OpenAlex query params (correct param names). */\n params?: Record<string, string | undefined>\n /** Cost classification for budgeting. */\n requestType: RequestType\n /** Bypass the response cache for this call. */\n noCache?: boolean\n /** Extra fetch signal to compose with the internal timeout signal. */\n signal?: AbortSignal\n}\n\nexport interface HttpResponse<T> {\n data: T\n status: number\n headers: Headers\n url: string\n cached: boolean\n}\n\nexport class HttpClient {\n private readonly semaphore: Semaphore\n private readonly firedWarnings = new Set<number>()\n private readonly throttle?: Throttle\n\n constructor(\n readonly config: ResolvedConfig,\n readonly usage: UsageTracker,\n ) {\n this.semaphore = new Semaphore(config.concurrency)\n if (config.throttle) this.throttle = new Throttle(config.throttle)\n }\n\n private async gateThrottle(): Promise<void> {\n if (!this.throttle) return\n const s = this.usage.snapshot()\n await this.throttle.gate({ remainingUsd: s.remainingUsd, dailyLimitUsd: s.dailyLimitUsd })\n }\n\n /** Build the full request URL plus a redacted variant (api_key hidden). */\n buildUrl(path: string, params: Record<string, string | undefined> = {}): {\n url: string\n redacted: string\n } {\n const qs = new URLSearchParams()\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== '') qs.set(k, v)\n }\n if (this.config.mailto) qs.set('mailto', this.config.mailto)\n const redactedQs = new URLSearchParams(qs)\n if (this.config.apiKey) {\n qs.set('api_key', this.config.apiKey)\n redactedQs.set('api_key', 'REDACTED')\n }\n const base = `${this.config.baseUrl}/${encodeURI(path)}`\n const q = qs.toString()\n const rq = redactedQs.toString()\n return {\n url: q ? `${base}?${q}` : base,\n redacted: rq ? `${base}?${rq}` : base,\n }\n }\n\n /** Cache key excludes api_key so a shared cache isn't keyed to a secret. */\n private cacheKey(path: string, params: Record<string, string | undefined>): string {\n const { redacted } = this.buildUrl(path, params)\n return redacted\n }\n\n async request<T>(opts: RequestOptions): Promise<HttpResponse<T>> {\n const params = opts.params ?? {}\n const { url, redacted } = this.buildUrl(opts.path, params)\n const useCache = this.config.cache && !opts.noCache\n const key = useCache ? this.cacheKey(opts.path, params) : undefined\n\n if (useCache && key) {\n const cached = await this.config.cache!.get(key)\n if (cached !== undefined) {\n this.config.logger.debug(`cache hit ${redacted}`)\n return {\n data: cached as T,\n status: 200,\n headers: new Headers(),\n url: redacted,\n cached: true,\n }\n }\n }\n\n // Enforce the soft budget *before* spending.\n this.usage.assertWithinBudget(PRICING_USD[opts.requestType])\n await this.gateThrottle()\n\n const response = await this.semaphore.run(() => this.fetchWithRetry<T>(url, redacted, opts))\n\n // Record spend + usage from headers.\n const results = countResults(response.data)\n this.usage.record(opts.requestType, { results, headers: response.headers })\n this.maybeWarnBudget()\n\n if (useCache && key) {\n await this.config.cache!.set(key, response.data, this.config.cacheTtlMs)\n }\n return { ...response, url: redacted }\n }\n\n private async fetchWithRetry<T>(\n url: string,\n redacted: string,\n opts: RequestOptions,\n ): Promise<HttpResponse<T>> {\n const res = await this.sendWithRetry(url, redacted, { signal: opts.signal })\n const data = (await res.json()) as T\n return { data, status: res.status, headers: res.headers, url: redacted, cached: false }\n }\n\n /** Shared GET-with-retry core: returns the ok `Response` or throws typed. */\n private async sendWithRetry(\n url: string,\n redacted: string,\n opts: { signal?: AbortSignal },\n ): Promise<Response> {\n const { retry } = this.config\n let attempt = 0\n // total tries = maxRetries + 1\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs)\n const signal = opts.signal ? anySignal([opts.signal, controller.signal]) : controller.signal\n try {\n this.config.logger.debug(`GET ${redacted} (attempt ${attempt + 1})`)\n const res = await this.config.fetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json, */*', 'User-Agent': this.config.userAgent },\n signal,\n })\n\n if (res.ok) return res\n\n const body = await safeReadBody(res)\n const retryAfterMs = parseRetryAfter(res.headers.get('retry-after'))\n const err = errorForStatus(res.status, describe(res.status, body, redacted), {\n url: redacted,\n body,\n ...(res.status === 429 ? { retryAfterMs } : {}),\n })\n if (this.shouldRetry(res.status, attempt)) {\n await sleep(this.backoff(attempt, retryAfterMs))\n attempt += 1\n continue\n }\n throw err\n } catch (e) {\n if (e instanceof OpenAlexError) throw e\n const isAbort = (e as { name?: string })?.name === 'AbortError'\n if (attempt < retry.maxRetries) {\n this.config.logger.warn(`${isAbort ? 'timeout' : 'network error'}, retrying: ${redacted}`)\n await sleep(this.backoff(attempt))\n attempt += 1\n continue\n }\n if (isAbort) {\n throw new TimeoutError(`Request timed out after ${this.config.timeoutMs}ms`, {\n url: redacted,\n cause: e,\n })\n }\n throw new NetworkError(`Network request failed: ${(e as Error)?.message ?? e}`, {\n url: redacted,\n cause: e,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n }\n\n /** Append api_key + mailto to any absolute URL (for the content host, etc.). */\n authorizeUrl(rawUrl: string): { url: string; redacted: string } {\n const u = new URL(rawUrl)\n const r = new URL(rawUrl)\n if (this.config.mailto) {\n u.searchParams.set('mailto', this.config.mailto)\n r.searchParams.set('mailto', this.config.mailto)\n }\n if (this.config.apiKey) {\n u.searchParams.set('api_key', this.config.apiKey)\n r.searchParams.set('api_key', 'REDACTED')\n }\n return { url: u.toString(), redacted: r.toString() }\n }\n\n /**\n * Download a binary resource (e.g. a full-text PDF/XML from the content host).\n * Budget-asserted and usage-recorded like any other request.\n */\n async download(opts: {\n url: string\n redacted?: string\n requestType: RequestType\n dest?: string\n signal?: AbortSignal\n }): Promise<{ bytes?: Uint8Array; path?: string; status: number; headers: Headers; contentType?: string }> {\n this.usage.assertWithinBudget(PRICING_USD[opts.requestType])\n await this.gateThrottle()\n const redacted = opts.redacted ?? opts.url\n const res = await this.semaphore.run(() =>\n this.sendWithRetry(opts.url, redacted, { signal: opts.signal }),\n )\n this.usage.record(opts.requestType, { headers: res.headers })\n this.maybeWarnBudget()\n const contentType = res.headers.get('content-type') ?? undefined\n\n if (opts.dest) {\n if (!res.body) throw new OpenAlexError('No response body to write', { url: redacted })\n const [{ Readable }, { pipeline }, { createWriteStream }] = await Promise.all([\n import('node:stream'),\n import('node:stream/promises'),\n import('node:fs'),\n ])\n await pipeline(Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]), createWriteStream(opts.dest))\n return { path: opts.dest, status: res.status, headers: res.headers, contentType }\n }\n const bytes = new Uint8Array(await res.arrayBuffer())\n return { bytes, status: res.status, headers: res.headers, contentType }\n }\n\n private shouldRetry(status: number, attempt: number): boolean {\n return attempt < this.config.retry.maxRetries && this.config.retry.retryOnStatus.includes(status)\n }\n\n private backoff(attempt: number, retryAfterMs?: number): number {\n const { baseDelayMs, maxDelayMs } = this.config.retry\n if (retryAfterMs !== undefined) return Math.min(retryAfterMs, maxDelayMs)\n const exp = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs)\n // Decorrelated jitter: half fixed, half random.\n return Math.round(exp / 2 + Math.random() * (exp / 2))\n }\n\n private maybeWarnBudget(): void {\n const cb = this.config.onBudgetWarning\n if (!cb) return\n const { spentUsd, dailyLimitUsd } = this.usage.snapshot()\n if (!dailyLimitUsd) return\n const ratio = spentUsd / dailyLimitUsd\n for (const threshold of [0.8, 1.0]) {\n if (ratio >= threshold && !this.firedWarnings.has(threshold)) {\n this.firedWarnings.add(threshold)\n cb({ spentUsd, limitUsd: dailyLimitUsd, ratio })\n }\n }\n }\n}\n\nfunction countResults(data: unknown): number {\n if (data && typeof data === 'object' && Array.isArray((data as { results?: unknown }).results)) {\n return (data as { results: unknown[] }).results.length\n }\n return 0\n}\n\nfunction describe(status: number, body: unknown, url: string): string {\n const msg =\n body && typeof body === 'object' && 'message' in body\n ? String((body as { message: unknown }).message)\n : body && typeof body === 'object' && 'error' in body\n ? String((body as { error: unknown }).error)\n : undefined\n return `OpenAlex request failed (${status})${msg ? `: ${msg}` : ''} [${url}]`\n}\n\nasync function safeReadBody(res: Response): Promise<unknown> {\n try {\n const text = await res.text()\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n } catch {\n return undefined\n }\n}\n\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined\n const secs = Number(value)\n if (Number.isFinite(secs)) return Math.max(0, secs * 1000)\n const date = Date.parse(value)\n if (Number.isFinite(date)) return Math.max(0, date - Date.now())\n return undefined\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/** Compose several AbortSignals into one (fires when any fires). */\nfunction anySignal(signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController()\n for (const s of signals) {\n if (s.aborted) {\n controller.abort()\n break\n }\n s.addEventListener('abort', () => controller.abort(), { once: true })\n }\n return controller.signal\n}\n","/**\n * Pagination strategies as async generators.\n *\n * - cursor paging → unlimited depth, sequential (the safe default for `.all()`)\n * - basic paging → first 10k results, sequential or parallel\n *\n * Parallel basic paging fires every page request up front; the HTTP layer's\n * semaphore caps how many are actually in flight, and pages are yielded in\n * order. It's the fast path for the very common \"≤10k results\" case.\n */\n\nimport type { EntityRunner } from './runner'\nimport type { ListResult } from './types/common'\nimport type { Checkpoint } from './checkpoint'\nimport { BASIC_PAGING_MAX_RESULTS, clampPerPage, type RequestType } from './budget'\n\nexport interface ProgressInfo {\n /** Items emitted so far (cumulative, includes any resumed count). */\n emitted: number\n /** Pages fetched so far in this run. */\n page: number\n /** Total matching results (`meta.count`), when known. */\n total: number | null\n}\n\nexport interface StreamOptions {\n /** Results per page (1–200). Default 200 for max throughput. */\n perPage?: number\n /** Stop after collecting this many items. */\n maxResults?: number\n /** Stop after this many page requests. */\n maxPages?: number\n /**\n * 'cursor' (unlimited, sequential), 'basic' (≤10k), or 'auto'.\n * 'auto' picks basic when `parallel` is set, else cursor.\n */\n strategy?: 'auto' | 'cursor' | 'basic'\n /** Use parallel basic paging (fast; capped at 10k results). */\n parallel?: boolean\n signal?: AbortSignal\n /** Cost classification passed through to the tracker. */\n requestType?: RequestType\n /** Called once per page with cumulative progress. */\n onProgress?: (info: ProgressInfo) => void\n /**\n * Persist cursor position after every page so the extraction can resume\n * after a crash/interrupt. Forces cursor paging. Cleared on completion.\n */\n resume?: Checkpoint\n}\n\nfunction resolveStrategy(opts: StreamOptions): 'cursor' | 'basic' {\n // Resuming requires cursor state, so it always uses cursor paging.\n if (opts.resume) return 'cursor'\n if (opts.strategy === 'cursor' || opts.strategy === 'basic') return opts.strategy\n return opts.parallel ? 'basic' : 'cursor'\n}\n\n/** Yield full pages (`ListResult`) according to the chosen strategy. */\nexport async function* streamPages<T>(\n runner: EntityRunner<T>,\n baseParams: Record<string, string | undefined>,\n opts: StreamOptions = {},\n): AsyncGenerator<ListResult<T>> {\n const strategy = resolveStrategy(opts)\n if (strategy === 'cursor') {\n yield* cursorPages(runner, baseParams, opts)\n } else if (opts.parallel) {\n yield* parallelBasicPages(runner, baseParams, opts)\n } else {\n yield* sequentialBasicPages(runner, baseParams, opts)\n }\n}\n\n/** Yield individual entities, flattening pages, honouring `maxResults`. */\nexport async function* streamItems<T>(\n runner: EntityRunner<T>,\n baseParams: Record<string, string | undefined>,\n opts: StreamOptions = {},\n): AsyncGenerator<T> {\n let emitted = 0\n const max = opts.maxResults ?? Infinity\n for await (const page of streamPages(runner, baseParams, opts)) {\n for (const item of page.results) {\n if (emitted >= max) return\n yield item\n emitted += 1\n }\n if (emitted >= max) return\n }\n}\n\nexport async function* cursorPages<T>(\n runner: EntityRunner<T>,\n baseParams: Record<string, string | undefined>,\n opts: StreamOptions,\n): AsyncGenerator<ListResult<T>> {\n const perPage = clampPerPage(opts.perPage ?? 200)\n const fetchOpts = { requestType: opts.requestType, signal: opts.signal }\n const maxResults = opts.maxResults ?? Infinity\n const maxPages = opts.maxPages ?? Infinity\n\n const saved = opts.resume?.load()\n let cursor: string | null = saved?.cursor ?? '*'\n let emitted = saved?.emitted ?? 0\n let pages = 0\n\n while (cursor && pages < maxPages && emitted < maxResults) {\n const page: ListResult<T> = await runner.fetchList(\n { ...baseParams, 'per-page': String(perPage), cursor },\n fetchOpts,\n )\n pages += 1\n emitted += page.results.length\n if (page.results.length === 0) {\n cursor = null\n } else {\n cursor = page.meta.next_cursor ?? null\n }\n opts.resume?.save({ cursor, emitted })\n opts.onProgress?.({ emitted, page: pages, total: page.meta.count ?? null })\n yield page\n }\n\n // Reached the end naturally → drop the checkpoint so a re-run starts fresh.\n if (cursor === null) opts.resume?.clear()\n}\n\nexport async function* sequentialBasicPages<T>(\n runner: EntityRunner<T>,\n baseParams: Record<string, string | undefined>,\n opts: StreamOptions,\n): AsyncGenerator<ListResult<T>> {\n const perPage = clampPerPage(opts.perPage ?? 200)\n const fetchOpts = { requestType: opts.requestType, signal: opts.signal }\n const lastAllowedPage = Math.floor(BASIC_PAGING_MAX_RESULTS / perPage)\n const maxPages = Math.min(opts.maxPages ?? Infinity, lastAllowedPage)\n const maxResults = opts.maxResults ?? Infinity\n let emitted = 0\n\n for (let page = 1; page <= maxPages; page++) {\n const result: ListResult<T> = await runner.fetchList(\n { ...baseParams, 'per-page': String(perPage), page: String(page) },\n fetchOpts,\n )\n emitted += result.results.length\n opts.onProgress?.({ emitted, page, total: result.meta.count ?? null })\n yield result\n if (result.results.length < perPage || emitted >= maxResults) break\n }\n}\n\nexport async function* parallelBasicPages<T>(\n runner: EntityRunner<T>,\n baseParams: Record<string, string | undefined>,\n opts: StreamOptions,\n): AsyncGenerator<ListResult<T>> {\n const perPage = clampPerPage(opts.perPage ?? 200)\n const fetchOpts = { requestType: opts.requestType, signal: opts.signal }\n const lastAllowedPage = Math.floor(BASIC_PAGING_MAX_RESULTS / perPage)\n\n // Page 1 tells us the total count.\n const first: ListResult<T> = await runner.fetchList(\n { ...baseParams, 'per-page': String(perPage), page: '1' },\n fetchOpts,\n )\n let emitted = first.results.length\n opts.onProgress?.({ emitted, page: 1, total: first.meta.count ?? null })\n yield first\n\n const count = first.meta.count ?? first.results.length\n if (count > BASIC_PAGING_MAX_RESULTS) {\n runner.logger.warn(\n `parallelBasicPages: ${count.toLocaleString()} results exceed the ${BASIC_PAGING_MAX_RESULTS.toLocaleString()} basic-paging cap; ` +\n `only the first ${BASIC_PAGING_MAX_RESULTS.toLocaleString()} are returned. Use strategy:'cursor' for the full set.`,\n )\n }\n\n const neededPages = Math.ceil(Math.min(count, BASIC_PAGING_MAX_RESULTS) / perPage)\n const totalPages = Math.min(neededPages, lastAllowedPage, opts.maxPages ?? Infinity)\n if (totalPages <= 1 || first.results.length < perPage) return\n\n // Fire all remaining page requests immediately; the HTTP semaphore throttles\n // concurrency. Await them in order so pages are yielded sequentially.\n const promises: Array<Promise<ListResult<T>>> = []\n for (let page = 2; page <= totalPages; page++) {\n promises.push(\n runner.fetchList({ ...baseParams, 'per-page': String(perPage), page: String(page) }, fetchOpts),\n )\n }\n let pageNum = 1\n for (const p of promises) {\n const page = await p\n pageNum += 1\n emitted += page.results.length\n opts.onProgress?.({ emitted, page: pageNum, total: count })\n yield page\n if (page.results.length === 0) break\n }\n}\n","/**\n * Fluent, immutable query builder.\n *\n * Every chainable method returns a *new* builder, so queries are safe to\n * branch and reuse:\n *\n * const base = client.works.filter({ 'publication_year': gt(2020) })\n * const oa = base.filter({ 'open_access.is_oa': true })\n * for await (const w of oa.all()) { ... }\n */\n\nimport type { EntityRunner } from './runner'\nimport type { GroupResult, ListResult } from './types/common'\nimport {\n BASIC_PAGING_MAX_RESULTS,\n clampPerPage,\n estimateExtractionCost,\n type CostEstimate,\n type RequestType,\n} from './budget'\nimport { streamItems, streamPages, type StreamOptions } from './pagination'\n\nexport type FilterScalar = string | number | boolean\nexport type FilterValue = FilterScalar | FilterScalar[]\nexport type Filters = Record<string, FilterValue>\n\n/**\n * Filter input with per-entity key autocomplete. Known keys (from the generated\n * `*FilterKey` unions) are suggested in your IDE, while any string is still\n * accepted — so new OpenAlex fields never break compilation.\n */\nexport type FilterInput<F extends string = string> = {\n [K in F]?: FilterValue\n} & { [key: string]: FilterValue }\n\n/** Negate a filter value → `!value`. */\nexport const not = (v: FilterScalar): string => `!${v}`\n/** Greater-than range filter → `>value`. */\nexport const gt = (v: FilterScalar): string => `>${v}`\n/** Less-than range filter → `<value`. */\nexport const lt = (v: FilterScalar): string => `<${v}`\n/** OR several values for one key (also expressible as a plain array). */\nexport const or = (...values: FilterScalar[]): FilterScalar[] => values\n\nexport type SortSpec = string | string[] | Record<string, 'asc' | 'desc'>\n\ninterface QueryState {\n filters: Filters\n search?: string\n sort?: string\n select?: string[]\n perPage?: number\n sampleN?: number\n seed?: number\n groupBy?: string\n extra: Record<string, string>\n}\n\nconst PAGINATION_DEFAULT_PER_PAGE = 200\n\nexport class QueryBuilder<T, F extends string = string> {\n constructor(\n private readonly runner: EntityRunner<T>,\n private readonly state: QueryState = { filters: {}, extra: {} },\n ) {}\n\n /** The entity type this query targets (e.g. 'works'). */\n get entityType() {\n return this.runner.entityType\n }\n\n private clone(patch: Partial<QueryState>): QueryBuilder<T, F> {\n return new QueryBuilder<T, F>(this.runner, {\n ...this.state,\n ...patch,\n filters: patch.filters ?? this.state.filters,\n extra: patch.extra ?? this.state.extra,\n })\n }\n\n // --- chainable builders --------------------------------------------------\n\n /** Merge filters. Array values are OR'd; use `not`/`gt`/`lt` helpers. */\n filter(filters: FilterInput<F>): QueryBuilder<T, F> {\n return this.clone({ filters: { ...this.state.filters, ...filters } })\n }\n\n /** Full-text relevance search across the entity's default searchable fields. */\n search(term: string): QueryBuilder<T, F> {\n return this.clone({ search: term })\n }\n\n /** Search within a specific field, e.g. `.searchField('title', 'crispr')`. */\n searchField(field: string, term: string): QueryBuilder<T, F> {\n return this.clone({ filters: { ...this.state.filters, [`${field}.search`]: term } })\n }\n\n sort(spec: SortSpec): QueryBuilder<T, F> {\n let s: string\n if (typeof spec === 'string') s = spec\n else if (Array.isArray(spec)) s = spec.join(',')\n else s = Object.entries(spec).map(([k, v]) => `${k}:${v}`).join(',')\n return this.clone({ sort: s })\n }\n\n /** Trim the response to specific fields (cheaper, faster payloads). */\n select(fields: string | string[]): QueryBuilder<T, F> {\n const arr = Array.isArray(fields) ? fields : fields.split(',').map((f) => f.trim())\n return this.clone({ select: arr })\n }\n\n /** Random sample of `n` results (optionally deterministic via `seed`). */\n sample(n: number, seed?: number): QueryBuilder<T, F> {\n return this.clone({ sampleN: n, seed: seed ?? this.state.seed })\n }\n\n seed(seed: number): QueryBuilder<T, F> {\n return this.clone({ seed })\n }\n\n perPage(n: number): QueryBuilder<T, F> {\n return this.clone({ perPage: clampPerPage(n) })\n }\n\n /** Aggregate counts by a field (or two comma-separated fields). */\n groupBy(field: string | string[]): QueryBuilder<T, F> {\n return this.clone({ groupBy: Array.isArray(field) ? field.join(',') : field })\n }\n\n /** Escape hatch: set any raw query parameter (advanced/semantic search, …). */\n param(key: string, value: string | number | boolean): QueryBuilder<T, F> {\n return this.clone({ extra: { ...this.state.extra, [key]: String(value) } })\n }\n\n // --- introspection -------------------------------------------------------\n\n /** The OpenAlex query params this builder will send (no paging keys). */\n toParams(): Record<string, string | undefined> {\n const p: Record<string, string | undefined> = { ...this.state.extra }\n const filter = serializeFilters(this.state.filters)\n if (filter) p.filter = filter\n if (this.state.search) p.search = this.state.search\n if (this.state.sort) p.sort = this.state.sort\n if (this.state.select?.length) p.select = this.state.select.join(',')\n if (this.state.groupBy) p.group_by = this.state.groupBy\n if (this.state.sampleN !== undefined) {\n p.sample = String(this.state.sampleN)\n if (this.state.seed !== undefined) p.seed = String(this.state.seed)\n }\n return p\n }\n\n private get requestType(): RequestType {\n if (this.state.search || this.state.extra.search) return 'search'\n for (const k of Object.keys(this.state.filters)) {\n if (k.endsWith('.search') || k === 'default.search') return 'search'\n }\n return 'list'\n }\n\n // --- terminal operations -------------------------------------------------\n\n /** Fetch a single page. */\n async get(opts: { perPage?: number; page?: number } = {}): Promise<ListResult<T>> {\n const params = this.toParams()\n if (opts.perPage !== undefined) params['per-page'] = String(clampPerPage(opts.perPage))\n else if (this.state.perPage !== undefined) params['per-page'] = String(this.state.perPage)\n if (opts.page !== undefined) params.page = String(opts.page)\n return this.runner.fetchList(params, { requestType: this.requestType })\n }\n\n /** Fetch just the first matching entity, or `null`. */\n async first(): Promise<T | null> {\n const res = await this.get({ perPage: 1 })\n return res.results[0] ?? null\n }\n\n /** Total number of matching results (one cheap probe call). */\n async count(): Promise<number> {\n const res = await this.runner.fetchList(\n { ...this.toParams(), 'per-page': '1' },\n { requestType: this.requestType },\n )\n return res.meta.count\n }\n\n /** Async-iterate every matching page. */\n paginate(opts: StreamOptions = {}): AsyncGenerator<ListResult<T>> {\n return streamPages(this.runner, this.toParams(), this.streamOpts(opts))\n }\n\n /** Async-iterate every matching entity across all pages. */\n all(opts: StreamOptions = {}): AsyncGenerator<T> {\n return streamItems(this.runner, this.toParams(), this.streamOpts(opts))\n }\n\n /** Collect all matching entities into an array (respecting `maxResults`). */\n async toArray(opts: StreamOptions = {}): Promise<T[]> {\n const out: T[] = []\n for await (const item of this.all(opts)) out.push(item)\n return out\n }\n\n /**\n * Stream all results using the cost-optimal strategy: one cheap count, then\n * parallel basic paging when ≤10k results (fastest) or cursor streaming above.\n */\n async *streamOptimized(opts: StreamOptions = {}): AsyncGenerator<T> {\n const est = await this.estimateCost({ perPage: opts.perPage ?? 200, strategy: 'cursor' })\n const parallel = est.matchingResults <= BASIC_PAGING_MAX_RESULTS\n yield* this.all({ ...opts, perPage: opts.perPage ?? 200, parallel })\n }\n\n /** Run a `group_by` aggregation and return the buckets. */\n async groups(field?: string | string[]): Promise<GroupResult[]> {\n const b = field ? this.groupBy(field) : this\n const res = await b.get({ perPage: 200 })\n return res.group_by ?? []\n }\n\n /** Estimate the dollar cost of extracting *all* matching results. */\n async estimateCost(\n opts: { perPage?: number; strategy?: 'basic' | 'cursor' } = {},\n ): Promise<CostEstimate> {\n const count = await this.count()\n return estimateExtractionCost({\n count,\n requestType: this.requestType,\n perPage: opts.perPage ?? this.state.perPage ?? PAGINATION_DEFAULT_PER_PAGE,\n strategy: opts.strategy ?? 'cursor',\n })\n }\n\n /** Stream all matching results to a file (JSONL / CSV / JSON). */\n async export(\n path: string,\n opts: StreamOptions & { format?: 'jsonl' | 'csv' | 'json'; flatten?: boolean } = {},\n ): Promise<{ path: string; count: number }> {\n const { exportToFile } = await import('./export')\n return exportToFile(this.all(opts), path, opts)\n }\n\n private streamOpts(opts: StreamOptions): StreamOptions {\n return {\n ...opts,\n perPage: opts.perPage ?? this.state.perPage ?? PAGINATION_DEFAULT_PER_PAGE,\n requestType: opts.requestType ?? this.requestType,\n }\n }\n}\n\nfunction serializeScalar(v: FilterScalar): string {\n if (typeof v === 'boolean') return v ? 'true' : 'false'\n return String(v)\n}\n\nfunction serializeFilterValue(v: FilterValue): string {\n return Array.isArray(v) ? v.map(serializeScalar).join('|') : serializeScalar(v)\n}\n\nexport function serializeFilters(filters: Filters): string | undefined {\n const entries = Object.entries(filters)\n if (entries.length === 0) return undefined\n return entries.map(([k, v]) => `${k}:${serializeFilterValue(v)}`).join(',')\n}\n","/**\n * Identifier normalization.\n *\n * OpenAlex lets you address an entity by many external IDs. This turns a raw\n * value a developer already has (a DOI, ORCID, ROR, ISSN, Wikidata QID, PMID,\n * MAG id, or full URL) into the path segment OpenAlex accepts, e.g.\n * \"10.7717/peerj.4375\" -> \"doi:10.7717/peerj.4375\"\n * \"0000-0002-1298-3089\" -> \"orcid:0000-0002-1298-3089\"\n * \"https://ror.org/03yrm5c26\" -> \"ror:03yrm5c26\"\n * \"2167-8359\" -> \"issn:2167-8359\"\n * \"W2741809807\" -> \"W2741809807\" (unchanged)\n *\n * Anything already prefixed (`doi:`, `orcid:`, …) or unrecognized is passed\n * through untouched, so callers always retain an escape hatch.\n */\n\nconst OPENALEX_ID = /^[WASITCPFKGDL]\\d{4,}$/i\nconst ORCID = /^(\\d{4}-){3}\\d{3}[\\dX]$/i\nconst ISSN = /^\\d{4}-\\d{3}[\\dX]$/i\nconst WIKIDATA = /^Q\\d+$/i\nconst DOI = /^10\\.\\d{4,9}\\/\\S+$/\nconst KNOWN_PREFIX =\n /^(openalex|doi|mag|orcid|ror|issn|issn_l|wikidata|pmid|pmcid):/i\n\n/** Normalize a single identifier to an OpenAlex-addressable path segment. */\nexport function normalizeId(input: string): string {\n const id = input.trim()\n if (!id) return id\n if (KNOWN_PREFIX.test(id)) return id\n\n // Full URLs → prefixed shorthand.\n const url = tryUrl(id)\n if (url) {\n const host = url.hostname.replace(/^www\\./, '')\n const last = url.pathname.split('/').filter(Boolean).pop() ?? ''\n if (host.endsWith('openalex.org')) return last\n if (host.endsWith('doi.org')) return `doi:${decodeURIComponent(url.pathname.slice(1))}`\n if (host.endsWith('orcid.org')) return `orcid:${last}`\n if (host.endsWith('ror.org')) return `ror:${last}`\n if (host.endsWith('wikidata.org')) return `wikidata:${last}`\n if (host.endsWith('ncbi.nlm.nih.gov') && url.pathname.includes('/pmc/'))\n return `pmcid:${last}`\n if (host.endsWith('ncbi.nlm.nih.gov') || host.includes('pubmed'))\n return `pmid:${last}`\n // Unknown URL: hand the last segment to OpenAlex and hope for the best.\n return last || id\n }\n\n if (OPENALEX_ID.test(id)) return id.toUpperCase().charAt(0) + id.slice(1)\n if (DOI.test(id)) return `doi:${id}`\n if (ORCID.test(id)) return `orcid:${id}`\n if (ISSN.test(id)) return `issn:${id}`\n if (WIKIDATA.test(id)) return `wikidata:${id.toUpperCase()}`\n\n return id\n}\n\n/** True when `input` is already a valid bare OpenAlex ID (e.g. `W2741809807`). */\nexport function isOpenAlexId(input: string): boolean {\n return OPENALEX_ID.test(input.trim())\n}\n\nfunction tryUrl(s: string): URL | null {\n if (!/^https?:\\/\\//i.test(s)) return null\n try {\n return new URL(s)\n } catch {\n return null\n }\n}\n","/**\n * Per-entity endpoint. One instance per entity type (`client.works`, …).\n *\n * Implements {@link EntityRunner} so a {@link QueryBuilder} can execute through\n * it, and exposes convenience proxies so simple calls stay terse:\n * await client.works.get('10.7717/peerj.4375')\n * await client.authors.filter({ ... }).sort({ cited_by_count: 'desc' }).toArray()\n */\n\nimport type { HttpClient } from './http'\nimport type { EntityRunner, FetchListOptions } from './runner'\nimport type { AutocompleteResult, EntityType, GroupResult, ListResult } from './types/common'\nimport type { Logger } from './config'\nimport { QueryBuilder, type FilterInput, type SortSpec } from './query'\nimport type { StreamOptions } from './pagination'\nimport type { CostEstimate } from './budget'\nimport { normalizeId } from './ids'\n\nexport interface GetOptions {\n select?: string | string[]\n noCache?: boolean\n}\n\nexport class EntityEndpoint<T, F extends string = string> implements EntityRunner<T> {\n constructor(\n protected readonly http: HttpClient,\n readonly entityType: EntityType,\n ) {}\n\n get logger(): Logger {\n return this.http.config.logger\n }\n\n get defaultConcurrency(): number {\n return this.http.config.concurrency\n }\n\n /** Low-level list fetch used by the query builder & paginators. */\n async fetchList(\n params: Record<string, string | undefined>,\n opts: FetchListOptions = {},\n ): Promise<ListResult<T>> {\n const res = await this.http.request<ListResult<T>>({\n path: this.entityType,\n params,\n requestType: opts.requestType ?? 'list',\n noCache: opts.noCache,\n signal: opts.signal,\n })\n return res.data\n }\n\n /** Start a fresh query. */\n query(): QueryBuilder<T, F> {\n return new QueryBuilder<T, F>(this)\n }\n\n // --- fluent proxies (each returns a QueryBuilder) ------------------------\n\n filter(filters: FilterInput<F>): QueryBuilder<T, F> {\n return this.query().filter(filters)\n }\n search(term: string): QueryBuilder<T, F> {\n return this.query().search(term)\n }\n searchField(field: string, term: string): QueryBuilder<T, F> {\n return this.query().searchField(field, term)\n }\n sort(spec: SortSpec): QueryBuilder<T, F> {\n return this.query().sort(spec)\n }\n select(fields: string | string[]): QueryBuilder<T, F> {\n return this.query().select(fields)\n }\n sample(n: number, seed?: number): QueryBuilder<T, F> {\n return this.query().sample(n, seed)\n }\n groupBy(field: string | string[]): QueryBuilder<T, F> {\n return this.query().groupBy(field)\n }\n perPage(n: number): QueryBuilder<T, F> {\n return this.query().perPage(n)\n }\n param(key: string, value: string | number | boolean): QueryBuilder<T, F> {\n return this.query().param(key, value)\n }\n\n // --- terminal proxies over the whole collection --------------------------\n\n count(): Promise<number> {\n return this.query().count()\n }\n all(opts?: StreamOptions): AsyncGenerator<T> {\n return this.query().all(opts)\n }\n paginate(opts?: StreamOptions): AsyncGenerator<ListResult<T>> {\n return this.query().paginate(opts)\n }\n toArray(opts?: StreamOptions): Promise<T[]> {\n return this.query().toArray(opts)\n }\n groups(field: string | string[]): Promise<GroupResult[]> {\n return this.query().groups(field)\n }\n estimateCost(opts?: { perPage?: number; strategy?: 'basic' | 'cursor' }): Promise<CostEstimate> {\n return this.query().estimateCost(opts)\n }\n export(\n path: string,\n opts?: StreamOptions & { format?: 'jsonl' | 'csv' | 'json'; flatten?: boolean },\n ): Promise<{ path: string; count: number }> {\n return this.query().export(path, opts)\n }\n\n // --- single & batch retrieval --------------------------------------------\n\n /**\n * Fetch one entity by any supported ID: OpenAlex ID, DOI, ORCID, ROR, ISSN,\n * Wikidata QID, PMID, MAG, or a full URL (all normalized automatically).\n */\n async get(id: string, opts: GetOptions = {}): Promise<T> {\n const params: Record<string, string | undefined> = {}\n if (opts.select) params.select = toCsv(opts.select)\n const res = await this.http.request<T>({\n path: `${this.entityType}/${normalizeId(id)}`,\n params,\n requestType: 'get',\n noCache: opts.noCache,\n })\n return res.data\n }\n\n /**\n * Fetch many entities by OpenAlex ID in as few calls as possible, batching\n * into `ids.openalex` OR-filters (default 50 per request) run in parallel.\n * Order is not guaranteed. For mixed external IDs, call {@link get} per ID.\n */\n async getMany(\n ids: string[],\n opts: { select?: string | string[]; chunkSize?: number } = {},\n ): Promise<T[]> {\n if (ids.length === 0) return []\n const chunkSize = Math.min(Math.max(1, opts.chunkSize ?? 50), 100)\n const select = opts.select ? toCsv(opts.select) : undefined\n const normalized = ids.map((id) => normalizeId(id))\n const chunks = chunk(normalized, chunkSize)\n\n const pages = await Promise.all(\n chunks.map((c) => {\n let q = this.query().filter({ 'ids.openalex': c })\n if (select) q = q.select(select)\n return q.get({ perPage: chunkSize })\n }),\n )\n return pages.flatMap((p) => p.results)\n }\n\n /** A single random entity (optionally trimmed with `select`). */\n async random(opts: { select?: string | string[] } = {}): Promise<T> {\n const params: Record<string, string | undefined> = {}\n if (opts.select) params.select = toCsv(opts.select)\n const res = await this.http.request<T>({\n path: `${this.entityType}/random`,\n params,\n requestType: 'get',\n noCache: true,\n })\n return res.data\n }\n\n /** Typeahead suggestions for this entity type. */\n async autocomplete(q: string): Promise<AutocompleteResult[]> {\n const res = await this.http.request<ListResult<AutocompleteResult>>({\n path: `autocomplete/${this.entityType}`,\n params: { q },\n requestType: 'autocomplete',\n })\n return res.data.results\n }\n}\n\nfunction toCsv(v: string | string[]): string {\n return Array.isArray(v) ? v.join(',') : v\n}\n\nfunction chunk<T>(arr: T[], size: number): T[][] {\n const out: T[][] = []\n for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size))\n return out\n}\n","/**\n * Works endpoint with citation-graph helpers layered on top of the generic\n * {@link EntityEndpoint}. Each helper returns a composable {@link QueryBuilder},\n * so you can chain `.select()`, `.filter()`, `.all()`, `.export()`, etc.\n */\n\nimport { EntityEndpoint } from './entities'\nimport type { HttpClient } from './http'\nimport type { Work } from './types/entities'\nimport type { QueryBuilder } from './query'\nimport type { WorksFilterKey } from './generated/filter-keys'\nimport { normalizeId } from './ids'\n\n/** Public host that serves cached full-text (PDF / TEI-XML) for OA works. */\nexport const CONTENT_BASE_URL = 'https://content.openalex.org'\n\nexport type FulltextFormat = 'pdf' | 'xml'\n\nexport class WorksEndpoint extends EntityEndpoint<Work, WorksFilterKey> {\n constructor(http: HttpClient) {\n super(http, 'works')\n }\n\n /** Works that **cite** the given work (incoming citations). */\n citedBy(workId: string): QueryBuilder<Work, WorksFilterKey> {\n return this.filter({ cites: normalizeId(workId) })\n }\n\n /** Works the given work **cites** — its references (outgoing citations). */\n references(workId: string): QueryBuilder<Work, WorksFilterKey> {\n return this.filter({ cited_by: normalizeId(workId) })\n }\n\n /** Works OpenAlex considers related to the given work. */\n related(workId: string): QueryBuilder<Work, WorksFilterKey> {\n return this.filter({ related_to: normalizeId(workId) })\n }\n\n /** Only works that have a downloadable cached PDF. */\n withPdf(): QueryBuilder<Work, WorksFilterKey> {\n return this.filter({ 'has_content.pdf': true })\n }\n\n /** Only works that have a downloadable TEI-XML (Grobid) parse. */\n withXml(): QueryBuilder<Work, WorksFilterKey> {\n return this.filter({ 'has_content.grobid_xml': true })\n }\n\n /** The content-host URL for a work's full text (api_key redacted). */\n fulltextUrl(workId: string, format: FulltextFormat = 'pdf'): string {\n return this.http.authorizeUrl(this.rawFulltextUrl(workId, format)).redacted\n }\n\n /**\n * Download a work's cached full text (PDF or TEI-XML). Costs $0.01/call and\n * is budget-tracked. Pass `dest` to stream to a file, otherwise get bytes.\n */\n async downloadFulltext(\n workId: string,\n opts: { format?: FulltextFormat; dest?: string; signal?: AbortSignal } = {},\n ): Promise<{ bytes?: Uint8Array; path?: string; contentType?: string }> {\n const { url, redacted } = this.http.authorizeUrl(\n this.rawFulltextUrl(workId, opts.format ?? 'pdf'),\n )\n const res = await this.http.download({\n url,\n redacted,\n requestType: 'download',\n dest: opts.dest,\n signal: opts.signal,\n })\n return { bytes: res.bytes, path: res.path, contentType: res.contentType }\n }\n\n private rawFulltextUrl(workId: string, format: FulltextFormat): string {\n const id = bareWorkId(normalizeId(workId))\n const ext = format === 'xml' ? 'grobid-xml' : 'pdf'\n return `${CONTENT_BASE_URL}/works/${id}.${ext}`\n }\n\n /**\n * One-hop citation neighbourhood: the work plus its references and citers.\n * Bounded by `limit` on each side to keep the cost predictable.\n */\n async citationGraph(\n workId: string,\n opts: { limit?: number; select?: string[] } = {},\n ): Promise<{ seed: Work; references: Work[]; citedBy: Work[] }> {\n const limit = opts.limit ?? 50\n const select = opts.select\n const [seed, references, citedBy] = await Promise.all([\n this.get(workId, select ? { select } : {}),\n this.references(workId).toArray({ maxResults: limit, perPage: Math.min(limit, 200) }),\n this.citedBy(workId).toArray({ maxResults: limit, perPage: Math.min(limit, 200) }),\n ])\n return { seed, references, citedBy }\n }\n}\n\n/** Reduce any normalized id to the bare OpenAlex work id the content host wants. */\nfunction bareWorkId(normalized: string): string {\n const m = /(W\\d+)/i.exec(normalized)\n return m ? m[1]!.toUpperCase() : normalized\n}\n","/**\n * Snapshot / bulk-data bridge — the \"$0 extract-all-data\" path.\n *\n * OpenAlex publishes the *entire* dataset as a free, public S3 snapshot\n * (gzipped JSON Lines, no credentials needed). For large extractions this is\n * far cheaper than the metered API: e.g. all Works = 510M records / 665 GB at\n * $0, versus paying $0.0001/list-call to crawl them.\n *\n * This module lets you:\n * - read the snapshot manifests (per-entity or combined) over HTTPS,\n * - compare the API cost of an extraction against the free snapshot,\n * - generate the exact `aws s3 sync` command, and\n * - stream entities straight from the public bucket in-process (gunzip +\n * NDJSON), with an optional client-side filter — zero dependencies.\n *\n * Paths/schema verified live (2026-07-25) against openalex.s3.amazonaws.com.\n */\n\nimport type { FetchLike } from './config'\nimport type { CostEstimate } from './budget'\nimport { OpenAlexError } from './errors'\n\nexport type SnapshotFormat = 'jsonl' | 'parquet'\n\n/** Default public HTTPS endpoint for the OpenAlex S3 bucket. */\nexport const SNAPSHOT_BASE_URL = 'https://openalex.s3.amazonaws.com'\nexport const SNAPSHOT_S3_URI = 's3://openalex'\n\nexport interface SnapshotFileEntry {\n /** Original `s3://openalex/...` URL from the manifest. */\n s3Url: string\n /** HTTPS URL for credential-free download. */\n httpsUrl: string\n contentLength: number\n recordCount: number\n}\n\nexport interface SnapshotManifest {\n entity: string\n format: string\n date?: string\n recordCount: number\n contentLength: number\n files: SnapshotFileEntry[]\n}\n\nexport interface EntityTotals {\n entity: string\n recordCount: number\n contentLength: number\n}\n\nexport interface SnapshotPlan {\n entity: string\n /** Results your filtered query would return from the API. */\n apiResults: number\n apiCalls: number\n apiCostUsd: number\n /** The snapshot is always free. */\n snapshotCostUsd: 0\n /** Total records in the entity's snapshot, when known. */\n snapshotRecords?: number\n /** Compressed bytes of the entity's snapshot, when known. */\n snapshotBytes?: number\n awsCommand: string\n recommendation: 'api' | 'snapshot'\n rationale: string\n}\n\nexport interface SnapshotClientOptions {\n /** Override the HTTPS base (e.g. a mirror or region host). */\n baseUrl?: string\n /** 'jsonl' (default, streamable in-process) or 'parquet'. */\n format?: SnapshotFormat\n /** Inject a fetch (defaults to global fetch / the client's fetch). */\n fetch?: FetchLike\n}\n\nexport interface StreamSnapshotOptions<T> {\n /** Keep only entities matching this predicate. */\n filter?: (entity: T) => boolean\n /** Only read the first N part files (useful for sampling/testing). */\n maxParts?: number\n /** Stop after emitting this many records. */\n maxRecords?: number\n /** Start from this part index (for manual resume). */\n startPart?: number\n /** Called after each part file completes. */\n onPart?: (info: { index: number; total: number; url: string; records: number }) => void\n signal?: AbortSignal\n}\n\nexport class SnapshotClient {\n readonly baseUrl: string\n readonly format: SnapshotFormat\n private readonly fetchImpl: FetchLike\n\n constructor(opts: SnapshotClientOptions = {}) {\n this.baseUrl = (opts.baseUrl ?? SNAPSHOT_BASE_URL).replace(/\\/+$/, '')\n this.format = opts.format ?? 'jsonl'\n const globalFetch = typeof fetch !== 'undefined' ? (fetch as FetchLike) : undefined\n const impl = opts.fetch ?? globalFetch\n if (!impl) throw new OpenAlexError('No fetch available for SnapshotClient (Node 18+ or pass `fetch`).')\n this.fetchImpl = impl\n }\n\n /** Convert an `s3://openalex/KEY` URL to a public HTTPS URL. */\n s3ToHttps(s3Url: string): string {\n const m = /^s3:\\/\\/([^/]+)\\/(.+)$/.exec(s3Url)\n if (!m) return s3Url\n // Route the openalex bucket through the configured baseUrl; others generically.\n if (m[1] === 'openalex') return `${this.baseUrl}/${m[2]}`\n return `https://${m[1]}.s3.amazonaws.com/${m[2]}`\n }\n\n manifestUrl(entity: string): string {\n return `${this.baseUrl}/data/${this.format}/${entity}/manifest.json`\n }\n\n combinedManifestUrl(): string {\n return `${this.baseUrl}/data/${this.format}/manifest.json`\n }\n\n /** `aws s3 sync` command to download the whole snapshot or one entity. */\n awsSyncCommand(entity?: string, dest = 'openalex-snapshot'): string {\n if (!entity) return `aws s3 sync \"${SNAPSHOT_S3_URI}\" \"${dest}\" --no-sign-request`\n const key = `data/${this.format}/${entity}`\n return `aws s3 sync \"${SNAPSHOT_S3_URI}/${key}\" \"${dest}/${key}\" --no-sign-request`\n }\n\n /** Fetch and normalize an entity's manifest. */\n async manifest(entity: string): Promise<SnapshotManifest> {\n const url = this.manifestUrl(entity)\n const raw = await this.getJson<RawManifest>(url)\n const files = (raw.files ?? []).map((f) => this.toFileEntry(f))\n return {\n entity: raw.entity ?? entity,\n format: raw.format ?? this.format,\n date: raw.date,\n recordCount: raw.record_count ?? files.reduce((a, f) => a + f.recordCount, 0),\n contentLength: raw.content_length ?? files.reduce((a, f) => a + f.contentLength, 0),\n files,\n }\n }\n\n /** Per-entity totals from the combined manifest (no per-file list). */\n async totals(): Promise<EntityTotals[]> {\n const raw = await this.getJson<RawCombinedManifest>(this.combinedManifestUrl())\n return (raw.entities ?? []).map((e) => ({\n entity: e.entity,\n recordCount: e.record_count,\n contentLength: e.content_length,\n }))\n }\n\n /** Totals for a single entity (cheap-ish: reads combined manifest). */\n async entityTotals(entity: string): Promise<EntityTotals | undefined> {\n return (await this.totals()).find((t) => t.entity === entity)\n }\n\n /**\n * Compare a filtered API extraction against downloading the whole snapshot.\n * Pass the {@link CostEstimate} from `query.estimateCost()` plus the entity.\n */\n async plan(\n apiEstimate: CostEstimate,\n entity: string,\n opts: { fetchTotals?: boolean; dest?: string } = {},\n ): Promise<SnapshotPlan> {\n let totals: EntityTotals | undefined\n if (opts.fetchTotals !== false) {\n try {\n totals = await this.entityTotals(entity)\n } catch {\n totals = undefined\n }\n }\n const apiCostUsd = apiEstimate.totalCostUsd\n const fraction = totals ? apiEstimate.matchingResults / totals.recordCount : undefined\n const recommendSnapshot = apiCostUsd >= 1 || (fraction !== undefined && fraction > 0.2)\n\n const rationale = recommendSnapshot\n ? `Your extraction (${apiEstimate.matchingResults.toLocaleString()} results) would cost ~$${apiCostUsd.toFixed(\n 4,\n )} via the API` +\n (totals\n ? `, and covers ${((fraction ?? 0) * 100).toFixed(1)}% of all ${entity}. The full ${entity} snapshot (${fmtBytes(\n totals.contentLength,\n )}, ${totals.recordCount.toLocaleString()} records) is free.`\n : `. The full ${entity} snapshot is free.`)\n : `Only ${apiEstimate.matchingResults.toLocaleString()} results (~$${apiCostUsd.toFixed(\n 4,\n )}) — cheaper/faster to pull the filtered subset via the API than to download the whole ${entity} snapshot.`\n\n return {\n entity,\n apiResults: apiEstimate.matchingResults,\n apiCalls: apiEstimate.pageCalls,\n apiCostUsd,\n snapshotCostUsd: 0,\n snapshotRecords: totals?.recordCount,\n snapshotBytes: totals?.contentLength,\n awsCommand: this.awsSyncCommand(entity, opts.dest),\n recommendation: recommendSnapshot ? 'snapshot' : 'api',\n rationale,\n }\n }\n\n /**\n * Stream entities straight from the public snapshot (gunzip + NDJSON), at $0.\n * Reads the manifest, then each gzipped part over HTTPS.\n */\n async *stream<T = Record<string, unknown>>(\n entity: string,\n opts: StreamSnapshotOptions<T> = {},\n ): AsyncGenerator<T> {\n const manifest = await this.manifest(entity)\n const startPart = opts.startPart ?? 0\n const endPart =\n opts.maxParts !== undefined\n ? Math.min(manifest.files.length, startPart + opts.maxParts)\n : manifest.files.length\n const maxRecords = opts.maxRecords ?? Infinity\n let emitted = 0\n\n for (let i = startPart; i < endPart; i++) {\n const file = manifest.files[i]!\n let recordsThisPart = 0\n for await (const record of streamGzippedNdjson<T>(this.fetchImpl, file.httpsUrl, opts.signal)) {\n recordsThisPart++\n if (opts.filter && !opts.filter(record)) continue\n yield record\n emitted++\n if (emitted >= maxRecords) {\n opts.onPart?.({ index: i, total: manifest.files.length, url: file.httpsUrl, records: recordsThisPart })\n return\n }\n }\n opts.onPart?.({ index: i, total: manifest.files.length, url: file.httpsUrl, records: recordsThisPart })\n }\n }\n\n private toFileEntry(f: RawFile): SnapshotFileEntry {\n return {\n s3Url: f.url,\n httpsUrl: this.s3ToHttps(f.url),\n contentLength: f.meta?.content_length ?? 0,\n recordCount: f.meta?.record_count ?? 0,\n }\n }\n\n private async getJson<T>(url: string): Promise<T> {\n const res = await this.fetchImpl(url, { headers: { Accept: 'application/json' } })\n if (!res.ok) {\n throw new OpenAlexError(`Snapshot request failed (${res.status}) [${url}]`, {\n status: res.status,\n url,\n })\n }\n return (await res.json()) as T\n }\n}\n\ninterface RawFile {\n url: string\n meta?: { content_length?: number; record_count?: number }\n}\ninterface RawManifest {\n entity?: string\n format?: string\n date?: string\n record_count?: number\n content_length?: number\n files?: RawFile[]\n}\ninterface RawCombinedManifest {\n entities?: Array<{ entity: string; record_count: number; content_length: number }>\n}\n\n/**\n * Stream newline-delimited JSON out of a gzipped HTTPS resource, without\n * buffering the whole file. Uses web streams (`DecompressionStream`) — no deps.\n */\nexport async function* streamGzippedNdjson<T>(\n fetchImpl: FetchLike,\n url: string,\n signal?: AbortSignal,\n): AsyncGenerator<T> {\n const res = await fetchImpl(url, signal ? { signal } : {})\n if (!res.ok || !res.body) {\n throw new OpenAlexError(`Snapshot part failed (${res.status}) [${url}]`, { status: res.status, url })\n }\n const textStream = res.body\n .pipeThrough(new DecompressionStream('gzip'))\n .pipeThrough(new TextDecoderStream())\n const reader = textStream.getReader()\n let buffer = ''\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += value\n let nl: number\n while ((nl = buffer.indexOf('\\n')) >= 0) {\n const line = buffer.slice(0, nl)\n buffer = buffer.slice(nl + 1)\n if (line.trim()) yield JSON.parse(line) as T\n }\n }\n if (buffer.trim()) yield JSON.parse(buffer) as T\n } finally {\n reader.releaseLock()\n }\n}\n\nfunction fmtBytes(n: number): string {\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let v = n\n let u = 0\n while (v >= 1024 && u < units.length - 1) {\n v /= 1024\n u++\n }\n return `${v.toFixed(v >= 10 || u === 0 ? 0 : 1)} ${units[u]}`\n}\n","/**\n * Cost-optimal extraction planner.\n *\n * Given a query, it counts once and recommends how to extract cheapest/fastest:\n * - per-page 200 (fewest calls),\n * - parallel basic paging when ≤10k results (fastest), else cursor streaming,\n * - and, when the API bill would be high, the free snapshot instead.\n */\n\nimport type { QueryBuilder } from './query'\nimport type { SnapshotClient } from './snapshot'\nimport { BASIC_PAGING_MAX_RESULTS, type CostEstimate, type RequestType } from './budget'\n\nexport type ExtractionStrategy = 'parallel-basic' | 'cursor' | 'snapshot'\n\nexport interface ExtractionPlan {\n entity: string\n matchingResults: number\n requestType: RequestType\n recommendedPerPage: number\n recommendedStrategy: ExtractionStrategy\n estimate: CostEstimate\n useSnapshot: boolean\n snapshotAwsCommand?: string\n rationale: string\n}\n\nexport interface PlanOptions {\n perPage?: number\n /** Recommend the snapshot when the API extraction would cost ≥ this (USD). Default 1. */\n snapshotThresholdUsd?: number\n}\n\nexport async function planExtraction(\n snapshot: SnapshotClient,\n query: QueryBuilder<unknown>,\n opts: PlanOptions = {},\n): Promise<ExtractionPlan> {\n const perPage = opts.perPage ?? 200\n const est = await query.estimateCost({ perPage, strategy: 'cursor' }) // single count probe\n const count = est.matchingResults\n const withinBasic = count <= BASIC_PAGING_MAX_RESULTS\n let strategy: ExtractionStrategy = withinBasic ? 'parallel-basic' : 'cursor'\n\n let useSnapshot = false\n let snapshotAwsCommand: string | undefined\n const threshold = opts.snapshotThresholdUsd ?? 1\n if (est.totalCostUsd >= threshold) {\n const plan = await snapshot.plan(est, query.entityType)\n snapshotAwsCommand = plan.awsCommand\n useSnapshot = plan.recommendation === 'snapshot'\n if (useSnapshot) strategy = 'snapshot'\n }\n\n const dollars = `$${est.totalCostUsd.toFixed(4)}`\n const rationale = useSnapshot\n ? `~${count.toLocaleString()} results ≈ ${dollars} via API — download the free snapshot instead.`\n : withinBasic\n ? `${count.toLocaleString()} results fit basic paging (≤10k): parallel page fetches at per-page ${perPage} are fastest (~${dollars}).`\n : `${count.toLocaleString()} results exceed 10k: cursor streaming at per-page ${perPage} (~${dollars}).`\n\n return {\n entity: query.entityType,\n matchingResults: count,\n requestType: est.requestType,\n recommendedPerPage: perPage,\n recommendedStrategy: strategy,\n estimate: est,\n useSnapshot,\n snapshotAwsCommand,\n rationale,\n }\n}\n","/**\n * N-grams for a Work — the fulltext n-gram frequencies OpenAlex exposes for\n * many works (when available).\n */\n\nimport type { HttpClient } from './http'\nimport { normalizeId } from './ids'\n\nexport interface NgramEntry {\n ngram: string\n ngram_count: number\n ngram_tokens: number\n term_frequency: number\n}\n\nexport interface NgramsResponse {\n meta: { count: number; doi?: string | null; [key: string]: unknown }\n ngrams: NgramEntry[]\n}\n\n/** Fetch the n-grams for a single Work (by any supported ID). */\nexport async function fetchNgrams(http: HttpClient, workId: string): Promise<NgramsResponse> {\n const res = await http.request<NgramsResponse>({\n path: `works/${normalizeId(workId)}/ngrams`,\n requestType: 'get',\n })\n return res.data\n}\n\n/**\n * Reconstruct readable abstract text from a Work's `abstract_inverted_index`.\n * Returns `null` when the index is absent (common for closed works).\n */\nexport function decodeAbstract(\n invertedIndex: Record<string, number[]> | null | undefined,\n): string | null {\n if (!invertedIndex) return null\n const positions: Array<[number, string]> = []\n for (const [word, idxs] of Object.entries(invertedIndex)) {\n for (const i of idxs) positions.push([i, word])\n }\n if (positions.length === 0) return null\n positions.sort((a, b) => a[0] - b[0])\n return positions.map(([, word]) => word).join(' ')\n}\n","/**\n * The main entry point: `new OpenAlexClient({ apiKey })`.\n *\n * Exposes one {@link EntityEndpoint} per entity type plus a shared\n * {@link UsageTracker} so budget/spend is visible across every call the client\n * makes.\n */\n\nimport { resolveConfig, type OpenAlexConfig, type ResolvedConfig } from './config'\nimport { HttpClient } from './http'\nimport { UsageTracker, type UsageSnapshot, type RequestType } from './budget'\nimport { EntityEndpoint } from './entities'\nimport { WorksEndpoint } from './works'\nimport { SnapshotClient, type SnapshotPlan } from './snapshot'\nimport { planExtraction, type ExtractionPlan, type PlanOptions } from './planner'\nimport { fetchNgrams, type NgramsResponse } from './ngrams'\nimport type { QueryBuilder } from './query'\nimport type {\n Work,\n Author,\n Source,\n Institution,\n Topic,\n Keyword,\n Publisher,\n Funder,\n} from './types/entities'\nimport type { AutocompleteResult, ListResult } from './types/common'\nimport type {\n AuthorsFilterKey,\n SourcesFilterKey,\n InstitutionsFilterKey,\n TopicsFilterKey,\n KeywordsFilterKey,\n PublishersFilterKey,\n FundersFilterKey,\n} from './generated/filter-keys'\n\nexport class OpenAlexClient {\n readonly config: ResolvedConfig\n readonly http: HttpClient\n /** Live spend/budget tracker, updated from every response. */\n readonly usage: UsageTracker\n\n /** Free bulk-data snapshot bridge (manifest, cost comparison, streaming). */\n readonly snapshot: SnapshotClient\n\n readonly works: WorksEndpoint\n readonly authors: EntityEndpoint<Author, AuthorsFilterKey>\n readonly sources: EntityEndpoint<Source, SourcesFilterKey>\n readonly institutions: EntityEndpoint<Institution, InstitutionsFilterKey>\n readonly topics: EntityEndpoint<Topic, TopicsFilterKey>\n readonly keywords: EntityEndpoint<Keyword, KeywordsFilterKey>\n readonly publishers: EntityEndpoint<Publisher, PublishersFilterKey>\n readonly funders: EntityEndpoint<Funder, FundersFilterKey>\n\n constructor(config: OpenAlexConfig = {}) {\n this.config = resolveConfig(config)\n this.usage = new UsageTracker(Boolean(this.config.apiKey), this.config.dailyBudgetUsd)\n this.http = new HttpClient(this.config, this.usage)\n\n this.snapshot = new SnapshotClient({ fetch: this.config.fetch })\n\n this.works = new WorksEndpoint(this.http)\n this.authors = new EntityEndpoint<Author, AuthorsFilterKey>(this.http, 'authors')\n this.sources = new EntityEndpoint<Source, SourcesFilterKey>(this.http, 'sources')\n this.institutions = new EntityEndpoint<Institution, InstitutionsFilterKey>(this.http, 'institutions')\n this.topics = new EntityEndpoint<Topic, TopicsFilterKey>(this.http, 'topics')\n this.keywords = new EntityEndpoint<Keyword, KeywordsFilterKey>(this.http, 'keywords')\n this.publishers = new EntityEndpoint<Publisher, PublishersFilterKey>(this.http, 'publishers')\n this.funders = new EntityEndpoint<Funder, FundersFilterKey>(this.http, 'funders')\n }\n\n /** Cross-entity typeahead. Optionally scope with `entityType`. */\n async autocomplete(\n q: string,\n entityType?:\n | 'works'\n | 'authors'\n | 'sources'\n | 'institutions'\n | 'topics'\n | 'keywords'\n | 'publishers'\n | 'funders',\n ): Promise<AutocompleteResult[]> {\n const path = entityType ? `autocomplete/${entityType}` : 'autocomplete'\n const res = await this.http.request<ListResult<AutocompleteResult>>({\n path,\n params: { q },\n requestType: 'autocomplete',\n })\n return res.data.results\n }\n\n /** N-grams for a Work. */\n ngrams(workId: string): Promise<NgramsResponse> {\n return fetchNgrams(this.http, workId)\n }\n\n /**\n * Decide API vs free snapshot for a query: estimates the API cost, then\n * compares against downloading the whole entity's snapshot ($0).\n */\n async snapshotPlan(query: QueryBuilder<unknown>): Promise<SnapshotPlan> {\n const estimate = await query.estimateCost()\n return this.snapshot.plan(estimate, query.entityType)\n }\n\n /**\n * Cost-optimal plan for extracting a query: recommends per-page, cursor-vs-\n * parallel strategy, and whether the free snapshot beats the API.\n */\n planExtraction(query: QueryBuilder<unknown>, opts?: PlanOptions): Promise<ExtractionPlan> {\n return planExtraction(this.snapshot, query, opts)\n }\n\n /** Escape hatch: issue a raw GET against any path with any params. */\n async request<T = unknown>(\n path: string,\n params: Record<string, string | undefined> = {},\n opts: { requestType?: RequestType; noCache?: boolean } = {},\n ): Promise<T> {\n const res = await this.http.request<T>({\n path,\n params,\n requestType: opts.requestType ?? 'list',\n noCache: opts.noCache,\n })\n return res.data\n }\n\n /** Point-in-time usage snapshot. */\n usageSnapshot(): UsageSnapshot {\n return this.usage.snapshot()\n }\n\n /** Human-readable spend/budget report. */\n usageReport(): string {\n return this.usage.report()\n }\n\n /** Reset the session usage counters (does not affect OpenAlex's daily total). */\n resetUsage(): void {\n this.usage.reset()\n }\n}\n","#!/usr/bin/env node\n/**\n * `oaw`: a thin CLI over the SDK.\n *\n * oaw works --filter \"publication_year:>2020,open_access.is_oa:true\" --limit 5\n * oaw works --search crispr --select id,display_name --export out.jsonl --parallel\n * oaw works --filter \"publication_year:2024\" --estimate\n * oaw get authors A5023888391 --select id,display_name\n * oaw snapshot totals works\n * oaw snapshot plan works --filter \"open_access.is_oa:true\"\n * oaw snapshot stream works --limit 100 --export works.jsonl\n * oaw fulltext W3038568908 --format pdf --out paper.pdf\n *\n * Global flags: --api-key, --mailto, --json (machine-readable output).\n */\n\nimport { OpenAlexClient } from './client'\nimport type { QueryBuilder } from './query'\n\nconst ENTITIES = [\n 'works',\n 'authors',\n 'sources',\n 'institutions',\n 'topics',\n 'keywords',\n 'publishers',\n 'funders',\n] as const\n\nexport interface CliDeps {\n client?: OpenAlexClient\n stdout?: (s: string) => void\n stderr?: (s: string) => void\n}\n\ninterface ParsedArgs {\n _: string[]\n flags: Record<string, string | boolean>\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const _: string[] = []\n const flags: Record<string, string | boolean> = {}\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!\n if (a.startsWith('--')) {\n const eq = a.indexOf('=')\n if (eq >= 0) {\n flags[a.slice(2, eq)] = a.slice(eq + 1)\n } else {\n const key = a.slice(2)\n const next = argv[i + 1]\n if (next === undefined || next.startsWith('--')) {\n flags[key] = true\n } else {\n flags[key] = next\n i++\n }\n }\n } else {\n _.push(a)\n }\n }\n return { _, flags }\n}\n\nexport async function runCli(argv: string[], deps: CliDeps = {}): Promise<number> {\n const out = deps.stdout ?? ((s) => process.stdout.write(s + '\\n'))\n const err = deps.stderr ?? ((s) => process.stderr.write(s + '\\n'))\n const { _, flags } = parseArgs(argv)\n const cmd = _[0]\n\n if (!cmd || flags.help || cmd === 'help') {\n out(HELP)\n return 0\n }\n\n const client =\n deps.client ??\n new OpenAlexClient({\n apiKey: str(flags['api-key']),\n mailto: str(flags.mailto),\n })\n const json = Boolean(flags.json)\n\n try {\n if (cmd === 'snapshot') return await snapshotCmd(_, flags, client, out, json)\n if (cmd === 'fulltext') return await fulltextCmd(_, flags, client, out)\n if (cmd === 'get') return await getCmd(_, flags, client, out)\n if ((ENTITIES as readonly string[]).includes(cmd)) {\n return await entityCmd(cmd, flags, client, out, json)\n }\n err(`Unknown command: ${cmd}`)\n out(HELP)\n return 1\n } catch (e) {\n err(`Error: ${(e as Error).message}`)\n return 1\n }\n}\n\nfunction endpoint(client: OpenAlexClient, entity: string): { query(): QueryBuilder<unknown> } {\n const ep = (client as unknown as Record<string, { query(): QueryBuilder<unknown> }>)[entity]\n if (!ep) throw new Error(`Unknown entity: ${entity}. One of: ${ENTITIES.join(', ')}`)\n return ep\n}\n\nfunction buildQuery(\n client: OpenAlexClient,\n entity: string,\n flags: Record<string, string | boolean>,\n): QueryBuilder<unknown> {\n let q = endpoint(client, entity).query()\n if (str(flags.filter)) q = q.param('filter', str(flags.filter)!)\n if (str(flags.search)) q = q.search(str(flags.search)!)\n if (str(flags.select)) q = q.select(str(flags.select)!)\n if (str(flags.sort)) q = q.sort(str(flags.sort)!)\n if (str(flags['per-page'])) q = q.perPage(Number(flags['per-page']))\n return q\n}\n\nasync function entityCmd(\n entity: string,\n flags: Record<string, string | boolean>,\n client: OpenAlexClient,\n out: (s: string) => void,\n json: boolean,\n): Promise<number> {\n const q = buildQuery(client, entity, flags)\n const limit = flags.limit !== undefined ? Number(flags.limit) : undefined\n const parallel = Boolean(flags.parallel)\n\n if (flags.count) {\n const n = await q.count()\n out(json ? JSON.stringify({ count: n }) : `${n}`)\n return 0\n }\n if (flags.estimate) {\n const e = await q.estimateCost()\n out(json ? JSON.stringify(e, null, 2) : formatEstimate(e))\n return 0\n }\n if (str(flags.export)) {\n const res = await q.export(str(flags.export)!, { maxResults: limit, parallel })\n out(json ? JSON.stringify(res) : `Wrote ${res.count} ${entity} to ${res.path}`)\n return 0\n }\n const items = await q.toArray({ maxResults: limit ?? 25, parallel })\n out(JSON.stringify(items, null, json ? 0 : 2))\n return 0\n}\n\nasync function getCmd(\n _: string[],\n flags: Record<string, string | boolean>,\n client: OpenAlexClient,\n out: (s: string) => void,\n): Promise<number> {\n const entity = _[1]\n const id = _[2]\n if (!entity || !id) throw new Error('usage: oaw get <entity> <id> [--select ...]')\n const ep = endpoint(client, entity) as unknown as {\n get(id: string, opts?: { select?: string }): Promise<unknown>\n }\n const item = await ep.get(id, str(flags.select) ? { select: str(flags.select)! } : {})\n out(JSON.stringify(item, null, 2))\n return 0\n}\n\nasync function snapshotCmd(\n _: string[],\n flags: Record<string, string | boolean>,\n client: OpenAlexClient,\n out: (s: string) => void,\n json: boolean,\n): Promise<number> {\n const sub = _[1]\n if (sub === 'totals') {\n const entity = _[2]\n if (entity) {\n const t = await client.snapshot.entityTotals(entity)\n out(json ? JSON.stringify(t) : formatTotals(t))\n } else {\n const ts = await client.snapshot.totals()\n out(json ? JSON.stringify(ts, null, 2) : ts.map(formatTotals).join('\\n'))\n }\n return 0\n }\n if (sub === 'plan') {\n const entity = _[2]\n if (!entity) throw new Error('usage: oaw snapshot plan <entity> [--filter ...]')\n const plan = await client.snapshotPlan(buildQuery(client, entity, flags))\n out(json ? JSON.stringify(plan, null, 2) : formatPlan(plan))\n return 0\n }\n if (sub === 'stream') {\n const entity = _[2]\n if (!entity) throw new Error('usage: oaw snapshot stream <entity> [--limit N] [--export FILE]')\n const limit = flags.limit !== undefined ? Number(flags.limit) : 10\n const gen = client.snapshot.stream(entity, { maxRecords: limit })\n if (str(flags.export)) {\n const { exportToFile } = await import('./export')\n const res = await exportToFile(gen, str(flags.export)!)\n out(`Wrote ${res.count} ${entity} to ${res.path}`)\n return 0\n }\n for await (const rec of gen) out(JSON.stringify(rec))\n return 0\n }\n throw new Error('usage: oaw snapshot <totals|plan|stream> [entity] [flags]')\n}\n\nasync function fulltextCmd(\n _: string[],\n flags: Record<string, string | boolean>,\n client: OpenAlexClient,\n out: (s: string) => void,\n): Promise<number> {\n const id = _[1]\n if (!id) throw new Error('usage: oaw fulltext <workId> [--format pdf|xml] [--out FILE]')\n const format = flags.format === 'xml' ? 'xml' : 'pdf'\n const dest = str(flags.out)\n if (!dest) {\n out(client.works.fulltextUrl(id, format))\n return 0\n }\n const res = await client.works.downloadFulltext(id, { format, dest })\n out(`Saved ${format.toUpperCase()} to ${res.path}`)\n return 0\n}\n\nfunction str(v: string | boolean | undefined): string | undefined {\n return typeof v === 'string' ? v : undefined\n}\n\nfunction formatEstimate(e: {\n matchingResults: number\n pageCalls: number\n totalCostUsd: number\n cappedByBasicPaging: boolean\n}): string {\n return (\n `results: ${e.matchingResults.toLocaleString()}\\n` +\n `api calls: ${e.pageCalls}\\n` +\n `est. cost: $${e.totalCostUsd.toFixed(4)}` +\n (e.cappedByBasicPaging ? ' (basic-paging capped at 10k)' : '')\n )\n}\n\nfunction formatTotals(t: { entity: string; recordCount: number; contentLength: number } | undefined): string {\n if (!t) return '(unknown entity)'\n const gb = (t.contentLength / 1e9).toFixed(1)\n return `${t.entity.padEnd(14)} ${t.recordCount.toLocaleString().padStart(14)} records ${gb} GB`\n}\n\nfunction formatPlan(p: {\n recommendation: string\n apiCostUsd: number\n rationale: string\n awsCommand: string\n}): string {\n return (\n `recommendation: ${p.recommendation.toUpperCase()}\\n` +\n `${p.rationale}\\n` +\n `api cost: ~$${p.apiCostUsd.toFixed(4)} snapshot: $0\\n` +\n `bulk download: ${p.awsCommand}`\n )\n}\n\nconst HELP = `oaw: OpenAlex CLI (open-alex-wrapper)\n\nUsage:\n oaw <entity> [flags] query an entity (works, authors, sources,\n institutions, topics, keywords, publishers, funders)\n oaw get <entity> <id> [--select ..] fetch one entity by any ID/DOI/ORCID/...\n oaw snapshot totals [entity] snapshot record counts & sizes\n oaw snapshot plan <entity> [flags] API $ cost vs free snapshot\n oaw snapshot stream <entity> [flags] stream the free snapshot ($0)\n oaw fulltext <workId> [flags] download full text (PDF/XML) or print its URL\n\nQuery flags:\n --filter \"k:v,k2:v2\" raw OpenAlex filter --search \"text\"\n --select id,title trim fields --sort field:desc\n --per-page N page size (≤200) --limit N cap results\n --parallel fast parallel paging --export FILE write JSONL/CSV/JSON\n --count just the match count --estimate dollar cost estimate\n\nGlobal flags:\n --api-key KEY (or OPENALEX_API_KEY) --mailto you@x.com --json --help`\n\nfunction isMain(): boolean {\n const entry = process.argv[1] ?? ''\n return /(?:^|[\\\\/])(cli|oaw)(\\.(c|m)?js)?$/.test(entry)\n}\n\nif (isMain()) {\n runCli(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (e) => {\n process.stderr.write(`Fatal: ${(e as Error).message}\\n`)\n process.exit(1)\n },\n )\n}\n"],"mappings":";;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAS,yBAAyB;AAWlC,eAAsB,aACpB,QACA,MACA,OAAsB,CAAC,GACmB;AAC1C,QAAM,SAAS,KAAK,UAAU,YAAY,IAAI;AAC9C,QAAM,SAAS,kBAAkB,MAAM,EAAE,UAAU,OAAO,CAAC;AAC3D,QAAM,QAAQ,WAAW,MAAM;AAC/B,MAAI,QAAQ;AAEZ,MAAI;AACF,QAAI,WAAW,SAAS;AACtB,uBAAiB,QAAQ,QAAQ;AAC/B,cAAM,MAAM,KAAK,UAAU,IAAI,IAAI,IAAI;AACvC;AAAA,MACF;AAAA,IACF,WAAW,WAAW,QAAQ;AAC5B,YAAM,MAAM,GAAG;AACf,UAAI,QAAQ;AACZ,uBAAiB,QAAQ,QAAQ;AAC/B,cAAM,OAAO,QAAQ,KAAK,SAAS,KAAK,UAAU,IAAI,CAAC;AACvD,gBAAQ;AACR;AAAA,MACF;AACA,YAAM,MAAM,KAAK;AAAA,IACnB,OAAO;AACL,UAAI,UAA2B;AAC/B,uBAAiB,QAAQ,QAAQ;AAC/B,cAAM,MACJ,KAAK,YAAY,QACZ,OACD,QAAQ,IAA+B;AAC7C,YAAI,CAAC,SAAS;AACZ,oBAAU,OAAO,KAAK,GAAG;AACzB,gBAAM,MAAM,QAAQ,IAAI,OAAO,EAAE,KAAK,GAAG,IAAI,IAAI;AAAA,QACnD;AACA,cAAM,MAAM,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,IAAI;AAChE;AAAA,MACF;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,UAAU,MAAM;AAAA,EACxB;AAEA,SAAO,EAAE,MAAM,MAAM;AACvB;AAEA,SAAS,WAAW,QAAgD;AAClE,SAAO,CAAC,MACN,IAAI,QAAc,CAAC,SAAS,WAAW;AACrC,WAAO,MAAM,GAAG,CAAC,QAAQ;AACvB,UAAI,IAAK,QAAO,GAAG;AAAA,IACrB,CAAC;AACD,QAAI,OAAO,kBAAmB,QAAO,KAAK,SAAS,OAAO;AAAA,QACrD,SAAQ;AAAA,EACf,CAAC;AACL;AAEA,SAAS,UAAU,QAAiC;AAClD,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAO,IAAI,CAAC,QAAwB,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAE;AAAA,EACpE,CAAC;AACH;AAEA,SAAS,YAAY,MAA4B;AAC/C,QAAM,IAAI,KAAK,YAAY;AAC3B,MAAI,EAAE,SAAS,QAAQ,KAAK,EAAE,SAAS,SAAS,EAAG,QAAO;AAC1D,MAAI,EAAE,SAAS,MAAM,EAAG,QAAO;AAC/B,MAAI,EAAE,SAAS,OAAO,EAAG,QAAO;AAChC,SAAO;AACT;AAEO,SAAS,QACd,KACA,SAAS,IACT,MAA+B,CAAC,GACP;AACzB,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,UAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK;AACxC,QAAI,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,GAAG;AACnD,cAAQ,GAA8B,KAAK,GAAG;AAAA,IAChD,OAAO;AACL,UAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,OAAwB;AACvC,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAM,IAAI,OAAO,UAAU,WAAW,KAAK,UAAU,KAAK,IAAI,OAAO,KAAK;AAC1E,MAAI,WAAW,KAAK,CAAC,EAAG,QAAO,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC;AACxD,SAAO;AACT;AA7GA;AAAA;AAAA;AAAA;AAAA;;;ACqBO,IAAM,WAAN,MAAqC;AAAA,EAG1C,YAA6B,aAAa,KAAM;AAAnB;AAAA,EAAoB;AAAA,EAApB;AAAA,EAFZ,MAAM,oBAAI,IAAmB;AAAA,EAI9C,IAAI,KAAkC;AACpC,UAAM,QAAQ,KAAK,IAAI,IAAI,GAAG;AAC9B,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,MAAM,cAAc,KAAK,MAAM,YAAY,KAAK,IAAI,GAAG;AACzD,WAAK,IAAI,OAAO,GAAG;AACnB,aAAO;AAAA,IACT;AAEA,SAAK,IAAI,OAAO,GAAG;AACnB,SAAK,IAAI,IAAI,KAAK,KAAK;AACvB,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,IAAI,KAAa,OAAgB,OAAqB;AACpD,QAAI,KAAK,IAAI,IAAI,GAAG,EAAG,MAAK,IAAI,OAAO,GAAG;AAC1C,SAAK,IAAI,IAAI,KAAK,EAAE,OAAO,WAAW,QAAQ,IAAI,KAAK,IAAI,IAAI,QAAQ,EAAE,CAAC;AAC1E,WAAO,KAAK,IAAI,OAAO,KAAK,YAAY;AACtC,YAAM,SAAS,KAAK,IAAI,KAAK,EAAE,KAAK,EAAE;AACtC,UAAI,WAAW,OAAW;AAC1B,WAAK,IAAI,OAAO,MAAM;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,OAAO,KAAmB;AACxB,SAAK,IAAI,OAAO,GAAG;AAAA,EACrB;AAAA,EAEA,QAAc;AACZ,SAAK,IAAI,MAAM;AAAA,EACjB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,IAAI;AAAA,EAClB;AACF;;;ACqCA,IAAM,cAAsB;AAAA,EAC1B,OAAO,MAAM;AAAA,EAAC;AAAA,EACd,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,OAAO,MAAM;AAAA,EAAC;AAChB;AAEA,IAAM,gBAA6B;AAAA,EACjC,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,eAAe,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AACzC;AAEA,IAAM,cAAc;AAEb,SAAS,cAAc,SAAyB,CAAC,GAAmB;AACzE,QAAM,MAAM,OAAO,YAAY,cAAc,QAAQ,MAAO,CAAC;AAC7D,QAAM,SAAS,OAAO,UAAU,IAAI;AACpC,QAAM,SAAS,OAAO,UAAU,IAAI;AAEpC,QAAM,cAAc,OAAO,UAAU,cAAe,QAAsB;AAC1E,QAAM,YAAY,OAAO,SAAS;AAClC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,MAAI,OAAO,UAAU,KAAM,SAAQ,IAAI,SAAS;AAAA,WACvC,OAAO,SAAS,OAAO,OAAO,UAAU,SAAU,SAAQ,OAAO;AAE1E,MAAI;AACJ,MAAI,OAAO,aAAa,KAAM,YAAW,EAAE,sBAAsB,GAAG;AAAA,WAC3D,OAAO,YAAY,OAAO,OAAO,aAAa,UAAU;AAC/D,eAAW;AAAA,MACT,sBAAsB,OAAO,SAAS,wBAAwB;AAAA,MAC9D,iBAAiB,OAAO,SAAS;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,KAAK,CAAC,qBAAqB,WAAW,IAAI,OAAO,WAAW,SAAS,UAAU,MAAM,KAAK,MAAS,EACtG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,OAAO,WAAW,4BAA4B,QAAQ,QAAQ,EAAE;AAAA,IAC1E,WAAW;AAAA,IACX,WAAW,OAAO,aAAa;AAAA,IAC/B,OAAO,EAAE,GAAG,eAAe,GAAG,OAAO,MAAM;AAAA,IAC3C,aAAa,KAAK,IAAI,GAAG,OAAO,eAAe,CAAC;AAAA,IAChD,gBAAgB,OAAO;AAAA,IACvB,iBAAiB,OAAO;AAAA,IACxB;AAAA,IACA,YAAY,OAAO,cAAc;AAAA,IACjC;AAAA,IACA,OAAO;AAAA,IACP,QAAQ,OAAO,UAAU;AAAA,EAC3B;AACF;;;AC3IO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,MAA4B,CAAC,GAAG;AAC3D,UAAM,SAAS,IAAI,UAAU,SAAY,EAAE,OAAO,IAAI,MAAM,IAAI,MAAS;AACzE,SAAK,OAAO,WAAW;AACvB,SAAK,SAAS,IAAI;AAClB,SAAK,MAAM,IAAI;AACf,SAAK,OAAO,IAAI;AAEhB,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAGO,IAAM,sBAAN,cAAkC,cAAc;AAAC;AAGjD,IAAM,gBAAN,cAA4B,cAAc;AAAC;AAG3C,IAAM,kBAAN,cAA8B,cAAc;AAAC;AAG7C,IAAM,iBAAN,cAA6B,cAAc;AAAA,EACvC;AAAA,EACT,YAAY,SAAiB,MAAwD,CAAC,GAAG;AACvF,UAAM,SAAS,GAAG;AAClB,SAAK,eAAe,IAAI;AAAA,EAC1B;AACF;AAGO,IAAM,cAAN,cAA0B,cAAc;AAAC;AAGzC,IAAM,eAAN,cAA2B,cAAc;AAAC;AAG1C,IAAM,eAAN,cAA2B,cAAc;AAAC;AAM1C,IAAM,sBAAN,cAAkC,cAAc;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACT,YACE,SACA,MACA;AACA,UAAM,OAAO;AACb,SAAK,WAAW,KAAK;AACrB,SAAK,WAAW,KAAK;AACrB,SAAK,gBAAgB,KAAK;AAAA,EAC5B;AACF;AAGO,SAAS,eACd,QACA,SACA,MAA4B,CAAC,GACd;AACf,QAAM,OAAO,EAAE,GAAG,KAAK,OAAO;AAC9B,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO,IAAI,oBAAoB,SAAS,IAAI;AAClF,MAAI,WAAW,IAAK,QAAO,IAAI,cAAc,SAAS,IAAI;AAC1D,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO,IAAI,gBAAgB,SAAS,IAAI;AAC9E,MAAI,WAAW,IAAK,QAAO,IAAI,eAAe,SAAS,IAAI;AAC3D,MAAI,UAAU,IAAK,QAAO,IAAI,YAAY,SAAS,IAAI;AACvD,SAAO,IAAI,cAAc,SAAS,IAAI;AACxC;;;AC7DO,IAAM,cAA2C;AAAA,EACtD,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,cAAc;AAChB;AAEO,IAAM,0BAA0B;AAChC,IAAM,6BAA6B;AAanC,IAAM,2BAA2B;AA+BjC,SAAS,uBAAuB,OAAoC;AACzE,QAAM,UAAU,aAAa,MAAM,WAAW,GAAG;AACjD,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,QAAQ,YAAY,MAAM,WAAW;AAE3C,QAAM,sBAAsB,aAAa,WAAW,MAAM,QAAQ;AAClE,QAAM,mBACJ,aAAa,UAAU,KAAK,IAAI,MAAM,OAAO,wBAAwB,IAAI,MAAM;AAEjF,QAAM,YAAY,qBAAqB,IAAI,IAAI,KAAK,KAAK,mBAAmB,OAAO;AACnF,QAAM,eAAe,OAAO,YAAY,KAAK;AAC7C,QAAM,eAAe,OAAO,KAAK;AAEjC,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,iBAAiB,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,OAAO,eAAe,YAAY;AAAA,IAChD;AAAA,IACA,MAAM,sBACF,uCAAuC,yBAAyB,eAAe,CAAC,0DAA0D,MAAM,MAAM,eAAe,CAAC,MACtK;AAAA,EACN;AACF;AAGO,SAAS,aAAa,GAAmB;AAC9C,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACjD;AAqDO,SAAS,kBAAkB,SAAwC;AACxE,QAAM,MAAM,iBAAiB,OAAO;AACpC,QAAM,MAA8B,CAAC;AACrC,aAAW,CAAC,GAAG,CAAC,KAAK,KAAK;AACxB,QAAI,EAAE,YAAY,EAAE,WAAW,aAAa,EAAG,KAAI,EAAE,YAAY,CAAC,IAAI;AAAA,EACxE;AACA,QAAM,MAAM,CAAC,QAAoC;AAC/C,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,QAAI,MAAM,OAAW,QAAO;AAC5B,UAAM,IAAI,OAAO,OAAO,CAAC,EAAE,QAAQ,WAAW,EAAE,CAAC;AACjD,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AAAA,IACL,SAAS,IAAI,sBAAsB;AAAA,IACnC,iBAAiB,IAAI,+BAA+B;AAAA,IACpD,cAAc,IAAI,2BAA2B;AAAA,IAC7C,UAAU,IAAI,uBAAuB;AAAA,IACrC,qBAAqB,IAAI,mCAAmC;AAAA,IAC5D,aAAa,IAAI,0BAA0B;AAAA,IAC3C,kBAAkB,IAAI,uBAAuB;AAAA,IAC7C,cAAc,IAAI,mBAAmB;AAAA,IACrC,cAAc,IAAI,mBAAmB;AAAA,IACrC;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,SAAyC;AACjE,QAAM,MAAM,oBAAI,IAAoB;AACpC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,mBAAmB,KAAK;AAC1B,eAAW,CAAC,GAAG,CAAC,KAAK,QAAS,KAAI,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC;AAChE,WAAO;AAAA,EACT;AACA,MAAI,OAAQ,QAAoB,YAAY,cAAc,CAAC,MAAM,QAAQ,OAAO,GAAG;AACjF;AAAC,IAAC,QAAoB,QAAQ,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,YAAY,GAAG,CAAC,CAAC;AACnE,WAAO;AAAA,EACT;AACA,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAkC,GAAG;AACvE,QAAI,MAAM,OAAW;AACrB,QAAI,IAAI,EAAE,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC;AAAA,EACrE;AACA,SAAO;AACT;AAEA,SAAS,cAAgD;AACvD,QAAM,OAAO,OAAoB,EAAE,OAAO,GAAG,SAAS,GAAG,kBAAkB,EAAE;AAC7E,SAAO,EAAE,KAAK,KAAK,GAAG,MAAM,KAAK,GAAG,QAAQ,KAAK,GAAG,UAAU,KAAK,GAAG,cAAc,KAAK,EAAE;AAC7F;AASO,IAAM,eAAN,MAAmB;AAAA,EAWxB,YACmB,WACA,gBACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAZX,SAAS,YAAY;AAAA,EACrB,oBAAoB;AAAA,EACpB,uBAAsC;AAAA,EACtC,mBAAkC;AAAA,EAClC,sBAAqC;AAAA,EACrC,mBAAkC;AAAA,EAClC,eAA8B;AAAA,EAC9B,qBAAoC;AAAA,EACpC,sBAA8C,CAAC;AAAA;AAAA,EAQvD,IAAI,gBAAwB;AAC1B,QAAI,KAAK,qBAAqB,KAAM,QAAO,KAAK;AAChD,QAAI,KAAK,mBAAmB,OAAW,QAAO,KAAK;AACnD,WAAO,KAAK,YAAY,0BAA0B;AAAA,EACpD;AAAA;AAAA,EAGA,IAAI,WAAmB;AACrB,QAAI,KAAK,qBAAqB,QAAQ,KAAK,yBAAyB,MAAM;AACxE,aAAO,OAAO,KAAK,mBAAmB,KAAK,oBAAoB;AAAA,IACjE;AACA,WAAO,OAAO,KAAK,iBAAiB;AAAA,EACtC;AAAA,EAEA,IAAI,eAAuB;AACzB,QAAI,KAAK,yBAAyB,KAAM,QAAO,KAAK;AACpD,WAAO,OAAO,KAAK,IAAI,GAAG,KAAK,gBAAgB,KAAK,QAAQ,CAAC;AAAA,EAC/D;AAAA;AAAA,EAGA,OAAO,MAAmB,OAAkD,CAAC,GAAS;AACpF,UAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,WAAO,SAAS;AAChB,WAAO,WAAW,KAAK,WAAW;AAClC,UAAM,WAAW,YAAY,IAAI;AACjC,WAAO,mBAAmB,OAAO,OAAO,mBAAmB,QAAQ;AACnE,SAAK,oBAAoB,OAAO,KAAK,oBAAoB,QAAQ;AAEjE,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,kBAAkB,KAAK,OAAO;AACxC,UAAI,OAAO,KAAK,EAAE,GAAG,EAAE,OAAQ,MAAK,sBAAsB,EAAE;AAC5D,UAAI,EAAE,iBAAiB,OAAW,MAAK,uBAAuB,EAAE;AAChE,UAAI,EAAE,aAAa,OAAW,MAAK,mBAAmB,EAAE;AACxD,UAAI,EAAE,wBAAwB,OAAW,MAAK,sBAAsB,EAAE;AACtE,UAAI,EAAE,qBAAqB,OAAW,MAAK,mBAAmB,EAAE;AAChE,UAAI,EAAE,iBAAiB,OAAW,MAAK,eAAe,EAAE;AACxD,UAAI,EAAE,YAAY,OAAW,MAAK,qBAAqB,EAAE;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,eAA6B;AAC9C,QAAI,KAAK,mBAAmB,OAAW;AACvC,UAAM,YAAY,KAAK,WAAW;AAClC,QAAI,YAAY,KAAK,gBAAgB;AACnC,YAAM,IAAI;AAAA,QACR,8BAA8B,OAAO,SAAS,EAAE;AAAA,UAC9C;AAAA,QACF,CAAC,0CAA0C,KAAK,eAAe,QAAQ,CAAC,CAAC;AAAA,QACzE,EAAE,UAAU,KAAK,UAAU,UAAU,KAAK,gBAAgB,eAAe,OAAO,aAAa,EAAE;AAAA,MACjG;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,WAA0B;AACxB,UAAM,gBACJ,KAAK,qBAAqB,QAAQ,KAAK,yBAAyB,OAC5D,OAAO,KAAK,mBAAmB,KAAK,oBAAoB,IACxD;AACN,WAAO;AAAA,MACL,YAAY,OAAO,OAAO,KAAK,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;AAAA,MACtE,mBAAmB,OAAO,KAAK,iBAAiB;AAAA,MAChD,kBAAkB;AAAA,MAClB,sBAAsB,KAAK;AAAA,MAC3B,qBAAqB,KAAK;AAAA,MAC1B,kBAAkB,KAAK;AAAA,MACvB,cAAc,KAAK;AAAA,MACnB,oBAAoB,KAAK;AAAA,MACzB,eAAe,KAAK;AAAA,MACpB,UAAU,KAAK;AAAA,MACf,cAAc,KAAK;AAAA,MACnB,QAAQ,oBAAoB,KAAK,MAAM;AAAA,MACvC,WAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,mBAA2C;AAC7C,WAAO,EAAE,GAAG,KAAK,oBAAoB;AAAA,EACvC;AAAA;AAAA,EAGA,SAAiB;AACf,UAAM,IAAI,KAAK,SAAS;AACxB,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,yBAAyB,EAAE,YAAY,QAAQ,gBAAgB;AAAA,MAC/D,yBAAyB,EAAE,UAAU;AAAA,MACrC,0BAA0B,EAAE,SAAS,QAAQ,CAAC,CAAC,GAC7C,EAAE,qBAAqB,OAAO,4BAA4B,cAC5D;AAAA,MACA,0BAA0B,EAAE,cAAc,QAAQ,CAAC,CAAC;AAAA,MACpD,0BAA0B,EAAE,aAAa,QAAQ,CAAC,CAAC;AAAA,IACrD;AACA,QAAI,EAAE,wBAAwB;AAC5B,YAAM,KAAK,0BAA0B,EAAE,oBAAoB,QAAQ,CAAC,CAAC,EAAE;AACzE,QAAI,EAAE,qBAAqB;AACzB,YAAM,KAAK,yBAAyB,EAAE,gBAAgB,EAAE;AAC1D,QAAI,EAAE,iBAAiB;AACrB,YAAM,KAAK,yBAAyB,eAAe,EAAE,YAAY,CAAC,EAAE;AACtE,UAAM,KAAK,YAAY;AACvB,eAAW,QAAQ,OAAO,KAAK,EAAE,MAAM,GAAoB;AACzD,YAAM,IAAI,EAAE,OAAO,IAAI;AACvB,UAAI,EAAE,UAAU,EAAG;AACnB,YAAM;AAAA,QACJ,OAAO,KAAK,OAAO,EAAE,CAAC,IAAI,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,YAAY,EAAE,iBAAiB,QAAQ,CAAC,CAAC;AAAA,MAChG;AAAA,IACF;AACA,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS,YAAY;AAC1B,SAAK,oBAAoB;AACzB,SAAK,uBAAuB;AAC5B,SAAK,mBAAmB;AACxB,SAAK,sBAAsB;AAC3B,SAAK,mBAAmB;AACxB,SAAK,eAAe;AACpB,SAAK,qBAAqB;AAC1B,SAAK,sBAAsB,CAAC;AAAA,EAC9B;AACF;AAEA,SAAS,eAAe,SAAyB;AAC/C,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;AACzC,QAAM,IAAI,KAAK,MAAM,IAAI,IAAI;AAC7B,QAAM,IAAI,KAAK,MAAO,IAAI,OAAQ,EAAE;AACpC,MAAI,IAAI,EAAG,QAAO,GAAG,CAAC,KAAK,CAAC;AAC5B,MAAI,IAAI,EAAG,QAAO,GAAG,CAAC,KAAK,IAAI,EAAE;AACjC,SAAO,GAAG,CAAC;AACb;AAEA,SAAS,OAAO,GAAmB;AACjC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;AAEA,SAAS,oBAAuB,GAAS;AACvC,SAAO,KAAK,MAAM,KAAK,UAAU,CAAC,CAAC;AACrC;;;ACvWO,IAAM,aAAoB;AAAA,EAC/B,KAAK,MAAM,KAAK,IAAI;AAAA,EACpB,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AACrD;AAEO,IAAM,WAAN,MAAe;AAAA,EAGpB,YACmB,MACA,QAAe,YAChC;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAJX,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxB,MAAM,KAAK,OAAqC;AAC9C,QAAI,KAAK,KAAK,oBAAoB,UAAa,MAAM,eAAe,KAAK,KAAK,iBAAiB;AAC7F,YAAM,IAAI;AAAA,QACR,8CAA8C,MAAM,aAAa,QAAQ,CAAC,CAAC,mCAChD,KAAK,KAAK,gBAAgB,QAAQ,CAAC,CAAC;AAAA,QAC/D;AAAA,UACE,UAAU,KAAK,IAAI,GAAG,MAAM,gBAAgB,MAAM,YAAY;AAAA,UAC9D,UAAU,MAAM;AAAA,UAChB,eAAe;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,KAAK,KAAK,uBAAuB,IAAI,MAAO,KAAK,KAAK,uBAAuB;AAC3F,UAAM,OAAO,MAAM,gBAAgB,IAAI,MAAM,eAAe,MAAM,gBAAgB;AAClF,QAAI,OAAO,IAAK,YAAW;AAAA,aAClB,OAAO,KAAM,YAAW;AAEjC,UAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,UAAM,OAAO,KAAK,IAAI,GAAG,KAAK,gBAAgB,GAAG;AAEjD,SAAK,gBAAgB,KAAK,IAAI,KAAK,KAAK,aAAa,IAAI;AACzD,QAAI,OAAO,EAAG,OAAM,KAAK,MAAM,MAAM,IAAI;AAAA,EAC3C;AACF;;;AC/CO,IAAM,YAAN,MAAgB;AAAA,EAGrB,YAA6B,KAAa;AAAb;AAAA,EAAc;AAAA,EAAd;AAAA,EAFrB,SAAS;AAAA,EACA,UAA6B,CAAC;AAAA,EAG/C,MAAM,IAAO,IAAkC;AAC7C,UAAM,KAAK,QAAQ;AACnB,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,UAAE;AACA,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA,EAEQ,UAAyB;AAC/B,QAAI,KAAK,SAAS,KAAK,KAAK;AAC1B,WAAK,UAAU;AACf,aAAO,QAAQ,QAAQ;AAAA,IACzB;AACA,WAAO,IAAI,QAAc,CAAC,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,EAClE;AAAA,EAEQ,UAAgB;AACtB,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAI,KAAM,MAAK;AAAA,QACV,MAAK,UAAU;AAAA,EACtB;AACF;AAuBO,IAAM,aAAN,MAAiB;AAAA,EAKtB,YACW,QACA,OACT;AAFS;AACA;AAET,SAAK,YAAY,IAAI,UAAU,OAAO,WAAW;AACjD,QAAI,OAAO,SAAU,MAAK,WAAW,IAAI,SAAS,OAAO,QAAQ;AAAA,EACnE;AAAA,EALW;AAAA,EACA;AAAA,EANM;AAAA,EACA,gBAAgB,oBAAI,IAAY;AAAA,EAChC;AAAA,EAUjB,MAAc,eAA8B;AAC1C,QAAI,CAAC,KAAK,SAAU;AACpB,UAAM,IAAI,KAAK,MAAM,SAAS;AAC9B,UAAM,KAAK,SAAS,KAAK,EAAE,cAAc,EAAE,cAAc,eAAe,EAAE,cAAc,CAAC;AAAA,EAC3F;AAAA;AAAA,EAGA,SAAS,MAAc,SAA6C,CAAC,GAGnE;AACA,UAAM,KAAK,IAAI,gBAAgB;AAC/B,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,UAAI,MAAM,UAAa,MAAM,GAAI,IAAG,IAAI,GAAG,CAAC;AAAA,IAC9C;AACA,QAAI,KAAK,OAAO,OAAQ,IAAG,IAAI,UAAU,KAAK,OAAO,MAAM;AAC3D,UAAM,aAAa,IAAI,gBAAgB,EAAE;AACzC,QAAI,KAAK,OAAO,QAAQ;AACtB,SAAG,IAAI,WAAW,KAAK,OAAO,MAAM;AACpC,iBAAW,IAAI,WAAW,UAAU;AAAA,IACtC;AACA,UAAM,OAAO,GAAG,KAAK,OAAO,OAAO,IAAI,UAAU,IAAI,CAAC;AACtD,UAAM,IAAI,GAAG,SAAS;AACtB,UAAM,KAAK,WAAW,SAAS;AAC/B,WAAO;AAAA,MACL,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK;AAAA,MAC1B,UAAU,KAAK,GAAG,IAAI,IAAI,EAAE,KAAK;AAAA,IACnC;AAAA,EACF;AAAA;AAAA,EAGQ,SAAS,MAAc,QAAoD;AACjF,UAAM,EAAE,SAAS,IAAI,KAAK,SAAS,MAAM,MAAM;AAC/C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAW,MAAgD;AAC/D,UAAM,SAAS,KAAK,UAAU,CAAC;AAC/B,UAAM,EAAE,KAAK,SAAS,IAAI,KAAK,SAAS,KAAK,MAAM,MAAM;AACzD,UAAM,WAAW,KAAK,OAAO,SAAS,CAAC,KAAK;AAC5C,UAAM,MAAM,WAAW,KAAK,SAAS,KAAK,MAAM,MAAM,IAAI;AAE1D,QAAI,YAAY,KAAK;AACnB,YAAM,SAAS,MAAM,KAAK,OAAO,MAAO,IAAI,GAAG;AAC/C,UAAI,WAAW,QAAW;AACxB,aAAK,OAAO,OAAO,MAAM,aAAa,QAAQ,EAAE;AAChD,eAAO;AAAA,UACL,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,SAAS,IAAI,QAAQ;AAAA,UACrB,KAAK;AAAA,UACL,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAGA,SAAK,MAAM,mBAAmB,YAAY,KAAK,WAAW,CAAC;AAC3D,UAAM,KAAK,aAAa;AAExB,UAAM,WAAW,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,eAAkB,KAAK,UAAU,IAAI,CAAC;AAG3F,UAAM,UAAU,aAAa,SAAS,IAAI;AAC1C,SAAK,MAAM,OAAO,KAAK,aAAa,EAAE,SAAS,SAAS,SAAS,QAAQ,CAAC;AAC1E,SAAK,gBAAgB;AAErB,QAAI,YAAY,KAAK;AACnB,YAAM,KAAK,OAAO,MAAO,IAAI,KAAK,SAAS,MAAM,KAAK,OAAO,UAAU;AAAA,IACzE;AACA,WAAO,EAAE,GAAG,UAAU,KAAK,SAAS;AAAA,EACtC;AAAA,EAEA,MAAc,eACZ,KACA,UACA,MAC0B;AAC1B,UAAM,MAAM,MAAM,KAAK,cAAc,KAAK,UAAU,EAAE,QAAQ,KAAK,OAAO,CAAC;AAC3E,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,EAAE,MAAM,QAAQ,IAAI,QAAQ,SAAS,IAAI,SAAS,KAAK,UAAU,QAAQ,MAAM;AAAA,EACxF;AAAA;AAAA,EAGA,MAAc,cACZ,KACA,UACA,MACmB;AACnB,UAAM,EAAE,MAAM,IAAI,KAAK;AACvB,QAAI,UAAU;AAGd,WAAO,MAAM;AACX,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,SAAS;AACxE,YAAM,SAAS,KAAK,SAAS,UAAU,CAAC,KAAK,QAAQ,WAAW,MAAM,CAAC,IAAI,WAAW;AACtF,UAAI;AACF,aAAK,OAAO,OAAO,MAAM,OAAO,QAAQ,aAAa,UAAU,CAAC,GAAG;AACnE,cAAM,MAAM,MAAM,KAAK,OAAO,MAAM,KAAK;AAAA,UACvC,QAAQ;AAAA,UACR,SAAS,EAAE,QAAQ,yBAAyB,cAAc,KAAK,OAAO,UAAU;AAAA,UAChF;AAAA,QACF,CAAC;AAED,YAAI,IAAI,GAAI,QAAO;AAEnB,cAAM,OAAO,MAAM,aAAa,GAAG;AACnC,cAAM,eAAe,gBAAgB,IAAI,QAAQ,IAAI,aAAa,CAAC;AACnE,cAAM,MAAM,eAAe,IAAI,QAAQ,SAAS,IAAI,QAAQ,MAAM,QAAQ,GAAG;AAAA,UAC3E,KAAK;AAAA,UACL;AAAA,UACA,GAAI,IAAI,WAAW,MAAM,EAAE,aAAa,IAAI,CAAC;AAAA,QAC/C,CAAC;AACD,YAAI,KAAK,YAAY,IAAI,QAAQ,OAAO,GAAG;AACzC,gBAAM,MAAM,KAAK,QAAQ,SAAS,YAAY,CAAC;AAC/C,qBAAW;AACX;AAAA,QACF;AACA,cAAM;AAAA,MACR,SAAS,GAAG;AACV,YAAI,aAAa,cAAe,OAAM;AACtC,cAAM,UAAW,GAAyB,SAAS;AACnD,YAAI,UAAU,MAAM,YAAY;AAC9B,eAAK,OAAO,OAAO,KAAK,GAAG,UAAU,YAAY,eAAe,eAAe,QAAQ,EAAE;AACzF,gBAAM,MAAM,KAAK,QAAQ,OAAO,CAAC;AACjC,qBAAW;AACX;AAAA,QACF;AACA,YAAI,SAAS;AACX,gBAAM,IAAI,aAAa,2BAA2B,KAAK,OAAO,SAAS,MAAM;AAAA,YAC3E,KAAK;AAAA,YACL,OAAO;AAAA,UACT,CAAC;AAAA,QACH;AACA,cAAM,IAAI,aAAa,2BAA4B,GAAa,WAAW,CAAC,IAAI;AAAA,UAC9E,KAAK;AAAA,UACL,OAAO;AAAA,QACT,CAAC;AAAA,MACH,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,aAAa,QAAmD;AAC9D,UAAM,IAAI,IAAI,IAAI,MAAM;AACxB,UAAM,IAAI,IAAI,IAAI,MAAM;AACxB,QAAI,KAAK,OAAO,QAAQ;AACtB,QAAE,aAAa,IAAI,UAAU,KAAK,OAAO,MAAM;AAC/C,QAAE,aAAa,IAAI,UAAU,KAAK,OAAO,MAAM;AAAA,IACjD;AACA,QAAI,KAAK,OAAO,QAAQ;AACtB,QAAE,aAAa,IAAI,WAAW,KAAK,OAAO,MAAM;AAChD,QAAE,aAAa,IAAI,WAAW,UAAU;AAAA,IAC1C;AACA,WAAO,EAAE,KAAK,EAAE,SAAS,GAAG,UAAU,EAAE,SAAS,EAAE;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,MAM4F;AACzG,SAAK,MAAM,mBAAmB,YAAY,KAAK,WAAW,CAAC;AAC3D,UAAM,KAAK,aAAa;AACxB,UAAM,WAAW,KAAK,YAAY,KAAK;AACvC,UAAM,MAAM,MAAM,KAAK,UAAU;AAAA,MAAI,MACnC,KAAK,cAAc,KAAK,KAAK,UAAU,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,IAChE;AACA,SAAK,MAAM,OAAO,KAAK,aAAa,EAAE,SAAS,IAAI,QAAQ,CAAC;AAC5D,SAAK,gBAAgB;AACrB,UAAM,cAAc,IAAI,QAAQ,IAAI,cAAc,KAAK;AAEvD,QAAI,KAAK,MAAM;AACb,UAAI,CAAC,IAAI,KAAM,OAAM,IAAI,cAAc,6BAA6B,EAAE,KAAK,SAAS,CAAC;AACrF,YAAM,CAAC,EAAE,SAAS,GAAG,EAAE,SAAS,GAAG,EAAE,mBAAAA,mBAAkB,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC5E,OAAO,QAAa;AAAA,QACpB,OAAO,iBAAsB;AAAA,QAC7B,OAAO,IAAS;AAAA,MAClB,CAAC;AACD,YAAM,SAAS,SAAS,QAAQ,IAAI,IAA8C,GAAGA,mBAAkB,KAAK,IAAI,CAAC;AACjH,aAAO,EAAE,MAAM,KAAK,MAAM,QAAQ,IAAI,QAAQ,SAAS,IAAI,SAAS,YAAY;AAAA,IAClF;AACA,UAAM,QAAQ,IAAI,WAAW,MAAM,IAAI,YAAY,CAAC;AACpD,WAAO,EAAE,OAAO,QAAQ,IAAI,QAAQ,SAAS,IAAI,SAAS,YAAY;AAAA,EACxE;AAAA,EAEQ,YAAY,QAAgB,SAA0B;AAC5D,WAAO,UAAU,KAAK,OAAO,MAAM,cAAc,KAAK,OAAO,MAAM,cAAc,SAAS,MAAM;AAAA,EAClG;AAAA,EAEQ,QAAQ,SAAiB,cAA+B;AAC9D,UAAM,EAAE,aAAa,WAAW,IAAI,KAAK,OAAO;AAChD,QAAI,iBAAiB,OAAW,QAAO,KAAK,IAAI,cAAc,UAAU;AACxE,UAAM,MAAM,KAAK,IAAI,cAAc,KAAK,SAAS,UAAU;AAE3D,WAAO,KAAK,MAAM,MAAM,IAAI,KAAK,OAAO,KAAK,MAAM,EAAE;AAAA,EACvD;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,KAAK,KAAK,OAAO;AACvB,QAAI,CAAC,GAAI;AACT,UAAM,EAAE,UAAU,cAAc,IAAI,KAAK,MAAM,SAAS;AACxD,QAAI,CAAC,cAAe;AACpB,UAAM,QAAQ,WAAW;AACzB,eAAW,aAAa,CAAC,KAAK,CAAG,GAAG;AAClC,UAAI,SAAS,aAAa,CAAC,KAAK,cAAc,IAAI,SAAS,GAAG;AAC5D,aAAK,cAAc,IAAI,SAAS;AAChC,WAAG,EAAE,UAAU,UAAU,eAAe,MAAM,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,aAAa,MAAuB;AAC3C,MAAI,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAS,KAA+B,OAAO,GAAG;AAC9F,WAAQ,KAAgC,QAAQ;AAAA,EAClD;AACA,SAAO;AACT;AAEA,SAAS,SAAS,QAAgB,MAAe,KAAqB;AACpE,QAAM,MACJ,QAAQ,OAAO,SAAS,YAAY,aAAa,OAC7C,OAAQ,KAA8B,OAAO,IAC7C,QAAQ,OAAO,SAAS,YAAY,WAAW,OAC7C,OAAQ,KAA4B,KAAK,IACzC;AACR,SAAO,4BAA4B,MAAM,IAAI,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG;AAC5E;AAEA,eAAe,aAAa,KAAiC;AAC3D,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,OAA0C;AACjE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,OAAO,KAAK;AACzB,MAAI,OAAO,SAAS,IAAI,EAAG,QAAO,KAAK,IAAI,GAAG,OAAO,GAAI;AACzD,QAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,MAAI,OAAO,SAAS,IAAI,EAAG,QAAO,KAAK,IAAI,GAAG,OAAO,KAAK,IAAI,CAAC;AAC/D,SAAO;AACT;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAGA,SAAS,UAAU,SAAqC;AACtD,QAAM,aAAa,IAAI,gBAAgB;AACvC,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,SAAS;AACb,iBAAW,MAAM;AACjB;AAAA,IACF;AACA,MAAE,iBAAiB,SAAS,MAAM,WAAW,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EACtE;AACA,SAAO,WAAW;AACpB;;;ACxTA,SAAS,gBAAgB,MAAyC;AAEhE,MAAI,KAAK,OAAQ,QAAO;AACxB,MAAI,KAAK,aAAa,YAAY,KAAK,aAAa,QAAS,QAAO,KAAK;AACzE,SAAO,KAAK,WAAW,UAAU;AACnC;AAGA,gBAAuB,YACrB,QACA,YACA,OAAsB,CAAC,GACQ;AAC/B,QAAM,WAAW,gBAAgB,IAAI;AACrC,MAAI,aAAa,UAAU;AACzB,WAAO,YAAY,QAAQ,YAAY,IAAI;AAAA,EAC7C,WAAW,KAAK,UAAU;AACxB,WAAO,mBAAmB,QAAQ,YAAY,IAAI;AAAA,EACpD,OAAO;AACL,WAAO,qBAAqB,QAAQ,YAAY,IAAI;AAAA,EACtD;AACF;AAGA,gBAAuB,YACrB,QACA,YACA,OAAsB,CAAC,GACJ;AACnB,MAAI,UAAU;AACd,QAAM,MAAM,KAAK,cAAc;AAC/B,mBAAiB,QAAQ,YAAY,QAAQ,YAAY,IAAI,GAAG;AAC9D,eAAW,QAAQ,KAAK,SAAS;AAC/B,UAAI,WAAW,IAAK;AACpB,YAAM;AACN,iBAAW;AAAA,IACb;AACA,QAAI,WAAW,IAAK;AAAA,EACtB;AACF;AAEA,gBAAuB,YACrB,QACA,YACA,MAC+B;AAC/B,QAAM,UAAU,aAAa,KAAK,WAAW,GAAG;AAChD,QAAM,YAAY,EAAE,aAAa,KAAK,aAAa,QAAQ,KAAK,OAAO;AACvE,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,WAAW,KAAK,YAAY;AAElC,QAAM,QAAQ,KAAK,QAAQ,KAAK;AAChC,MAAI,SAAwB,OAAO,UAAU;AAC7C,MAAI,UAAU,OAAO,WAAW;AAChC,MAAI,QAAQ;AAEZ,SAAO,UAAU,QAAQ,YAAY,UAAU,YAAY;AACzD,UAAM,OAAsB,MAAM,OAAO;AAAA,MACvC,EAAE,GAAG,YAAY,YAAY,OAAO,OAAO,GAAG,OAAO;AAAA,MACrD;AAAA,IACF;AACA,aAAS;AACT,eAAW,KAAK,QAAQ;AACxB,QAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,eAAS;AAAA,IACX,OAAO;AACL,eAAS,KAAK,KAAK,eAAe;AAAA,IACpC;AACA,SAAK,QAAQ,KAAK,EAAE,QAAQ,QAAQ,CAAC;AACrC,SAAK,aAAa,EAAE,SAAS,MAAM,OAAO,OAAO,KAAK,KAAK,SAAS,KAAK,CAAC;AAC1E,UAAM;AAAA,EACR;AAGA,MAAI,WAAW,KAAM,MAAK,QAAQ,MAAM;AAC1C;AAEA,gBAAuB,qBACrB,QACA,YACA,MAC+B;AAC/B,QAAM,UAAU,aAAa,KAAK,WAAW,GAAG;AAChD,QAAM,YAAY,EAAE,aAAa,KAAK,aAAa,QAAQ,KAAK,OAAO;AACvE,QAAM,kBAAkB,KAAK,MAAM,2BAA2B,OAAO;AACrE,QAAM,WAAW,KAAK,IAAI,KAAK,YAAY,UAAU,eAAe;AACpE,QAAM,aAAa,KAAK,cAAc;AACtC,MAAI,UAAU;AAEd,WAAS,OAAO,GAAG,QAAQ,UAAU,QAAQ;AAC3C,UAAM,SAAwB,MAAM,OAAO;AAAA,MACzC,EAAE,GAAG,YAAY,YAAY,OAAO,OAAO,GAAG,MAAM,OAAO,IAAI,EAAE;AAAA,MACjE;AAAA,IACF;AACA,eAAW,OAAO,QAAQ;AAC1B,SAAK,aAAa,EAAE,SAAS,MAAM,OAAO,OAAO,KAAK,SAAS,KAAK,CAAC;AACrE,UAAM;AACN,QAAI,OAAO,QAAQ,SAAS,WAAW,WAAW,WAAY;AAAA,EAChE;AACF;AAEA,gBAAuB,mBACrB,QACA,YACA,MAC+B;AAC/B,QAAM,UAAU,aAAa,KAAK,WAAW,GAAG;AAChD,QAAM,YAAY,EAAE,aAAa,KAAK,aAAa,QAAQ,KAAK,OAAO;AACvE,QAAM,kBAAkB,KAAK,MAAM,2BAA2B,OAAO;AAGrE,QAAM,QAAuB,MAAM,OAAO;AAAA,IACxC,EAAE,GAAG,YAAY,YAAY,OAAO,OAAO,GAAG,MAAM,IAAI;AAAA,IACxD;AAAA,EACF;AACA,MAAI,UAAU,MAAM,QAAQ;AAC5B,OAAK,aAAa,EAAE,SAAS,MAAM,GAAG,OAAO,MAAM,KAAK,SAAS,KAAK,CAAC;AACvE,QAAM;AAEN,QAAM,QAAQ,MAAM,KAAK,SAAS,MAAM,QAAQ;AAChD,MAAI,QAAQ,0BAA0B;AACpC,WAAO,OAAO;AAAA,MACZ,uBAAuB,MAAM,eAAe,CAAC,uBAAuB,yBAAyB,eAAe,CAAC,qCACzF,yBAAyB,eAAe,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,KAAK,KAAK,IAAI,OAAO,wBAAwB,IAAI,OAAO;AACjF,QAAM,aAAa,KAAK,IAAI,aAAa,iBAAiB,KAAK,YAAY,QAAQ;AACnF,MAAI,cAAc,KAAK,MAAM,QAAQ,SAAS,QAAS;AAIvD,QAAM,WAA0C,CAAC;AACjD,WAAS,OAAO,GAAG,QAAQ,YAAY,QAAQ;AAC7C,aAAS;AAAA,MACP,OAAO,UAAU,EAAE,GAAG,YAAY,YAAY,OAAO,OAAO,GAAG,MAAM,OAAO,IAAI,EAAE,GAAG,SAAS;AAAA,IAChG;AAAA,EACF;AACA,MAAI,UAAU;AACd,aAAW,KAAK,UAAU;AACxB,UAAM,OAAO,MAAM;AACnB,eAAW;AACX,eAAW,KAAK,QAAQ;AACxB,SAAK,aAAa,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,CAAC;AAC1D,UAAM;AACN,QAAI,KAAK,QAAQ,WAAW,EAAG;AAAA,EACjC;AACF;;;AC7IA,IAAM,8BAA8B;AAE7B,IAAM,eAAN,MAAM,cAA2C;AAAA,EACtD,YACmB,QACA,QAAoB,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,EAAE,GAC9D;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA;AAAA,EAInB,IAAI,aAAa;AACf,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEQ,MAAM,OAAgD;AAC5D,WAAO,IAAI,cAAmB,KAAK,QAAQ;AAAA,MACzC,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,SAAS,MAAM,WAAW,KAAK,MAAM;AAAA,MACrC,OAAO,MAAM,SAAS,KAAK,MAAM;AAAA,IACnC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA,EAKA,OAAO,SAA6C;AAClD,WAAO,KAAK,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,MAAM,SAAS,GAAG,QAAQ,EAAE,CAAC;AAAA,EACtE;AAAA;AAAA,EAGA,OAAO,MAAkC;AACvC,WAAO,KAAK,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,EACpC;AAAA;AAAA,EAGA,YAAY,OAAe,MAAkC;AAC3D,WAAO,KAAK,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,MAAM,SAAS,CAAC,GAAG,KAAK,SAAS,GAAG,KAAK,EAAE,CAAC;AAAA,EACrF;AAAA,EAEA,KAAK,MAAoC;AACvC,QAAI;AACJ,QAAI,OAAO,SAAS,SAAU,KAAI;AAAA,aACzB,MAAM,QAAQ,IAAI,EAAG,KAAI,KAAK,KAAK,GAAG;AAAA,QAC1C,KAAI,OAAO,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,GAAG;AACnE,WAAO,KAAK,MAAM,EAAE,MAAM,EAAE,CAAC;AAAA,EAC/B;AAAA;AAAA,EAGA,OAAO,QAA+C;AACpD,UAAM,MAAM,MAAM,QAAQ,MAAM,IAAI,SAAS,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAClF,WAAO,KAAK,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnC;AAAA;AAAA,EAGA,OAAO,GAAW,MAAmC;AACnD,WAAO,KAAK,MAAM,EAAE,SAAS,GAAG,MAAM,QAAQ,KAAK,MAAM,KAAK,CAAC;AAAA,EACjE;AAAA,EAEA,KAAK,MAAkC;AACrC,WAAO,KAAK,MAAM,EAAE,KAAK,CAAC;AAAA,EAC5B;AAAA,EAEA,QAAQ,GAA+B;AACrC,WAAO,KAAK,MAAM,EAAE,SAAS,aAAa,CAAC,EAAE,CAAC;AAAA,EAChD;AAAA;AAAA,EAGA,QAAQ,OAA8C;AACpD,WAAO,KAAK,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC;AAAA,EAC/E;AAAA;AAAA,EAGA,MAAM,KAAa,OAAsD;AACvE,WAAO,KAAK,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,MAAM,OAAO,CAAC,GAAG,GAAG,OAAO,KAAK,EAAE,EAAE,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA,EAKA,WAA+C;AAC7C,UAAM,IAAwC,EAAE,GAAG,KAAK,MAAM,MAAM;AACpE,UAAM,SAAS,iBAAiB,KAAK,MAAM,OAAO;AAClD,QAAI,OAAQ,GAAE,SAAS;AACvB,QAAI,KAAK,MAAM,OAAQ,GAAE,SAAS,KAAK,MAAM;AAC7C,QAAI,KAAK,MAAM,KAAM,GAAE,OAAO,KAAK,MAAM;AACzC,QAAI,KAAK,MAAM,QAAQ,OAAQ,GAAE,SAAS,KAAK,MAAM,OAAO,KAAK,GAAG;AACpE,QAAI,KAAK,MAAM,QAAS,GAAE,WAAW,KAAK,MAAM;AAChD,QAAI,KAAK,MAAM,YAAY,QAAW;AACpC,QAAE,SAAS,OAAO,KAAK,MAAM,OAAO;AACpC,UAAI,KAAK,MAAM,SAAS,OAAW,GAAE,OAAO,OAAO,KAAK,MAAM,IAAI;AAAA,IACpE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAY,cAA2B;AACrC,QAAI,KAAK,MAAM,UAAU,KAAK,MAAM,MAAM,OAAQ,QAAO;AACzD,eAAW,KAAK,OAAO,KAAK,KAAK,MAAM,OAAO,GAAG;AAC/C,UAAI,EAAE,SAAS,SAAS,KAAK,MAAM,iBAAkB,QAAO;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,OAA4C,CAAC,GAA2B;AAChF,UAAM,SAAS,KAAK,SAAS;AAC7B,QAAI,KAAK,YAAY,OAAW,QAAO,UAAU,IAAI,OAAO,aAAa,KAAK,OAAO,CAAC;AAAA,aAC7E,KAAK,MAAM,YAAY,OAAW,QAAO,UAAU,IAAI,OAAO,KAAK,MAAM,OAAO;AACzF,QAAI,KAAK,SAAS,OAAW,QAAO,OAAO,OAAO,KAAK,IAAI;AAC3D,WAAO,KAAK,OAAO,UAAU,QAAQ,EAAE,aAAa,KAAK,YAAY,CAAC;AAAA,EACxE;AAAA;AAAA,EAGA,MAAM,QAA2B;AAC/B,UAAM,MAAM,MAAM,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AACzC,WAAO,IAAI,QAAQ,CAAC,KAAK;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,QAAyB;AAC7B,UAAM,MAAM,MAAM,KAAK,OAAO;AAAA,MAC5B,EAAE,GAAG,KAAK,SAAS,GAAG,YAAY,IAAI;AAAA,MACtC,EAAE,aAAa,KAAK,YAAY;AAAA,IAClC;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,SAAS,OAAsB,CAAC,GAAkC;AAChE,WAAO,YAAY,KAAK,QAAQ,KAAK,SAAS,GAAG,KAAK,WAAW,IAAI,CAAC;AAAA,EACxE;AAAA;AAAA,EAGA,IAAI,OAAsB,CAAC,GAAsB;AAC/C,WAAO,YAAY,KAAK,QAAQ,KAAK,SAAS,GAAG,KAAK,WAAW,IAAI,CAAC;AAAA,EACxE;AAAA;AAAA,EAGA,MAAM,QAAQ,OAAsB,CAAC,GAAiB;AACpD,UAAM,MAAW,CAAC;AAClB,qBAAiB,QAAQ,KAAK,IAAI,IAAI,EAAG,KAAI,KAAK,IAAI;AACtD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,gBAAgB,OAAsB,CAAC,GAAsB;AAClE,UAAM,MAAM,MAAM,KAAK,aAAa,EAAE,SAAS,KAAK,WAAW,KAAK,UAAU,SAAS,CAAC;AACxF,UAAM,WAAW,IAAI,mBAAmB;AACxC,WAAO,KAAK,IAAI,EAAE,GAAG,MAAM,SAAS,KAAK,WAAW,KAAK,SAAS,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,OAAO,OAAmD;AAC9D,UAAM,IAAI,QAAQ,KAAK,QAAQ,KAAK,IAAI;AACxC,UAAM,MAAM,MAAM,EAAE,IAAI,EAAE,SAAS,IAAI,CAAC;AACxC,WAAO,IAAI,YAAY,CAAC;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAM,aACJ,OAA4D,CAAC,GACtC;AACvB,UAAM,QAAQ,MAAM,KAAK,MAAM;AAC/B,WAAO,uBAAuB;AAAA,MAC5B;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,SAAS,KAAK,WAAW,KAAK,MAAM,WAAW;AAAA,MAC/C,UAAU,KAAK,YAAY;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,OACJ,MACA,OAAiF,CAAC,GACxC;AAC1C,UAAM,EAAE,cAAAC,cAAa,IAAI,MAAM;AAC/B,WAAOA,cAAa,KAAK,IAAI,IAAI,GAAG,MAAM,IAAI;AAAA,EAChD;AAAA,EAEQ,WAAW,MAAoC;AACrD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,KAAK,WAAW,KAAK,MAAM,WAAW;AAAA,MAC/C,aAAa,KAAK,eAAe,KAAK;AAAA,IACxC;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,GAAyB;AAChD,MAAI,OAAO,MAAM,UAAW,QAAO,IAAI,SAAS;AAChD,SAAO,OAAO,CAAC;AACjB;AAEA,SAAS,qBAAqB,GAAwB;AACpD,SAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,eAAe,EAAE,KAAK,GAAG,IAAI,gBAAgB,CAAC;AAChF;AAEO,SAAS,iBAAiB,SAAsC;AACrE,QAAM,UAAU,OAAO,QAAQ,OAAO;AACtC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,qBAAqB,CAAC,CAAC,EAAE,EAAE,KAAK,GAAG;AAC5E;;;ACxPA,IAAM,cAAc;AACpB,IAAM,QAAQ;AACd,IAAM,OAAO;AACb,IAAM,WAAW;AACjB,IAAM,MAAM;AACZ,IAAM,eACJ;AAGK,SAAS,YAAY,OAAuB;AACjD,QAAM,KAAK,MAAM,KAAK;AACtB,MAAI,CAAC,GAAI,QAAO;AAChB,MAAI,aAAa,KAAK,EAAE,EAAG,QAAO;AAGlC,QAAM,MAAM,OAAO,EAAE;AACrB,MAAI,KAAK;AACP,UAAM,OAAO,IAAI,SAAS,QAAQ,UAAU,EAAE;AAC9C,UAAM,OAAO,IAAI,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI,KAAK;AAC9D,QAAI,KAAK,SAAS,cAAc,EAAG,QAAO;AAC1C,QAAI,KAAK,SAAS,SAAS,EAAG,QAAO,OAAO,mBAAmB,IAAI,SAAS,MAAM,CAAC,CAAC,CAAC;AACrF,QAAI,KAAK,SAAS,WAAW,EAAG,QAAO,SAAS,IAAI;AACpD,QAAI,KAAK,SAAS,SAAS,EAAG,QAAO,OAAO,IAAI;AAChD,QAAI,KAAK,SAAS,cAAc,EAAG,QAAO,YAAY,IAAI;AAC1D,QAAI,KAAK,SAAS,kBAAkB,KAAK,IAAI,SAAS,SAAS,OAAO;AACpE,aAAO,SAAS,IAAI;AACtB,QAAI,KAAK,SAAS,kBAAkB,KAAK,KAAK,SAAS,QAAQ;AAC7D,aAAO,QAAQ,IAAI;AAErB,WAAO,QAAQ;AAAA,EACjB;AAEA,MAAI,YAAY,KAAK,EAAE,EAAG,QAAO,GAAG,YAAY,EAAE,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC;AACxE,MAAI,IAAI,KAAK,EAAE,EAAG,QAAO,OAAO,EAAE;AAClC,MAAI,MAAM,KAAK,EAAE,EAAG,QAAO,SAAS,EAAE;AACtC,MAAI,KAAK,KAAK,EAAE,EAAG,QAAO,QAAQ,EAAE;AACpC,MAAI,SAAS,KAAK,EAAE,EAAG,QAAO,YAAY,GAAG,YAAY,CAAC;AAE1D,SAAO;AACT;AAOA,SAAS,OAAO,GAAuB;AACrC,MAAI,CAAC,gBAAgB,KAAK,CAAC,EAAG,QAAO;AACrC,MAAI;AACF,WAAO,IAAI,IAAI,CAAC;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC9CO,IAAM,iBAAN,MAA8E;AAAA,EACnF,YACqB,MACV,YACT;AAFmB;AACV;AAAA,EACR;AAAA,EAFkB;AAAA,EACV;AAAA,EAGX,IAAI,SAAiB;AACnB,WAAO,KAAK,KAAK,OAAO;AAAA,EAC1B;AAAA,EAEA,IAAI,qBAA6B;AAC/B,WAAO,KAAK,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAM,UACJ,QACA,OAAyB,CAAC,GACF;AACxB,UAAM,MAAM,MAAM,KAAK,KAAK,QAAuB;AAAA,MACjD,MAAM,KAAK;AAAA,MACX;AAAA,MACA,aAAa,KAAK,eAAe;AAAA,MACjC,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,QAA4B;AAC1B,WAAO,IAAI,aAAmB,IAAI;AAAA,EACpC;AAAA;AAAA,EAIA,OAAO,SAA6C;AAClD,WAAO,KAAK,MAAM,EAAE,OAAO,OAAO;AAAA,EACpC;AAAA,EACA,OAAO,MAAkC;AACvC,WAAO,KAAK,MAAM,EAAE,OAAO,IAAI;AAAA,EACjC;AAAA,EACA,YAAY,OAAe,MAAkC;AAC3D,WAAO,KAAK,MAAM,EAAE,YAAY,OAAO,IAAI;AAAA,EAC7C;AAAA,EACA,KAAK,MAAoC;AACvC,WAAO,KAAK,MAAM,EAAE,KAAK,IAAI;AAAA,EAC/B;AAAA,EACA,OAAO,QAA+C;AACpD,WAAO,KAAK,MAAM,EAAE,OAAO,MAAM;AAAA,EACnC;AAAA,EACA,OAAO,GAAW,MAAmC;AACnD,WAAO,KAAK,MAAM,EAAE,OAAO,GAAG,IAAI;AAAA,EACpC;AAAA,EACA,QAAQ,OAA8C;AACpD,WAAO,KAAK,MAAM,EAAE,QAAQ,KAAK;AAAA,EACnC;AAAA,EACA,QAAQ,GAA+B;AACrC,WAAO,KAAK,MAAM,EAAE,QAAQ,CAAC;AAAA,EAC/B;AAAA,EACA,MAAM,KAAa,OAAsD;AACvE,WAAO,KAAK,MAAM,EAAE,MAAM,KAAK,KAAK;AAAA,EACtC;AAAA;AAAA,EAIA,QAAyB;AACvB,WAAO,KAAK,MAAM,EAAE,MAAM;AAAA,EAC5B;AAAA,EACA,IAAI,MAAyC;AAC3C,WAAO,KAAK,MAAM,EAAE,IAAI,IAAI;AAAA,EAC9B;AAAA,EACA,SAAS,MAAqD;AAC5D,WAAO,KAAK,MAAM,EAAE,SAAS,IAAI;AAAA,EACnC;AAAA,EACA,QAAQ,MAAoC;AAC1C,WAAO,KAAK,MAAM,EAAE,QAAQ,IAAI;AAAA,EAClC;AAAA,EACA,OAAO,OAAkD;AACvD,WAAO,KAAK,MAAM,EAAE,OAAO,KAAK;AAAA,EAClC;AAAA,EACA,aAAa,MAAmF;AAC9F,WAAO,KAAK,MAAM,EAAE,aAAa,IAAI;AAAA,EACvC;AAAA,EACA,OACE,MACA,MAC0C;AAC1C,WAAO,KAAK,MAAM,EAAE,OAAO,MAAM,IAAI;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IAAI,IAAY,OAAmB,CAAC,GAAe;AACvD,UAAM,SAA6C,CAAC;AACpD,QAAI,KAAK,OAAQ,QAAO,SAAS,MAAM,KAAK,MAAM;AAClD,UAAM,MAAM,MAAM,KAAK,KAAK,QAAW;AAAA,MACrC,MAAM,GAAG,KAAK,UAAU,IAAI,YAAY,EAAE,CAAC;AAAA,MAC3C;AAAA,MACA,aAAa;AAAA,MACb,SAAS,KAAK;AAAA,IAChB,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QACJ,KACA,OAA2D,CAAC,GAC9C;AACd,QAAI,IAAI,WAAW,EAAG,QAAO,CAAC;AAC9B,UAAM,YAAY,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,aAAa,EAAE,GAAG,GAAG;AACjE,UAAM,SAAS,KAAK,SAAS,MAAM,KAAK,MAAM,IAAI;AAClD,UAAM,aAAa,IAAI,IAAI,CAAC,OAAO,YAAY,EAAE,CAAC;AAClD,UAAM,SAAS,MAAM,YAAY,SAAS;AAE1C,UAAM,QAAQ,MAAM,QAAQ;AAAA,MAC1B,OAAO,IAAI,CAAC,MAAM;AAChB,YAAI,IAAI,KAAK,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;AACjD,YAAI,OAAQ,KAAI,EAAE,OAAO,MAAM;AAC/B,eAAO,EAAE,IAAI,EAAE,SAAS,UAAU,CAAC;AAAA,MACrC,CAAC;AAAA,IACH;AACA,WAAO,MAAM,QAAQ,CAAC,MAAM,EAAE,OAAO;AAAA,EACvC;AAAA;AAAA,EAGA,MAAM,OAAO,OAAuC,CAAC,GAAe;AAClE,UAAM,SAA6C,CAAC;AACpD,QAAI,KAAK,OAAQ,QAAO,SAAS,MAAM,KAAK,MAAM;AAClD,UAAM,MAAM,MAAM,KAAK,KAAK,QAAW;AAAA,MACrC,MAAM,GAAG,KAAK,UAAU;AAAA,MACxB;AAAA,MACA,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,aAAa,GAA0C;AAC3D,UAAM,MAAM,MAAM,KAAK,KAAK,QAAwC;AAAA,MAClE,MAAM,gBAAgB,KAAK,UAAU;AAAA,MACrC,QAAQ,EAAE,EAAE;AAAA,MACZ,aAAa;AAAA,IACf,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;AAEA,SAAS,MAAM,GAA8B;AAC3C,SAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI;AAC1C;AAEA,SAAS,MAAS,KAAU,MAAqB;AAC/C,QAAM,MAAa,CAAC;AACpB,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,KAAM,KAAI,KAAK,IAAI,MAAM,GAAG,IAAI,IAAI,CAAC;AAC1E,SAAO;AACT;;;AC/KO,IAAM,mBAAmB;AAIzB,IAAM,gBAAN,cAA4B,eAAqC;AAAA,EACtE,YAAY,MAAkB;AAC5B,UAAM,MAAM,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,QAAQ,QAAoD;AAC1D,WAAO,KAAK,OAAO,EAAE,OAAO,YAAY,MAAM,EAAE,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,WAAW,QAAoD;AAC7D,WAAO,KAAK,OAAO,EAAE,UAAU,YAAY,MAAM,EAAE,CAAC;AAAA,EACtD;AAAA;AAAA,EAGA,QAAQ,QAAoD;AAC1D,WAAO,KAAK,OAAO,EAAE,YAAY,YAAY,MAAM,EAAE,CAAC;AAAA,EACxD;AAAA;AAAA,EAGA,UAA8C;AAC5C,WAAO,KAAK,OAAO,EAAE,mBAAmB,KAAK,CAAC;AAAA,EAChD;AAAA;AAAA,EAGA,UAA8C;AAC5C,WAAO,KAAK,OAAO,EAAE,0BAA0B,KAAK,CAAC;AAAA,EACvD;AAAA;AAAA,EAGA,YAAY,QAAgB,SAAyB,OAAe;AAClE,WAAO,KAAK,KAAK,aAAa,KAAK,eAAe,QAAQ,MAAM,CAAC,EAAE;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBACJ,QACA,OAAyE,CAAC,GACJ;AACtE,UAAM,EAAE,KAAK,SAAS,IAAI,KAAK,KAAK;AAAA,MAClC,KAAK,eAAe,QAAQ,KAAK,UAAU,KAAK;AAAA,IAClD;AACA,UAAM,MAAM,MAAM,KAAK,KAAK,SAAS;AAAA,MACnC;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO,EAAE,OAAO,IAAI,OAAO,MAAM,IAAI,MAAM,aAAa,IAAI,YAAY;AAAA,EAC1E;AAAA,EAEQ,eAAe,QAAgB,QAAgC;AACrE,UAAM,KAAK,WAAW,YAAY,MAAM,CAAC;AACzC,UAAM,MAAM,WAAW,QAAQ,eAAe;AAC9C,WAAO,GAAG,gBAAgB,UAAU,EAAE,IAAI,GAAG;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cACJ,QACA,OAA8C,CAAC,GACe;AAC9D,UAAM,QAAQ,KAAK,SAAS;AAC5B,UAAM,SAAS,KAAK;AACpB,UAAM,CAAC,MAAM,YAAY,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACpD,KAAK,IAAI,QAAQ,SAAS,EAAE,OAAO,IAAI,CAAC,CAAC;AAAA,MACzC,KAAK,WAAW,MAAM,EAAE,QAAQ,EAAE,YAAY,OAAO,SAAS,KAAK,IAAI,OAAO,GAAG,EAAE,CAAC;AAAA,MACpF,KAAK,QAAQ,MAAM,EAAE,QAAQ,EAAE,YAAY,OAAO,SAAS,KAAK,IAAI,OAAO,GAAG,EAAE,CAAC;AAAA,IACnF,CAAC;AACD,WAAO,EAAE,MAAM,YAAY,QAAQ;AAAA,EACrC;AACF;AAGA,SAAS,WAAW,YAA4B;AAC9C,QAAM,IAAI,UAAU,KAAK,UAAU;AACnC,SAAO,IAAI,EAAE,CAAC,EAAG,YAAY,IAAI;AACnC;;;AC9EO,IAAM,oBAAoB;AAC1B,IAAM,kBAAkB;AAkExB,IAAM,iBAAN,MAAqB;AAAA,EACjB;AAAA,EACA;AAAA,EACQ;AAAA,EAEjB,YAAY,OAA8B,CAAC,GAAG;AAC5C,SAAK,WAAW,KAAK,WAAW,mBAAmB,QAAQ,QAAQ,EAAE;AACrE,SAAK,SAAS,KAAK,UAAU;AAC7B,UAAM,cAAc,OAAO,UAAU,cAAe,QAAsB;AAC1E,UAAM,OAAO,KAAK,SAAS;AAC3B,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,mEAAmE;AACtG,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAGA,UAAU,OAAuB;AAC/B,UAAM,IAAI,yBAAyB,KAAK,KAAK;AAC7C,QAAI,CAAC,EAAG,QAAO;AAEf,QAAI,EAAE,CAAC,MAAM,WAAY,QAAO,GAAG,KAAK,OAAO,IAAI,EAAE,CAAC,CAAC;AACvD,WAAO,WAAW,EAAE,CAAC,CAAC,qBAAqB,EAAE,CAAC,CAAC;AAAA,EACjD;AAAA,EAEA,YAAY,QAAwB;AAClC,WAAO,GAAG,KAAK,OAAO,SAAS,KAAK,MAAM,IAAI,MAAM;AAAA,EACtD;AAAA,EAEA,sBAA8B;AAC5B,WAAO,GAAG,KAAK,OAAO,SAAS,KAAK,MAAM;AAAA,EAC5C;AAAA;AAAA,EAGA,eAAe,QAAiB,OAAO,qBAA6B;AAClE,QAAI,CAAC,OAAQ,QAAO,gBAAgB,eAAe,MAAM,IAAI;AAC7D,UAAM,MAAM,QAAQ,KAAK,MAAM,IAAI,MAAM;AACzC,WAAO,gBAAgB,eAAe,IAAI,GAAG,MAAM,IAAI,IAAI,GAAG;AAAA,EAChE;AAAA;AAAA,EAGA,MAAM,SAAS,QAA2C;AACxD,UAAM,MAAM,KAAK,YAAY,MAAM;AACnC,UAAM,MAAM,MAAM,KAAK,QAAqB,GAAG;AAC/C,UAAM,SAAS,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC;AAC9D,WAAO;AAAA,MACL,QAAQ,IAAI,UAAU;AAAA,MACtB,QAAQ,IAAI,UAAU,KAAK;AAAA,MAC3B,MAAM,IAAI;AAAA,MACV,aAAa,IAAI,gBAAgB,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,aAAa,CAAC;AAAA,MAC5E,eAAe,IAAI,kBAAkB,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,eAAe,CAAC;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,SAAkC;AACtC,UAAM,MAAM,MAAM,KAAK,QAA6B,KAAK,oBAAoB,CAAC;AAC9E,YAAQ,IAAI,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,MACtC,QAAQ,EAAE;AAAA,MACV,aAAa,EAAE;AAAA,MACf,eAAe,EAAE;AAAA,IACnB,EAAE;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,aAAa,QAAmD;AACpE,YAAQ,MAAM,KAAK,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KACJ,aACA,QACA,OAAiD,CAAC,GAC3B;AACvB,QAAI;AACJ,QAAI,KAAK,gBAAgB,OAAO;AAC9B,UAAI;AACF,iBAAS,MAAM,KAAK,aAAa,MAAM;AAAA,MACzC,QAAQ;AACN,iBAAS;AAAA,MACX;AAAA,IACF;AACA,UAAM,aAAa,YAAY;AAC/B,UAAM,WAAW,SAAS,YAAY,kBAAkB,OAAO,cAAc;AAC7E,UAAM,oBAAoB,cAAc,KAAM,aAAa,UAAa,WAAW;AAEnF,UAAM,YAAY,oBACd,oBAAoB,YAAY,gBAAgB,eAAe,CAAC,0BAA0B,WAAW;AAAA,MACnG;AAAA,IACF,CAAC,kBACA,SACG,kBAAkB,YAAY,KAAK,KAAK,QAAQ,CAAC,CAAC,YAAY,MAAM,cAAc,MAAM,cAAc;AAAA,MACpG,OAAO;AAAA,IACT,CAAC,KAAK,OAAO,YAAY,eAAe,CAAC,uBACzC,cAAc,MAAM,wBACxB,QAAQ,YAAY,gBAAgB,eAAe,CAAC,eAAe,WAAW;AAAA,MAC5E;AAAA,IACF,CAAC,8FAAyF,MAAM;AAEpG,WAAO;AAAA,MACL;AAAA,MACA,YAAY,YAAY;AAAA,MACxB,UAAU,YAAY;AAAA,MACtB;AAAA,MACA,iBAAiB;AAAA,MACjB,iBAAiB,QAAQ;AAAA,MACzB,eAAe,QAAQ;AAAA,MACvB,YAAY,KAAK,eAAe,QAAQ,KAAK,IAAI;AAAA,MACjD,gBAAgB,oBAAoB,aAAa;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,OACL,QACA,OAAiC,CAAC,GACf;AACnB,UAAM,WAAW,MAAM,KAAK,SAAS,MAAM;AAC3C,UAAM,YAAY,KAAK,aAAa;AACpC,UAAM,UACJ,KAAK,aAAa,SACd,KAAK,IAAI,SAAS,MAAM,QAAQ,YAAY,KAAK,QAAQ,IACzD,SAAS,MAAM;AACrB,UAAM,aAAa,KAAK,cAAc;AACtC,QAAI,UAAU;AAEd,aAAS,IAAI,WAAW,IAAI,SAAS,KAAK;AACxC,YAAM,OAAO,SAAS,MAAM,CAAC;AAC7B,UAAI,kBAAkB;AACtB,uBAAiB,UAAU,oBAAuB,KAAK,WAAW,KAAK,UAAU,KAAK,MAAM,GAAG;AAC7F;AACA,YAAI,KAAK,UAAU,CAAC,KAAK,OAAO,MAAM,EAAG;AACzC,cAAM;AACN;AACA,YAAI,WAAW,YAAY;AACzB,eAAK,SAAS,EAAE,OAAO,GAAG,OAAO,SAAS,MAAM,QAAQ,KAAK,KAAK,UAAU,SAAS,gBAAgB,CAAC;AACtG;AAAA,QACF;AAAA,MACF;AACA,WAAK,SAAS,EAAE,OAAO,GAAG,OAAO,SAAS,MAAM,QAAQ,KAAK,KAAK,UAAU,SAAS,gBAAgB,CAAC;AAAA,IACxG;AAAA,EACF;AAAA,EAEQ,YAAY,GAA+B;AACjD,WAAO;AAAA,MACL,OAAO,EAAE;AAAA,MACT,UAAU,KAAK,UAAU,EAAE,GAAG;AAAA,MAC9B,eAAe,EAAE,MAAM,kBAAkB;AAAA,MACzC,aAAa,EAAE,MAAM,gBAAgB;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAc,QAAW,KAAyB;AAChD,UAAM,MAAM,MAAM,KAAK,UAAU,KAAK,EAAE,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;AACjF,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,cAAc,4BAA4B,IAAI,MAAM,MAAM,GAAG,KAAK;AAAA,QAC1E,QAAQ,IAAI;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AACF;AAsBA,gBAAuB,oBACrB,WACA,KACA,QACmB;AACnB,QAAM,MAAM,MAAM,UAAU,KAAK,SAAS,EAAE,OAAO,IAAI,CAAC,CAAC;AACzD,MAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM;AACxB,UAAM,IAAI,cAAc,yBAAyB,IAAI,MAAM,MAAM,GAAG,KAAK,EAAE,QAAQ,IAAI,QAAQ,IAAI,CAAC;AAAA,EACtG;AACA,QAAM,aAAa,IAAI,KACpB,YAAY,IAAI,oBAAoB,MAAM,CAAC,EAC3C,YAAY,IAAI,kBAAkB,CAAC;AACtC,QAAM,SAAS,WAAW,UAAU;AACpC,MAAI,SAAS;AACb,MAAI;AACF,eAAS;AACP,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,gBAAU;AACV,UAAI;AACJ,cAAQ,KAAK,OAAO,QAAQ,IAAI,MAAM,GAAG;AACvC,cAAM,OAAO,OAAO,MAAM,GAAG,EAAE;AAC/B,iBAAS,OAAO,MAAM,KAAK,CAAC;AAC5B,YAAI,KAAK,KAAK,EAAG,OAAM,KAAK,MAAM,IAAI;AAAA,MACxC;AAAA,IACF;AACA,QAAI,OAAO,KAAK,EAAG,OAAM,KAAK,MAAM,MAAM;AAAA,EAC5C,UAAE;AACA,WAAO,YAAY;AAAA,EACrB;AACF;AAEA,SAAS,SAAS,GAAmB;AACnC,QAAM,QAAQ,CAAC,KAAK,MAAM,MAAM,MAAM,IAAI;AAC1C,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,KAAK,QAAQ,IAAI,MAAM,SAAS,GAAG;AACxC,SAAK;AACL;AAAA,EACF;AACA,SAAO,GAAG,EAAE,QAAQ,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAC7D;;;ACnSA,eAAsB,eACpB,UACA,OACA,OAAoB,CAAC,GACI;AACzB,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,MAAM,MAAM,MAAM,aAAa,EAAE,SAAS,UAAU,SAAS,CAAC;AACpE,QAAM,QAAQ,IAAI;AAClB,QAAM,cAAc,SAAS;AAC7B,MAAI,WAA+B,cAAc,mBAAmB;AAEpE,MAAI,cAAc;AAClB,MAAI;AACJ,QAAM,YAAY,KAAK,wBAAwB;AAC/C,MAAI,IAAI,gBAAgB,WAAW;AACjC,UAAM,OAAO,MAAM,SAAS,KAAK,KAAK,MAAM,UAAU;AACtD,yBAAqB,KAAK;AAC1B,kBAAc,KAAK,mBAAmB;AACtC,QAAI,YAAa,YAAW;AAAA,EAC9B;AAEA,QAAM,UAAU,IAAI,IAAI,aAAa,QAAQ,CAAC,CAAC;AAC/C,QAAM,YAAY,cACd,IAAI,MAAM,eAAe,CAAC,mBAAc,OAAO,wDAC/C,cACE,GAAG,MAAM,eAAe,CAAC,4EAAuE,OAAO,kBAAkB,OAAO,OAChI,GAAG,MAAM,eAAe,CAAC,qDAAqD,OAAO,MAAM,OAAO;AAExG,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,iBAAiB;AAAA,IACjB,aAAa,IAAI;AAAA,IACjB,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACnDA,eAAsB,YAAY,MAAkB,QAAyC;AAC3F,QAAM,MAAM,MAAM,KAAK,QAAwB;AAAA,IAC7C,MAAM,SAAS,YAAY,MAAM,CAAC;AAAA,IAClC,aAAa;AAAA,EACf,CAAC;AACD,SAAO,IAAI;AACb;;;ACWO,IAAM,iBAAN,MAAqB;AAAA,EACjB;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAGA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAAyB,CAAC,GAAG;AACvC,SAAK,SAAS,cAAc,MAAM;AAClC,SAAK,QAAQ,IAAI,aAAa,QAAQ,KAAK,OAAO,MAAM,GAAG,KAAK,OAAO,cAAc;AACrF,SAAK,OAAO,IAAI,WAAW,KAAK,QAAQ,KAAK,KAAK;AAElD,SAAK,WAAW,IAAI,eAAe,EAAE,OAAO,KAAK,OAAO,MAAM,CAAC;AAE/D,SAAK,QAAQ,IAAI,cAAc,KAAK,IAAI;AACxC,SAAK,UAAU,IAAI,eAAyC,KAAK,MAAM,SAAS;AAChF,SAAK,UAAU,IAAI,eAAyC,KAAK,MAAM,SAAS;AAChF,SAAK,eAAe,IAAI,eAAmD,KAAK,MAAM,cAAc;AACpG,SAAK,SAAS,IAAI,eAAuC,KAAK,MAAM,QAAQ;AAC5E,SAAK,WAAW,IAAI,eAA2C,KAAK,MAAM,UAAU;AACpF,SAAK,aAAa,IAAI,eAA+C,KAAK,MAAM,YAAY;AAC5F,SAAK,UAAU,IAAI,eAAyC,KAAK,MAAM,SAAS;AAAA,EAClF;AAAA;AAAA,EAGA,MAAM,aACJ,GACA,YAS+B;AAC/B,UAAM,OAAO,aAAa,gBAAgB,UAAU,KAAK;AACzD,UAAM,MAAM,MAAM,KAAK,KAAK,QAAwC;AAAA,MAClE;AAAA,MACA,QAAQ,EAAE,EAAE;AAAA,MACZ,aAAa;AAAA,IACf,CAAC;AACD,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAGA,OAAO,QAAyC;AAC9C,WAAO,YAAY,KAAK,MAAM,MAAM;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,OAAqD;AACtE,UAAM,WAAW,MAAM,MAAM,aAAa;AAC1C,WAAO,KAAK,SAAS,KAAK,UAAU,MAAM,UAAU;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,OAA8B,MAA6C;AACxF,WAAO,eAAe,KAAK,UAAU,OAAO,IAAI;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,QACJ,MACA,SAA6C,CAAC,GAC9C,OAAyD,CAAC,GAC9C;AACZ,UAAM,MAAM,MAAM,KAAK,KAAK,QAAW;AAAA,MACrC;AAAA,MACA;AAAA,MACA,aAAa,KAAK,eAAe;AAAA,MACjC,SAAS,KAAK;AAAA,IAChB,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,gBAA+B;AAC7B,WAAO,KAAK,MAAM,SAAS;AAAA,EAC7B;AAAA;AAAA,EAGA,cAAsB;AACpB,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;;;AC/HA,IAAM,WAAW;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAaO,SAAS,UAAU,MAA4B;AACpD,QAAM,IAAc,CAAC;AACrB,QAAM,QAA0C,CAAC;AACjD,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,EAAE,WAAW,IAAI,GAAG;AACtB,YAAM,KAAK,EAAE,QAAQ,GAAG;AACxB,UAAI,MAAM,GAAG;AACX,cAAM,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC;AAAA,MACxC,OAAO;AACL,cAAM,MAAM,EAAE,MAAM,CAAC;AACrB,cAAM,OAAO,KAAK,IAAI,CAAC;AACvB,YAAI,SAAS,UAAa,KAAK,WAAW,IAAI,GAAG;AAC/C,gBAAM,GAAG,IAAI;AAAA,QACf,OAAO;AACL,gBAAM,GAAG,IAAI;AACb;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,QAAE,KAAK,CAAC;AAAA,IACV;AAAA,EACF;AACA,SAAO,EAAE,GAAG,MAAM;AACpB;AAEA,eAAsB,OAAO,MAAgB,OAAgB,CAAC,GAAoB;AAChF,QAAM,MAAM,KAAK,WAAW,CAAC,MAAM,QAAQ,OAAO,MAAM,IAAI,IAAI;AAChE,QAAM,MAAM,KAAK,WAAW,CAAC,MAAM,QAAQ,OAAO,MAAM,IAAI,IAAI;AAChE,QAAM,EAAE,GAAG,MAAM,IAAI,UAAU,IAAI;AACnC,QAAM,MAAM,EAAE,CAAC;AAEf,MAAI,CAAC,OAAO,MAAM,QAAQ,QAAQ,QAAQ;AACxC,QAAI,IAAI;AACR,WAAO;AAAA,EACT;AAEA,QAAM,SACJ,KAAK,UACL,IAAI,eAAe;AAAA,IACjB,QAAQ,IAAI,MAAM,SAAS,CAAC;AAAA,IAC5B,QAAQ,IAAI,MAAM,MAAM;AAAA,EAC1B,CAAC;AACH,QAAM,OAAO,QAAQ,MAAM,IAAI;AAE/B,MAAI;AACF,QAAI,QAAQ,WAAY,QAAO,MAAM,YAAY,GAAG,OAAO,QAAQ,KAAK,IAAI;AAC5E,QAAI,QAAQ,WAAY,QAAO,MAAM,YAAY,GAAG,OAAO,QAAQ,GAAG;AACtE,QAAI,QAAQ,MAAO,QAAO,MAAM,OAAO,GAAG,OAAO,QAAQ,GAAG;AAC5D,QAAK,SAA+B,SAAS,GAAG,GAAG;AACjD,aAAO,MAAM,UAAU,KAAK,OAAO,QAAQ,KAAK,IAAI;AAAA,IACtD;AACA,QAAI,oBAAoB,GAAG,EAAE;AAC7B,QAAI,IAAI;AACR,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,UAAW,EAAY,OAAO,EAAE;AACpC,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,QAAwB,QAAoD;AAC5F,QAAM,KAAM,OAAyE,MAAM;AAC3F,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,mBAAmB,MAAM,aAAa,SAAS,KAAK,IAAI,CAAC,EAAE;AACpF,SAAO;AACT;AAEA,SAAS,WACP,QACA,QACA,OACuB;AACvB,MAAI,IAAI,SAAS,QAAQ,MAAM,EAAE,MAAM;AACvC,MAAI,IAAI,MAAM,MAAM,EAAG,KAAI,EAAE,MAAM,UAAU,IAAI,MAAM,MAAM,CAAE;AAC/D,MAAI,IAAI,MAAM,MAAM,EAAG,KAAI,EAAE,OAAO,IAAI,MAAM,MAAM,CAAE;AACtD,MAAI,IAAI,MAAM,MAAM,EAAG,KAAI,EAAE,OAAO,IAAI,MAAM,MAAM,CAAE;AACtD,MAAI,IAAI,MAAM,IAAI,EAAG,KAAI,EAAE,KAAK,IAAI,MAAM,IAAI,CAAE;AAChD,MAAI,IAAI,MAAM,UAAU,CAAC,EAAG,KAAI,EAAE,QAAQ,OAAO,MAAM,UAAU,CAAC,CAAC;AACnE,SAAO;AACT;AAEA,eAAe,UACb,QACA,OACA,QACA,KACA,MACiB;AACjB,QAAM,IAAI,WAAW,QAAQ,QAAQ,KAAK;AAC1C,QAAM,QAAQ,MAAM,UAAU,SAAY,OAAO,MAAM,KAAK,IAAI;AAChE,QAAM,WAAW,QAAQ,MAAM,QAAQ;AAEvC,MAAI,MAAM,OAAO;AACf,UAAM,IAAI,MAAM,EAAE,MAAM;AACxB,QAAI,OAAO,KAAK,UAAU,EAAE,OAAO,EAAE,CAAC,IAAI,GAAG,CAAC,EAAE;AAChD,WAAO;AAAA,EACT;AACA,MAAI,MAAM,UAAU;AAClB,UAAM,IAAI,MAAM,EAAE,aAAa;AAC/B,QAAI,OAAO,KAAK,UAAU,GAAG,MAAM,CAAC,IAAI,eAAe,CAAC,CAAC;AACzD,WAAO;AAAA,EACT;AACA,MAAI,IAAI,MAAM,MAAM,GAAG;AACrB,UAAM,MAAM,MAAM,EAAE,OAAO,IAAI,MAAM,MAAM,GAAI,EAAE,YAAY,OAAO,SAAS,CAAC;AAC9E,QAAI,OAAO,KAAK,UAAU,GAAG,IAAI,SAAS,IAAI,KAAK,IAAI,MAAM,OAAO,IAAI,IAAI,EAAE;AAC9E,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,MAAM,EAAE,QAAQ,EAAE,YAAY,SAAS,IAAI,SAAS,CAAC;AACnE,MAAI,KAAK,UAAU,OAAO,MAAM,OAAO,IAAI,CAAC,CAAC;AAC7C,SAAO;AACT;AAEA,eAAe,OACb,GACA,OACA,QACA,KACiB;AACjB,QAAM,SAAS,EAAE,CAAC;AAClB,QAAM,KAAK,EAAE,CAAC;AACd,MAAI,CAAC,UAAU,CAAC,GAAI,OAAM,IAAI,MAAM,6CAA6C;AACjF,QAAM,KAAK,SAAS,QAAQ,MAAM;AAGlC,QAAM,OAAO,MAAM,GAAG,IAAI,IAAI,IAAI,MAAM,MAAM,IAAI,EAAE,QAAQ,IAAI,MAAM,MAAM,EAAG,IAAI,CAAC,CAAC;AACrF,MAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AACjC,SAAO;AACT;AAEA,eAAe,YACb,GACA,OACA,QACA,KACA,MACiB;AACjB,QAAM,MAAM,EAAE,CAAC;AACf,MAAI,QAAQ,UAAU;AACpB,UAAM,SAAS,EAAE,CAAC;AAClB,QAAI,QAAQ;AACV,YAAM,IAAI,MAAM,OAAO,SAAS,aAAa,MAAM;AACnD,UAAI,OAAO,KAAK,UAAU,CAAC,IAAI,aAAa,CAAC,CAAC;AAAA,IAChD,OAAO;AACL,YAAM,KAAK,MAAM,OAAO,SAAS,OAAO;AACxC,UAAI,OAAO,KAAK,UAAU,IAAI,MAAM,CAAC,IAAI,GAAG,IAAI,YAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAQ;AAClB,UAAM,SAAS,EAAE,CAAC;AAClB,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,kDAAkD;AAC/E,UAAM,OAAO,MAAM,OAAO,aAAa,WAAW,QAAQ,QAAQ,KAAK,CAAC;AACxE,QAAI,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,WAAW,IAAI,CAAC;AAC3D,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,UAAU;AACpB,UAAM,SAAS,EAAE,CAAC;AAClB,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,iEAAiE;AAC9F,UAAM,QAAQ,MAAM,UAAU,SAAY,OAAO,MAAM,KAAK,IAAI;AAChE,UAAM,MAAM,OAAO,SAAS,OAAO,QAAQ,EAAE,YAAY,MAAM,CAAC;AAChE,QAAI,IAAI,MAAM,MAAM,GAAG;AACrB,YAAM,EAAE,cAAAC,cAAa,IAAI,MAAM;AAC/B,YAAM,MAAM,MAAMA,cAAa,KAAK,IAAI,MAAM,MAAM,CAAE;AACtD,UAAI,SAAS,IAAI,KAAK,IAAI,MAAM,OAAO,IAAI,IAAI,EAAE;AACjD,aAAO;AAAA,IACT;AACA,qBAAiB,OAAO,IAAK,KAAI,KAAK,UAAU,GAAG,CAAC;AACpD,WAAO;AAAA,EACT;AACA,QAAM,IAAI,MAAM,2DAA2D;AAC7E;AAEA,eAAe,YACb,GACA,OACA,QACA,KACiB;AACjB,QAAM,KAAK,EAAE,CAAC;AACd,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,8DAA8D;AACvF,QAAM,SAAS,MAAM,WAAW,QAAQ,QAAQ;AAChD,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,MAAI,CAAC,MAAM;AACT,QAAI,OAAO,MAAM,YAAY,IAAI,MAAM,CAAC;AACxC,WAAO;AAAA,EACT;AACA,QAAM,MAAM,MAAM,OAAO,MAAM,iBAAiB,IAAI,EAAE,QAAQ,KAAK,CAAC;AACpE,MAAI,SAAS,OAAO,YAAY,CAAC,OAAO,IAAI,IAAI,EAAE;AAClD,SAAO;AACT;AAEA,SAAS,IAAI,GAAqD;AAChE,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,eAAe,GAKb;AACT,SACE,cAAc,EAAE,gBAAgB,eAAe,CAAC;AAAA,aAClC,EAAE,SAAS;AAAA,cACV,EAAE,aAAa,QAAQ,CAAC,CAAC,MACvC,EAAE,sBAAsB,mCAAmC;AAEhE;AAEA,SAAS,aAAa,GAAuF;AAC3G,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,MAAM,EAAE,gBAAgB,KAAK,QAAQ,CAAC;AAC5C,SAAO,GAAG,EAAE,OAAO,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,eAAe,EAAE,SAAS,EAAE,CAAC,cAAc,EAAE;AAC9F;AAEA,SAAS,WAAW,GAKT;AACT,SACE,mBAAmB,EAAE,eAAe,YAAY,CAAC;AAAA,EAC9C,EAAE,SAAS;AAAA,cACC,EAAE,WAAW,QAAQ,CAAC,CAAC;AAAA,iBACpB,EAAE,UAAU;AAElC;AAEA,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBb,SAAS,SAAkB;AACzB,QAAM,QAAQ,QAAQ,KAAK,CAAC,KAAK;AACjC,SAAO,qCAAqC,KAAK,KAAK;AACxD;AAEA,IAAI,OAAO,GAAG;AACZ,SAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE;AAAA,IAC5B,CAAC,SAAS,QAAQ,KAAK,IAAI;AAAA,IAC3B,CAAC,MAAM;AACL,cAAQ,OAAO,MAAM,UAAW,EAAY,OAAO;AAAA,CAAI;AACvD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;","names":["createWriteStream","exportToFile","exportToFile"]}
|