@datafuel/sdk 0.3.0 → 0.4.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/dist/index.d.cts CHANGED
@@ -40,7 +40,9 @@ interface AI {
40
40
  prompt?: string;
41
41
  /** Example JSON object the output must follow. */
42
42
  format?: unknown;
43
- provider?: "openai" | "anthropic" | "google";
43
+ /** Required. One of the providers `df.aiProviders()` lists; the API answers 400 INVALID_ATTRIBUTES without it. */
44
+ provider?: string;
45
+ /** One of the models `df.aiProviders()` lists for `provider`. Omit for the provider's default. */
44
46
  model?: string;
45
47
  apiKey?: string;
46
48
  }
@@ -54,9 +56,15 @@ interface ScrapeOptions {
54
56
  jsRendering?: boolean;
55
57
  waitFor?: string;
56
58
  waitForTimeoutMs?: number;
57
- /** An object keyed by action, e.g. `{ click: "#more" }`. `df.jsInstructions()` lists them. */
59
+ /**
60
+ * Browser actions run after load: an array of single-action objects, run in
61
+ * order, e.g. `[{ click: "#more" }, { wait_ms: 1000 }, { click: "#more" }]`.
62
+ * An object keyed by action is still accepted, but its order is not
63
+ * guaranteed and an action cannot repeat. `df.jsInstructions()` lists them.
64
+ */
58
65
  jsInstructions?: unknown;
59
- blockResource?: string;
66
+ /** Resource types the browser must not load: one, e.g. `"Image"`, or several. */
67
+ blockResource?: string | string[];
60
68
  /** Markdown only: always render just the `<main>` / `<article>` container. */
61
69
  mainContentOnly?: boolean;
62
70
  /** Markdown only: `false` drops images and saves tokens. */
@@ -445,6 +453,28 @@ interface JsInstruction {
445
453
  iframe: boolean;
446
454
  example: unknown;
447
455
  }
456
+ /** An LLM provider `ai.provider` accepts, with the models `ai.model` accepts for it. */
457
+ interface AIProvider {
458
+ name: string;
459
+ models: string[];
460
+ }
461
+ /** The anti-bot protection in front of one host and path, from `checkProtection`. */
462
+ interface ProtectionCheck {
463
+ host: string;
464
+ path: string;
465
+ /** cloudflare, cloudflare_5sec, akamai, imperva, perimeterx or unprotected. */
466
+ protection_type: string;
467
+ }
468
+ /** The state of the API. `checks` is filled by a deep check only, keyed by dependency. */
469
+ interface Health {
470
+ status: "ok" | "degraded" | (string & {});
471
+ checks?: Record<string, {
472
+ status: "ok" | "fail" | (string & {});
473
+ latency_ms: number;
474
+ }>;
475
+ /** Whether the API can serve requests. */
476
+ ok: boolean;
477
+ }
448
478
  /** A named proxy location: a city, or an ASN. */
449
479
  interface ProxyLocation {
450
480
  code: string;
@@ -488,7 +518,7 @@ declare class Capabilities {
488
518
 
489
519
  declare const DEFAULT_BASE_URL = "https://scraping-api.datafuel.ai/api/v1";
490
520
  /** Kept in step with package.json by a test; see test/hardening.test.ts. */
491
- declare const VERSION = "0.3.0";
521
+ declare const VERSION = "0.4.0";
492
522
  /** Options for the llm_scraping module, which reads its proxy country here. */
493
523
  interface AskOptions {
494
524
  engine: Engine | string;
@@ -556,7 +586,10 @@ interface SearchOptions {
556
586
  ludocid?: string;
557
587
  lsig?: string;
558
588
  ibp?: string;
559
- /** Exit country of the request. */
589
+ /**
590
+ * Accepted but not used yet: searches leave through DataFuel's own pool.
591
+ * Target a market with `country` and `language`.
592
+ */
560
593
  proxyCountry?: string;
561
594
  /** Default json. */
562
595
  format?: "json" | "html" | "markdown";
@@ -739,6 +772,21 @@ declare class DataFuel {
739
772
  capabilities(options?: CallOptions): Promise<Capabilities>;
740
773
  /** The browser actions `jsInstructions` accepts, with their arguments. Needs no key. */
741
774
  jsInstructions(options?: CallOptions): Promise<JsInstruction[]>;
775
+ /** The LLM providers and models `ai` accepts. Needs no key. */
776
+ aiProviders(options?: CallOptions): Promise<AIProvider[]>;
777
+ /**
778
+ * Whether the API is up. `deep` also checks the dependencies it needs to
779
+ * serve scrapes. A degraded report is returned, not thrown: read `ok`.
780
+ * Needs no key.
781
+ */
782
+ health(options?: CallOptions & {
783
+ deep?: boolean;
784
+ }): Promise<Health>;
785
+ /**
786
+ * Which anti-bot protection sits in front of each URL. Nothing is scraped
787
+ * and nothing is charged; invalid URLs are skipped.
788
+ */
789
+ checkProtection(urls: string[], options?: CallOptions): Promise<ProtectionCheck[]>;
742
790
  /** Countries, regions and cities a proxy type can exit from. */
743
791
  proxyLocations(options?: CallOptions & {
744
792
  proxyType?: string;
@@ -792,7 +840,7 @@ declare class NoApiKey extends DataFuelError {
792
840
  declare class TransportError extends DataFuelError {
793
841
  }
794
842
  /** The `code` of an API error. The API may add codes; unknown ones pass through. */
795
- type ErrorCode = "UNAUTHORIZED" | "INVALID_API_KEY" | "FORBIDDEN" | "INSUFFICIENT_CREDITS" | "RATE_LIMIT_EXCEEDED" | "CONCURRENCY_LIMIT_REACHED" | "INVALID_REQUEST_BODY" | "INVALID_ATTRIBUTES" | "MISSING_TARGET" | "UNSUPPORTED_TASK_TYPE" | "JOB_REQUIRES_MULTIPLE_TARGETS" | "INVALID_IDEMPOTENCY_KEY" | "IDEMPOTENCY_KEY_REUSED" | "INVALID_TASK_ID" | "INVALID_JOB_ID" | "TASK_NOT_FOUND" | "JOB_NOT_FOUND" | "CRAWL_NOT_FOUND" | "JOB_NOT_CANCELLABLE" | "INVALID_CRAWL_PATTERN" | "CRAWL_UNSUPPORTED_OPTION" | "INVALID_CURSOR" | "INVALID_QUERY_PARAM" | "INVALID_PROXY_TYPE" | "INVALID_COUNTRY" | "INVALID_DATE_FORMAT" | "INVALID_DATE_RANGE" | "INVALID_INTERVAL" | "MODULE_UNAVAILABLE" | "ENGINE_UNAVAILABLE" | "API_KEY_RESET_FAILED" | "TASK_RESULT_TIMEOUT" | "INTERNAL_ERROR" | (string & {});
843
+ type ErrorCode = "UNAUTHORIZED" | "INVALID_API_KEY" | "FORBIDDEN" | "INSUFFICIENT_CREDITS" | "RATE_LIMIT_EXCEEDED" | "CONCURRENCY_LIMIT_REACHED" | "INVALID_REQUEST_BODY" | "INVALID_ATTRIBUTES" | "MISSING_TARGET" | "UNSUPPORTED_TASK_TYPE" | "JOB_REQUIRES_MULTIPLE_TARGETS" | "INVALID_IDEMPOTENCY_KEY" | "IDEMPOTENCY_KEY_REUSED" | "INVALID_TASK_ID" | "INVALID_JOB_ID" | "TASK_NOT_FOUND" | "JOB_NOT_FOUND" | "CRAWL_NOT_FOUND" | "JOB_NOT_CANCELLABLE" | "TASK_ALREADY_EXISTS" | "JOB_ALREADY_EXISTS" | "URL_LENGTH_CANNOT_BE_ZERO" | "ANALYTICS_FETCH_FAILED" | "INVALID_CRAWL_PATTERN" | "CRAWL_UNSUPPORTED_OPTION" | "INVALID_CURSOR" | "INVALID_QUERY_PARAM" | "INVALID_PROXY_TYPE" | "INVALID_COUNTRY" | "INVALID_DATE_FORMAT" | "INVALID_DATE_RANGE" | "INVALID_INTERVAL" | "MODULE_UNAVAILABLE" | "ENGINE_UNAVAILABLE" | "API_KEY_RESET_FAILED" | "TASK_RESULT_TIMEOUT" | "INTERNAL_ERROR" | (string & {});
796
844
  /** A non-2xx answer from the API itself. */
797
845
  declare class APIError extends DataFuelError {
798
846
  readonly status: number;
@@ -825,6 +873,13 @@ declare class IdempotencyKeyReused extends APIError {
825
873
  /** 409 JOB_NOT_CANCELLABLE: the job or crawl already finished. */
826
874
  declare class JobNotCancellable extends APIError {
827
875
  }
876
+ /**
877
+ * 409 TASK_ALREADY_EXISTS / JOB_ALREADY_EXISTS: the create collided with an
878
+ * existing task or job and is not an idempotent replay. Nothing was charged;
879
+ * send the request again.
880
+ */
881
+ declare class AlreadyExists extends APIError {
882
+ }
828
883
  /** 503: an operator switched something off, or a dependency is down. The message carries the reason. */
829
884
  declare class Unavailable extends APIError {
830
885
  }
@@ -864,4 +919,4 @@ declare class WaitTimeout extends DataFuelError {
864
919
  constructor(message: string, id?: string, status?: unknown);
865
920
  }
866
921
 
867
- export { type AI, APIError, type Analytics, type AnalyticsCounts, type AnalyticsOptions, type AskOptions, type BalanceSplit, Blocked, type CallOptions, type CancelResult, Capabilities, type Capability, type ClientOptions, type CrawlOptions, CrawlPage, type CrawlResult, type CrawlResultsPage, type CrawlStatus, DEFAULT_BASE_URL, DataFuel, DataFuelError, type Engine, EngineUnavailable, type ErrorCode, Forbidden, type Format, IdempotencyKeyReused, InsufficientCredits, InvalidAttributes, JobNotCancellable, type JobResults, type JobStatus, type JobSummary, type JobsPage, type JsInstruction, type JsInstructionArg, type Link, type ListOptions, type ListTasksOptions, type MapOptions, ModuleUnavailable, NoApiKey, NotFound, type Payload, type PreviousPeriod, type Profile, type Proxy, type ProxyCountry, type ProxyLocation, type ProxyType, RateLimited, Result, type ScrapeOptions, type SearchOptions, type SiteMap, type Status, type StatusCodeBreakdown, TaskFailed, type TaskSummary, type TaskType, type TasksPage, type Transaction, type TransactionOperation, type TransactionSum, type TransactionsOptions, type TransactionsPage, TransportError, Unauthorized, Unavailable, VERSION, WaitTimeout, isDone };
922
+ export { type AI, type AIProvider, APIError, AlreadyExists, type Analytics, type AnalyticsCounts, type AnalyticsOptions, type AskOptions, type BalanceSplit, Blocked, type CallOptions, type CancelResult, Capabilities, type Capability, type ClientOptions, type CrawlOptions, CrawlPage, type CrawlResult, type CrawlResultsPage, type CrawlStatus, DEFAULT_BASE_URL, DataFuel, DataFuelError, type Engine, EngineUnavailable, type ErrorCode, Forbidden, type Format, type Health, IdempotencyKeyReused, InsufficientCredits, InvalidAttributes, JobNotCancellable, type JobResults, type JobStatus, type JobSummary, type JobsPage, type JsInstruction, type JsInstructionArg, type Link, type ListOptions, type ListTasksOptions, type MapOptions, ModuleUnavailable, NoApiKey, NotFound, type Payload, type PreviousPeriod, type Profile, type ProtectionCheck, type Proxy, type ProxyCountry, type ProxyLocation, type ProxyType, RateLimited, Result, type ScrapeOptions, type SearchOptions, type SiteMap, type Status, type StatusCodeBreakdown, TaskFailed, type TaskSummary, type TaskType, type TasksPage, type Transaction, type TransactionOperation, type TransactionSum, type TransactionsOptions, type TransactionsPage, TransportError, Unauthorized, Unavailable, VERSION, WaitTimeout, isDone };
package/dist/index.d.ts CHANGED
@@ -40,7 +40,9 @@ interface AI {
40
40
  prompt?: string;
41
41
  /** Example JSON object the output must follow. */
42
42
  format?: unknown;
43
- provider?: "openai" | "anthropic" | "google";
43
+ /** Required. One of the providers `df.aiProviders()` lists; the API answers 400 INVALID_ATTRIBUTES without it. */
44
+ provider?: string;
45
+ /** One of the models `df.aiProviders()` lists for `provider`. Omit for the provider's default. */
44
46
  model?: string;
45
47
  apiKey?: string;
46
48
  }
@@ -54,9 +56,15 @@ interface ScrapeOptions {
54
56
  jsRendering?: boolean;
55
57
  waitFor?: string;
56
58
  waitForTimeoutMs?: number;
57
- /** An object keyed by action, e.g. `{ click: "#more" }`. `df.jsInstructions()` lists them. */
59
+ /**
60
+ * Browser actions run after load: an array of single-action objects, run in
61
+ * order, e.g. `[{ click: "#more" }, { wait_ms: 1000 }, { click: "#more" }]`.
62
+ * An object keyed by action is still accepted, but its order is not
63
+ * guaranteed and an action cannot repeat. `df.jsInstructions()` lists them.
64
+ */
58
65
  jsInstructions?: unknown;
59
- blockResource?: string;
66
+ /** Resource types the browser must not load: one, e.g. `"Image"`, or several. */
67
+ blockResource?: string | string[];
60
68
  /** Markdown only: always render just the `<main>` / `<article>` container. */
61
69
  mainContentOnly?: boolean;
62
70
  /** Markdown only: `false` drops images and saves tokens. */
@@ -445,6 +453,28 @@ interface JsInstruction {
445
453
  iframe: boolean;
446
454
  example: unknown;
447
455
  }
456
+ /** An LLM provider `ai.provider` accepts, with the models `ai.model` accepts for it. */
457
+ interface AIProvider {
458
+ name: string;
459
+ models: string[];
460
+ }
461
+ /** The anti-bot protection in front of one host and path, from `checkProtection`. */
462
+ interface ProtectionCheck {
463
+ host: string;
464
+ path: string;
465
+ /** cloudflare, cloudflare_5sec, akamai, imperva, perimeterx or unprotected. */
466
+ protection_type: string;
467
+ }
468
+ /** The state of the API. `checks` is filled by a deep check only, keyed by dependency. */
469
+ interface Health {
470
+ status: "ok" | "degraded" | (string & {});
471
+ checks?: Record<string, {
472
+ status: "ok" | "fail" | (string & {});
473
+ latency_ms: number;
474
+ }>;
475
+ /** Whether the API can serve requests. */
476
+ ok: boolean;
477
+ }
448
478
  /** A named proxy location: a city, or an ASN. */
449
479
  interface ProxyLocation {
450
480
  code: string;
@@ -488,7 +518,7 @@ declare class Capabilities {
488
518
 
489
519
  declare const DEFAULT_BASE_URL = "https://scraping-api.datafuel.ai/api/v1";
490
520
  /** Kept in step with package.json by a test; see test/hardening.test.ts. */
491
- declare const VERSION = "0.3.0";
521
+ declare const VERSION = "0.4.0";
492
522
  /** Options for the llm_scraping module, which reads its proxy country here. */
493
523
  interface AskOptions {
494
524
  engine: Engine | string;
@@ -556,7 +586,10 @@ interface SearchOptions {
556
586
  ludocid?: string;
557
587
  lsig?: string;
558
588
  ibp?: string;
559
- /** Exit country of the request. */
589
+ /**
590
+ * Accepted but not used yet: searches leave through DataFuel's own pool.
591
+ * Target a market with `country` and `language`.
592
+ */
560
593
  proxyCountry?: string;
561
594
  /** Default json. */
562
595
  format?: "json" | "html" | "markdown";
@@ -739,6 +772,21 @@ declare class DataFuel {
739
772
  capabilities(options?: CallOptions): Promise<Capabilities>;
740
773
  /** The browser actions `jsInstructions` accepts, with their arguments. Needs no key. */
741
774
  jsInstructions(options?: CallOptions): Promise<JsInstruction[]>;
775
+ /** The LLM providers and models `ai` accepts. Needs no key. */
776
+ aiProviders(options?: CallOptions): Promise<AIProvider[]>;
777
+ /**
778
+ * Whether the API is up. `deep` also checks the dependencies it needs to
779
+ * serve scrapes. A degraded report is returned, not thrown: read `ok`.
780
+ * Needs no key.
781
+ */
782
+ health(options?: CallOptions & {
783
+ deep?: boolean;
784
+ }): Promise<Health>;
785
+ /**
786
+ * Which anti-bot protection sits in front of each URL. Nothing is scraped
787
+ * and nothing is charged; invalid URLs are skipped.
788
+ */
789
+ checkProtection(urls: string[], options?: CallOptions): Promise<ProtectionCheck[]>;
742
790
  /** Countries, regions and cities a proxy type can exit from. */
743
791
  proxyLocations(options?: CallOptions & {
744
792
  proxyType?: string;
@@ -792,7 +840,7 @@ declare class NoApiKey extends DataFuelError {
792
840
  declare class TransportError extends DataFuelError {
793
841
  }
794
842
  /** The `code` of an API error. The API may add codes; unknown ones pass through. */
795
- type ErrorCode = "UNAUTHORIZED" | "INVALID_API_KEY" | "FORBIDDEN" | "INSUFFICIENT_CREDITS" | "RATE_LIMIT_EXCEEDED" | "CONCURRENCY_LIMIT_REACHED" | "INVALID_REQUEST_BODY" | "INVALID_ATTRIBUTES" | "MISSING_TARGET" | "UNSUPPORTED_TASK_TYPE" | "JOB_REQUIRES_MULTIPLE_TARGETS" | "INVALID_IDEMPOTENCY_KEY" | "IDEMPOTENCY_KEY_REUSED" | "INVALID_TASK_ID" | "INVALID_JOB_ID" | "TASK_NOT_FOUND" | "JOB_NOT_FOUND" | "CRAWL_NOT_FOUND" | "JOB_NOT_CANCELLABLE" | "INVALID_CRAWL_PATTERN" | "CRAWL_UNSUPPORTED_OPTION" | "INVALID_CURSOR" | "INVALID_QUERY_PARAM" | "INVALID_PROXY_TYPE" | "INVALID_COUNTRY" | "INVALID_DATE_FORMAT" | "INVALID_DATE_RANGE" | "INVALID_INTERVAL" | "MODULE_UNAVAILABLE" | "ENGINE_UNAVAILABLE" | "API_KEY_RESET_FAILED" | "TASK_RESULT_TIMEOUT" | "INTERNAL_ERROR" | (string & {});
843
+ type ErrorCode = "UNAUTHORIZED" | "INVALID_API_KEY" | "FORBIDDEN" | "INSUFFICIENT_CREDITS" | "RATE_LIMIT_EXCEEDED" | "CONCURRENCY_LIMIT_REACHED" | "INVALID_REQUEST_BODY" | "INVALID_ATTRIBUTES" | "MISSING_TARGET" | "UNSUPPORTED_TASK_TYPE" | "JOB_REQUIRES_MULTIPLE_TARGETS" | "INVALID_IDEMPOTENCY_KEY" | "IDEMPOTENCY_KEY_REUSED" | "INVALID_TASK_ID" | "INVALID_JOB_ID" | "TASK_NOT_FOUND" | "JOB_NOT_FOUND" | "CRAWL_NOT_FOUND" | "JOB_NOT_CANCELLABLE" | "TASK_ALREADY_EXISTS" | "JOB_ALREADY_EXISTS" | "URL_LENGTH_CANNOT_BE_ZERO" | "ANALYTICS_FETCH_FAILED" | "INVALID_CRAWL_PATTERN" | "CRAWL_UNSUPPORTED_OPTION" | "INVALID_CURSOR" | "INVALID_QUERY_PARAM" | "INVALID_PROXY_TYPE" | "INVALID_COUNTRY" | "INVALID_DATE_FORMAT" | "INVALID_DATE_RANGE" | "INVALID_INTERVAL" | "MODULE_UNAVAILABLE" | "ENGINE_UNAVAILABLE" | "API_KEY_RESET_FAILED" | "TASK_RESULT_TIMEOUT" | "INTERNAL_ERROR" | (string & {});
796
844
  /** A non-2xx answer from the API itself. */
797
845
  declare class APIError extends DataFuelError {
798
846
  readonly status: number;
@@ -825,6 +873,13 @@ declare class IdempotencyKeyReused extends APIError {
825
873
  /** 409 JOB_NOT_CANCELLABLE: the job or crawl already finished. */
826
874
  declare class JobNotCancellable extends APIError {
827
875
  }
876
+ /**
877
+ * 409 TASK_ALREADY_EXISTS / JOB_ALREADY_EXISTS: the create collided with an
878
+ * existing task or job and is not an idempotent replay. Nothing was charged;
879
+ * send the request again.
880
+ */
881
+ declare class AlreadyExists extends APIError {
882
+ }
828
883
  /** 503: an operator switched something off, or a dependency is down. The message carries the reason. */
829
884
  declare class Unavailable extends APIError {
830
885
  }
@@ -864,4 +919,4 @@ declare class WaitTimeout extends DataFuelError {
864
919
  constructor(message: string, id?: string, status?: unknown);
865
920
  }
866
921
 
867
- export { type AI, APIError, type Analytics, type AnalyticsCounts, type AnalyticsOptions, type AskOptions, type BalanceSplit, Blocked, type CallOptions, type CancelResult, Capabilities, type Capability, type ClientOptions, type CrawlOptions, CrawlPage, type CrawlResult, type CrawlResultsPage, type CrawlStatus, DEFAULT_BASE_URL, DataFuel, DataFuelError, type Engine, EngineUnavailable, type ErrorCode, Forbidden, type Format, IdempotencyKeyReused, InsufficientCredits, InvalidAttributes, JobNotCancellable, type JobResults, type JobStatus, type JobSummary, type JobsPage, type JsInstruction, type JsInstructionArg, type Link, type ListOptions, type ListTasksOptions, type MapOptions, ModuleUnavailable, NoApiKey, NotFound, type Payload, type PreviousPeriod, type Profile, type Proxy, type ProxyCountry, type ProxyLocation, type ProxyType, RateLimited, Result, type ScrapeOptions, type SearchOptions, type SiteMap, type Status, type StatusCodeBreakdown, TaskFailed, type TaskSummary, type TaskType, type TasksPage, type Transaction, type TransactionOperation, type TransactionSum, type TransactionsOptions, type TransactionsPage, TransportError, Unauthorized, Unavailable, VERSION, WaitTimeout, isDone };
922
+ export { type AI, type AIProvider, APIError, AlreadyExists, type Analytics, type AnalyticsCounts, type AnalyticsOptions, type AskOptions, type BalanceSplit, Blocked, type CallOptions, type CancelResult, Capabilities, type Capability, type ClientOptions, type CrawlOptions, CrawlPage, type CrawlResult, type CrawlResultsPage, type CrawlStatus, DEFAULT_BASE_URL, DataFuel, DataFuelError, type Engine, EngineUnavailable, type ErrorCode, Forbidden, type Format, type Health, IdempotencyKeyReused, InsufficientCredits, InvalidAttributes, JobNotCancellable, type JobResults, type JobStatus, type JobSummary, type JobsPage, type JsInstruction, type JsInstructionArg, type Link, type ListOptions, type ListTasksOptions, type MapOptions, ModuleUnavailable, NoApiKey, NotFound, type Payload, type PreviousPeriod, type Profile, type ProtectionCheck, type Proxy, type ProxyCountry, type ProxyLocation, type ProxyType, RateLimited, Result, type ScrapeOptions, type SearchOptions, type SiteMap, type Status, type StatusCodeBreakdown, TaskFailed, type TaskSummary, type TaskType, type TasksPage, type Transaction, type TransactionOperation, type TransactionSum, type TransactionsOptions, type TransactionsPage, TransportError, Unauthorized, Unavailable, VERSION, WaitTimeout, isDone };
package/dist/index.js CHANGED
@@ -37,6 +37,8 @@ var IdempotencyKeyReused = class extends APIError {
37
37
  };
38
38
  var JobNotCancellable = class extends APIError {
39
39
  };
40
+ var AlreadyExists = class extends APIError {
41
+ };
40
42
  var Unavailable = class extends APIError {
41
43
  };
42
44
  var ModuleUnavailable = class extends Unavailable {
@@ -74,14 +76,15 @@ var BY_CODE = {
74
76
  IDEMPOTENCY_KEY_REUSED: IdempotencyKeyReused,
75
77
  INVALID_API_KEY: Unauthorized,
76
78
  FORBIDDEN: Forbidden,
77
- JOB_NOT_CANCELLABLE: JobNotCancellable
79
+ JOB_NOT_CANCELLABLE: JobNotCancellable,
80
+ TASK_ALREADY_EXISTS: AlreadyExists,
81
+ JOB_ALREADY_EXISTS: AlreadyExists
78
82
  };
79
83
  var BY_STATUS = {
80
84
  401: Unauthorized,
81
85
  402: InsufficientCredits,
82
86
  403: Forbidden,
83
87
  404: NotFound,
84
- 409: JobNotCancellable,
85
88
  422: IdempotencyKeyReused,
86
89
  429: RateLimited,
87
90
  503: Unavailable
@@ -102,19 +105,20 @@ function apiError(status, body, retryAfter = 0) {
102
105
 
103
106
  // src/core.ts
104
107
  var DEFAULT_BASE_URL = "https://scraping-api.datafuel.ai/api/v1";
105
- var VERSION = "0.3.0";
108
+ var VERSION = "0.4.0";
106
109
  var DEFAULT_TIMEOUT_MS = 18e4;
107
110
  var STILL_PROCESSING_DELAY_MS = 2e3;
108
111
  var STILL_PROCESSING = "TASK_STILL_PROCESSING";
109
112
  var MAX_IDEMPOTENCY_KEY = 255;
110
113
  var Request = class {
111
- constructor(method, path, params, body, idempotencyKey, auth = true) {
114
+ constructor(method, path, params, body, idempotencyKey, auth = true, degradedOk = false) {
112
115
  this.method = method;
113
116
  this.path = path;
114
117
  this.params = params;
115
118
  this.body = body;
116
119
  this.idempotencyKey = idempotencyKey;
117
120
  this.auth = auth;
121
+ this.degradedOk = degradedOk;
118
122
  }
119
123
  method;
120
124
  path;
@@ -122,6 +126,7 @@ var Request = class {
122
126
  body;
123
127
  idempotencyKey;
124
128
  auth;
129
+ degradedOk;
125
130
  /** GETs are safe by nature, writes because they carry an idempotency key. */
126
131
  get retryable() {
127
132
  return this.method === "GET" || this.idempotencyKey !== void 0;
@@ -169,15 +174,8 @@ function aiAttributes(ai) {
169
174
  return out;
170
175
  }
171
176
  function validateAI(ai) {
172
- const given = {
173
- provider: ai.provider !== void 0,
174
- model: ai.model !== void 0,
175
- apiKey: ai.apiKey !== void 0
176
- };
177
- const present = Object.values(given).filter(Boolean).length;
178
- if (present > 0 && present < 3) {
179
- const missing = Object.entries(given).filter(([, ok]) => !ok).map(([name]) => name).join(", ");
180
- throw new TypeError(`ai needs provider, model and apiKey together; missing: ${missing}`);
177
+ if (!ai.provider) {
178
+ throw new TypeError("ai needs a provider; aiProviders() lists the ones the API supports");
181
179
  }
182
180
  }
183
181
  function scrapeAttributes(options = {}) {
@@ -187,7 +185,9 @@ function scrapeAttributes(options = {}) {
187
185
  if (options.waitFor) attrs.wait_for_selector = options.waitFor;
188
186
  if (options.waitForTimeoutMs) attrs.wait_for_selector_timeout_ms = options.waitForTimeoutMs;
189
187
  if (options.jsInstructions) attrs.js_instructions = options.jsInstructions;
190
- if (options.blockResource) attrs.block_resource = options.blockResource;
188
+ if (options.blockResource?.length) {
189
+ attrs.block_resource = typeof options.blockResource === "string" ? options.blockResource : [...options.blockResource];
190
+ }
191
191
  if (options.mainContentOnly) attrs.main_content_only = true;
192
192
  if (options.includeImages !== void 0) attrs.include_images = options.includeImages;
193
193
  if (options.extract) attrs.extract_selector = selector(options.extract);
@@ -369,6 +369,17 @@ function crawlResultsRequest(crawlId, cursor, limit) {
369
369
  Object.keys(params).length > 0 ? params : void 0
370
370
  );
371
371
  }
372
+ function healthRequest(deep) {
373
+ return new Request(
374
+ "GET",
375
+ "/healthz",
376
+ deep ? { deep: "1" } : void 0,
377
+ void 0,
378
+ void 0,
379
+ false,
380
+ true
381
+ );
382
+ }
372
383
  function day(value) {
373
384
  return typeof value === "string" ? value : value.toISOString().slice(0, 10);
374
385
  }
@@ -601,7 +612,7 @@ var DataFuel = class {
601
612
  const signal = this.signalFor(opts);
602
613
  const send = this.fetchImpl;
603
614
  const response = await send(url, signal ? { ...init, signal } : init);
604
- return await parse(response);
615
+ return await parse(response, request.degradedOk);
605
616
  } catch (caught) {
606
617
  if (isAbort(caught)) {
607
618
  throw new TransportError("the request was aborted or timed out", { cause: caught });
@@ -963,6 +974,37 @@ var DataFuel = class {
963
974
  const body = record(await this.send(request, options));
964
975
  return Array.isArray(body.instructions) ? body.instructions : [];
965
976
  }
977
+ /** The LLM providers and models `ai` accepts. Needs no key. */
978
+ async aiProviders(options = {}) {
979
+ const request = new Request(
980
+ "GET",
981
+ "/config/ai-providers",
982
+ void 0,
983
+ void 0,
984
+ void 0,
985
+ false
986
+ );
987
+ const body = record(await this.send(request, options));
988
+ return Array.isArray(body.providers) ? body.providers : [];
989
+ }
990
+ /**
991
+ * Whether the API is up. `deep` also checks the dependencies it needs to
992
+ * serve scrapes. A degraded report is returned, not thrown: read `ok`.
993
+ * Needs no key.
994
+ */
995
+ async health(options = {}) {
996
+ const body = record(await this.send(healthRequest(options.deep ?? false), options));
997
+ return { ...body, ok: body.status === "ok" };
998
+ }
999
+ /**
1000
+ * Which anti-bot protection sits in front of each URL. Nothing is scraped
1001
+ * and nothing is charged; invalid URLs are skipped.
1002
+ */
1003
+ async checkProtection(urls, options = {}) {
1004
+ return list(
1005
+ await this.send(new Request("POST", "/filter/check", void 0, [...urls]), options)
1006
+ );
1007
+ }
966
1008
  /** Countries, regions and cities a proxy type can exit from. */
967
1009
  async proxyLocations(options = {}) {
968
1010
  const params = options.proxyType ? { proxy_type: options.proxyType } : void 0;
@@ -1034,7 +1076,7 @@ var DataFuel = class {
1034
1076
  function envApiKey() {
1035
1077
  return typeof process !== "undefined" ? process.env?.DATAFUEL_API_KEY : void 0;
1036
1078
  }
1037
- async function parse(response) {
1079
+ async function parse(response, degradedOk = false) {
1038
1080
  const text = await response.text();
1039
1081
  let body;
1040
1082
  if (text.length > 0) {
@@ -1044,6 +1086,7 @@ async function parse(response) {
1044
1086
  body = text;
1045
1087
  }
1046
1088
  }
1089
+ if (degradedOk && response.status === 503 && isHealthReport(body)) return body;
1047
1090
  if (!response.ok) {
1048
1091
  const header = response.headers.get("Retry-After");
1049
1092
  const retryAfter = header !== null && !Number.isNaN(Number(header)) ? Number(header) : 0;
@@ -1051,6 +1094,9 @@ async function parse(response) {
1051
1094
  }
1052
1095
  return body;
1053
1096
  }
1097
+ function isHealthReport(body) {
1098
+ return body !== null && typeof body === "object" && "status" in body;
1099
+ }
1054
1100
  function isAbort(error) {
1055
1101
  return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
1056
1102
  }
@@ -1107,6 +1153,7 @@ function intField(body, name) {
1107
1153
  }
1108
1154
  export {
1109
1155
  APIError,
1156
+ AlreadyExists,
1110
1157
  Blocked,
1111
1158
  Capabilities,
1112
1159
  CrawlPage,