@crawlora-org/sdk 1.2.0-sdk.11
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/CHANGELOG.md +71 -0
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/examples/bing-search.mjs +18 -0
- package/examples/youtube-transcript.mjs +26 -0
- package/openapi/public.json +52962 -0
- package/package.json +53 -0
- package/src/client.js +268 -0
- package/src/index.d.ts +75 -0
- package/src/index.js +2 -0
- package/src/operations.js +12472 -0
- package/src/types.d.ts +12425 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crawlora-org/sdk",
|
|
3
|
+
"version": "1.2.0-sdk.11",
|
|
4
|
+
"description": "JavaScript and TypeScript SDK for the public Crawlora API.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"crawlora",
|
|
7
|
+
"sdk",
|
|
8
|
+
"web-scraping",
|
|
9
|
+
"api-client"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/Crawlora-org/crawlora-typescript-sdk#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Crawlora-org/crawlora-typescript-sdk/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/Crawlora-org/crawlora-typescript-sdk.git"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./src/index.js",
|
|
21
|
+
"types": "./src/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./src/index.d.ts",
|
|
25
|
+
"import": "./src/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"examples",
|
|
34
|
+
"src",
|
|
35
|
+
"openapi/public.json",
|
|
36
|
+
"README.md",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"example:bing-search": "node examples/bing-search.mjs",
|
|
41
|
+
"example:youtube-transcript": "node examples/youtube-transcript.mjs",
|
|
42
|
+
"generate": "python3 scripts/generate.py",
|
|
43
|
+
"pack:check": "npm pack --dry-run",
|
|
44
|
+
"smoke:live": "npm run example:bing-search && npm run example:youtube-transcript",
|
|
45
|
+
"typecheck": "npx --yes -p typescript@5.5.4 tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict src/index.d.ts src/types.d.ts test/types-usage.ts",
|
|
46
|
+
"test": "node --test"
|
|
47
|
+
},
|
|
48
|
+
"sideEffects": false,
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { operations, groups } from "./operations.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_BASE_URL = "https://api.crawlora.net/api/v1";
|
|
4
|
+
export const VERSION = "1.2.0-sdk.11";
|
|
5
|
+
const DEFAULT_USER_AGENT = `crawlora-js-sdk/${VERSION}`;
|
|
6
|
+
|
|
7
|
+
export class CrawloraError extends Error {
|
|
8
|
+
constructor(message, { status = 0, code, body, response, cause } = {}) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "CrawloraError";
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.body = body;
|
|
14
|
+
this.response = response;
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CrawloraClient {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.apiKey = options.apiKey || "";
|
|
22
|
+
this.jwtToken = options.jwtToken || "";
|
|
23
|
+
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
24
|
+
this.timeout = options.timeout ?? 30000;
|
|
25
|
+
this.retries = normalizeNonNegativeInteger(options.retries ?? 0);
|
|
26
|
+
this.retryDelay = normalizeNonNegativeNumber(options.retryDelay ?? 250);
|
|
27
|
+
this.headers = { ...(options.headers || {}) };
|
|
28
|
+
this.userAgent = options.userAgent === false ? "" : (options.userAgent || DEFAULT_USER_AGENT);
|
|
29
|
+
this.fetch = options.fetch || globalThis.fetch;
|
|
30
|
+
|
|
31
|
+
if (typeof this.fetch !== "function") {
|
|
32
|
+
throw new TypeError("CrawloraClient requires a fetch implementation");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const [groupName, groupOperations] of Object.entries(groups)) {
|
|
36
|
+
this[groupName] = {};
|
|
37
|
+
for (const [methodName, operationId] of Object.entries(groupOperations)) {
|
|
38
|
+
this[groupName][methodName] = (params = {}, requestOptions = {}) =>
|
|
39
|
+
this.request(operationId, params, requestOptions);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
operation(operationId, params = {}, options = {}) {
|
|
45
|
+
return this.request(operationId, params, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async request(operationId, params = {}, options = {}) {
|
|
49
|
+
const operation = operations[operationId];
|
|
50
|
+
if (!operation) {
|
|
51
|
+
throw new TypeError(`Unknown Crawlora operation: ${operationId}`);
|
|
52
|
+
}
|
|
53
|
+
params = params ?? {};
|
|
54
|
+
options = options ?? {};
|
|
55
|
+
|
|
56
|
+
let attempt = 0;
|
|
57
|
+
for (;;) {
|
|
58
|
+
try {
|
|
59
|
+
return await this.#send(operation, params, options);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof CrawloraError) || attempt >= this.retries || !shouldRetry(error.status)) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
attempt++;
|
|
65
|
+
await sleep(retryDelay(this.retryDelay, attempt), options.signal);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async #send(operation, params, options) {
|
|
71
|
+
const { url, body, bodyHeaders } = buildRequest(operation, this.baseUrl, params);
|
|
72
|
+
const headers = {
|
|
73
|
+
...this.headers,
|
|
74
|
+
...authHeaders(operation.security, this.apiKey, this.jwtToken),
|
|
75
|
+
...userAgentHeader(this.userAgent),
|
|
76
|
+
...bodyHeaders,
|
|
77
|
+
...(options.headers || {})
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timeoutMs = options.timeout ?? this.timeout;
|
|
82
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
|
|
83
|
+
const signal = composeSignal(controller, options.signal);
|
|
84
|
+
let response;
|
|
85
|
+
try {
|
|
86
|
+
response = await this.fetch(url, {
|
|
87
|
+
method: operation.method,
|
|
88
|
+
headers,
|
|
89
|
+
body,
|
|
90
|
+
signal
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
throw new CrawloraError("Crawlora transport error", { cause: error });
|
|
94
|
+
} finally {
|
|
95
|
+
if (timeout) clearTimeout(timeout);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const parsed = await parseResponse(response, options.responseType || "auto");
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const code = parsed && typeof parsed === "object" ? parsed.code : undefined;
|
|
101
|
+
const message = parsed && typeof parsed === "object" && parsed.msg ? parsed.msg : response.statusText;
|
|
102
|
+
throw new CrawloraError(message || `Crawlora request failed with status ${response.status}`, {
|
|
103
|
+
status: response.status,
|
|
104
|
+
code,
|
|
105
|
+
body: parsed,
|
|
106
|
+
response
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildRequest(operation, baseUrl, params) {
|
|
114
|
+
validateRequiredParams(operation, params);
|
|
115
|
+
validateEnumParams(operation, params);
|
|
116
|
+
let path = operation.path;
|
|
117
|
+
for (const name of operation.pathParams) {
|
|
118
|
+
const value = params[name];
|
|
119
|
+
if (value === undefined || value === null || value === "") {
|
|
120
|
+
throw new TypeError(`Missing required path parameter: ${name}`);
|
|
121
|
+
}
|
|
122
|
+
path = path.replace(`{${name}}`, encodeURIComponent(String(value)));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const query = new URLSearchParams();
|
|
126
|
+
for (const parameter of operation.queryParams) {
|
|
127
|
+
const value = params[parameter.name];
|
|
128
|
+
if (value === undefined || value === null || value === "") {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
for (const item of value) query.append(parameter.name, String(item));
|
|
133
|
+
} else {
|
|
134
|
+
query.append(parameter.name, String(value));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const suffix = query.toString();
|
|
139
|
+
const url = `${baseUrl}${path}${suffix ? `?${suffix}` : ""}`;
|
|
140
|
+
const bodyHeaders = {};
|
|
141
|
+
let body;
|
|
142
|
+
|
|
143
|
+
if (operation.formParams.length > 0) {
|
|
144
|
+
const form = new FormData();
|
|
145
|
+
for (const parameter of operation.formParams) {
|
|
146
|
+
const value = params[parameter.name];
|
|
147
|
+
if (value !== undefined && value !== null) form.append(parameter.name, value);
|
|
148
|
+
}
|
|
149
|
+
body = form;
|
|
150
|
+
} else if (operation.bodyParam) {
|
|
151
|
+
const value = params[operation.bodyParam] ?? params.body;
|
|
152
|
+
if (value !== undefined) {
|
|
153
|
+
body = JSON.stringify(value);
|
|
154
|
+
bodyHeaders["content-type"] = "application/json";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { url, body, bodyHeaders };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function validateRequiredParams(operation, params) {
|
|
162
|
+
for (const parameter of [...operation.pathParams.map((name) => ({ name, in: "path", required: true })), ...operation.queryParams, ...operation.formParams]) {
|
|
163
|
+
if (parameter.required && isMissing(params[parameter.name])) {
|
|
164
|
+
throw new TypeError(`Missing required ${parameter.in || "request"} parameter: ${parameter.name}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (operation.bodyRequired && isMissing(params[operation.bodyParam]) && isMissing(params.body)) {
|
|
168
|
+
throw new TypeError(`Missing required body parameter: ${operation.bodyParam}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function validateEnumParams(operation, params) {
|
|
173
|
+
for (const parameter of [...operation.queryParams, ...operation.formParams]) {
|
|
174
|
+
if (!parameter.enum?.length || isMissing(params[parameter.name])) continue;
|
|
175
|
+
const values = Array.isArray(params[parameter.name]) ? params[parameter.name] : [params[parameter.name]];
|
|
176
|
+
for (const value of values) {
|
|
177
|
+
if (!parameter.enum.includes(String(value))) {
|
|
178
|
+
throw new TypeError(`invalid ${parameter.in || "request"} parameter ${parameter.name}: expected one of ${parameter.enum.join(", ")}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isMissing(value) {
|
|
185
|
+
return value === undefined || value === null || value === "" || (Array.isArray(value) && value.length === 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function authHeaders(security, apiKey, jwtToken) {
|
|
189
|
+
const headers = {};
|
|
190
|
+
if (security.includes("ApiKeyAuth") && apiKey) {
|
|
191
|
+
headers["x-api-key"] = apiKey;
|
|
192
|
+
}
|
|
193
|
+
if (security.includes("JWTAuth") && jwtToken) {
|
|
194
|
+
headers.Authorization = /^(Token|Bearer)\s+/i.test(jwtToken) ? jwtToken : `Token ${jwtToken}`;
|
|
195
|
+
}
|
|
196
|
+
return headers;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function userAgentHeader(userAgent) {
|
|
200
|
+
if (!userAgent || typeof process === "undefined" || !process.versions?.node) {
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
return { "user-agent": userAgent };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function composeSignal(controller, signal) {
|
|
207
|
+
if (!signal) return controller.signal;
|
|
208
|
+
if (signal.aborted) controller.abort();
|
|
209
|
+
signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
210
|
+
return controller.signal;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function parseResponse(response, responseType) {
|
|
214
|
+
if (responseType === "text") return response.text();
|
|
215
|
+
const contentType = response.headers.get("content-type") || "";
|
|
216
|
+
if (responseType === "json" || contentType.toLowerCase().includes("application/json")) {
|
|
217
|
+
const text = await response.text();
|
|
218
|
+
try {
|
|
219
|
+
return text ? JSON.parse(text) : null;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
throw new CrawloraError("Crawlora JSON parse error", {
|
|
222
|
+
status: response.status,
|
|
223
|
+
body: text,
|
|
224
|
+
response,
|
|
225
|
+
cause: error
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return response.text();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function shouldRetry(status) {
|
|
233
|
+
return status === 0 || status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function retryDelay(baseDelay, attempt) {
|
|
237
|
+
if (!baseDelay || baseDelay <= 0) return 0;
|
|
238
|
+
const delay = baseDelay * 2 ** Math.max(0, attempt - 1);
|
|
239
|
+
const jitter = Math.floor(Math.random() * Math.max(1, baseDelay / 2));
|
|
240
|
+
return delay + jitter;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function normalizeNonNegativeInteger(value) {
|
|
244
|
+
const number = Number(value);
|
|
245
|
+
if (!Number.isFinite(number) || number <= 0) return 0;
|
|
246
|
+
return Math.trunc(number);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizeNonNegativeNumber(value) {
|
|
250
|
+
const number = Number(value);
|
|
251
|
+
if (!Number.isFinite(number) || number <= 0) return 0;
|
|
252
|
+
return number;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function sleep(ms, signal) {
|
|
256
|
+
if (!ms || ms <= 0) return Promise.resolve();
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
if (signal?.aborted) {
|
|
259
|
+
reject(new CrawloraError("Crawlora request aborted", { cause: signal.reason }));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const timer = setTimeout(resolve, ms);
|
|
263
|
+
signal?.addEventListener("abort", () => {
|
|
264
|
+
clearTimeout(timer);
|
|
265
|
+
reject(new CrawloraError("Crawlora request aborted", { cause: signal.reason }));
|
|
266
|
+
}, { once: true });
|
|
267
|
+
});
|
|
268
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CrawloraGeneratedGroups,
|
|
3
|
+
OperationId,
|
|
4
|
+
OperationRequestArgs,
|
|
5
|
+
OperationResponseMap
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
|
|
8
|
+
export type CrawloraParams = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export interface CrawloraClientOptions {
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
jwtToken?: string;
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
retries?: number;
|
|
16
|
+
retryDelay?: number;
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
userAgent?: string | false;
|
|
19
|
+
fetch?: typeof globalThis.fetch;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CrawloraRequestOptions {
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
responseType?: "auto" | "json" | "text";
|
|
25
|
+
timeout?: number;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type OperationMethod = <T = unknown>(
|
|
30
|
+
params?: CrawloraParams,
|
|
31
|
+
options?: CrawloraRequestOptions
|
|
32
|
+
) => Promise<T>;
|
|
33
|
+
|
|
34
|
+
export interface OperationDefinition {
|
|
35
|
+
id: string;
|
|
36
|
+
method: string;
|
|
37
|
+
path: string;
|
|
38
|
+
pathParams: string[];
|
|
39
|
+
queryParams: Array<{ name: string; in?: "query"; collectionFormat?: string; type?: string; required?: boolean; enum?: string[] }>;
|
|
40
|
+
formParams: Array<{ name: string; in?: "formData"; type?: string; required?: boolean; enum?: string[] }>;
|
|
41
|
+
bodyParam?: string;
|
|
42
|
+
bodyRequired?: boolean;
|
|
43
|
+
consumes: string[];
|
|
44
|
+
produces: string[];
|
|
45
|
+
security: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class CrawloraError extends Error {
|
|
49
|
+
status: number;
|
|
50
|
+
code?: number;
|
|
51
|
+
body: unknown;
|
|
52
|
+
response?: Response;
|
|
53
|
+
cause?: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class CrawloraClient {
|
|
57
|
+
constructor(options?: CrawloraClientOptions);
|
|
58
|
+
request<I extends OperationId>(
|
|
59
|
+
operationId: I,
|
|
60
|
+
...args: OperationRequestArgs<I>
|
|
61
|
+
): Promise<OperationResponseMap[I]>;
|
|
62
|
+
operation<I extends OperationId>(
|
|
63
|
+
operationId: I,
|
|
64
|
+
...args: OperationRequestArgs<I>
|
|
65
|
+
): Promise<OperationResponseMap[I]>;
|
|
66
|
+
[group: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CrawloraClient extends CrawloraGeneratedGroups {}
|
|
70
|
+
|
|
71
|
+
export const operations: Record<string, OperationDefinition>;
|
|
72
|
+
export const groups: Record<string, Record<string, string>>;
|
|
73
|
+
export const operationCount: number;
|
|
74
|
+
export const VERSION: string;
|
|
75
|
+
export * from "./types.js";
|
package/src/index.js
ADDED