@datafuel/sdk 0.1.0 → 0.2.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 +46 -10
- package/dist/index.cjs +274 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +300 -7
- package/dist/index.d.ts +300 -7
- package/dist/index.js +272 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +203 -16
- package/src/core.ts +172 -4
- package/src/errors.ts +47 -2
- package/src/index.ts +29 -1
- package/src/models.ts +228 -1
package/README.md
CHANGED
|
@@ -16,13 +16,16 @@ const markdown = await df.markdown("https://example.com");
|
|
|
16
16
|
|
|
17
17
|
## Pick the call
|
|
18
18
|
|
|
19
|
-
| You have | Call
|
|
20
|
-
| ---------------------------- |
|
|
21
|
-
| One URL | `scrape` / `markdown`
|
|
22
|
-
| A site, need its URL list | `map`
|
|
23
|
-
| A start URL, need many pages | `crawl`, or `startCrawl` + `waitCrawl` + `crawlPages`
|
|
24
|
-
| A list of known URLs | `runJob`, or `createJob` + `waitJob` + `jobResults`
|
|
25
|
-
| A question for an AI engine | `ask`
|
|
19
|
+
| You have | Call | Waits? |
|
|
20
|
+
| ---------------------------- | ------------------------------------------------------- | ------------- |
|
|
21
|
+
| One URL | `scrape` / `markdown` | yes |
|
|
22
|
+
| A site, need its URL list | `map` | yes |
|
|
23
|
+
| A start URL, need many pages | `crawl`, or `startCrawl` + `waitCrawl` + `crawlPages` | `crawl` does |
|
|
24
|
+
| A list of known URLs | `runJob`, or `createJob` + `waitJob` + `jobResults` | `runJob` does |
|
|
25
|
+
| A question for an AI engine | `ask` | yes |
|
|
26
|
+
| A Google search | `search` | yes |
|
|
27
|
+
| Many prompts or searches | `runAskJob` / `runSearchJob` | yes |
|
|
28
|
+
| Earlier jobs, tasks, usage | `listJobs` / `listTasks` / `analytics` / `transactions` | yes |
|
|
26
29
|
|
|
27
30
|
Start with plain `scrape`. Turn on `jsRendering` only when the page comes back empty: it is slower and costs five times the credits on a Basic proxy. `map` a section before you `crawl` it, it costs one credit and tells you how big it is.
|
|
28
31
|
|
|
@@ -60,7 +63,9 @@ try {
|
|
|
60
63
|
|
|
61
64
|
`res.text` returns html or markdown, `res.data` structured output, `res.image` screenshot bytes.
|
|
62
65
|
|
|
63
|
-
Page options, shared by `scrape`, jobs and crawls: `format` (`html`, `markdown`, `json`, `png`, `jpeg`), `jsRendering`, `waitFor`, `waitForTimeoutMs`, `jsInstructions
|
|
66
|
+
Page options, shared by `scrape`, jobs and crawls: `format` (`html`, `markdown`, `json`, `png`, `jpeg`), `jsRendering`, `waitFor`, `waitForTimeoutMs`, `jsInstructions` (an object keyed by action, e.g. `{ click: "#more" }`; `df.jsInstructions()` lists the actions), `blockResource`, `mainContentOnly`, `includeImages`, `extract`, `extractRegex`, `template`, `method`, `body`, `contentType`, `headers`, `headerOrder`, `cookies`, `userAgent`, `userAgentType`, `ai`.
|
|
67
|
+
|
|
68
|
+
`proxy` takes `type`, `country`, `city`, `state`, `asn`, and a sticky `sessionId` with `ttl` (seconds) on `scrape`, `map`, URL jobs and crawls. `df.proxyLocations()` and `df.proxyAsns(country)` list what a proxy type can exit from.
|
|
64
69
|
|
|
65
70
|
## AI after the scrape
|
|
66
71
|
|
|
@@ -108,6 +113,8 @@ Check `stop_reason`: `insufficient_credits` means the crawl ended early. If the
|
|
|
108
113
|
|
|
109
114
|
Unset limits use the API defaults: 100 pages, depth 3, 5 pages in flight.
|
|
110
115
|
|
|
116
|
+
`df.cancelCrawl(id)` stops a crawl: queued pages are refunded, pages in flight finish and bill. `df.cancelJob(id)` does the same for a job. Both throw `JobNotCancellable` once the work has finished.
|
|
117
|
+
|
|
111
118
|
## Jobs
|
|
112
119
|
|
|
113
120
|
```ts
|
|
@@ -118,7 +125,23 @@ for (const task of results.tasks) {
|
|
|
118
125
|
}
|
|
119
126
|
```
|
|
120
127
|
|
|
121
|
-
`sequential: true` runs the URLs one after the other; the default runs them concurrently, bounded by your account's concurrency limit. `runAskJob(prompts, { engine })`
|
|
128
|
+
`sequential: true` runs the URLs one after the other; the default runs them concurrently, bounded by your account's concurrency limit. `runAskJob(prompts, { engine })` and `runSearchJob(queries)` do the same for a batch of prompts or Google searches. A job needs at least two targets.
|
|
129
|
+
|
|
130
|
+
## Ask and search
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const answer = await df.ask("best CRM for a 10-person team", {
|
|
134
|
+
engine: "perplexity", // openai, gemini, google_ai_mode, perplexity, copilot
|
|
135
|
+
websearch: true,
|
|
136
|
+
country: "us",
|
|
137
|
+
});
|
|
138
|
+
console.log(answer.text);
|
|
139
|
+
|
|
140
|
+
const serp = await df.search("best crm", { country: "us", language: "en", page: 1 });
|
|
141
|
+
console.log(serp.data); // parsed results page; format "html" or "markdown" for the raw page
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`search` also takes `location` (or `uule`, or `lat`/`lon` with `radius`), `googleDomain`, `tbs`, `safe`, `cr`, `lr`, `nfpr`, `filter` and `proxyCountry`.
|
|
122
145
|
|
|
123
146
|
## Map
|
|
124
147
|
|
|
@@ -129,6 +152,19 @@ for (const link of site.links) console.log(link.url);
|
|
|
129
152
|
|
|
130
153
|
An empty `site.links` comes with a `site.reason`. `no_links_on_page` usually means the navigation is rendered client-side.
|
|
131
154
|
|
|
155
|
+
## Usage and history
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const usage = await df.analytics({ startDate: "2026-09-01", endDate: "2026-09-30" });
|
|
159
|
+
console.log(usage.summary.success_rate, usage.summary.avg_credits_per_request);
|
|
160
|
+
|
|
161
|
+
const { jobs, nextCursor } = await df.listJobs({ type: "crawl", limit: 20 });
|
|
162
|
+
const failed = await df.listTasks({ jobId: jobs[0]!.id, status: "failed" });
|
|
163
|
+
const history = await df.transactions({ operation: "refund", limit: 50 });
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`listJobs` and `listTasks` run newest first; pass `nextCursor` back as `cursor` until it is absent. Task items carry no result: call `getTask(id)` for it. Dates are `YYYY-MM-DD` in UTC (a `Date` is sent as its UTC day) and `endDate` is inclusive. `transactions` pages with `page` and `limit`, and its `sums` total each operation over the whole range. In `analytics`, `status_code` 0 means the target never answered (timeout, DNS).
|
|
167
|
+
|
|
132
168
|
## Errors
|
|
133
169
|
|
|
134
170
|
```ts
|
|
@@ -148,7 +184,7 @@ try {
|
|
|
148
184
|
```
|
|
149
185
|
|
|
150
186
|
- `NoApiKey`: no key was passed and `DATAFUEL_API_KEY` is empty. Thrown before any request.
|
|
151
|
-
- `APIError`: the API refused the request. Subclasses: `Unauthorized`, `InsufficientCredits`, `RateLimited`, `NotFound`, `InvalidAttributes`, `IdempotencyKeyReused`.
|
|
187
|
+
- `APIError`: the API refused the request. `.code` holds the API's error code (typed as `ErrorCode`). Subclasses: `Unauthorized`, `Forbidden`, `InsufficientCredits`, `RateLimited`, `NotFound`, `InvalidAttributes`, `IdempotencyKeyReused`, `JobNotCancellable`.
|
|
152
188
|
- `ModuleUnavailable`, `EngineUnavailable`: an operator switched a task type or LLM engine off, e.g. during a provider outage. The reason is in the message, nothing is charged, and the SDK does not retry. `df.capabilities()` lists what is on.
|
|
153
189
|
- `TaskFailed`, and `Blocked` when the target refused: the API accepted the task but the page could not be scraped. The error carries `.result`, so the envelope is still readable. Failed tasks are refunded.
|
|
154
190
|
- `WaitTimeout`: a wait ran out of time. `.id` picks the work back up.
|
package/dist/index.cjs
CHANGED
|
@@ -28,9 +28,11 @@ __export(index_exports, {
|
|
|
28
28
|
DataFuel: () => DataFuel,
|
|
29
29
|
DataFuelError: () => DataFuelError,
|
|
30
30
|
EngineUnavailable: () => EngineUnavailable,
|
|
31
|
+
Forbidden: () => Forbidden,
|
|
31
32
|
IdempotencyKeyReused: () => IdempotencyKeyReused,
|
|
32
33
|
InsufficientCredits: () => InsufficientCredits,
|
|
33
34
|
InvalidAttributes: () => InvalidAttributes,
|
|
35
|
+
JobNotCancellable: () => JobNotCancellable,
|
|
34
36
|
ModuleUnavailable: () => ModuleUnavailable,
|
|
35
37
|
NoApiKey: () => NoApiKey,
|
|
36
38
|
NotFound: () => NotFound,
|
|
@@ -71,6 +73,8 @@ var APIError = class extends DataFuelError {
|
|
|
71
73
|
};
|
|
72
74
|
var Unauthorized = class extends APIError {
|
|
73
75
|
};
|
|
76
|
+
var Forbidden = class extends APIError {
|
|
77
|
+
};
|
|
74
78
|
var NotFound = class extends APIError {
|
|
75
79
|
};
|
|
76
80
|
var RateLimited = class extends APIError {
|
|
@@ -81,6 +85,8 @@ var InvalidAttributes = class extends APIError {
|
|
|
81
85
|
};
|
|
82
86
|
var IdempotencyKeyReused = class extends APIError {
|
|
83
87
|
};
|
|
88
|
+
var JobNotCancellable = class extends APIError {
|
|
89
|
+
};
|
|
84
90
|
var Unavailable = class extends APIError {
|
|
85
91
|
};
|
|
86
92
|
var ModuleUnavailable = class extends Unavailable {
|
|
@@ -116,12 +122,16 @@ var BY_CODE = {
|
|
|
116
122
|
INSUFFICIENT_CREDITS: InsufficientCredits,
|
|
117
123
|
INVALID_ATTRIBUTES: InvalidAttributes,
|
|
118
124
|
IDEMPOTENCY_KEY_REUSED: IdempotencyKeyReused,
|
|
119
|
-
INVALID_API_KEY: Unauthorized
|
|
125
|
+
INVALID_API_KEY: Unauthorized,
|
|
126
|
+
FORBIDDEN: Forbidden,
|
|
127
|
+
JOB_NOT_CANCELLABLE: JobNotCancellable
|
|
120
128
|
};
|
|
121
129
|
var BY_STATUS = {
|
|
122
130
|
401: Unauthorized,
|
|
123
131
|
402: InsufficientCredits,
|
|
132
|
+
403: Forbidden,
|
|
124
133
|
404: NotFound,
|
|
134
|
+
409: JobNotCancellable,
|
|
125
135
|
422: IdempotencyKeyReused,
|
|
126
136
|
429: RateLimited,
|
|
127
137
|
503: Unavailable
|
|
@@ -142,24 +152,26 @@ function apiError(status, body, retryAfter = 0) {
|
|
|
142
152
|
|
|
143
153
|
// src/core.ts
|
|
144
154
|
var DEFAULT_BASE_URL = "https://scraping-api.datafuel.ai/api/v1";
|
|
145
|
-
var VERSION = "0.
|
|
155
|
+
var VERSION = "0.2.0";
|
|
146
156
|
var DEFAULT_TIMEOUT_MS = 18e4;
|
|
147
157
|
var STILL_PROCESSING_DELAY_MS = 2e3;
|
|
148
158
|
var STILL_PROCESSING = "TASK_STILL_PROCESSING";
|
|
149
159
|
var MAX_IDEMPOTENCY_KEY = 255;
|
|
150
160
|
var Request = class {
|
|
151
|
-
constructor(method, path, params, body, idempotencyKey) {
|
|
161
|
+
constructor(method, path, params, body, idempotencyKey, auth = true) {
|
|
152
162
|
this.method = method;
|
|
153
163
|
this.path = path;
|
|
154
164
|
this.params = params;
|
|
155
165
|
this.body = body;
|
|
156
166
|
this.idempotencyKey = idempotencyKey;
|
|
167
|
+
this.auth = auth;
|
|
157
168
|
}
|
|
158
169
|
method;
|
|
159
170
|
path;
|
|
160
171
|
params;
|
|
161
172
|
body;
|
|
162
173
|
idempotencyKey;
|
|
174
|
+
auth;
|
|
163
175
|
/** GETs are safe by nature, writes because they carry an idempotency key. */
|
|
164
176
|
get retryable() {
|
|
165
177
|
return this.method === "GET" || this.idempotencyKey !== void 0;
|
|
@@ -186,10 +198,10 @@ function newIdempotencyKey() {
|
|
|
186
198
|
}
|
|
187
199
|
function headers(apiKey, userAgent, request) {
|
|
188
200
|
const out = {
|
|
189
|
-
"X-API-Key": apiKey,
|
|
190
201
|
Accept: "application/json",
|
|
191
202
|
"User-Agent": userAgent
|
|
192
203
|
};
|
|
204
|
+
if (apiKey) out["X-API-Key"] = apiKey;
|
|
193
205
|
if (request.body !== void 0) out["Content-Type"] = "application/json";
|
|
194
206
|
if (request.idempotencyKey !== void 0) out["Idempotency-Key"] = request.idempotencyKey;
|
|
195
207
|
return out;
|
|
@@ -285,7 +297,7 @@ function buildScrape(url, options, proxy, idempotencyKey) {
|
|
|
285
297
|
);
|
|
286
298
|
}
|
|
287
299
|
function buildJob(urls, options, proxy, sequential, idempotencyKey) {
|
|
288
|
-
const attrs = { ...scrapeAttributes(options), urls: [...urls] };
|
|
300
|
+
const attrs = { ...scrapeAttributes(options), urls: [...urls], ...proxySession(proxy) };
|
|
289
301
|
return new Request(
|
|
290
302
|
"POST",
|
|
291
303
|
"/job",
|
|
@@ -299,6 +311,7 @@ function askAttributes(promptField, prompt, options) {
|
|
|
299
311
|
if (options.websearch) attrs.websearch = true;
|
|
300
312
|
if (options.followUp) attrs.follow_up_prompt = options.followUp;
|
|
301
313
|
if (options.country) attrs.proxy_country = options.country;
|
|
314
|
+
if (options.location) attrs.location = options.location;
|
|
302
315
|
if (options.format) attrs.result_format = options.format;
|
|
303
316
|
return attrs;
|
|
304
317
|
}
|
|
@@ -341,6 +354,7 @@ function buildCrawl(url, options, proxy, idempotencyKey) {
|
|
|
341
354
|
if (options.excludePaths?.length) attrs.exclude_paths = [...options.excludePaths];
|
|
342
355
|
if (options.includeSubdomains) attrs.include_subdomains = true;
|
|
343
356
|
if (options.allowBackwardLinks) attrs.allow_backward_links = true;
|
|
357
|
+
Object.assign(attrs, proxySession(proxy));
|
|
344
358
|
return new Request(
|
|
345
359
|
"POST",
|
|
346
360
|
"/crawl",
|
|
@@ -349,6 +363,52 @@ function buildCrawl(url, options, proxy, idempotencyKey) {
|
|
|
349
363
|
idempotencyKey
|
|
350
364
|
);
|
|
351
365
|
}
|
|
366
|
+
function searchAttributes(queryField, query2, options) {
|
|
367
|
+
const attrs = { [queryField]: query2 };
|
|
368
|
+
const strings = [
|
|
369
|
+
["country", "country"],
|
|
370
|
+
["language", "language"],
|
|
371
|
+
["location", "location"],
|
|
372
|
+
["googleDomain", "google_domain"],
|
|
373
|
+
["uule", "uule"],
|
|
374
|
+
["cr", "cr"],
|
|
375
|
+
["lr", "lr"],
|
|
376
|
+
["tbs", "tbs"],
|
|
377
|
+
["safe", "safe"],
|
|
378
|
+
["uds", "uds"],
|
|
379
|
+
["kgmid", "kgmid"],
|
|
380
|
+
["si", "si"],
|
|
381
|
+
["ludocid", "ludocid"],
|
|
382
|
+
["lsig", "lsig"],
|
|
383
|
+
["ibp", "ibp"],
|
|
384
|
+
["proxyCountry", "proxy_country"],
|
|
385
|
+
["format", "result_format"]
|
|
386
|
+
];
|
|
387
|
+
for (const [option, attribute] of strings) {
|
|
388
|
+
if (options[option]) attrs[attribute] = options[option];
|
|
389
|
+
}
|
|
390
|
+
if (options.page) attrs.page = options.page;
|
|
391
|
+
if (options.radius) attrs.radius = options.radius;
|
|
392
|
+
if (options.lat !== void 0) attrs.lat = options.lat;
|
|
393
|
+
if (options.lon !== void 0) attrs.lon = options.lon;
|
|
394
|
+
if (options.nfpr !== void 0) attrs.nfpr = options.nfpr;
|
|
395
|
+
if (options.filter !== void 0) attrs.filter = options.filter;
|
|
396
|
+
return attrs;
|
|
397
|
+
}
|
|
398
|
+
function buildSearch(query2, options, idempotencyKey) {
|
|
399
|
+
const attrs = searchAttributes("query", query2, options);
|
|
400
|
+
return new Request("POST", "/task", void 0, envelope("serp", attrs), idempotencyKey);
|
|
401
|
+
}
|
|
402
|
+
function buildSearchJob(queries, options, sequential, idempotencyKey) {
|
|
403
|
+
const attrs = searchAttributes("queries", [...queries], options);
|
|
404
|
+
return new Request(
|
|
405
|
+
"POST",
|
|
406
|
+
"/job",
|
|
407
|
+
void 0,
|
|
408
|
+
envelope("serp", attrs, { multithreaded: !sequential }),
|
|
409
|
+
idempotencyKey
|
|
410
|
+
);
|
|
411
|
+
}
|
|
352
412
|
function crawlResultsRequest(crawlId, cursor, limit) {
|
|
353
413
|
const params = {};
|
|
354
414
|
if (cursor) params.cursor = cursor;
|
|
@@ -359,6 +419,54 @@ function crawlResultsRequest(crawlId, cursor, limit) {
|
|
|
359
419
|
Object.keys(params).length > 0 ? params : void 0
|
|
360
420
|
);
|
|
361
421
|
}
|
|
422
|
+
function day(value) {
|
|
423
|
+
return typeof value === "string" ? value : value.toISOString().slice(0, 10);
|
|
424
|
+
}
|
|
425
|
+
function query(entries) {
|
|
426
|
+
const params = {};
|
|
427
|
+
for (const [name, value] of entries) {
|
|
428
|
+
if (value === void 0 || value === "") continue;
|
|
429
|
+
params[name] = value instanceof Date ? day(value) : String(value);
|
|
430
|
+
}
|
|
431
|
+
return Object.keys(params).length > 0 ? params : void 0;
|
|
432
|
+
}
|
|
433
|
+
function listRequest(path, options, jobId) {
|
|
434
|
+
const params = query([
|
|
435
|
+
["status", options.status],
|
|
436
|
+
["type", options.type],
|
|
437
|
+
["job_id", jobId],
|
|
438
|
+
["start_date", options.startDate],
|
|
439
|
+
["end_date", options.endDate],
|
|
440
|
+
["limit", options.limit],
|
|
441
|
+
["cursor", options.cursor]
|
|
442
|
+
]);
|
|
443
|
+
return new Request("GET", path, params);
|
|
444
|
+
}
|
|
445
|
+
function listJobsRequest(options) {
|
|
446
|
+
return listRequest("/job", options);
|
|
447
|
+
}
|
|
448
|
+
function listTasksRequest(options) {
|
|
449
|
+
return listRequest("/task", options, options.jobId);
|
|
450
|
+
}
|
|
451
|
+
function transactionsRequest(options) {
|
|
452
|
+
const params = query([
|
|
453
|
+
["operation", options.operation],
|
|
454
|
+
["start_date", options.startDate],
|
|
455
|
+
["end_date", options.endDate],
|
|
456
|
+
["page", options.page],
|
|
457
|
+
["limit", options.limit]
|
|
458
|
+
]);
|
|
459
|
+
return new Request("GET", "/users/@me/transactions", params);
|
|
460
|
+
}
|
|
461
|
+
function analyticsRequest(options) {
|
|
462
|
+
const params = query([
|
|
463
|
+
["start_date", options.startDate],
|
|
464
|
+
["end_date", options.endDate],
|
|
465
|
+
["interval", options.interval],
|
|
466
|
+
["module", options.module]
|
|
467
|
+
]);
|
|
468
|
+
return new Request("GET", "/task/analytics/dashboard", params);
|
|
469
|
+
}
|
|
362
470
|
function stillProcessing(body) {
|
|
363
471
|
return body !== null && typeof body === "object" && body.code === STILL_PROCESSING;
|
|
364
472
|
}
|
|
@@ -522,7 +630,7 @@ var DataFuel = class {
|
|
|
522
630
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
523
631
|
}
|
|
524
632
|
async send(request, opts = {}) {
|
|
525
|
-
if (!this.apiKey) {
|
|
633
|
+
if (!this.apiKey && request.auth) {
|
|
526
634
|
throw new NoApiKey("no API key: pass one to the client or set DATAFUEL_API_KEY");
|
|
527
635
|
}
|
|
528
636
|
const url = new URL(this.baseUrl + request.path);
|
|
@@ -626,6 +734,11 @@ var DataFuel = class {
|
|
|
626
734
|
const request = buildAsk(prompt, options, key(options.idempotencyKey));
|
|
627
735
|
return this.runTask(request, options);
|
|
628
736
|
}
|
|
737
|
+
/** Run a Google search and return the results page, parsed to JSON by default. */
|
|
738
|
+
async search(query2, options = {}) {
|
|
739
|
+
const request = buildSearch(query2, options, key(options.idempotencyKey));
|
|
740
|
+
return this.runTask(request, options);
|
|
741
|
+
}
|
|
629
742
|
/**
|
|
630
743
|
* List the URLs of a site without scraping them.
|
|
631
744
|
*
|
|
@@ -650,6 +763,18 @@ var DataFuel = class {
|
|
|
650
763
|
const request = buildCrawl(url, options, options.proxy, key(options.idempotencyKey));
|
|
651
764
|
return strField(await this.send(request, options), "job_id");
|
|
652
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Stop a crawl. Queued pages are refunded, pages in flight finish and bill.
|
|
768
|
+
* Throws {@link JobNotCancellable} when it already finished.
|
|
769
|
+
*/
|
|
770
|
+
async cancelCrawl(crawlId, options = {}) {
|
|
771
|
+
return cancelResult(
|
|
772
|
+
await this.send(
|
|
773
|
+
new Request("POST", `/crawl/${pathSegment(crawlId)}/cancel`),
|
|
774
|
+
options
|
|
775
|
+
)
|
|
776
|
+
);
|
|
777
|
+
}
|
|
653
778
|
/** Return the progress of a crawl. */
|
|
654
779
|
async getCrawl(crawlId, options = {}) {
|
|
655
780
|
const body = record(
|
|
@@ -760,20 +885,30 @@ var DataFuel = class {
|
|
|
760
885
|
);
|
|
761
886
|
return strField(await this.send(request, options), "id");
|
|
762
887
|
}
|
|
888
|
+
/** Queue a batch of Google searches and return the job id. */
|
|
889
|
+
async createSearchJob(queries, options = {}) {
|
|
890
|
+
const request = buildSearchJob(
|
|
891
|
+
queries,
|
|
892
|
+
options,
|
|
893
|
+
options.sequential ?? false,
|
|
894
|
+
key(options.idempotencyKey)
|
|
895
|
+
);
|
|
896
|
+
return strField(await this.send(request, options), "id");
|
|
897
|
+
}
|
|
763
898
|
/** Return the progress of a job. */
|
|
764
899
|
async getJob(jobId, options = {}) {
|
|
765
|
-
|
|
900
|
+
return jobStatus(
|
|
766
901
|
await this.send(new Request("GET", `/job/${pathSegment(jobId)}`), options)
|
|
767
902
|
);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Stop a job. Queued tasks are refunded, tasks in flight finish and bill.
|
|
906
|
+
* Throws {@link JobNotCancellable} when it already finished.
|
|
907
|
+
*/
|
|
908
|
+
async cancelJob(jobId, options = {}) {
|
|
909
|
+
return cancelResult(
|
|
910
|
+
await this.send(new Request("POST", `/job/${pathSegment(jobId)}/cancel`), options)
|
|
911
|
+
);
|
|
777
912
|
}
|
|
778
913
|
/**
|
|
779
914
|
* Return the per-task results of a job.
|
|
@@ -794,6 +929,33 @@ var DataFuel = class {
|
|
|
794
929
|
tasks: tasks.map((task) => new Result(record(task)))
|
|
795
930
|
};
|
|
796
931
|
}
|
|
932
|
+
/** One page of your jobs and crawls, newest first. Pass `nextCursor` back as `cursor`. */
|
|
933
|
+
async listJobs(options = {}) {
|
|
934
|
+
const body = record(await this.send(listJobsRequest(options), options));
|
|
935
|
+
const next = typeof body.next_cursor === "string" ? body.next_cursor : void 0;
|
|
936
|
+
return {
|
|
937
|
+
jobs: array(body.jobs).map((raw) => {
|
|
938
|
+
const job = record(raw);
|
|
939
|
+
return { ...job, ...jobStatus(job) };
|
|
940
|
+
}),
|
|
941
|
+
...next ? { nextCursor: next } : {}
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* One page of your tasks, newest first, including those of jobs and crawls.
|
|
946
|
+
* Items carry no result: fetch it with {@link getTask}.
|
|
947
|
+
*/
|
|
948
|
+
async listTasks(options = {}) {
|
|
949
|
+
const body = record(await this.send(listTasksRequest(options), options));
|
|
950
|
+
const next = typeof body.next_cursor === "string" ? body.next_cursor : void 0;
|
|
951
|
+
return {
|
|
952
|
+
tasks: array(body.tasks).map((raw) => {
|
|
953
|
+
const task = record(raw);
|
|
954
|
+
return { ...task, job_id: task.job_id ?? null };
|
|
955
|
+
}),
|
|
956
|
+
...next ? { nextCursor: next } : {}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
797
959
|
/** Poll until the job is done. Without `timeoutMs` it waits indefinitely. */
|
|
798
960
|
async waitJob(jobId, options = {}) {
|
|
799
961
|
const { timeoutMs, ...perPoll } = options;
|
|
@@ -819,11 +981,51 @@ var DataFuel = class {
|
|
|
819
981
|
await this.waitJob(id, options);
|
|
820
982
|
return this.jobResults(id, options);
|
|
821
983
|
}
|
|
984
|
+
/** Create a search job, wait for it, and return its results. */
|
|
985
|
+
async runSearchJob(queries, options = {}) {
|
|
986
|
+
const id = await this.createSearchJob(queries, options);
|
|
987
|
+
await this.waitJob(id, options);
|
|
988
|
+
return this.jobResults(id, options);
|
|
989
|
+
}
|
|
822
990
|
// --- account -----------------------------------------------------------
|
|
823
|
-
/** Which task types and LLM engines are switched on right now. */
|
|
991
|
+
/** Which task types and LLM engines are switched on right now. Needs no key. */
|
|
824
992
|
async capabilities(options = {}) {
|
|
825
|
-
|
|
826
|
-
|
|
993
|
+
const request = new Request(
|
|
994
|
+
"GET",
|
|
995
|
+
"/config/capabilities",
|
|
996
|
+
void 0,
|
|
997
|
+
void 0,
|
|
998
|
+
void 0,
|
|
999
|
+
false
|
|
1000
|
+
);
|
|
1001
|
+
return new Capabilities(record(await this.send(request, options)));
|
|
1002
|
+
}
|
|
1003
|
+
/** The browser actions `jsInstructions` accepts, with their arguments. Needs no key. */
|
|
1004
|
+
async jsInstructions(options = {}) {
|
|
1005
|
+
const request = new Request(
|
|
1006
|
+
"GET",
|
|
1007
|
+
"/config/js-instructions",
|
|
1008
|
+
void 0,
|
|
1009
|
+
void 0,
|
|
1010
|
+
void 0,
|
|
1011
|
+
false
|
|
1012
|
+
);
|
|
1013
|
+
const body = record(await this.send(request, options));
|
|
1014
|
+
return Array.isArray(body.instructions) ? body.instructions : [];
|
|
1015
|
+
}
|
|
1016
|
+
/** Countries, regions and cities a proxy type can exit from. */
|
|
1017
|
+
async proxyLocations(options = {}) {
|
|
1018
|
+
const params = options.proxyType ? { proxy_type: options.proxyType } : void 0;
|
|
1019
|
+
return list(
|
|
1020
|
+
await this.send(new Request("GET", "/config/proxy/locations", params), options)
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
/** ASNs a proxy type can exit from in one country (ISO 3166-1 alpha-2). */
|
|
1024
|
+
async proxyAsns(country, options = {}) {
|
|
1025
|
+
const params = { country };
|
|
1026
|
+
if (options.proxyType) params.proxy_type = options.proxyType;
|
|
1027
|
+
return list(
|
|
1028
|
+
await this.send(new Request("GET", "/config/proxy/asn", params), options)
|
|
827
1029
|
);
|
|
828
1030
|
}
|
|
829
1031
|
/** Remaining credits. */
|
|
@@ -831,6 +1033,29 @@ var DataFuel = class {
|
|
|
831
1033
|
const body = await this.send(new Request("GET", "/users/@me/balance"), options);
|
|
832
1034
|
return intField(body, "balance");
|
|
833
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Credit movements, newest first: purchases, usage, refunds, expiry.
|
|
1038
|
+
* `sums` totals each operation over the whole range, not just this page.
|
|
1039
|
+
*/
|
|
1040
|
+
async transactions(options = {}) {
|
|
1041
|
+
const body = record(await this.send(transactionsRequest(options), options));
|
|
1042
|
+
return {
|
|
1043
|
+
transactions: array(body.transactions),
|
|
1044
|
+
total_count: Number(body.total_count ?? 0),
|
|
1045
|
+
sums: array(body.sums)
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
/** Usage over a date range: totals, a time series and breakdowns. Default: the last 30 days. */
|
|
1049
|
+
async analytics(options = {}) {
|
|
1050
|
+
const body = record(await this.send(analyticsRequest(options), options));
|
|
1051
|
+
return {
|
|
1052
|
+
...body,
|
|
1053
|
+
timeseries: array(body.timeseries),
|
|
1054
|
+
by_module: array(body.by_module),
|
|
1055
|
+
top_targets: array(body.top_targets),
|
|
1056
|
+
by_status_code: array(body.by_status_code)
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
834
1059
|
/** The account behind the API key. */
|
|
835
1060
|
async me(options = {}) {
|
|
836
1061
|
const body = await this.send(new Request("GET", "/users/@me"), options);
|
|
@@ -876,6 +1101,34 @@ function record(body) {
|
|
|
876
1101
|
}
|
|
877
1102
|
return body;
|
|
878
1103
|
}
|
|
1104
|
+
function list(body) {
|
|
1105
|
+
if (!Array.isArray(body)) {
|
|
1106
|
+
throw new DataFuelError(`unexpected answer from the API: ${JSON.stringify(body) ?? "empty"}`);
|
|
1107
|
+
}
|
|
1108
|
+
return body;
|
|
1109
|
+
}
|
|
1110
|
+
function array(value) {
|
|
1111
|
+
return Array.isArray(value) ? value : [];
|
|
1112
|
+
}
|
|
1113
|
+
function jobStatus(body) {
|
|
1114
|
+
const raw = record(body);
|
|
1115
|
+
return {
|
|
1116
|
+
status: raw.status,
|
|
1117
|
+
tasks_count: raw.tasks_count ?? 0,
|
|
1118
|
+
tasks_done: raw.tasks_done ?? 0,
|
|
1119
|
+
tasks_remaining: raw.tasks_remaining ?? 0,
|
|
1120
|
+
total_cost: raw.total_cost ?? 0,
|
|
1121
|
+
done: isDone(raw.status)
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function cancelResult(body) {
|
|
1125
|
+
const raw = record(body);
|
|
1126
|
+
return {
|
|
1127
|
+
...jobStatus(body),
|
|
1128
|
+
refunded_tasks: raw.refunded_tasks ?? 0,
|
|
1129
|
+
refunded_credits: raw.refunded_credits ?? 0
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
879
1132
|
function field(body, name) {
|
|
880
1133
|
const value = record(body)[name];
|
|
881
1134
|
if (value === void 0 || value === null) {
|
|
@@ -903,9 +1156,11 @@ function intField(body, name) {
|
|
|
903
1156
|
DataFuel,
|
|
904
1157
|
DataFuelError,
|
|
905
1158
|
EngineUnavailable,
|
|
1159
|
+
Forbidden,
|
|
906
1160
|
IdempotencyKeyReused,
|
|
907
1161
|
InsufficientCredits,
|
|
908
1162
|
InvalidAttributes,
|
|
1163
|
+
JobNotCancellable,
|
|
909
1164
|
ModuleUnavailable,
|
|
910
1165
|
NoApiKey,
|
|
911
1166
|
NotFound,
|