zenrows 1.3.2 → 2.0.1

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/.editorconfig ADDED
@@ -0,0 +1,16 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ indent_style = space
8
+ indent_size = 2
9
+ trim_trailing_whitespace = true
10
+ tab_width = 2
11
+
12
+ [*.md]
13
+ trim_trailing_whitespace = false
14
+
15
+ [*.{yml,yaml}]
16
+ indent_size = 2
package/LICENSE CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,76 +1,89 @@
1
1
  # ZenRows Node.js SDK
2
+
2
3
  SDK to access [ZenRows](https://www.zenrows.com/) API directly from Node.js. ZenRows handles proxies rotation, headless browsers, and CAPTCHAs for you.
3
4
 
4
5
  ## Installation
5
- Install the SDK with npm.
6
+
7
+ Install the SDK with your package manager of choice.
6
8
 
7
9
  ```bash
8
10
  npm install zenrows
11
+ yarn add zenrows
12
+ pnpm install zenrows
13
+ bun install zenrows
9
14
  ```
10
15
 
11
16
  ## Usage
17
+
12
18
  Start using the API by [creating your API Key](https://app.zenrows.com/register?p=free).
13
19
 
14
- The SDK uses [axios](https://axios-http.com/) for HTTP requests. The client's response will be an `AxiosPromise` if using TypeScript, a regular `Promise` otherwise.
20
+ The SDK uses the official [fetch api](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html) for HTTP requests. The client's response will be a [`Response`](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#response).
15
21
 
16
- It also uses [axios-retry](https://github.com/softonic/axios-retry) to automatically retry failed requests (status code 429 and 5XX). Retries are not active by default; you need to specify the number of retries, as shown below. It already includes an exponential back-off retry delay between failed requests.
22
+ It also uses [fetch-retry](https://github.com/jonbern/fetch-retry) to automatically retry failed requests (status code 429 and 5XX). Retries are not active by default; you need to specify the number of retries, as shown below. It already includes an exponential back-off retry delay between failed requests.
17
23
 
18
24
  ```javascript
19
- const { ZenRows } = require('zenrows', { retries: 1 });
25
+ const { ZenRows } = require("zenrows");
20
26
 
21
- const apiKey = 'YOUR-API-KEY';
22
- const url = 'https://www.zenrows.com/';
27
+ const apiKey = "YOUR-API-KEY";
28
+ const url = "https://www.zenrows.com/";
23
29
 
24
30
  (async () => {
25
- const client = new ZenRows(apiKey);
31
+ const client = new ZenRows(apiKey, { retries: 1 });
32
+
33
+ const response = await client.get(
34
+ url,
35
+ {
36
+ // Our algorithm allows to automatically extract content from any website
37
+ autoparse: false,
26
38
 
27
- const { data } = await client.get(url, {
28
- // Our algorithm allows to automatically extract content from any website
29
- autoparse: false,
39
+ // CSS Selectors for data extraction (i.e. {"links":"a @href"} to get href attributes from links)
40
+ css_extractor: "",
30
41
 
31
- // CSS Selectors for data extraction (i.e. {"links":"a @href"} to get href attributes from links)
32
- css_extractor: '',
42
+ // Enable Javascript with a headless browser (5 credits)
43
+ js_render: false,
33
44
 
34
- // Enable Javascript with a headless browser (5 credits)
35
- js_render: false,
45
+ // Use residential proxies (10 credits)
46
+ premium_proxy: false,
36
47
 
37
- // Use residential proxies (10 credits)
38
- premium_proxy: false,
48
+ // Make your request from a given country. Requires premium_proxy
49
+ proxy_country: "",
39
50
 
40
- // Make your request from a given country. Requires premium_proxy
41
- proxy_country: '',
51
+ // Wait for a given CSS Selector to load in the DOM. Requires js_render
52
+ wait_for: ".content",
42
53
 
43
- // Wait for a given CSS Selector to load in the DOM. Requires js_render
44
- wait_for: '.content',
54
+ // Wait a fixed amount of time in milliseconds. Requires js_render
55
+ wait: 2500,
45
56
 
46
- // Wait a fixed amount of time in milliseconds. Requires js_render
47
- wait: 2500,
57
+ // Block specific resources from loading, check docs for the full list. Requires js_render
58
+ block_resources: "image,media,font",
48
59
 
49
- // Block specific resources from loading, check docs for the full list. Requires js_render
50
- block_resources: 'image,media,font',
60
+ // Change the browser's window width and height. Requires js_render
61
+ window_width: 1920,
62
+ window_height: 1080,
51
63
 
52
- // Change the browser's window width and height. Requires js_render
53
- window_width: 1920,
54
- window_height: 1080,
64
+ // Will automatically use either desktop or mobile user agents in the headers
65
+ device: "desktop",
55
66
 
56
- // Will automatically use either desktop or mobile user agents in the headers
57
- device: 'desktop',
67
+ // Will return the status code returned by the website
68
+ original_status: false,
69
+ },
70
+ {
71
+ headers: {
72
+ Referrer: "https://www.google.com",
73
+ "User-Agent": "MyCustomUserAgent",
74
+ },
75
+ }
76
+ );
58
77
 
59
- // Will return the status code returned by the website
60
- original_status: false,
61
- }, {
62
- headers: {
63
- 'Referrer': 'https://www.google.com',
64
- 'User-Agent': 'MyCustomUserAgent',
65
- },
66
- });
78
+ // You can also use response.json() if you're expecting JSON data.
79
+ const data = await response.text();
67
80
 
68
- console.log(data);
81
+ console.log(data);
69
82
 
70
- /* <!doctype html> <html... */
83
+ /* <!doctype html> <html... */
71
84
 
72
- // With the CSS selector {"links":"a @href"}
73
- /*
85
+ // With the CSS selector {"links":"a @href"}
86
+ /*
74
87
  {
75
88
  links: [
76
89
  'https://www.zenrows.com',
@@ -88,28 +101,34 @@ Sending headers to the target URL will overwrite our defaults. Be careful when d
88
101
 
89
102
  ### POST Requests
90
103
 
91
- The SDK also offers POST requests by calling the `client.post` method. It can receive a new parameter `data` that represents the data sent in, for example, a form.
104
+ The SDK also offers POST requests by calling the `client.post` method. It can receive a new parameter `data` that represents the data sent in, for example, a form.
92
105
 
93
106
  ```javascript
94
- const { ZenRows } = require('zenrows', { retries: 1 });
107
+ const { ZenRows } = require("zenrows");
95
108
 
96
- const apiKey = 'YOUR-API-KEY';
97
- const url = 'https://httpbin.org/anything';
109
+ const apiKey = "YOUR-API-KEY";
110
+ const url = "https://httpbin.org/anything";
98
111
 
99
112
  (async () => {
100
- const client = new ZenRows(apiKey);
101
-
102
- const { data } = await client.post(url, {
103
- // The same params as in GET requests
104
- }, {
105
- data: new URLSearchParams({
106
- 'key1': 'value1',
107
- 'key2': 'value2',
108
- }).toString(),
109
- });
110
-
111
- console.log(data);
112
- /*
113
+ const client = new ZenRows(apiKey, { retries: 1 });
114
+
115
+ const response = await client.post(
116
+ url,
117
+ {
118
+ // The same params as in GET requests
119
+ },
120
+ {
121
+ data: new URLSearchParams({
122
+ key1: "value1",
123
+ key2: "value2",
124
+ }).toString(),
125
+ }
126
+ );
127
+
128
+ const data = await response.json();
129
+
130
+ console.log(data);
131
+ /*
113
132
  ...
114
133
  form: { key1: 'value1', key2: 'value2' },
115
134
  ...
@@ -124,23 +143,23 @@ To limit the concurrency, it uses [fastq](https://github.com/mcollina/fastq), wh
124
143
  We use [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) in the example below, available from Node 12.9. It will wait for all the promises to finish, and the results are objects with a status marking them as fulfilled or rejected. The main difference with `Promise.all()` is that it won't fail if any requests fail. It might make your scraping more robust since the whole list of URLs will run, even if some of them fail.
125
144
 
126
145
  ```javascript
127
- const { ZenRows } = require('zenrows');
146
+ const { ZenRows } = require("zenrows");
128
147
 
129
- const apiKey = 'YOUR-API-KEY';
148
+ const apiKey = "YOUR-API-KEY";
130
149
 
131
150
  (async () => {
132
- const client = new ZenRows(apiKey, { concurrency: 5, retries: 1 });
151
+ const client = new ZenRows(apiKey, { concurrency: 5, retries: 1 });
133
152
 
134
- const urls = [
135
- 'https://www.zenrows.com/',
136
- // ...
137
- ];
153
+ const urls = [
154
+ "https://www.zenrows.com/",
155
+ // ...
156
+ ];
138
157
 
139
- const promises = urls.map(url => client.get(url));
158
+ const promises = urls.map((url) => client.get(url));
140
159
 
141
- const results = await Promise.allSettled(promises);
142
- console.log(results);
143
- /*
160
+ const results = await Promise.allSettled(promises);
161
+ console.log(results);
162
+ /*
144
163
  [
145
164
  {
146
165
  status: 'fulfilled',
@@ -152,11 +171,29 @@ const apiKey = 'YOUR-API-KEY';
152
171
  ...
153
172
  */
154
173
 
155
- // separate results list into rejected and fulfilled for later processing
156
- const rejected = results.filter(({ status }) => status === 'rejected');
157
- const fulfilled = results.filter(({ status }) => status === 'fulfilled');
174
+ // separate results list into rejected and fulfilled for later processing
175
+ const rejected = results.filter(({ status }) => status === "rejected");
176
+ const fulfilled = results.filter(({ status }) => status === "fulfilled");
158
177
  })();
159
178
  ```
179
+
180
+ #### An important note about Promise.allSettled() on TypeScript
181
+
182
+ `Promise.allSettled()` does not narrow the type of the array elements in the callback function. This means that you will need to cast the type of the array elements to `PromiseSettledResult<Response>` to access the `status` and `value` properties.
183
+
184
+ ```typescript
185
+ const promises = urls.map((url) => client.get(url));
186
+
187
+ const results = await Promise.allSettled(promises);
188
+
189
+ const fulfilled = results
190
+ .filter(
191
+ (item): item is PromiseFulfilledResult<Response> =>
192
+ item.status === "fulfilled"
193
+ )
194
+ .map((item) => item.value.json());
195
+ ```
196
+
160
197
  ## Examples
161
198
 
162
199
  Take a look at the [examples directory](./examples) for Javascript and TypeScript files using the SDK.
@@ -167,11 +204,13 @@ Each file makes two requests, the first with CSS selectors and the second with C
167
204
  cd examples
168
205
  npm install
169
206
  node index.js # JS example
170
- npx ts-node index.ts # TS example
207
+ npx tsx index.ts # TS example
171
208
  ```
172
209
 
173
210
  ## Contributing
211
+
174
212
  Pull requests are welcome. For significant changes, please open an issue first to discuss what you would like to change.
175
213
 
176
214
  ## License
215
+
177
216
  [MIT](./LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ ZenRows: () => ZenRows
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+ var import_fastq = __toESM(require("fastq"), 1);
37
+ var import_fetch_retry = __toESM(require("fetch-retry"), 1);
38
+
39
+ // package.json
40
+ var package_default = {
41
+ name: "zenrows",
42
+ version: "2.0.1",
43
+ description: "ZenRows Node SDK",
44
+ main: "./dist/index.cjs",
45
+ module: "./dist/index.js",
46
+ types: "./dist/index.d.ts",
47
+ engines: {
48
+ node: ">=20"
49
+ },
50
+ packageManager: "pnpm@9.4.0",
51
+ exports: {
52
+ ".": {
53
+ require: "./dist/index.cjs",
54
+ import: "./dist/index.js",
55
+ types: "./dist/index.d.ts"
56
+ }
57
+ },
58
+ scripts: {
59
+ build: "tsup src/index.ts --dts --format cjs,esm --clean",
60
+ test: "vitest"
61
+ },
62
+ keywords: ["sdk", "zenrows", "scraping"],
63
+ author: "ZenRows",
64
+ repository: {
65
+ type: "git",
66
+ url: "https://github.com/ZenRows/zenrows-node-sdk.git"
67
+ },
68
+ homepage: "https://github.com/ZenRows/zenrows-node-sdk#readme",
69
+ url: "https://github.com/ZenRows/zenrows-node-sdk/issues",
70
+ type: "module",
71
+ license: "MIT",
72
+ devDependencies: {
73
+ "@biomejs/biome": "1.8.1",
74
+ "@types/async-retry": "^1.4.8",
75
+ "@types/node": "^20.14.2",
76
+ msw: "^2.3.1",
77
+ tsup: "^8.1.0",
78
+ typescript: "^5.4.5",
79
+ vitest: "^1.6.0"
80
+ },
81
+ dependencies: {
82
+ fastq: "^1.17.1",
83
+ "fetch-retry": "^6.0.0"
84
+ }
85
+ };
86
+
87
+ // src/index.ts
88
+ var API_URL = "https://api.zenrows.com/v1/";
89
+ var ZenRows = class {
90
+ apiKey;
91
+ clientConfig;
92
+ queue;
93
+ fetchWithRetry;
94
+ constructor(apiKey, clientConfig = {}) {
95
+ this.apiKey = apiKey;
96
+ this.clientConfig = clientConfig;
97
+ const retries = this.clientConfig.retries ?? 0;
98
+ this.queue = import_fastq.default.promise(
99
+ this,
100
+ this.worker,
101
+ this.clientConfig.concurrency ?? 5
102
+ );
103
+ this.fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
104
+ retryDelay: (attempt) => 2 ** attempt * 1e3,
105
+ // retryOn: [422, 503, 504],
106
+ retryOn: (attempt, error, response) => {
107
+ if (attempt > retries) {
108
+ return false;
109
+ }
110
+ if (error !== null || response?.status === 422 || response?.status === 503 || response?.status === 504) {
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+ });
116
+ }
117
+ get(url, config, { headers = {} } = {}) {
118
+ return this.queue.push({ url, config, headers });
119
+ }
120
+ post(url, config, { headers = {}, data = {} } = {
121
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
122
+ }) {
123
+ const normalizedHeaders = Object.keys(headers).reduce(
124
+ (acc, key) => {
125
+ const value = headers[key];
126
+ if (value !== void 0) {
127
+ if (key.toLowerCase() === "content-type") {
128
+ acc["Content-Type"] = value;
129
+ } else {
130
+ acc[key] = value;
131
+ }
132
+ }
133
+ return acc;
134
+ },
135
+ {}
136
+ );
137
+ return this.queue.push({
138
+ url,
139
+ method: "POST",
140
+ config,
141
+ headers: {
142
+ "Content-Type": "application/x-www-form-urlencoded",
143
+ ...normalizedHeaders
144
+ },
145
+ data
146
+ });
147
+ }
148
+ async worker({
149
+ url,
150
+ method = "GET",
151
+ config,
152
+ headers,
153
+ data
154
+ }) {
155
+ const params = new URLSearchParams({
156
+ url,
157
+ apikey: this.apiKey
158
+ });
159
+ if (config) {
160
+ for (const [key, value] of Object.entries(config)) {
161
+ if (value !== void 0) {
162
+ params.append(key, String(value));
163
+ }
164
+ }
165
+ }
166
+ if (headers && Object.keys(headers).length) {
167
+ params.append("custom_headers", "true");
168
+ }
169
+ const finalHeaders = {
170
+ "User-Agent": `zenrows/${package_default.version} node`,
171
+ ...headers
172
+ };
173
+ const fetchOptions = {
174
+ method,
175
+ headers: finalHeaders
176
+ };
177
+ if (method === "POST" && data) {
178
+ if (typeof data === "object") {
179
+ fetchOptions.body = JSON.stringify(data);
180
+ } else {
181
+ fetchOptions.body = String(data);
182
+ }
183
+ }
184
+ const response = await this.fetchWithRetry(
185
+ `${API_URL}?${params.toString()}`,
186
+ fetchOptions
187
+ );
188
+ return response;
189
+ }
190
+ };
191
+ // Annotate the CommonJS export names for ESM import in node:
192
+ 0 && (module.exports = {
193
+ ZenRows
194
+ });
@@ -0,0 +1,50 @@
1
+ import * as fetch_retry from 'fetch-retry';
2
+ import fastq from 'fastq';
3
+
4
+ type HttpMethods = "GET" | "POST" | "PUT";
5
+ interface ClientConfig {
6
+ concurrency?: number;
7
+ retries?: number;
8
+ }
9
+ interface ZenRowsConfig {
10
+ autoparse?: boolean;
11
+ css_extractor?: string;
12
+ js_render?: boolean;
13
+ premium_proxy?: boolean;
14
+ proxy_country?: string;
15
+ wait_for?: string;
16
+ wait?: number;
17
+ block_resources?: string;
18
+ window_width?: number;
19
+ window_height?: number;
20
+ device?: string;
21
+ original_status?: boolean;
22
+ custom_headers?: boolean;
23
+ [x: string]: unknown;
24
+ }
25
+ interface Headers {
26
+ [x: string]: string;
27
+ }
28
+ declare class ZenRows {
29
+ readonly apiKey: string;
30
+ readonly clientConfig: ClientConfig;
31
+ readonly queue: fastq.queueAsPromised<{
32
+ url: string;
33
+ method?: HttpMethods | undefined;
34
+ config?: ZenRowsConfig | undefined;
35
+ headers: Headers;
36
+ data?: unknown;
37
+ }, Response>;
38
+ readonly fetchWithRetry: (input: string | URL | Request, init?: (RequestInit & fetch_retry.RequestInitRetryParams<typeof fetch>) | undefined) => Promise<Response>;
39
+ constructor(apiKey: string, clientConfig?: ClientConfig);
40
+ get(url: string, config?: ZenRowsConfig, { headers }?: {
41
+ headers?: Headers;
42
+ }): Promise<Response>;
43
+ post(url: string, config?: ZenRowsConfig, { headers, data }?: {
44
+ headers?: Headers;
45
+ data?: unknown;
46
+ }): Promise<Response>;
47
+ private worker;
48
+ }
49
+
50
+ export { ZenRows, type ZenRowsConfig };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { AxiosPromise, Method } from 'axios';
2
- import * as fastq from 'fastq';
1
+ import * as fetch_retry from 'fetch-retry';
2
+ import fastq from 'fastq';
3
+
4
+ type HttpMethods = "GET" | "POST" | "PUT";
3
5
  interface ClientConfig {
4
6
  concurrency?: number;
5
7
  retries?: number;
6
8
  }
7
- interface Config {
9
+ interface ZenRowsConfig {
8
10
  autoparse?: boolean;
9
11
  css_extractor?: string;
10
12
  js_render?: boolean;
@@ -28,20 +30,21 @@ declare class ZenRows {
28
30
  readonly clientConfig: ClientConfig;
29
31
  readonly queue: fastq.queueAsPromised<{
30
32
  url: string;
31
- method?: Method | undefined;
32
- config?: Config | undefined;
33
+ method?: HttpMethods | undefined;
34
+ config?: ZenRowsConfig | undefined;
33
35
  headers: Headers;
34
- data?: any;
35
- }, import("axios").AxiosResponse<any>>;
36
+ data?: unknown;
37
+ }, Response>;
38
+ readonly fetchWithRetry: (input: string | URL | Request, init?: (RequestInit & fetch_retry.RequestInitRetryParams<typeof fetch>) | undefined) => Promise<Response>;
36
39
  constructor(apiKey: string, clientConfig?: ClientConfig);
37
- get(url: string, config?: Config, { headers }?: {
40
+ get(url: string, config?: ZenRowsConfig, { headers }?: {
38
41
  headers?: Headers;
39
- }): AxiosPromise;
40
- post(url: string, config?: Config, { headers, data }?: {
42
+ }): Promise<Response>;
43
+ post(url: string, config?: ZenRowsConfig, { headers, data }?: {
41
44
  headers?: Headers;
42
- data?: any;
43
- }): AxiosPromise;
45
+ data?: unknown;
46
+ }): Promise<Response>;
44
47
  private worker;
45
- private applyRetries;
46
48
  }
47
- export { ZenRows };
49
+
50
+ export { ZenRows, type ZenRowsConfig };
package/dist/index.js CHANGED
@@ -1,57 +1,159 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ZenRows = void 0;
4
- /* eslint-disable @typescript-eslint/no-explicit-any */
5
- const axios_1 = require("axios");
6
- const axios_retry_1 = require("axios-retry");
7
- const fastq = require("fastq");
8
- const version_1 = require("./version");
9
- const API_URL = 'https://api.zenrows.com/v1/';
10
- class ZenRows {
11
- constructor(apiKey, clientConfig = {}) {
12
- var _a;
13
- this.apiKey = apiKey;
14
- this.clientConfig = clientConfig;
15
- this.queue = fastq.promise(this, this.worker, (_a = this.clientConfig.concurrency) !== null && _a !== void 0 ? _a : 5);
16
- this.applyRetries();
1
+ // src/index.ts
2
+ import fastq from "fastq";
3
+ import fetchRetry from "fetch-retry";
4
+
5
+ // package.json
6
+ var package_default = {
7
+ name: "zenrows",
8
+ version: "2.0.1",
9
+ description: "ZenRows Node SDK",
10
+ main: "./dist/index.cjs",
11
+ module: "./dist/index.js",
12
+ types: "./dist/index.d.ts",
13
+ engines: {
14
+ node: ">=20"
15
+ },
16
+ packageManager: "pnpm@9.4.0",
17
+ exports: {
18
+ ".": {
19
+ require: "./dist/index.cjs",
20
+ import: "./dist/index.js",
21
+ types: "./dist/index.d.ts"
17
22
  }
18
- get(url, config, { headers = {} } = {}) {
19
- return this.queue.push({ url, config, headers });
20
- }
21
- post(url, config, { headers = {}, data = {} } = {}) {
22
- return this.queue.push({ url, method: 'POST', config, headers, data });
23
- }
24
- worker({ url, method = 'GET', config, headers, data }) {
25
- const params = Object.assign(Object.assign({}, config), { url, apikey: this.apiKey });
26
- const finalHeaders = Object.assign({ 'User-Agent': `zenrows/${version_1.VERSION} node` }, headers);
27
- const axiosRequestConfig = {
28
- baseURL: API_URL,
29
- method,
30
- params,
31
- headers: finalHeaders,
32
- data,
33
- };
34
- if (headers && Object.keys(headers).length) {
35
- params.custom_headers = true;
23
+ },
24
+ scripts: {
25
+ build: "tsup src/index.ts --dts --format cjs,esm --clean",
26
+ test: "vitest"
27
+ },
28
+ keywords: ["sdk", "zenrows", "scraping"],
29
+ author: "ZenRows",
30
+ repository: {
31
+ type: "git",
32
+ url: "https://github.com/ZenRows/zenrows-node-sdk.git"
33
+ },
34
+ homepage: "https://github.com/ZenRows/zenrows-node-sdk#readme",
35
+ url: "https://github.com/ZenRows/zenrows-node-sdk/issues",
36
+ type: "module",
37
+ license: "MIT",
38
+ devDependencies: {
39
+ "@biomejs/biome": "1.8.1",
40
+ "@types/async-retry": "^1.4.8",
41
+ "@types/node": "^20.14.2",
42
+ msw: "^2.3.1",
43
+ tsup: "^8.1.0",
44
+ typescript: "^5.4.5",
45
+ vitest: "^1.6.0"
46
+ },
47
+ dependencies: {
48
+ fastq: "^1.17.1",
49
+ "fetch-retry": "^6.0.0"
50
+ }
51
+ };
52
+
53
+ // src/index.ts
54
+ var API_URL = "https://api.zenrows.com/v1/";
55
+ var ZenRows = class {
56
+ apiKey;
57
+ clientConfig;
58
+ queue;
59
+ fetchWithRetry;
60
+ constructor(apiKey, clientConfig = {}) {
61
+ this.apiKey = apiKey;
62
+ this.clientConfig = clientConfig;
63
+ const retries = this.clientConfig.retries ?? 0;
64
+ this.queue = fastq.promise(
65
+ this,
66
+ this.worker,
67
+ this.clientConfig.concurrency ?? 5
68
+ );
69
+ this.fetchWithRetry = fetchRetry(fetch, {
70
+ retryDelay: (attempt) => 2 ** attempt * 1e3,
71
+ // retryOn: [422, 503, 504],
72
+ retryOn: (attempt, error, response) => {
73
+ if (attempt > retries) {
74
+ return false;
36
75
  }
37
- return (0, axios_1.default)(axiosRequestConfig);
38
- }
39
- applyRetries() {
40
- var _a;
41
- const retries = (_a = this.clientConfig.retries) !== null && _a !== void 0 ? _a : 0;
42
- if (retries > 0) {
43
- (0, axios_retry_1.default)(axios_1.default, {
44
- retries,
45
- retryDelay: axios_retry_1.default.exponentialDelay,
46
- retryCondition: (error) => {
47
- var _a;
48
- if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 429) {
49
- return true;
50
- }
51
- return axios_retry_1.default.isNetworkOrIdempotentRequestError(error);
52
- },
53
- });
76
+ if (error !== null || response?.status === 422 || response?.status === 503 || response?.status === 504) {
77
+ return true;
78
+ }
79
+ return false;
80
+ }
81
+ });
82
+ }
83
+ get(url, config, { headers = {} } = {}) {
84
+ return this.queue.push({ url, config, headers });
85
+ }
86
+ post(url, config, { headers = {}, data = {} } = {
87
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
88
+ }) {
89
+ const normalizedHeaders = Object.keys(headers).reduce(
90
+ (acc, key) => {
91
+ const value = headers[key];
92
+ if (value !== void 0) {
93
+ if (key.toLowerCase() === "content-type") {
94
+ acc["Content-Type"] = value;
95
+ } else {
96
+ acc[key] = value;
97
+ }
54
98
  }
99
+ return acc;
100
+ },
101
+ {}
102
+ );
103
+ return this.queue.push({
104
+ url,
105
+ method: "POST",
106
+ config,
107
+ headers: {
108
+ "Content-Type": "application/x-www-form-urlencoded",
109
+ ...normalizedHeaders
110
+ },
111
+ data
112
+ });
113
+ }
114
+ async worker({
115
+ url,
116
+ method = "GET",
117
+ config,
118
+ headers,
119
+ data
120
+ }) {
121
+ const params = new URLSearchParams({
122
+ url,
123
+ apikey: this.apiKey
124
+ });
125
+ if (config) {
126
+ for (const [key, value] of Object.entries(config)) {
127
+ if (value !== void 0) {
128
+ params.append(key, String(value));
129
+ }
130
+ }
131
+ }
132
+ if (headers && Object.keys(headers).length) {
133
+ params.append("custom_headers", "true");
134
+ }
135
+ const finalHeaders = {
136
+ "User-Agent": `zenrows/${package_default.version} node`,
137
+ ...headers
138
+ };
139
+ const fetchOptions = {
140
+ method,
141
+ headers: finalHeaders
142
+ };
143
+ if (method === "POST" && data) {
144
+ if (typeof data === "object") {
145
+ fetchOptions.body = JSON.stringify(data);
146
+ } else {
147
+ fetchOptions.body = String(data);
148
+ }
55
149
  }
56
- }
57
- exports.ZenRows = ZenRows;
150
+ const response = await this.fetchWithRetry(
151
+ `${API_URL}?${params.toString()}`,
152
+ fetchOptions
153
+ );
154
+ return response;
155
+ }
156
+ };
157
+ export {
158
+ ZenRows
159
+ };
package/package.json CHANGED
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "name": "zenrows",
3
- "version": "1.3.2",
3
+ "version": "2.0.1",
4
4
  "description": "ZenRows Node SDK",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
11
+ "packageManager": "pnpm@9.4.0",
12
+ "exports": {
13
+ ".": {
14
+ "require": "./dist/index.cjs",
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
7
19
  "scripts": {
8
- "eslint": "eslint .",
9
- "prepare": "npm run build",
10
- "prebuild": "node -p \"'export const VERSION = \\'' + require('./package.json').version + '\\';'\" > src/version.ts",
11
- "build": "tsc",
12
- "test": "jest"
20
+ "build": "tsup src/index.ts --dts --format cjs,esm --clean",
21
+ "test": "vitest"
13
22
  },
14
- "keywords": [
15
- "sdk",
16
- "zenrows",
17
- "scraping"
18
- ],
23
+ "keywords": ["sdk", "zenrows", "scraping"],
19
24
  "author": "ZenRows",
20
25
  "repository": {
21
26
  "type": "git",
@@ -23,25 +28,19 @@
23
28
  },
24
29
  "homepage": "https://github.com/ZenRows/zenrows-node-sdk#readme",
25
30
  "url": "https://github.com/ZenRows/zenrows-node-sdk/issues",
31
+ "type": "module",
26
32
  "license": "MIT",
27
33
  "devDependencies": {
28
- "@types/jest": "^27.0.1",
29
- "@types/node": "^16.6.0",
30
- "@typescript-eslint/eslint-plugin": "^4.29.1",
31
- "@typescript-eslint/parser": "^4.29.1",
32
- "axios-mock-adapter": "^1.20.0",
33
- "eslint": "^7.32.0",
34
- "eslint-config-airbnb-base": "^14.2.1",
35
- "eslint-config-prettier": "^8.3.0",
36
- "eslint-plugin-prettier": "^3.4.0",
37
- "jest": "^27.2.1",
38
- "prettier": "^2.3.2",
39
- "ts-jest": "^27.0.5",
40
- "typescript": "^4.4.2"
34
+ "@biomejs/biome": "1.8.1",
35
+ "@types/async-retry": "^1.4.8",
36
+ "@types/node": "^20.14.2",
37
+ "msw": "^2.3.1",
38
+ "tsup": "^8.1.0",
39
+ "typescript": "^5.4.5",
40
+ "vitest": "^1.6.0"
41
41
  },
42
42
  "dependencies": {
43
- "axios": "^0.21.4",
44
- "axios-retry": "^3.2.4",
45
- "fastq": "^1.13.0"
43
+ "fastq": "^1.17.1",
44
+ "fetch-retry": "^6.0.0"
46
45
  }
47
46
  }
@@ -0,0 +1,10 @@
1
+ // vitest.config.ts
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ include: ["tests/**/*.test.ts"],
8
+ setupFiles: ["tests/_setup.ts"],
9
+ },
10
+ });
package/dist/version.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare const VERSION = "1.3.2";
package/dist/version.js DELETED
@@ -1,4 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VERSION = void 0;
4
- exports.VERSION = '1.3.2';