blocket.js 1.1.2 → 1.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 CHANGED
@@ -14,6 +14,7 @@ blocket.js is a lightweight and easy-to-use npm package that provides a TypeScri
14
14
  - **Configurable**: Global and per-request configuration options let you override API endpoints, logging preferences, retry attempts, and more.
15
15
  - **TypeScript Support**: Fully typed interfaces for query parameters, API responses, and advertisements.
16
16
  - **Robust Error Handling & Logging**: Automatic retries on token expiry with configurable logging to assist in debugging.
17
+ - **Automatic Pagination**: Seamlessly fetches all results across multiple pages without any additional configuration.
17
18
 
18
19
  ## Installation
19
20
 
@@ -40,8 +41,9 @@ import client from 'blocket.js';
40
41
 
41
42
  (async () => {
42
43
  try {
44
+ // This automatically fetches all results across all pages
43
45
  const ads = await client.find({ query: 'macbook air' });
44
- console.log(ads);
46
+ console.log(`Found ${ads.length} total listings`);
45
47
  } catch (error) {
46
48
  console.error('Error fetching ads:', error);
47
49
  }
@@ -99,19 +101,19 @@ import client from 'blocket.js';
99
101
 
100
102
  `client.find(query: BlocketQueryConfig, fetchOptions?: FetchOptions<'json', any>): Promise<BlocketAd[]>`
101
103
 
102
- Searches for ads on Blocket based on the provided query parameters.
104
+ Searches for ads on Blocket based on the provided query parameters. Automatically handles pagination to return all matching listings across all pages.
103
105
 
104
106
  - Parameters:
105
107
  - `query`: An object conforming to the `BlocketQueryConfig` interface:
106
108
  - `query` (string): The search query (e.g., `'macbook air'`).
107
- - `limit` (number, optional): Maximum number of results to return (default: 20).
109
+ - `limit` (number, optional): Maximum number of results to return per page (default: 20).
108
110
  - `sort` (string, optional): Sorting order (default: `'rel'`).
109
111
  - `listingType` (string, optional): Listing type; `'s'` for selling, `'b'` for buying (default: `'s'`).
110
112
  - `status` (string, optional): Ad status (`'active'` or `'inactive'`, default: `'active'`).
111
113
  - `geolocation` (number, optional): Maximum distance in kilometers.
112
114
  - `include` (string, optional): Additional filters or fields to include (e.g., 'extend_with_shipping').
113
115
  - `fetchOptions` (optional): Additional options to pass to the underlying fetch request.
114
- - Returns: A promise that resolves to an array of `BlocketAd` objects.
116
+ - Returns: A promise that resolves to an array of `BlocketAd` objects from all available pages.
115
117
 
116
118
  `client.findById(adId: string, fetchOptions?: FetchOptions<'json', any>): Promise<BlocketAd>`
117
119
 
@@ -126,8 +128,4 @@ Retrieves a specific ad by its ID.
126
128
 
127
129
  This project is licensed under the [MIT License](https://github.com/rutbergphilip/blocket.js/blob/main/LICENSE) – free for personal and commercial use.
128
130
 
129
- If you find this project useful, crediting the author is greatly appreciated!
130
-
131
- ## Star History
132
-
133
- [![Star History Chart](https://api.star-history.com/svg?repos=rutbergphilip/blocket.js&type=Date)](https://star-history.com/#rutbergphilip/blocket.js&Date)
131
+ If you find this project useful, crediting the author and contributing to the project is greatly appreciated!
@@ -2,9 +2,10 @@ import type { FetchOptions } from 'ofetch';
2
2
  import type { BlocketAd, BlocketQueryConfig } from '../types';
3
3
  /**
4
4
  * Find ads on Blocket based on query parameters.
5
+ * Automatically handles pagination if the API returns multiple pages of results.
5
6
  * @param query Blocket query parameters.
6
7
  * @param fetchOptions Additional fetch options.
7
- * @returns Array of Blocket ads.
8
+ * @returns Array of Blocket ads from all available pages.
8
9
  */
9
10
  export declare function find(query: BlocketQueryConfig, fetchOptions?: FetchOptions<'json', any>): Promise<BlocketAd[]>;
10
11
  /**
@@ -41,9 +41,10 @@ function remapQueryParams(params) {
41
41
  }
42
42
  /**
43
43
  * Find ads on Blocket based on query parameters.
44
+ * Automatically handles pagination if the API returns multiple pages of results.
44
45
  * @param query Blocket query parameters.
45
46
  * @param fetchOptions Additional fetch options.
46
- * @returns Array of Blocket ads.
47
+ * @returns Array of Blocket ads from all available pages.
47
48
  */
48
49
  function find(query, fetchOptions) {
49
50
  return __awaiter(this, void 0, void 0, function* () {
@@ -51,11 +52,36 @@ function find(query, fetchOptions) {
51
52
  throw new Error('Query string is required');
52
53
  const config = (0, config_1.getBaseConfig)();
53
54
  const queryConfig = (0, config_1.createQueryConfig)(query);
54
- const response = yield (0, request_1.apiRequest)(config.apiBaseUrl, Object.assign({ query: remapQueryParams(queryConfig) }, fetchOptions));
55
- if (!response || !response.data || !Array.isArray(response.data)) {
56
- throw new Error(`Unexpected Blocket API response structure, expected array of ads, got: ${typeof (response === null || response === void 0 ? void 0 : response.data)}`);
55
+ const params = remapQueryParams(queryConfig);
56
+ const firstPageResponse = yield (0, request_1.apiRequest)(config.apiBaseUrl, Object.assign({ query: params }, fetchOptions));
57
+ if (!firstPageResponse ||
58
+ !firstPageResponse.data ||
59
+ !Array.isArray(firstPageResponse.data)) {
60
+ throw new Error(`Unexpected Blocket API response structure, expected array of ads, got: ${typeof (firstPageResponse === null || firstPageResponse === void 0 ? void 0 : firstPageResponse.data)}`);
57
61
  }
58
- return response.data;
62
+ if (firstPageResponse.total_page_count <= 1) {
63
+ return firstPageResponse.data;
64
+ }
65
+ const allAds = [...firstPageResponse.data];
66
+ const totalPages = firstPageResponse.total_page_count;
67
+ const pagePromises = [];
68
+ for (let page = 2; page <= totalPages; page++) {
69
+ Object.assign(params, {
70
+ page,
71
+ });
72
+ const pagePromise = (0, request_1.apiRequest)(config.apiBaseUrl, Object.assign({ query: params }, fetchOptions));
73
+ pagePromises.push(pagePromise);
74
+ }
75
+ const pageResponses = yield Promise.all(pagePromises);
76
+ for (const response of pageResponses) {
77
+ if (response && response.data && Array.isArray(response.data)) {
78
+ allAds.push(...response.data);
79
+ }
80
+ else {
81
+ throw new Error(`Unexpected Blocket API response structure in paginated results, expected array of ads`);
82
+ }
83
+ }
84
+ return allAds;
59
85
  });
60
86
  }
61
87
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/client/index.ts"],"names":[],"mappings":";;;;;;;;;;;AAqDA,oBAwBC;AAQD,4BAcC;AAnGD,uCAAuC;AACvC,sCAA6D;AAW7D;;;;GAIG;AACH,SAAS,gBAAgB,CACvB,MAA0B;IAE1B,MAAM,OAAO,GAGT;QACF,KAAK,EAAE,GAAG;QACV,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,SAAS;KACnB,CAAC;IAEF,MAAM,QAAQ,GAAsC,EAAE,CAAC;IAEvD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,GAA+B,CAAC,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,GAA+B,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE;gBACtB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAA+B,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAoC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,SAAsB,IAAI,CACxB,KAAyB,EACzB,YAAwC;;QAExC,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAE9D,MAAM,MAAM,GAAG,IAAA,sBAAa,GAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,IAAA,0BAAiB,EAAC,KAAK,CAAC,CAAC;QAE7C,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAU,EAC/B,MAAM,CAAC,UAAU,kBAEf,KAAK,EAAE,gBAAgB,CAAC,WAAW,CAAC,IACjC,YAAY,EAElB,CAAC;QAEF,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACjE,MAAM,IAAI,KAAK,CACb,0EAA0E,OAAO,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,IAAI,CAAA,EAAE,CAClG,CAAC;QACJ,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;CAAA;AAED;;;;;GAKG;AACH,SAAsB,QAAQ,CAC5B,IAAY,EACZ,YAAwC;;QAExC,MAAM,MAAM,GAAG,IAAA,sBAAa,GAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QAE3C,MAAM,EAAE,GAAG,MAAM,IAAA,oBAAU,EAAoB,GAAG,EAAE,YAAY,CAAC,CAAC;QAElE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,IAAI,CAAA,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,EAAE,CAAC,IAAI,CAAC;IACjB,CAAC;CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/client/index.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export default client;
3
3
  import { configure } from './config';
4
4
  export { configure };
5
5
  import type { BlocketQueryConfig } from './types/config';
6
- export type { BlocketQueryConfig };
6
+ import type { BlocketAd } from './types';
7
+ export type { BlocketQueryConfig, BlocketAd };
@@ -7,18 +7,30 @@ export type BlocketAccessToken = {
7
7
  isLoggedIn: false;
8
8
  bearerToken: string;
9
9
  };
10
- /**
11
- * Blocket API response containing an array of ads.
12
- */
13
- export interface BlocketAdSearchResponse {
14
- data: BlocketAd[];
15
- }
16
10
  /**
17
11
  * Blocket API response containing a single ad.
18
12
  */
19
13
  export interface BlocketAdResponse {
20
14
  data: BlocketAd;
21
15
  }
16
+ /**
17
+ * Blocket API response containing an array of ads with additional metadata.
18
+ */
19
+ export interface BlocketApiResponse {
20
+ data: BlocketAd[];
21
+ gallery: unknown[];
22
+ inventory: Record<string, unknown>;
23
+ next_scroll_block: number;
24
+ next_scroll_id: string;
25
+ non_shipping_count: number;
26
+ query_signature: string;
27
+ saveable: boolean;
28
+ selected_values: string;
29
+ share_url: string;
30
+ title: string;
31
+ total_count: number;
32
+ total_page_count: number;
33
+ }
22
34
  /**
23
35
  * Blocket advertisement object.
24
36
  */
@@ -6,8 +6,8 @@ import type {
6
6
  BlocketQueryParamsNative,
7
7
  BlocketAd,
8
8
  BlocketAdResponse,
9
- BlocketAdSearchResponse,
10
9
  BlocketQueryConfig,
10
+ BlocketApiResponse,
11
11
  } from '../types';
12
12
 
13
13
  /**
@@ -47,9 +47,10 @@ function remapQueryParams(
47
47
 
48
48
  /**
49
49
  * Find ads on Blocket based on query parameters.
50
+ * Automatically handles pagination if the API returns multiple pages of results.
50
51
  * @param query Blocket query parameters.
51
52
  * @param fetchOptions Additional fetch options.
52
- * @returns Array of Blocket ads.
53
+ * @returns Array of Blocket ads from all available pages.
53
54
  */
54
55
  export async function find(
55
56
  query: BlocketQueryConfig,
@@ -60,21 +61,60 @@ export async function find(
60
61
  const config = getBaseConfig();
61
62
  const queryConfig = createQueryConfig(query);
62
63
 
63
- const response = await apiRequest<BlocketAdSearchResponse>(
64
+ const params = remapQueryParams(queryConfig);
65
+
66
+ const firstPageResponse = await apiRequest<BlocketApiResponse>(
64
67
  config.apiBaseUrl,
65
68
  {
66
- query: remapQueryParams(queryConfig),
69
+ query: params,
67
70
  ...fetchOptions,
68
71
  }
69
72
  );
70
73
 
71
- if (!response || !response.data || !Array.isArray(response.data)) {
74
+ if (
75
+ !firstPageResponse ||
76
+ !firstPageResponse.data ||
77
+ !Array.isArray(firstPageResponse.data)
78
+ ) {
72
79
  throw new Error(
73
- `Unexpected Blocket API response structure, expected array of ads, got: ${typeof response?.data}`
80
+ `Unexpected Blocket API response structure, expected array of ads, got: ${typeof firstPageResponse?.data}`
74
81
  );
75
82
  }
76
83
 
77
- return response.data;
84
+ if (firstPageResponse.total_page_count <= 1) {
85
+ return firstPageResponse.data;
86
+ }
87
+
88
+ const allAds = [...firstPageResponse.data];
89
+ const totalPages = firstPageResponse.total_page_count;
90
+
91
+ const pagePromises = [];
92
+ for (let page = 2; page <= totalPages; page++) {
93
+ Object.assign(params, {
94
+ page,
95
+ });
96
+
97
+ const pagePromise = apiRequest<BlocketApiResponse>(config.apiBaseUrl, {
98
+ query: params,
99
+ ...fetchOptions,
100
+ });
101
+
102
+ pagePromises.push(pagePromise);
103
+ }
104
+
105
+ const pageResponses = await Promise.all(pagePromises);
106
+
107
+ for (const response of pageResponses) {
108
+ if (response && response.data && Array.isArray(response.data)) {
109
+ allAds.push(...response.data);
110
+ } else {
111
+ throw new Error(
112
+ `Unexpected Blocket API response structure in paginated results, expected array of ads`
113
+ );
114
+ }
115
+ }
116
+
117
+ return allAds;
78
118
  }
79
119
 
80
120
  /**
package/lib/index.ts CHANGED
@@ -5,4 +5,5 @@ import { configure } from './config';
5
5
  export { configure };
6
6
 
7
7
  import type { BlocketQueryConfig } from './types/config';
8
- export type { BlocketQueryConfig };
8
+ import type { BlocketAd } from './types';
9
+ export type { BlocketQueryConfig, BlocketAd };
@@ -10,17 +10,29 @@ export type BlocketAccessToken = {
10
10
  };
11
11
 
12
12
  /**
13
- * Blocket API response containing an array of ads.
13
+ * Blocket API response containing a single ad.
14
14
  */
15
- export interface BlocketAdSearchResponse {
16
- data: BlocketAd[];
15
+ export interface BlocketAdResponse {
16
+ data: BlocketAd;
17
17
  }
18
18
 
19
19
  /**
20
- * Blocket API response containing a single ad.
20
+ * Blocket API response containing an array of ads with additional metadata.
21
21
  */
22
- export interface BlocketAdResponse {
23
- data: BlocketAd;
22
+ export interface BlocketApiResponse {
23
+ data: BlocketAd[];
24
+ gallery: unknown[];
25
+ inventory: Record<string, unknown>;
26
+ next_scroll_block: number;
27
+ next_scroll_id: string;
28
+ non_shipping_count: number;
29
+ query_signature: string;
30
+ saveable: boolean;
31
+ selected_values: string;
32
+ share_url: string;
33
+ title: string;
34
+ total_count: number;
35
+ total_page_count: number;
24
36
  }
25
37
 
26
38
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blocket.js",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "A user-friendly js wrapper for blocket.se",
5
5
  "keywords": [
6
6
  "blocket",