Skip to content

Commit c5bf757

Browse files
committed
feat(api): align js sdk with sgai v2
1 parent 0f07d93 commit c5bf757

8 files changed

Lines changed: 588 additions & 117 deletions

File tree

src/client.ts

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
import { request } from "./http.js";
1+
import request from "./http.js";
2+
import {
3+
buildCrawlBody,
4+
buildExtractBody,
5+
buildHistoryQuery,
6+
buildMonitorBody,
7+
buildSchemaBody,
8+
buildScrapeBody,
9+
buildSearchBody,
10+
} from "./lib/utils.js";
211
import type {
312
ApiCrawlOptions,
413
ApiExtractOptions,
14+
ApiGenerateSchemaOptions,
515
ApiHistoryFilterInput,
616
ApiMonitorCreateInput,
717
ApiScrapeOptions,
@@ -10,7 +20,6 @@ import type {
1020
RequestOptions,
1121
} from "./types/index.js";
1222
import { DEFAULT_BASE_URL } from "./types/index.js";
13-
import { toJsonSchema } from "./zod.js";
1423

1524
/** Create a ScrapeGraphAI client. All methods return `{ data, requestId }`. */
1625
export function scrapegraphai(config: ClientConfig) {
@@ -32,21 +41,21 @@ export function scrapegraphai(config: ClientConfig) {
3241
"POST",
3342
buildUrl("/api/v2/scrape"),
3443
key,
35-
{ url, ...scrapeOptions },
44+
buildScrapeBody(url, scrapeOptions),
3645
mergeRequestOptions(requestOptions),
3746
);
3847
},
3948

40-
async extract(url: string, extractOptions: ApiExtractOptions, requestOptions?: RequestOptions) {
41-
const body: Record<string, unknown> = { url, prompt: extractOptions.prompt };
42-
if (extractOptions.schema) body.schema = toJsonSchema(extractOptions.schema);
43-
if (extractOptions.mode) body.mode = extractOptions.mode;
44-
if (extractOptions.contentType) body.contentType = extractOptions.contentType;
49+
async extract(
50+
url: string | undefined,
51+
extractOptions: ApiExtractOptions,
52+
requestOptions?: RequestOptions,
53+
) {
4554
return request(
4655
"POST",
4756
buildUrl("/api/v2/extract"),
4857
key,
49-
body,
58+
buildExtractBody(url, extractOptions),
5059
mergeRequestOptions(requestOptions),
5160
);
5261
},
@@ -56,7 +65,32 @@ export function scrapegraphai(config: ClientConfig) {
5665
"POST",
5766
buildUrl("/api/v2/search"),
5867
key,
59-
{ query, ...searchOptions },
68+
buildSearchBody(query, searchOptions),
69+
mergeRequestOptions(requestOptions),
70+
);
71+
},
72+
73+
async schema(
74+
prompt: string,
75+
schemaOptions?: ApiGenerateSchemaOptions,
76+
requestOptions?: RequestOptions,
77+
) {
78+
return request(
79+
"POST",
80+
buildUrl("/api/v2/schema"),
81+
key,
82+
buildSchemaBody(prompt, schemaOptions),
83+
mergeRequestOptions(requestOptions),
84+
);
85+
},
86+
87+
async validate(email: string, requestOptions?: RequestOptions) {
88+
const query = new URLSearchParams({ email }).toString();
89+
return request(
90+
"GET",
91+
buildUrl(`/api/v2/validate?${query}`),
92+
key,
93+
undefined,
6094
mergeRequestOptions(requestOptions),
6195
);
6296
},
@@ -72,11 +106,7 @@ export function scrapegraphai(config: ClientConfig) {
72106
},
73107

74108
async history(historyFilter?: ApiHistoryFilterInput, requestOptions?: RequestOptions) {
75-
const qs = new URLSearchParams();
76-
if (historyFilter?.page != null) qs.set("page", String(historyFilter.page));
77-
if (historyFilter?.limit != null) qs.set("limit", String(historyFilter.limit));
78-
if (historyFilter?.service) qs.set("service", historyFilter.service);
79-
const query = qs.toString();
109+
const query = buildHistoryQuery(historyFilter);
80110
return request(
81111
"GET",
82112
buildUrl(`/api/v2/history${query ? `?${query}` : ""}`),
@@ -92,7 +122,7 @@ export function scrapegraphai(config: ClientConfig) {
92122
"POST",
93123
buildUrl("/api/v2/crawl"),
94124
key,
95-
{ url, ...crawlOptions },
125+
buildCrawlBody(url, crawlOptions),
96126
mergeRequestOptions(requestOptions),
97127
);
98128
},
@@ -134,7 +164,7 @@ export function scrapegraphai(config: ClientConfig) {
134164
"POST",
135165
buildUrl("/api/v2/monitor"),
136166
key,
137-
{ ...monitorCreateInput },
167+
buildMonitorBody(monitorCreateInput),
138168
mergeRequestOptions(requestOptions),
139169
);
140170
},

src/http.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface ApiResult<T> {
66
requestId: string;
77
}
88

9-
export async function request<T = unknown>(
9+
export default async function request<T = unknown>(
1010
method: "GET" | "POST" | "DELETE",
1111
url: string,
1212
apiKey: string,
@@ -51,7 +51,6 @@ export async function request<T = unknown>(
5151
} catch (e) {
5252
lastError = e instanceof Error ? e : new Error(String(e));
5353
if (e instanceof DOMException && e.name === "TimeoutError") throw lastError;
54-
// [NOTE] @Claude retry on network errors (fetch failed, connection refused)
5554
if (e instanceof TypeError && attempt < maxRetries) {
5655
await sleep(500 * 2 ** attempt);
5756
continue;

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@ export type {
77
ApiExtractRequestBase,
88
ApiFetchConfig,
99
ApiFetchMode,
10+
ApiGenerateSchemaOptions,
11+
ApiGenerateSchemaRequest,
1012
ApiHistoryFilterInput,
1113
ApiHistoryService,
1214
ApiHtmlMode,
1315
ApiLlmConfig,
1416
ApiModelName,
1517
ApiMonitorCreateInput,
18+
ApiMonitorCreateRequest,
1619
ApiMonitorUpdateInput,
20+
ApiScrapeFormat,
21+
ApiScrapeFormatEntry,
1722
ApiScrapeOptions,
1823
ApiScrapeRequest,
1924
ApiSearchOptions,
2025
ApiSearchRequest,
2126
ClientConfig,
27+
LegacyApiMonitorCreateInput,
2228
RequestOptions,
2329
} from "./types/index.js";
2430
export { DEFAULT_BASE_URL, VERSION } from "./types/index.js";

src/lib/utils.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import type {
2+
ApiCrawlOptions,
3+
ApiExtractOptions,
4+
ApiGenerateSchemaOptions,
5+
ApiHistoryFilterInput,
6+
ApiMonitorCreateInput,
7+
ApiScrapeFormat,
8+
ApiScrapeFormatEntry,
9+
ApiScrapeOptions,
10+
ApiSearchOptions,
11+
LegacyApiMonitorCreateInput,
12+
} from "../types/index.js";
13+
import { toJsonSchema } from "../zod.js";
14+
15+
function normalizeSchema(schema: unknown) {
16+
return schema ? toJsonSchema(schema) : undefined;
17+
}
18+
19+
function normalizeScrapeFormatEntry(format: ApiScrapeFormatEntry): ApiScrapeFormatEntry {
20+
if (format.type !== "json" || !format.schema) return format;
21+
return {
22+
...format,
23+
schema: normalizeSchema(format.schema),
24+
};
25+
}
26+
27+
function buildSingleFormatEntry(
28+
format: ApiScrapeFormat,
29+
options?: Pick<ApiScrapeOptions, "html" | "json" | "markdown" | "screenshot" | "summary">,
30+
): ApiScrapeFormatEntry {
31+
switch (format) {
32+
case "markdown":
33+
return { type: "markdown", ...(options?.markdown ?? { mode: "normal" }) };
34+
case "html":
35+
return { type: "html", ...(options?.html ?? { mode: "normal" }) };
36+
case "screenshot":
37+
return {
38+
type: "screenshot",
39+
...(options?.screenshot ?? {
40+
fullPage: false,
41+
width: 1440,
42+
height: 900,
43+
quality: 80,
44+
}),
45+
};
46+
case "links":
47+
return { type: "links" };
48+
case "images":
49+
return { type: "images" };
50+
case "summary":
51+
return { type: "summary", ...(options?.summary ?? {}) };
52+
case "branding":
53+
return { type: "branding" };
54+
case "json": {
55+
if (!options?.json?.prompt) {
56+
throw new Error("JSON scrape format requires `json.prompt`");
57+
}
58+
return {
59+
type: "json",
60+
...options.json,
61+
schema: normalizeSchema(options.json.schema),
62+
};
63+
}
64+
}
65+
}
66+
67+
export function buildScrapeBody(url: string, scrapeOptions?: ApiScrapeOptions) {
68+
if (scrapeOptions?.formats?.length) {
69+
return {
70+
url,
71+
contentType: scrapeOptions.contentType,
72+
fetchConfig: scrapeOptions.fetchConfig,
73+
formats: scrapeOptions.formats.map(normalizeScrapeFormatEntry),
74+
};
75+
}
76+
77+
const format = scrapeOptions?.format ?? "markdown";
78+
return {
79+
url,
80+
contentType: scrapeOptions?.contentType,
81+
fetchConfig: scrapeOptions?.fetchConfig,
82+
formats: [
83+
buildSingleFormatEntry(format, {
84+
markdown: scrapeOptions?.markdown,
85+
html: scrapeOptions?.html,
86+
screenshot: scrapeOptions?.screenshot,
87+
json: scrapeOptions?.json,
88+
summary: scrapeOptions?.summary,
89+
}),
90+
],
91+
};
92+
}
93+
94+
export function buildExtractBody(url: string | undefined, extractOptions: ApiExtractOptions) {
95+
const body: Record<string, unknown> = { prompt: extractOptions.prompt };
96+
if (url) body.url = url;
97+
if (extractOptions.html) body.html = extractOptions.html;
98+
if (extractOptions.markdown) body.markdown = extractOptions.markdown;
99+
if (extractOptions.schema) body.schema = toJsonSchema(extractOptions.schema);
100+
if (extractOptions.mode) body.mode = extractOptions.mode;
101+
if (extractOptions.contentType) body.contentType = extractOptions.contentType;
102+
if (extractOptions.fetchConfig) body.fetchConfig = extractOptions.fetchConfig;
103+
return body;
104+
}
105+
106+
export function buildSearchBody(query: string, searchOptions?: ApiSearchOptions) {
107+
const body: Record<string, unknown> = { query, ...searchOptions };
108+
if (searchOptions?.schema) body.schema = toJsonSchema(searchOptions.schema);
109+
return body;
110+
}
111+
112+
export function buildSchemaBody(prompt: string, schemaOptions?: ApiGenerateSchemaOptions) {
113+
const body: Record<string, unknown> = { prompt };
114+
if (schemaOptions?.existingSchema) {
115+
body.existingSchema = toJsonSchema(schemaOptions.existingSchema);
116+
}
117+
if (schemaOptions?.model) body.model = schemaOptions.model;
118+
return body;
119+
}
120+
121+
export function buildHistoryQuery(historyFilter?: ApiHistoryFilterInput) {
122+
const query = new URLSearchParams();
123+
if (historyFilter?.page != null) query.set("page", String(historyFilter.page));
124+
if (historyFilter?.limit != null) query.set("limit", String(historyFilter.limit));
125+
if (historyFilter?.service) query.set("service", historyFilter.service);
126+
return query.toString();
127+
}
128+
129+
export function buildMonitorBody(monitorCreateInput: ApiMonitorCreateInput) {
130+
if ("formats" in monitorCreateInput && monitorCreateInput.formats?.length) {
131+
return {
132+
...monitorCreateInput,
133+
formats: monitorCreateInput.formats.map(normalizeScrapeFormatEntry),
134+
};
135+
}
136+
137+
const legacyMonitorInput = monitorCreateInput as LegacyApiMonitorCreateInput;
138+
return {
139+
url: legacyMonitorInput.url,
140+
name: legacyMonitorInput.name,
141+
webhookUrl: legacyMonitorInput.webhookUrl,
142+
interval: legacyMonitorInput.interval,
143+
fetchConfig: legacyMonitorInput.fetchConfig,
144+
formats: [
145+
{
146+
type: "json" as const,
147+
prompt: legacyMonitorInput.prompt,
148+
schema: normalizeSchema(legacyMonitorInput.schema),
149+
llmConfig: legacyMonitorInput.llmConfig,
150+
mode: "normal" as const,
151+
},
152+
],
153+
};
154+
}
155+
156+
export function buildCrawlBody(url: string, crawlOptions?: ApiCrawlOptions) {
157+
if (crawlOptions?.formats?.length) {
158+
return {
159+
url,
160+
...crawlOptions,
161+
formats: crawlOptions.formats.map(normalizeScrapeFormatEntry),
162+
};
163+
}
164+
165+
const { format, ...rest } = crawlOptions ?? {};
166+
return {
167+
url,
168+
...rest,
169+
formats: [buildSingleFormatEntry(format ?? "markdown")],
170+
};
171+
}

0 commit comments

Comments
 (0)