@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/README.md +33 -7
- package/dist/index.cjs +64 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -7
- package/dist/index.d.ts +62 -7
- package/dist/index.js +63 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +44 -2
- package/src/core.ts +29 -20
- package/src/errors.ts +12 -1
- package/src/index.ts +4 -0
- package/src/models.ts +33 -3
package/src/core.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
export const DEFAULT_BASE_URL = "https://scraping-api.datafuel.ai/api/v1";
|
|
22
22
|
|
|
23
23
|
/** Kept in step with package.json by a test; see test/hardening.test.ts. */
|
|
24
|
-
export const VERSION = "0.
|
|
24
|
+
export const VERSION = "0.4.0";
|
|
25
25
|
|
|
26
26
|
/** How long a synchronous call waits for a result before giving up. */
|
|
27
27
|
export const DEFAULT_TIMEOUT_MS = 180_000;
|
|
@@ -37,9 +37,11 @@ export class Request {
|
|
|
37
37
|
readonly method: string,
|
|
38
38
|
readonly path: string,
|
|
39
39
|
readonly params?: Record<string, string>,
|
|
40
|
-
readonly body?: Record<string, unknown
|
|
40
|
+
readonly body?: Record<string, unknown> | unknown[],
|
|
41
41
|
readonly idempotencyKey?: string,
|
|
42
42
|
readonly auth: boolean = true,
|
|
43
|
+
/** A 503 that carries a health report is an answer, not an error. */
|
|
44
|
+
readonly degradedOk: boolean = false,
|
|
43
45
|
) {}
|
|
44
46
|
|
|
45
47
|
/** GETs are safe by nature, writes because they carry an idempotency key. */
|
|
@@ -109,23 +111,10 @@ function aiAttributes(ai: AI): Record<string, unknown> {
|
|
|
109
111
|
return out;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
/**
|
|
113
|
-
* Catch a half-filled AI key before it costs a request. The API does not check
|
|
114
|
-
* these, so a missing one would be spent on a scrape whose AI step then fails.
|
|
115
|
-
*/
|
|
114
|
+
/** The API answers 400 INVALID_ATTRIBUTES without a provider; say so before sending. */
|
|
116
115
|
export function validateAI(ai: AI): void {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
model: ai.model !== undefined,
|
|
120
|
-
apiKey: ai.apiKey !== undefined,
|
|
121
|
-
};
|
|
122
|
-
const present = Object.values(given).filter(Boolean).length;
|
|
123
|
-
if (present > 0 && present < 3) {
|
|
124
|
-
const missing = Object.entries(given)
|
|
125
|
-
.filter(([, ok]) => !ok)
|
|
126
|
-
.map(([name]) => name)
|
|
127
|
-
.join(", ");
|
|
128
|
-
throw new TypeError(`ai needs provider, model and apiKey together; missing: ${missing}`);
|
|
116
|
+
if (!ai.provider) {
|
|
117
|
+
throw new TypeError("ai needs a provider; aiProviders() lists the ones the API supports");
|
|
129
118
|
}
|
|
130
119
|
}
|
|
131
120
|
|
|
@@ -141,7 +130,12 @@ export function scrapeAttributes(options: ScrapeOptions = {}): Record<string, un
|
|
|
141
130
|
if (options.waitFor) attrs.wait_for_selector = options.waitFor;
|
|
142
131
|
if (options.waitForTimeoutMs) attrs.wait_for_selector_timeout_ms = options.waitForTimeoutMs;
|
|
143
132
|
if (options.jsInstructions) attrs.js_instructions = options.jsInstructions;
|
|
144
|
-
if (options.blockResource)
|
|
133
|
+
if (options.blockResource?.length) {
|
|
134
|
+
attrs.block_resource =
|
|
135
|
+
typeof options.blockResource === "string"
|
|
136
|
+
? options.blockResource
|
|
137
|
+
: [...options.blockResource];
|
|
138
|
+
}
|
|
145
139
|
if (options.mainContentOnly) attrs.main_content_only = true;
|
|
146
140
|
if (options.includeImages !== undefined) attrs.include_images = options.includeImages;
|
|
147
141
|
if (options.extract) attrs.extract_selector = selector(options.extract);
|
|
@@ -386,7 +380,10 @@ export interface SearchOptions {
|
|
|
386
380
|
ludocid?: string;
|
|
387
381
|
lsig?: string;
|
|
388
382
|
ibp?: string;
|
|
389
|
-
/**
|
|
383
|
+
/**
|
|
384
|
+
* Accepted but not used yet: searches leave through DataFuel's own pool.
|
|
385
|
+
* Target a market with `country` and `language`.
|
|
386
|
+
*/
|
|
390
387
|
proxyCountry?: string;
|
|
391
388
|
/** Default json. */
|
|
392
389
|
format?: "json" | "html" | "markdown";
|
|
@@ -465,6 +462,18 @@ export function crawlResultsRequest(crawlId: string, cursor?: string, limit?: nu
|
|
|
465
462
|
);
|
|
466
463
|
}
|
|
467
464
|
|
|
465
|
+
export function healthRequest(deep: boolean): Request {
|
|
466
|
+
return new Request(
|
|
467
|
+
"GET",
|
|
468
|
+
"/healthz",
|
|
469
|
+
deep ? { deep: "1" } : undefined,
|
|
470
|
+
undefined,
|
|
471
|
+
undefined,
|
|
472
|
+
false,
|
|
473
|
+
true,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
468
477
|
/** A Date as its UTC day, which is what the API's date filters take. */
|
|
469
478
|
export function day(value: string | Date): string {
|
|
470
479
|
return typeof value === "string" ? value : value.toISOString().slice(0, 10);
|
package/src/errors.ts
CHANGED
|
@@ -45,6 +45,10 @@ export type ErrorCode =
|
|
|
45
45
|
| "JOB_NOT_FOUND"
|
|
46
46
|
| "CRAWL_NOT_FOUND"
|
|
47
47
|
| "JOB_NOT_CANCELLABLE"
|
|
48
|
+
| "TASK_ALREADY_EXISTS"
|
|
49
|
+
| "JOB_ALREADY_EXISTS"
|
|
50
|
+
| "URL_LENGTH_CANNOT_BE_ZERO"
|
|
51
|
+
| "ANALYTICS_FETCH_FAILED"
|
|
48
52
|
| "INVALID_CRAWL_PATTERN"
|
|
49
53
|
| "CRAWL_UNSUPPORTED_OPTION"
|
|
50
54
|
| "INVALID_CURSOR"
|
|
@@ -92,6 +96,12 @@ export class InvalidAttributes extends APIError {}
|
|
|
92
96
|
export class IdempotencyKeyReused extends APIError {}
|
|
93
97
|
/** 409 JOB_NOT_CANCELLABLE: the job or crawl already finished. */
|
|
94
98
|
export class JobNotCancellable extends APIError {}
|
|
99
|
+
/**
|
|
100
|
+
* 409 TASK_ALREADY_EXISTS / JOB_ALREADY_EXISTS: the create collided with an
|
|
101
|
+
* existing task or job and is not an idempotent replay. Nothing was charged;
|
|
102
|
+
* send the request again.
|
|
103
|
+
*/
|
|
104
|
+
export class AlreadyExists extends APIError {}
|
|
95
105
|
/** 503: an operator switched something off, or a dependency is down. The message carries the reason. */
|
|
96
106
|
export class Unavailable extends APIError {}
|
|
97
107
|
/** 503 MODULE_UNAVAILABLE: this task type is switched off. Nothing was charged. */
|
|
@@ -152,6 +162,8 @@ const BY_CODE: Record<string, new (s: number, c: string, m: string, r?: number)
|
|
|
152
162
|
INVALID_API_KEY: Unauthorized,
|
|
153
163
|
FORBIDDEN: Forbidden,
|
|
154
164
|
JOB_NOT_CANCELLABLE: JobNotCancellable,
|
|
165
|
+
TASK_ALREADY_EXISTS: AlreadyExists,
|
|
166
|
+
JOB_ALREADY_EXISTS: AlreadyExists,
|
|
155
167
|
};
|
|
156
168
|
|
|
157
169
|
const BY_STATUS: Record<number, new (s: number, c: string, m: string, r?: number) => APIError> = {
|
|
@@ -159,7 +171,6 @@ const BY_STATUS: Record<number, new (s: number, c: string, m: string, r?: number
|
|
|
159
171
|
402: InsufficientCredits,
|
|
160
172
|
403: Forbidden,
|
|
161
173
|
404: NotFound,
|
|
162
|
-
409: JobNotCancellable,
|
|
163
174
|
422: IdempotencyKeyReused,
|
|
164
175
|
429: RateLimited,
|
|
165
176
|
503: Unavailable,
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export type { ClientOptions } from "./client.js";
|
|
|
32
32
|
export { DEFAULT_BASE_URL, VERSION } from "./core.js";
|
|
33
33
|
export type { AskOptions, CrawlOptions, MapOptions, SearchOptions } from "./core.js";
|
|
34
34
|
export {
|
|
35
|
+
AlreadyExists,
|
|
35
36
|
APIError,
|
|
36
37
|
Blocked,
|
|
37
38
|
DataFuelError,
|
|
@@ -55,6 +56,7 @@ export type { ErrorCode } from "./errors.js";
|
|
|
55
56
|
export { Capabilities, CrawlPage, isDone, Result } from "./models.js";
|
|
56
57
|
export type {
|
|
57
58
|
AI,
|
|
59
|
+
AIProvider,
|
|
58
60
|
Analytics,
|
|
59
61
|
AnalyticsCounts,
|
|
60
62
|
AnalyticsOptions,
|
|
@@ -67,6 +69,7 @@ export type {
|
|
|
67
69
|
CrawlStatus,
|
|
68
70
|
Engine,
|
|
69
71
|
Format,
|
|
72
|
+
Health,
|
|
70
73
|
JobResults,
|
|
71
74
|
JobsPage,
|
|
72
75
|
JobStatus,
|
|
@@ -78,6 +81,7 @@ export type {
|
|
|
78
81
|
ListTasksOptions,
|
|
79
82
|
Payload,
|
|
80
83
|
Profile,
|
|
84
|
+
ProtectionCheck,
|
|
81
85
|
Proxy,
|
|
82
86
|
ProxyCountry,
|
|
83
87
|
ProxyLocation,
|
package/src/models.ts
CHANGED
|
@@ -60,7 +60,9 @@ export interface AI {
|
|
|
60
60
|
prompt?: string;
|
|
61
61
|
/** Example JSON object the output must follow. */
|
|
62
62
|
format?: unknown;
|
|
63
|
-
|
|
63
|
+
/** Required. One of the providers `df.aiProviders()` lists; the API answers 400 INVALID_ATTRIBUTES without it. */
|
|
64
|
+
provider?: string;
|
|
65
|
+
/** One of the models `df.aiProviders()` lists for `provider`. Omit for the provider's default. */
|
|
64
66
|
model?: string;
|
|
65
67
|
apiKey?: string;
|
|
66
68
|
}
|
|
@@ -75,9 +77,15 @@ export interface ScrapeOptions {
|
|
|
75
77
|
jsRendering?: boolean;
|
|
76
78
|
waitFor?: string;
|
|
77
79
|
waitForTimeoutMs?: number;
|
|
78
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Browser actions run after load: an array of single-action objects, run in
|
|
82
|
+
* order, e.g. `[{ click: "#more" }, { wait_ms: 1000 }, { click: "#more" }]`.
|
|
83
|
+
* An object keyed by action is still accepted, but its order is not
|
|
84
|
+
* guaranteed and an action cannot repeat. `df.jsInstructions()` lists them.
|
|
85
|
+
*/
|
|
79
86
|
jsInstructions?: unknown;
|
|
80
|
-
|
|
87
|
+
/** Resource types the browser must not load: one, e.g. `"Image"`, or several. */
|
|
88
|
+
blockResource?: string | string[];
|
|
81
89
|
/** Markdown only: always render just the `<main>` / `<article>` container. */
|
|
82
90
|
mainContentOnly?: boolean;
|
|
83
91
|
/** Markdown only: `false` drops images and saves tokens. */
|
|
@@ -557,6 +565,28 @@ export interface JsInstruction {
|
|
|
557
565
|
example: unknown;
|
|
558
566
|
}
|
|
559
567
|
|
|
568
|
+
/** An LLM provider `ai.provider` accepts, with the models `ai.model` accepts for it. */
|
|
569
|
+
export interface AIProvider {
|
|
570
|
+
name: string;
|
|
571
|
+
models: string[];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** The anti-bot protection in front of one host and path, from `checkProtection`. */
|
|
575
|
+
export interface ProtectionCheck {
|
|
576
|
+
host: string;
|
|
577
|
+
path: string;
|
|
578
|
+
/** cloudflare, cloudflare_5sec, akamai, imperva, perimeterx or unprotected. */
|
|
579
|
+
protection_type: string;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** The state of the API. `checks` is filled by a deep check only, keyed by dependency. */
|
|
583
|
+
export interface Health {
|
|
584
|
+
status: "ok" | "degraded" | (string & {});
|
|
585
|
+
checks?: Record<string, { status: "ok" | "fail" | (string & {}); latency_ms: number }>;
|
|
586
|
+
/** Whether the API can serve requests. */
|
|
587
|
+
ok: boolean;
|
|
588
|
+
}
|
|
589
|
+
|
|
560
590
|
/** A named proxy location: a city, or an ASN. */
|
|
561
591
|
export interface ProxyLocation {
|
|
562
592
|
code: string;
|