@datafuel/sdk 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/core.ts","../src/models.ts","../src/client.ts"],"sourcesContent":["/**\n * Errors this package throws.\n *\n * Two families, as in the Go and Python SDKs:\n *\n * - {@link APIError} and its subclasses — the API refused the request.\n * - {@link TaskFailed} / {@link Blocked} — the API accepted the task but the\n * page could not be scraped. Failed tasks are refunded.\n */\n\nimport type { Result } from \"./models.js\";\n\n/** Base class for every error this package throws. */\nexport class DataFuelError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/** No key was passed and DATAFUEL_API_KEY is empty. Thrown before any request. */\nexport class NoApiKey extends DataFuelError {}\n\n/** The request never got an answer: DNS, connection, abort, read timeout. */\nexport class TransportError extends DataFuelError {}\n\n/** A non-2xx answer from the API itself. */\nexport class APIError extends DataFuelError {\n readonly status: number;\n readonly code: string;\n /** Seconds the API asked us to wait, from Retry-After. 0 when absent. */\n readonly retryAfter: number;\n\n constructor(status: number, code: string, message: string, retryAfter = 0) {\n super(code ? `${message} (${status} ${code})` : `${message} (${status})`);\n this.status = status;\n this.code = code;\n this.retryAfter = retryAfter;\n }\n}\n\n/** 401: the API key is missing or invalid. */\nexport class Unauthorized extends APIError {}\n/** 404: unknown id, or one that belongs to another account. */\nexport class NotFound extends APIError {}\n/** 429: the account's request rate or concurrency limit was reached. */\nexport class RateLimited extends APIError {}\n/** 402: not enough credits for this task. */\nexport class InsufficientCredits extends APIError {}\n/** 400 INVALID_ATTRIBUTES: the attributes do not match the task type. */\nexport class InvalidAttributes extends APIError {}\n/** 422: the key was already used for a different request. */\nexport class IdempotencyKeyReused extends APIError {}\n/** 503: an operator switched something off. The message carries the reason. */\nexport class Unavailable extends APIError {}\n/** 503 MODULE_UNAVAILABLE: this task type is switched off. Nothing was charged. */\nexport class ModuleUnavailable extends Unavailable {}\n/** 503 ENGINE_UNAVAILABLE: this LLM engine is switched off. Nothing was charged. */\nexport class EngineUnavailable extends Unavailable {}\n\n/**\n * The task was accepted but could not be completed. Refunded.\n *\n * `result` carries the envelope: `statusCode`, `blocked`, `protection`, `error`.\n */\nexport class TaskFailed extends DataFuelError {\n readonly result: Result;\n\n constructor(result: Result) {\n const detail = result.error ?? result.payload?.error_detail ?? \"no detail\";\n super(`task failed: ${detail}`);\n this.result = result;\n }\n\n /** Anti-bot vendor recognised on the target, when there was one. */\n get protection(): string | undefined {\n return this.result.protection;\n }\n}\n\n/**\n * The target refused or challenged the request (403/429/503, anti-bot wall).\n *\n * Refunded. Retry with `jsRendering: true` or a Premium proxy.\n */\nexport class Blocked extends TaskFailed {}\n\n/**\n * A wait ran out of time. The work keeps running and billing server-side.\n *\n * `id` picks it back up (`waitCrawl`, `jobResults`, or a re-send under the same\n * idempotency key); `status` is the last one seen, when there was one.\n */\nexport class WaitTimeout extends DataFuelError {\n readonly id: string | undefined;\n readonly status: unknown;\n\n constructor(message: string, id?: string, status?: unknown) {\n super(message);\n this.id = id;\n this.status = status;\n }\n}\n\nconst BY_CODE: Record<string, new (s: number, c: string, m: string, r?: number) => APIError> = {\n MODULE_UNAVAILABLE: ModuleUnavailable,\n ENGINE_UNAVAILABLE: EngineUnavailable,\n INSUFFICIENT_CREDITS: InsufficientCredits,\n INVALID_ATTRIBUTES: InvalidAttributes,\n IDEMPOTENCY_KEY_REUSED: IdempotencyKeyReused,\n INVALID_API_KEY: Unauthorized,\n};\n\nconst BY_STATUS: Record<number, new (s: number, c: string, m: string, r?: number) => APIError> = {\n 401: Unauthorized,\n 402: InsufficientCredits,\n 404: NotFound,\n 422: IdempotencyKeyReused,\n 429: RateLimited,\n 503: Unavailable,\n};\n\n/** Map an error answer onto the narrowest class that fits. */\nexport function apiError(status: number, body: unknown, retryAfter = 0): APIError {\n let code = \"\";\n let message = \"\";\n if (body !== null && typeof body === \"object\") {\n const record = body as Record<string, unknown>;\n code = typeof record.code === \"string\" ? record.code : \"\";\n message = typeof record.message === \"string\" ? record.message : \"\";\n } else if (typeof body === \"string\" && body.length > 0 && body.length <= 300) {\n message = body;\n }\n const Cls = BY_CODE[code] ?? BY_STATUS[status] ?? APIError;\n return new Cls(status, code, message || `HTTP ${status}`, retryAfter);\n}\n","/**\n * Request building and answer classification. Pure functions, no I/O.\n *\n * Every decision lives here — which envelope a module needs, where the sticky\n * session keys go, how `ai` expands, whether an answer is retryable — so the\n * client only moves bytes.\n */\n\nimport { APIError, Unavailable } from \"./errors.js\";\nimport type { AI, Engine, Proxy, ScrapeOptions } from \"./models.js\";\n\nexport const DEFAULT_BASE_URL = \"https://scraping-api.datafuel.ai/api/v1\";\n\n/** Kept in step with package.json by a test; see test/hardening.test.ts. */\nexport const VERSION = \"0.1.0\";\n\n/** How long a synchronous call waits for a result before giving up. */\nexport const DEFAULT_TIMEOUT_MS = 180_000;\n/** Gap between two attempts at a task the API is still processing. */\nexport const STILL_PROCESSING_DELAY_MS = 2_000;\nexport const STILL_PROCESSING = \"TASK_STILL_PROCESSING\";\n/** The API rejects a longer key with 400; catching it here saves the round trip. */\nexport const MAX_IDEMPOTENCY_KEY = 255;\n\n/** One HTTP call, ready for the client to send. */\nexport class Request {\n constructor(\n readonly method: string,\n readonly path: string,\n readonly params?: Record<string, string>,\n readonly body?: Record<string, unknown>,\n readonly idempotencyKey?: string,\n ) {}\n\n /** GETs are safe by nature, writes because they carry an idempotency key. */\n get retryable(): boolean {\n return this.method === \"GET\" || this.idempotencyKey !== undefined;\n }\n\n /**\n * Method and path only. The body can hold an LLM key or a cookie header, so\n * neither logging nor JSON.stringify may spill it.\n */\n toString(): string {\n return `<Request ${this.method} ${this.path}>`;\n }\n\n toJSON(): string {\n return this.toString();\n }\n\n [Symbol.for(\"nodejs.util.inspect.custom\")](): string {\n return this.toString();\n }\n}\n\n/**\n * Escape an id before it becomes part of a URL path.\n *\n * An id is caller data. Unescaped, a stray `/` or `?` in one would move the\n * request to a different endpoint.\n */\nexport function pathSegment(value: string): string {\n return encodeURIComponent(value);\n}\n\nexport function newIdempotencyKey(): string {\n return crypto.randomUUID();\n}\n\n/** The headers every call carries. The idempotency key sits beside the API key. */\nexport function headers(\n apiKey: string,\n userAgent: string,\n request: Request,\n): Record<string, string> {\n const out: Record<string, string> = {\n \"X-API-Key\": apiKey,\n Accept: \"application/json\",\n \"User-Agent\": userAgent,\n };\n if (request.body !== undefined) out[\"Content-Type\"] = \"application/json\";\n if (request.idempotencyKey !== undefined) out[\"Idempotency-Key\"] = request.idempotencyKey;\n return out;\n}\n\n/** The API takes multi-field extractors as a JSON object string. */\nfunction selector(value: string | Record<string, string>): string {\n return typeof value === \"string\" ? value : JSON.stringify(value);\n}\n\nfunction aiAttributes(ai: AI): Record<string, unknown> {\n const out: Record<string, unknown> = { result_use_ai: true };\n if (ai.prompt) out.result_ai_prompt = ai.prompt;\n if (ai.format !== undefined) out.result_ai_format = ai.format;\n if (ai.provider) out.ai_provider = ai.provider;\n if (ai.model) out.ai_model = ai.model;\n if (ai.apiKey) out.ai_api_key = ai.apiKey;\n return out;\n}\n\n/**\n * Catch a half-filled AI key before it costs a request. The API does not check\n * these, so a missing one would be spent on a scrape whose AI step then fails.\n */\nexport function validateAI(ai: AI): void {\n const given = {\n provider: ai.provider !== undefined,\n model: ai.model !== undefined,\n apiKey: ai.apiKey !== undefined,\n };\n const present = Object.values(given).filter(Boolean).length;\n if (present > 0 && present < 3) {\n const missing = Object.entries(given)\n .filter(([, ok]) => !ok)\n .map(([name]) => name)\n .join(\", \");\n throw new TypeError(`ai needs provider, model and apiKey together; missing: ${missing}`);\n }\n}\n\n/**\n * Flatten the page options into the API's `attributes` object.\n *\n * Unset options are left out of the body entirely, never sent as null.\n */\nexport function scrapeAttributes(options: ScrapeOptions = {}): Record<string, unknown> {\n const attrs: Record<string, unknown> = {};\n if (options.format) attrs.result_format = options.format;\n if (options.jsRendering) attrs.js_rendering = true;\n if (options.waitFor) attrs.wait_for_selector = options.waitFor;\n if (options.waitForTimeoutMs) attrs.wait_for_selector_timeout_ms = options.waitForTimeoutMs;\n if (options.jsInstructions) attrs.js_instructions = options.jsInstructions;\n if (options.blockResource) attrs.block_resource = options.blockResource;\n if (options.mainContentOnly) attrs.main_content_only = true;\n if (options.includeImages !== undefined) attrs.include_images = options.includeImages;\n if (options.extract) attrs.extract_selector = selector(options.extract);\n if (options.extractRegex) attrs.extract_regex = selector(options.extractRegex);\n if (options.template) attrs.result_template = options.template;\n if (options.method) attrs.method = options.method;\n if (options.body) attrs.body = options.body;\n if (options.contentType) attrs.content_type = options.contentType;\n if (options.headers) attrs.headers = options.headers;\n if (options.headerOrder) attrs.header_order = options.headerOrder;\n if (options.cookies) attrs.cookie_string = options.cookies;\n if (options.userAgent) attrs.user_agent = options.userAgent;\n if (options.userAgentType) attrs.user_agent_type = options.userAgentType;\n if (options.ai) {\n validateAI(options.ai);\n Object.assign(attrs, aiAttributes(options.ai));\n }\n return attrs;\n}\n\n/** The proxy fields that travel in the request envelope. */\nexport function proxyEnvelope(proxy: Proxy | undefined): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (!proxy) return out;\n if (proxy.type) out.proxy_type = proxy.type;\n if (proxy.country) out.proxy_country = proxy.country;\n if (proxy.city) out.proxy_city = proxy.city;\n if (proxy.state) out.proxy_state = proxy.state;\n if (proxy.asn) out.proxy_asn = proxy.asn;\n return out;\n}\n\n/** The sticky-session fields, which travel in `attributes`. */\nexport function proxySession(proxy: Proxy | undefined): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (!proxy) return out;\n if (proxy.sessionId) out.proxy_session_id = proxy.sessionId;\n if (proxy.ttl) out.proxy_ttl = proxy.ttl;\n return out;\n}\n\n/** The body every write shares. */\nexport function envelope(\n taskType: string,\n attributes: Record<string, unknown>,\n extra: { proxy?: Proxy | undefined; multithreaded?: boolean | undefined } = {},\n): Record<string, unknown> {\n const body: Record<string, unknown> = { type: taskType, ...proxyEnvelope(extra.proxy) };\n if (extra.multithreaded !== undefined) body.multithreaded = extra.multithreaded;\n body.attributes = attributes;\n return body;\n}\n\nexport function key(explicit: string | undefined): string {\n if (explicit !== undefined && explicit.length > MAX_IDEMPOTENCY_KEY) {\n throw new TypeError(`idempotencyKey is longer than ${MAX_IDEMPOTENCY_KEY} characters`);\n }\n return explicit ?? newIdempotencyKey();\n}\n\nexport function buildScrape(\n url: string,\n options: ScrapeOptions,\n proxy: Proxy | undefined,\n idempotencyKey: string,\n): Request {\n const attrs = { ...scrapeAttributes(options), url, ...proxySession(proxy) };\n return new Request(\n \"POST\",\n \"/task\",\n undefined,\n envelope(\"unlocker\", attrs, { proxy }),\n idempotencyKey,\n );\n}\n\nexport function buildJob(\n urls: string[],\n options: ScrapeOptions,\n proxy: Proxy | undefined,\n sequential: boolean,\n idempotencyKey: string,\n): Request {\n const attrs = { ...scrapeAttributes(options), urls: [...urls] };\n return new Request(\n \"POST\",\n \"/job\",\n undefined,\n envelope(\"unlocker\", attrs, { proxy, multithreaded: !sequential }),\n idempotencyKey,\n );\n}\n\n/** Options for the llm_scraping module, which reads its proxy country here. */\nexport interface AskOptions {\n engine: Engine | string;\n websearch?: boolean;\n followUp?: string;\n country?: string;\n format?: string;\n}\n\nexport function askAttributes(\n promptField: string,\n prompt: string | string[],\n options: AskOptions,\n): Record<string, unknown> {\n const attrs: Record<string, unknown> = { [promptField]: prompt, engine: options.engine };\n if (options.websearch) attrs.websearch = true;\n if (options.followUp) attrs.follow_up_prompt = options.followUp;\n if (options.country) attrs.proxy_country = options.country;\n if (options.format) attrs.result_format = options.format;\n return attrs;\n}\n\nexport function buildAsk(prompt: string, options: AskOptions, idempotencyKey: string): Request {\n const attrs = askAttributes(\"prompt\", prompt, options);\n return new Request(\"POST\", \"/task\", undefined, envelope(\"llm_scraping\", attrs), idempotencyKey);\n}\n\nexport function buildAskJob(\n prompts: string[],\n options: AskOptions,\n sequential: boolean,\n idempotencyKey: string,\n): Request {\n const attrs = askAttributes(\"prompts\", [...prompts], options);\n return new Request(\n \"POST\",\n \"/job\",\n undefined,\n envelope(\"llm_scraping\", attrs, { multithreaded: !sequential }),\n idempotencyKey,\n );\n}\n\n/** Options for `map`. */\nexport interface MapOptions {\n /** Keep only links whose URL or title contains this. */\n search?: string;\n /** Map only this sitemap, from a previous `SiteMap.sitemaps`. */\n sitemap?: string;\n /** Default 5000, capped at 10000. */\n limit?: number;\n includeSubdomains?: boolean;\n ignoreSitemap?: boolean;\n sitemapOnly?: boolean;\n userAgent?: string;\n userAgentType?: string;\n}\n\nexport function buildMap(\n url: string,\n options: MapOptions,\n proxy: Proxy | undefined,\n idempotencyKey: string,\n): Request {\n const attrs: Record<string, unknown> = { url };\n if (options.search) attrs.search = options.search;\n if (options.sitemap) attrs.sitemap = options.sitemap;\n if (options.limit) attrs.limit = options.limit;\n if (options.includeSubdomains) attrs.include_subdomains = true;\n if (options.ignoreSitemap) attrs.ignore_sitemap = true;\n if (options.sitemapOnly) attrs.sitemap_only = true;\n if (options.userAgent) attrs.user_agent = options.userAgent;\n if (options.userAgentType) attrs.user_agent_type = options.userAgentType;\n Object.assign(attrs, proxySession(proxy));\n return new Request(\"POST\", \"/map\", undefined, envelope(\"map\", attrs, { proxy }), idempotencyKey);\n}\n\n/** Options for `crawl`, on top of the page options applied to every page. */\nexport interface CrawlOptions extends ScrapeOptions {\n /** Default 100, max 10000. The hard budget of the crawl. */\n maxPages?: number;\n /** Default 3, max 10. The start URL is depth 0. */\n maxDepth?: number;\n /** RE2 on path?query; when set only matches are followed. */\n includePaths?: string[];\n /** RE2 on path?query; exclude wins. */\n excludePaths?: string[];\n includeSubdomains?: boolean;\n allowBackwardLinks?: boolean;\n /** Pages in flight, default 5. */\n concurrency?: number;\n}\n\nexport function buildCrawl(\n url: string,\n options: CrawlOptions,\n proxy: Proxy | undefined,\n idempotencyKey: string,\n): Request {\n if (options.ai) {\n throw new TypeError(\"crawl does not support ai: the API rejects result_use_ai on crawls\");\n }\n const attrs: Record<string, unknown> = { ...scrapeAttributes(options), url };\n if (options.maxPages) attrs.max_pages = options.maxPages;\n if (options.maxDepth) attrs.max_depth = options.maxDepth;\n if (options.concurrency) attrs.concurrency = options.concurrency;\n if (options.includePaths?.length) attrs.include_paths = [...options.includePaths];\n if (options.excludePaths?.length) attrs.exclude_paths = [...options.excludePaths];\n if (options.includeSubdomains) attrs.include_subdomains = true;\n if (options.allowBackwardLinks) attrs.allow_backward_links = true;\n return new Request(\n \"POST\",\n \"/crawl\",\n undefined,\n envelope(\"crawl\", attrs, { proxy }),\n idempotencyKey,\n );\n}\n\nexport function crawlResultsRequest(crawlId: string, cursor?: string, limit?: number): Request {\n const params: Record<string, string> = {};\n if (cursor) params.cursor = cursor;\n if (limit) params.limit = String(limit);\n return new Request(\n \"GET\",\n `/crawl/${pathSegment(crawlId)}/results`,\n Object.keys(params).length > 0 ? params : undefined,\n );\n}\n\n/** Whether the API answered \"the task is still running\" instead of a result. */\nexport function stillProcessing(body: unknown): boolean {\n return (\n body !== null &&\n typeof body === \"object\" &&\n (body as Record<string, unknown>).code === STILL_PROCESSING\n );\n}\n\n/** Go's retry policy: transport errors, 429, 502, 503, 504 — nothing else. */\nexport function shouldRetry(error: unknown): boolean {\n if (error instanceof Unavailable) {\n // A deliberate switch-off is a 503 too, but retrying it only adds load: it\n // stays off until an operator turns it back on.\n if (error.code === \"MODULE_UNAVAILABLE\" || error.code === \"ENGINE_UNAVAILABLE\") return false;\n }\n if (error instanceof APIError) return [429, 502, 503, 504].includes(error.status);\n return true; // transport error\n}\n\n/** 500ms, 1s, 2s, 4s, then 8s flat — halved and randomized. Retry-After wins. */\nexport function backoff(attempt: number, retryAfterSeconds = 0): number {\n if (retryAfterSeconds > 0) return Math.min(retryAfterSeconds * 1000, 30_000);\n const base = 500 * 2 ** Math.min(attempt, 4);\n return base / 2 + Math.random() * (base / 2);\n}\n\n/** Gap before re-sending a task the API is still processing. */\nexport function pollDelay(attempt: number): number {\n return Math.max(STILL_PROCESSING_DELAY_MS, backoff(attempt));\n}\n","/** Request options and response shapes. */\n\nimport { Blocked, DataFuelError, TaskFailed } from \"./errors.js\";\n\n/** State of a task, job or crawl. Unknown states are treated as not final. */\nexport type Status =\n | \"created\"\n | \"pending\"\n | \"processing\"\n | \"completed\"\n | \"completed_with_errors\"\n | \"failed\"\n | \"cancelled\";\n\nconst TERMINAL = new Set([\"completed\", \"completed_with_errors\", \"failed\", \"cancelled\"]);\n\n/** Whether a state is final. */\nexport function isDone(status: string | undefined): boolean {\n return status !== undefined && TERMINAL.has(status);\n}\n\n/**\n * Shape of the scraped content.\n *\n * `html` raw page (API default), `markdown` cleaned text (best for LLMs),\n * `json` schema.org / JSON-LD, `png` / `jpeg` full-page screenshot.\n */\nexport type Format = \"html\" | \"markdown\" | \"json\" | \"png\" | \"jpeg\";\n\n/** AI assistant `ask` can query. Engines can be switched off at runtime. */\nexport type Engine = \"openai\" | \"gemini\" | \"google_ai_mode\" | \"perplexity\" | \"copilot\";\n\n/** Proxy plan. Matched case-insensitively; deployments may define others. */\nexport type ProxyType = \"Basic\" | \"Premium\" | (string & {});\n\n/** The exit the request leaves from. Every field optional: the account default. */\nexport interface Proxy {\n type?: ProxyType;\n /** ISO 3166-1 alpha-2, e.g. \"US\". */\n country?: string;\n city?: string;\n state?: string;\n asn?: string;\n /**\n * Sticky session: the same exit across requests, `ttl` in seconds. Read by\n * `scrape` and `map` only, and sent in attributes rather than the envelope.\n */\n sessionId?: string;\n ttl?: number;\n}\n\n/**\n * Post-process the page with an LLM, using your own key.\n *\n * Not supported on crawls: the API rejects `result_use_ai` there.\n */\nexport interface AI {\n /** What to extract. */\n prompt?: string;\n /** Example JSON object the output must follow. */\n format?: unknown;\n provider?: \"openai\" | \"anthropic\" | \"google\";\n model?: string;\n apiKey?: string;\n}\n\n/** Page options, shared by `scrape`, jobs and crawls. */\nexport interface ScrapeOptions {\n format?: Format;\n /**\n * Render the page in a real browser: slower, five times the credits on a\n * Basic proxy. Leave it off unless the page comes back empty without it.\n */\n jsRendering?: boolean;\n waitFor?: string;\n waitForTimeoutMs?: number;\n jsInstructions?: unknown;\n blockResource?: string;\n /** Markdown only: always render just the `<main>` / `<article>` container. */\n mainContentOnly?: boolean;\n /** Markdown only: `false` drops images and saves tokens. */\n includeImages?: boolean;\n /**\n * Extract only matching elements: field name to CSS selector. Append\n * ` @attr` to read an attribute, e.g. `{ links: \"a @href\" }`.\n */\n extract?: string | Record<string, string>;\n /** The same with RE2 patterns on the raw HTML. */\n extractRegex?: string | Record<string, string>;\n /**\n * Built-in extractors, comma separated: images, links, headings,\n * phone_numbers, emails, meta_tags, tables, schema_org, all.\n */\n template?: string;\n method?: string;\n body?: string;\n contentType?: string;\n headers?: Record<string, string>;\n headerOrder?: string[];\n cookies?: string;\n userAgent?: string;\n /** chrome, firefox, safari, edge. */\n userAgentType?: string;\n ai?: AI;\n}\n\n/** Options every call accepts. */\nexport interface CallOptions {\n proxy?: Proxy;\n /** Generated per request when omitted. Max 255 characters. */\n idempotencyKey?: string;\n /** Overrides the client's timeout for this call. */\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\n/** The raw `result` object of a task. */\nexport interface Payload {\n data?: unknown;\n status?: Status;\n error_detail?: string;\n status_code?: number;\n}\n\n/**\n * One scraped target with its metadata.\n *\n * Read the metadata before the content: a 200 can be an error page,\n * `redirected` means `finalUrl` is not what you asked for, and a 404 still\n * completes and bills.\n */\nexport class Result {\n readonly id: string | undefined;\n readonly status: Status | undefined;\n /** HTTP status the target answered. */\n readonly statusCode: number | undefined;\n readonly finalUrl: string | undefined;\n readonly redirected: boolean;\n /** Charged for this task, 0 when it failed. */\n readonly creditsUsed: number;\n readonly durationMs: number | undefined;\n readonly blocked: boolean;\n /** Anti-bot vendor recognised when blocked. */\n readonly protection: string | undefined;\n readonly error: string | undefined;\n /** The raw `result` object. Prefer `text`, `data` and `image`. */\n readonly payload: Payload | undefined;\n /** Everything the API sent, including fields this SDK does not know yet. */\n readonly raw: Record<string, unknown>;\n\n constructor(raw: Record<string, unknown>) {\n this.raw = raw;\n this.id = str(raw.id);\n this.status = str(raw.status) as Status | undefined;\n this.statusCode = num(raw.status_code);\n this.finalUrl = str(raw.final_url);\n this.redirected = raw.redirected === true;\n this.creditsUsed = num(raw.credits_used) ?? 0;\n this.durationMs = num(raw.duration_ms);\n this.blocked = raw.blocked === true;\n this.protection = str(raw.protection);\n this.error = str(raw.error);\n this.payload =\n raw.result !== null && typeof raw.result === \"object\" ? (raw.result as Payload) : undefined;\n }\n\n /** Effective state: the envelope's, else the payload's, else inferred. */\n get state(): string {\n if (this.status) return this.status;\n if (this.payload?.status) return this.payload.status;\n if (this.payload?.data !== undefined && this.payload.data !== null) return \"completed\";\n return \"pending\";\n }\n\n /** Whether the task has not finished yet (job and crawl results list stubs). */\n get pending(): boolean {\n return !isDone(this.state);\n }\n\n /** Whether the task completed and the target was not blocked. */\n get ok(): boolean {\n return this.state === \"completed\" && !this.blocked;\n }\n\n /** The structured output: format json, extract, template, AI. */\n get data(): unknown {\n return this.payload?.data;\n }\n\n /** Content of an html or markdown result; the raw JSON for structured ones. */\n get text(): string {\n const value = this.data;\n if (value === undefined || value === null) return \"\";\n return typeof value === \"string\" ? value : JSON.stringify(value);\n }\n\n /** Bytes of a png or jpeg screenshot. Throws when the result is not one. */\n get image(): Uint8Array {\n try {\n return Uint8Array.from(atob(this.text), (char) => char.charCodeAt(0));\n } catch (cause) {\n throw new DataFuelError(\"result is not a png or jpeg screenshot\", { cause });\n }\n }\n\n /** `data`, or throw when the task carried none. */\n requireData(): unknown {\n if (this.data === undefined || this.data === null) {\n throw new DataFuelError(`result has no data (status ${this.state})`);\n }\n return this.data;\n }\n\n /** Throw TaskFailed, or Blocked, when this task failed. Refunded either way. */\n raiseForStatus(): void {\n if (this.state === \"failed\") {\n throw this.blocked ? new Blocked(this) : new TaskFailed(this);\n }\n }\n}\n\n/** One crawled page: where it was found, plus the scrape result. */\nexport class CrawlPage extends Result {\n readonly url: string;\n readonly depth: number;\n readonly taskId: string | undefined;\n\n constructor(raw: Record<string, unknown>) {\n super(raw);\n this.url = str(raw.url) ?? \"\";\n this.depth = num(raw.depth) ?? 0;\n this.taskId = str(raw.task_id);\n }\n}\n\n/** One discovered URL. */\nexport interface Link {\n url: string;\n /** \"sitemap\" or \"page\". */\n source: string;\n /** Page links only. */\n title?: string;\n /** Sitemap links only. */\n lastmod?: string;\n}\n\n/**\n * What `map` found.\n *\n * `reason` says why `links` is empty: page_blocked, page_error,\n * page_unreachable, no_links_on_page (try scrape with jsRendering), no_sitemap,\n * sitemap_no_entries, search_no_match.\n */\nexport interface SiteMap {\n url: string;\n links: Link[];\n total: number;\n /** `limit` or the time budget cut the list. */\n truncated: boolean;\n sitemaps: string[];\n page_status_code: number;\n reason?: string;\n credits: number;\n /** Envelope of the underlying task. */\n task: Result;\n}\n\n/** Progress of a crawl. */\nexport interface CrawlStatus {\n status: Status;\n /** Empty while the crawl still grows: max_pages, max_depth_exhausted, insufficient_credits, cancelled. */\n stop_reason?: string;\n pages: { discovered: number; enqueued: number; done: number; failed: number; skipped: number };\n depth_reached: number;\n /**\n * Charged at queue time; refunds of failed pages are not subtracted. Sum\n * `creditsUsed` over the pages for the net figure.\n */\n total_cost: number;\n /** Whether the crawl reached a final state. */\n done: boolean;\n}\n\n/** One page of crawl results. */\nexport interface CrawlResultsPage {\n pages: CrawlPage[];\n /** Absent on the last page. */\n nextCursor?: string;\n}\n\n/** A finished crawl: why it stopped and what it found. */\nexport interface CrawlResult {\n id: string;\n status: CrawlStatus;\n pages: CrawlPage[];\n}\n\n/** Progress of a job. */\nexport interface JobStatus {\n status: Status;\n tasks_count: number;\n tasks_done: number;\n tasks_remaining: number;\n total_cost: number;\n done: boolean;\n}\n\n/** Every task of a job. A job completes even when some of its tasks failed. */\nexport interface JobResults {\n id: string;\n tasks_count: number;\n tasks_failed: number;\n tasks_complete: number;\n tasks: Result[];\n}\n\n/** The account behind the API key. */\nexport interface Profile {\n email: string;\n username: string;\n current_concurrency: number;\n concurrency_limit: number;\n credit_balance: number;\n monthly_credit_limit: number;\n}\n\n/** One task type or LLM engine, and whether it accepts new work. */\nexport interface Capability {\n name: string;\n enabled: boolean;\n /** Operator's note when switched off. */\n reason?: string;\n}\n\n/** What the API accepts right now. */\nexport class Capabilities {\n readonly modules: Capability[];\n readonly engines: Capability[];\n\n constructor(raw: Record<string, unknown>) {\n this.modules = Array.isArray(raw.modules) ? (raw.modules as Capability[]) : [];\n this.engines = Array.isArray(raw.engines) ? (raw.engines as Capability[]) : [];\n }\n\n /** Whether a task type accepts work. Unknown names report false. */\n moduleEnabled(name: string): boolean {\n return this.modules.some((item) => item.name === name && item.enabled);\n }\n\n /** Whether an LLM engine accepts work. Unknown names report false. */\n engineEnabled(name: Engine | string): boolean {\n return this.engines.some((item) => item.name === name && item.enabled);\n }\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === \"number\" ? value : undefined;\n}\n","/** The client. It only moves bytes; `core` decides what goes on the wire. */\n\nimport * as core from \"./core.js\";\nimport type { AskOptions, CrawlOptions, MapOptions } from \"./core.js\";\nimport { apiError, DataFuelError, NoApiKey, TransportError, WaitTimeout } from \"./errors.js\";\nimport type {\n CallOptions,\n Capability,\n CrawlResult,\n CrawlResultsPage,\n CrawlStatus,\n JobResults,\n JobStatus,\n Profile,\n ScrapeOptions,\n SiteMap,\n} from \"./models.js\";\nimport { Capabilities, CrawlPage, isDone, Result } from \"./models.js\";\n\n/** How to build the client. */\nexport interface ClientOptions {\n /** Falls back to `process.env.DATAFUEL_API_KEY`. */\n apiKey?: string;\n baseUrl?: string;\n /** Milliseconds. `null` disables the bound; scrape blocks until the page is ready. */\n timeoutMs?: number | null;\n /** Retries after a network error or a 429/502/503/504 answer. Default 2. */\n maxRetries?: number;\n /** How often `waitJob` and `waitCrawl` poll. Default 2000ms. */\n pollIntervalMs?: number;\n /** Prefixes the SDK User-Agent with your application's. */\n userAgent?: string;\n /** Swap the fetch implementation, e.g. in tests. */\n fetch?: typeof globalThis.fetch;\n}\n\ntype Send = CallOptions & { timeoutMs?: number; signal?: AbortSignal };\n\n/**\n * Client for the DataFuel scraping API.\n *\n * ```ts\n * const df = new DataFuel(); // reads DATAFUEL_API_KEY\n * const markdown = await df.markdown(\"https://example.com\");\n * ```\n *\n * Pick the call by the shape of the work: one URL is {@link scrape}, a site's\n * URL list is {@link map}, many pages from a start URL is {@link crawl}, a list\n * of known URLs is {@link runJob}, a question for an AI engine is {@link ask}.\n *\n * Every write carries an `Idempotency-Key`, generated per request, so a retry\n * attaches to the task already running instead of charging twice.\n */\nexport class DataFuel {\n readonly baseUrl: string;\n readonly timeoutMs: number | null;\n readonly maxRetries: number;\n readonly pollIntervalMs: number;\n readonly userAgent: string;\n private readonly apiKey: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n\n constructor(options: ClientOptions | string = {}) {\n const opts: ClientOptions = typeof options === \"string\" ? { apiKey: options } : options;\n this.apiKey = opts.apiKey ?? envApiKey() ?? \"\";\n this.baseUrl = (opts.baseUrl ?? core.DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = opts.timeoutMs === undefined ? core.DEFAULT_TIMEOUT_MS : opts.timeoutMs;\n this.maxRetries = Math.max(opts.maxRetries ?? 2, 0);\n // A non-positive interval would poll in a tight loop against the API.\n this.pollIntervalMs =\n opts.pollIntervalMs !== undefined && opts.pollIntervalMs > 0 ? opts.pollIntervalMs : 2_000;\n const sdk = `datafuel-js/${core.VERSION}`;\n this.userAgent = opts.userAgent ? `${opts.userAgent} ${sdk}` : sdk;\n // Bound: a detached fetch throws \"Illegal invocation\" in browsers and\n // workers, even though Node tolerates it.\n this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n // --- transport ---------------------------------------------------------\n\n /** Pause between attempts. Overridable so tests need not wait in real time. */\n protected sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private async send(request: core.Request, opts: Send = {}): Promise<unknown> {\n if (!this.apiKey) {\n throw new NoApiKey(\"no API key: pass one to the client or set DATAFUEL_API_KEY\");\n }\n const url = new URL(this.baseUrl + request.path);\n for (const [name, value] of Object.entries(request.params ?? {})) {\n url.searchParams.set(name, value);\n }\n const init: RequestInit = {\n method: request.method,\n headers: core.headers(this.apiKey, this.userAgent, request),\n // Redirects stay off: a redirect to another host would carry X-API-Key\n // to it, since fetch does not strip custom headers.\n redirect: \"manual\",\n };\n if (request.body !== undefined) init.body = JSON.stringify(request.body);\n\n for (let attempt = 0; ; attempt++) {\n let error: unknown;\n try {\n const signal = this.signalFor(opts);\n // Detached on purpose: calling `this.fetchImpl(...)` would invoke it as\n // a method of the client, and a fetch bound to another receiver (or\n // none) throws \"Illegal invocation\".\n const send = this.fetchImpl;\n const response = await send(url, signal ? { ...init, signal } : init);\n return await parse(response);\n } catch (caught) {\n if (isAbort(caught)) {\n throw new TransportError(\"the request was aborted or timed out\", { cause: caught });\n }\n error =\n caught instanceof DataFuelError\n ? caught\n : new TransportError(String(caught), {\n cause: caught,\n });\n }\n const retryAfter =\n error instanceof Object && \"retryAfter\" in error ? Number(error.retryAfter) : 0;\n if (attempt >= this.maxRetries || !request.retryable || !core.shouldRetry(error)) throw error;\n await this.sleep(core.backoff(attempt, retryAfter));\n }\n }\n\n private signalFor(opts: Send): AbortSignal | undefined {\n const budget = opts.timeoutMs ?? this.timeoutMs;\n const timeout =\n budget === null || budget === undefined ? undefined : AbortSignal.timeout(budget);\n if (timeout && opts.signal) return AbortSignal.any([timeout, opts.signal]);\n return timeout ?? opts.signal;\n }\n\n /**\n * Send a task and wait for its result.\n *\n * On a 202 the API is still working: the identical request goes out again\n * under the identical idempotency key, so it attaches to the running task\n * rather than starting a second one.\n */\n private async runTask(request: core.Request, opts: Send): Promise<Result> {\n const budget = opts.timeoutMs ?? this.timeoutMs ?? core.DEFAULT_TIMEOUT_MS;\n const deadline = Date.now() + budget;\n let body: unknown;\n for (let attempt = 0; ; attempt++) {\n body = await this.send(request, opts);\n if (!core.stillProcessing(body)) break;\n const delay = core.pollDelay(attempt);\n if (Date.now() + delay >= deadline) {\n throw new WaitTimeout(\n \"the task is still processing; re-send under the same idempotency key to pick it up\",\n request.idempotencyKey,\n );\n }\n await this.sleep(delay);\n }\n const result = new Result(record(body));\n result.raiseForStatus();\n return result;\n }\n\n // --- pages -------------------------------------------------------------\n\n /**\n * Fetch one URL and wait for the result.\n *\n * Throws {@link Blocked} when the target refused the request and\n * {@link TaskFailed} otherwise; both carry `.result`.\n */\n async scrape(url: string, options: ScrapeOptions & CallOptions = {}): Promise<Result> {\n const request = core.buildScrape(url, options, options.proxy, core.key(options.idempotencyKey));\n return this.runTask(request, options);\n }\n\n /** The one-liner: the page as LLM-ready markdown, images dropped. */\n async markdown(url: string, options: ScrapeOptions & CallOptions = {}): Promise<string> {\n const result = await this.scrape(url, { ...options, format: \"markdown\", includeImages: false });\n return result.text;\n }\n\n /**\n * Return a task by id, e.g. one created by a job or a crawl.\n *\n * A task that is still running comes back with `pending` true.\n */\n async getTask(taskId: string, options: CallOptions = {}): Promise<Result> {\n const body = await this.send(\n new core.Request(\"GET\", `/task/${core.pathSegment(taskId)}`),\n options,\n );\n if (core.stillProcessing(body)) return new Result({ id: taskId, status: \"processing\" });\n const result = new Result(record(body));\n result.raiseForStatus();\n return result;\n }\n\n /** Send a prompt to an AI engine and return its answer. */\n async ask(prompt: string, options: AskOptions & CallOptions): Promise<Result> {\n const request = core.buildAsk(prompt, options, core.key(options.idempotencyKey));\n return this.runTask(request, options);\n }\n\n /**\n * List the URLs of a site without scraping them.\n *\n * One credit per call on a Basic proxy, however many links come back. Use it\n * before a crawl to see how big a section is.\n */\n async map(url: string, options: MapOptions & CallOptions = {}): Promise<SiteMap> {\n const request = core.buildMap(url, options, options.proxy, core.key(options.idempotencyKey));\n const task = await this.runTask(request, options);\n const data = record(task.requireData()) as unknown as SiteMap;\n return { ...data, links: data.links ?? [], sitemaps: data.sitemaps ?? [], task };\n }\n\n // --- crawl -------------------------------------------------------------\n\n /**\n * Queue a crawl and return its id immediately.\n *\n * Follows links from the start URL and scrapes every page. Pages are charged\n * like single scrapes when they are queued; failed and blocked pages are\n * refunded. Unset limits use the API defaults: 100 pages, depth 3, 5 in flight.\n */\n async startCrawl(url: string, options: CrawlOptions & CallOptions = {}): Promise<string> {\n const request = core.buildCrawl(url, options, options.proxy, core.key(options.idempotencyKey));\n return strField(await this.send(request, options), \"job_id\");\n }\n\n /** Return the progress of a crawl. */\n async getCrawl(crawlId: string, options: CallOptions = {}): Promise<CrawlStatus> {\n const body = record(\n await this.send(new core.Request(\"GET\", `/crawl/${core.pathSegment(crawlId)}`), options),\n );\n const raw = body as unknown as Partial<CrawlStatus>;\n return {\n ...raw,\n status: raw.status as CrawlStatus[\"status\"],\n // Counters the caller reads unconditionally must exist even on a thin answer.\n pages: {\n discovered: 0,\n enqueued: 0,\n done: 0,\n failed: 0,\n skipped: 0,\n ...(raw.pages ?? {}),\n },\n depth_reached: raw.depth_reached ?? 0,\n total_cost: raw.total_cost ?? 0,\n done: isDone(raw.status),\n };\n }\n\n /** One page of results, in discovery order. `limit` unset uses the API default. */\n async crawlResults(\n crawlId: string,\n options: CallOptions & { cursor?: string; limit?: number } = {},\n ): Promise<CrawlResultsPage> {\n const body = record(\n await this.send(core.crawlResultsRequest(crawlId, options.cursor, options.limit), options),\n );\n const pages = Array.isArray(body.pages) ? body.pages : [];\n const next = typeof body.next_cursor === \"string\" ? body.next_cursor : undefined;\n return {\n pages: pages.map((page) => new CrawlPage(record(page))),\n ...(next ? { nextCursor: next } : {}),\n };\n }\n\n /**\n * Walk every page of a crawl, fetching result pages as needed.\n *\n * Readable while the crawl runs; unfinished pages report `pending`.\n */\n async *crawlPages(\n crawlId: string,\n options: CallOptions & { limit?: number } = {},\n ): AsyncGenerator<CrawlPage> {\n let cursor: string | undefined;\n const seen = new Set<string>();\n for (;;) {\n const batch = await this.crawlResults(crawlId, { ...options, ...(cursor ? { cursor } : {}) });\n yield* batch.pages;\n cursor = batch.nextCursor;\n if (!cursor) return;\n if (seen.has(cursor)) {\n throw new DataFuelError(\"the API repeated a crawl cursor; stopping to avoid a loop\");\n }\n seen.add(cursor);\n }\n }\n\n /** Poll until the crawl is done. Without `timeoutMs` it waits indefinitely. */\n async waitCrawl(crawlId: string, options: CallOptions = {}): Promise<CrawlStatus> {\n // The timeout bounds the whole wait, not each poll.\n const { timeoutMs, ...perPoll } = options;\n const deadline = timeoutMs === undefined ? null : Date.now() + timeoutMs;\n for (;;) {\n const status = await this.getCrawl(crawlId, perPoll);\n if (status.done) return status;\n if (deadline !== null && Date.now() + this.pollIntervalMs >= deadline) {\n throw new WaitTimeout(`crawl ${crawlId} is still running`, crawlId, status);\n }\n await this.sleep(this.pollIntervalMs);\n }\n }\n\n /**\n * Start a crawl, wait for it, and return every page.\n *\n * Check `stop_reason`: `insufficient_credits` means it ended early. For large\n * crawls prefer {@link startCrawl} + {@link waitCrawl} + {@link crawlPages},\n * which stream instead of holding every page in memory. On {@link WaitTimeout}\n * the error carries the id: the crawl keeps running and billing.\n */\n async crawl(url: string, options: CrawlOptions & CallOptions = {}): Promise<CrawlResult> {\n const id = await this.startCrawl(url, options);\n const status = await this.waitCrawl(id, options);\n const pages: CrawlPage[] = [];\n for await (const page of this.crawlPages(id, options)) pages.push(page);\n return { id, status, pages };\n }\n\n // --- jobs --------------------------------------------------------------\n\n /**\n * Queue a batch of known URLs and return the job id.\n *\n * Cheaper and more predictable than a crawl when you already have the URLs.\n * `sequential` runs them one after the other; the default runs them\n * concurrently, bounded by the account's concurrency limit.\n */\n async createJob(\n urls: string[],\n options: ScrapeOptions & CallOptions & { sequential?: boolean } = {},\n ): Promise<string> {\n const request = core.buildJob(\n urls,\n options,\n options.proxy,\n options.sequential ?? false,\n core.key(options.idempotencyKey),\n );\n return strField(await this.send(request, options), \"id\");\n }\n\n /** Queue a batch of prompts for an AI engine and return the job id. */\n async createAskJob(\n prompts: string[],\n options: AskOptions & CallOptions & { sequential?: boolean },\n ): Promise<string> {\n const request = core.buildAskJob(\n prompts,\n options,\n options.sequential ?? false,\n core.key(options.idempotencyKey),\n );\n return strField(await this.send(request, options), \"id\");\n }\n\n /** Return the progress of a job. */\n async getJob(jobId: string, options: CallOptions = {}): Promise<JobStatus> {\n const body = record(\n await this.send(new core.Request(\"GET\", `/job/${core.pathSegment(jobId)}`), options),\n );\n const raw = body as unknown as Partial<JobStatus>;\n return {\n status: raw.status as JobStatus[\"status\"],\n tasks_count: raw.tasks_count ?? 0,\n tasks_done: raw.tasks_done ?? 0,\n tasks_remaining: raw.tasks_remaining ?? 0,\n total_cost: raw.total_cost ?? 0,\n done: isDone(raw.status),\n };\n }\n\n /**\n * Return the per-task results of a job.\n *\n * A job completes even when some of its tasks failed: check `task.ok` or call\n * `task.raiseForStatus()` per task.\n */\n async jobResults(jobId: string, options: CallOptions = {}): Promise<JobResults> {\n const body = record(\n await this.send(new core.Request(\"GET\", `/job/${core.pathSegment(jobId)}/results`), options),\n );\n const tasks = Array.isArray(body.tasks_result) ? body.tasks_result : [];\n return {\n id: jobId,\n tasks_count: Number(body.tasks_count ?? 0),\n tasks_failed: Number(body.tasks_failed ?? 0),\n tasks_complete: Number(body.tasks_complete ?? 0),\n tasks: tasks.map((task) => new Result(record(task))),\n };\n }\n\n /** Poll until the job is done. Without `timeoutMs` it waits indefinitely. */\n async waitJob(jobId: string, options: CallOptions = {}): Promise<JobStatus> {\n // The timeout bounds the whole wait, not each poll.\n const { timeoutMs, ...perPoll } = options;\n const deadline = timeoutMs === undefined ? null : Date.now() + timeoutMs;\n for (;;) {\n const status = await this.getJob(jobId, perPoll);\n if (status.done) return status;\n if (deadline !== null && Date.now() + this.pollIntervalMs >= deadline) {\n throw new WaitTimeout(`job ${jobId} is still running`, jobId, status);\n }\n await this.sleep(this.pollIntervalMs);\n }\n }\n\n /** Create a job, wait for it, and return its results. */\n async runJob(\n urls: string[],\n options: ScrapeOptions & CallOptions & { sequential?: boolean } = {},\n ): Promise<JobResults> {\n const id = await this.createJob(urls, options);\n await this.waitJob(id, options);\n return this.jobResults(id, options);\n }\n\n /** Create a prompt job, wait for it, and return its results. */\n async runAskJob(\n prompts: string[],\n options: AskOptions & CallOptions & { sequential?: boolean },\n ): Promise<JobResults> {\n const id = await this.createAskJob(prompts, options);\n await this.waitJob(id, options);\n return this.jobResults(id, options);\n }\n\n // --- account -----------------------------------------------------------\n\n /** Which task types and LLM engines are switched on right now. */\n async capabilities(options: CallOptions = {}): Promise<Capabilities> {\n return new Capabilities(\n record(await this.send(new core.Request(\"GET\", \"/capabilities\"), options)),\n );\n }\n\n /** Remaining credits. */\n async balance(options: CallOptions = {}): Promise<number> {\n const body = await this.send(new core.Request(\"GET\", \"/users/@me/balance\"), options);\n return intField(body, \"balance\");\n }\n\n /** The account behind the API key. */\n async me(options: CallOptions = {}): Promise<Profile> {\n const body = await this.send(new core.Request(\"GET\", \"/users/@me\"), options);\n return record(body) as unknown as Profile;\n }\n\n /**\n * Revoke the current key and return the new one.\n *\n * This is the only time the new key is shown. The client keeps using the\n * revoked one: store the new key and build a new client with it.\n */\n async resetApiKey(options: CallOptions = {}): Promise<string> {\n const body = await this.send(new core.Request(\"POST\", \"/users/@me/api-key/reset\"), options);\n return strField(body, \"api_key\");\n }\n}\n\nexport type { Capability };\n\n/** The API key from the environment, on runtimes that have one. */\nfunction envApiKey(): string | undefined {\n return typeof process !== \"undefined\" ? process.env?.DATAFUEL_API_KEY : undefined;\n}\n\nasync function parse(response: Response): Promise<unknown> {\n const text = await response.text();\n let body: unknown;\n if (text.length > 0) {\n try {\n body = JSON.parse(text);\n } catch {\n body = text;\n }\n }\n if (!response.ok) {\n const header = response.headers.get(\"Retry-After\");\n const retryAfter = header !== null && !Number.isNaN(Number(header)) ? Number(header) : 0;\n throw apiError(response.status, body, retryAfter);\n }\n return body;\n}\n\nfunction isAbort(error: unknown): boolean {\n return error instanceof Error && (error.name === \"AbortError\" || error.name === \"TimeoutError\");\n}\n\n/** Turn a surprise answer into a DataFuelError rather than a TypeError later. */\nfunction record(body: unknown): Record<string, unknown> {\n if (body === null || typeof body !== \"object\" || Array.isArray(body)) {\n throw new DataFuelError(`unexpected answer from the API: ${JSON.stringify(body) ?? \"empty\"}`);\n }\n return body as Record<string, unknown>;\n}\n\nfunction field(body: unknown, name: string): unknown {\n const value = record(body)[name];\n if (value === undefined || value === null) {\n throw new DataFuelError(`unexpected answer from the API: no ${name} field`);\n }\n return value;\n}\n\nfunction strField(body: unknown, name: string): string {\n return String(field(body, name));\n}\n\nfunction intField(body: unknown, name: string): number {\n const value = Number(field(body, name));\n if (Number.isNaN(value)) {\n throw new DataFuelError(`unexpected answer from the API: ${name} is not a number`);\n }\n return value;\n}\n"],"mappings":"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","names":["record"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@datafuel/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript client for the DataFuel scraping API",
5
+ "license": "MIT",
6
+ "homepage": "https://datafuel.ai",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/index.d.cts",
19
+ "default": "./dist/index.cjs"
20
+ }
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "engines": {
30
+ "node": ">=20.3"
31
+ },
32
+ "sideEffects": false,
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest run --exclude '**/live.test.ts'",
37
+ "test:live": "vitest run test/live.test.ts --testTimeout=300000",
38
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
39
+ },
40
+ "keywords": [
41
+ "datafuel",
42
+ "scraping",
43
+ "crawler",
44
+ "web-scraping",
45
+ "markdown",
46
+ "llm"
47
+ ],
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/Datafuel-ai/datafuel-js.git",
54
+ "directory": "packages/sdk"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/Datafuel-ai/datafuel-js/issues"
58
+ }
59
+ }