encarapi 1.0.0 → 1.1.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 +12 -1
- package/package.json +1 -1
- package/src/china.js +35 -0
- package/src/index.d.ts +32 -3
package/README.md
CHANGED
|
@@ -66,6 +66,16 @@ for await (const change of client.korea.iterateChanges({ cursor: savedCursor ??
|
|
|
66
66
|
saveCursor(client.korea.lastCursor);
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
The China API works the same way (`client.china`); its events are
|
|
70
|
+
`{ id, source, vehicleId, type: "new" | "price" | "removed" | "relisted", oldPrice, newPrice, at }`:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
for await (const change of client.china.iterateChanges({ cursor: savedCursor ?? 0 })) {
|
|
74
|
+
// change.type: "new" | "price" | "removed" | "relisted"
|
|
75
|
+
}
|
|
76
|
+
saveCursor(client.china.lastCursor);
|
|
77
|
+
```
|
|
78
|
+
|
|
69
79
|
## Korea API (`client.korea`)
|
|
70
80
|
|
|
71
81
|
| Method | Endpoint | Notes |
|
|
@@ -92,10 +102,11 @@ saveCursor(client.korea.lastCursor);
|
|
|
92
102
|
| Method | Endpoint | Notes |
|
|
93
103
|
|---|---|---|
|
|
94
104
|
| `catalog(params)` | `GET /api/catalog` | `make`, `price_min/max` (CNY), `export_ready`, `sort`, ... |
|
|
105
|
+
| `iterateCatalog(params)` | `GET /api/catalog` | Async iterator over all pages (up to the 10,000-result depth limit) |
|
|
95
106
|
| `vehicle(id)` | `GET /api/vehicle/:id` | Price history, photos, seller, export status |
|
|
96
107
|
| `inspection(id)` | `GET /api/inspection/:id` | Accident, flood and fire checks, EV battery data |
|
|
97
108
|
| `bulkVehicles(ids)` | `POST /api/vehicle/bulk` | Up to 500 ids |
|
|
98
|
-
| `changes(params)` | `GET /api/catalog/changes` | Change feed |
|
|
109
|
+
| `changes(params)` / `iterateChanges(params)` | `GET /api/catalog/changes` | Change feed (Business/Scale) |
|
|
99
110
|
| `exportCsv()` | `GET /api/catalog/export` | Full catalog CSV |
|
|
100
111
|
| `enums()` / `models(make)` | `GET /api/enums`, `/api/models` | Filter values, models per make |
|
|
101
112
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "encarapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Official Node.js client for EnCarAPI: Korean used car data (Encar, KB Chachacha, K Car) and Chinese used car data (Dongchedi, Che168) in one REST API.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
package/src/china.js
CHANGED
|
@@ -4,6 +4,9 @@ const { HttpClient } = require("./http");
|
|
|
4
4
|
|
|
5
5
|
const enc = encodeURIComponent;
|
|
6
6
|
|
|
7
|
+
// /api/catalog serves at most this many results per query (page * limit).
|
|
8
|
+
const CATALOG_DEPTH = 10000;
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* Chinese used-car data (Dongchedi and Che168) in English. Works with a
|
|
9
12
|
* ChinaCarAPI key or an EnCarAPI key that has the China add-on.
|
|
@@ -22,6 +25,21 @@ class ChinaClient {
|
|
|
22
25
|
/** Search listings. Filters: source, make, model, year_min/max, price_min/max (CNY), mileage_max, city, fuel, has_report, export_ready, sort, page, limit, lang. */
|
|
23
26
|
catalog(params) { return this.http.request("GET", "/api/catalog", { params }); }
|
|
24
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Async iterator over all catalog pages: for await (const car of china.iterateCatalog({...})) {}
|
|
30
|
+
* Stops at the API's 10,000-result depth limit; for the whole catalog use
|
|
31
|
+
* exportCsv() plus iterateChanges().
|
|
32
|
+
*/
|
|
33
|
+
async *iterateCatalog(params = {}) {
|
|
34
|
+
const limit = Math.min(params.limit || 100, 100);
|
|
35
|
+
for (let page = params.page || 1; ; page++) {
|
|
36
|
+
const res = await this.catalog({ ...params, limit, page });
|
|
37
|
+
const items = res.results || [];
|
|
38
|
+
for (const item of items) yield item;
|
|
39
|
+
if (items.length < limit || (page + 1) * limit > CATALOG_DEPTH) return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
/** Full record for one car: price history, photos, seller, export status, alsoListedOn. */
|
|
26
44
|
vehicle(id, params) { return this.http.request("GET", `/api/vehicle/${enc(id)}`, { params }); }
|
|
27
45
|
|
|
@@ -37,6 +55,23 @@ class ChinaClient {
|
|
|
37
55
|
/** Change feed: { since } once, then { cursor: nextCursor } (Business/Scale). */
|
|
38
56
|
changes(params) { return this.http.request("GET", "/api/catalog/changes", { params }); }
|
|
39
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Follows the change feed until it is drained and yields every event once:
|
|
60
|
+
* { id, source, vehicleId, type: "new" | "price" | "removed" | "relisted", oldPrice, newPrice, at }.
|
|
61
|
+
* Pass { since } (right after an export) or { cursor } to start; after the loop,
|
|
62
|
+
* `china.lastCursor` holds the cursor to resume from next time.
|
|
63
|
+
*/
|
|
64
|
+
async *iterateChanges(params = {}) {
|
|
65
|
+
let query = { ...params };
|
|
66
|
+
for (;;) {
|
|
67
|
+
const res = await this.changes(query);
|
|
68
|
+
for (const item of res.changes || []) yield item;
|
|
69
|
+
if (res.nextCursor !== undefined) this.lastCursor = res.nextCursor;
|
|
70
|
+
if (!res.hasMore || res.nextCursor === undefined) return;
|
|
71
|
+
query = { ...params, since: undefined, cursor: res.nextCursor };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
40
75
|
/** Full catalog as CSV text (Business/Scale). */
|
|
41
76
|
exportCsv(params) { return this.http.request("GET", "/api/catalog/export", { params, text: true }); }
|
|
42
77
|
|
package/src/index.d.ts
CHANGED
|
@@ -169,16 +169,45 @@ export interface ChinaCatalogResponse {
|
|
|
169
169
|
results: Json[];
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
export interface ChinaChangesParams {
|
|
173
|
+
/** ISO timestamp for the first call. */
|
|
174
|
+
since?: string;
|
|
175
|
+
/** nextCursor from the previous call. */
|
|
176
|
+
cursor?: number;
|
|
177
|
+
source?: "dongchedi" | "che168" | (string & {});
|
|
178
|
+
limit?: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ChinaChange extends Json {
|
|
182
|
+
id: number;
|
|
183
|
+
source: string;
|
|
184
|
+
vehicleId: string;
|
|
185
|
+
type: "new" | "price" | "removed" | "relisted";
|
|
186
|
+
at: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface ChinaChangesResponse {
|
|
190
|
+
cursor: number;
|
|
191
|
+
nextCursor: number;
|
|
192
|
+
hasMore: boolean;
|
|
193
|
+
changes: ChinaChange[];
|
|
194
|
+
}
|
|
195
|
+
|
|
172
196
|
/** Chinese used-car data: Dongchedi and Che168. */
|
|
173
197
|
export class ChinaClient {
|
|
174
198
|
constructor(apiKey: string, options?: Pick<ClientOptions, "fetch"> & { baseUrl?: string });
|
|
199
|
+
/** Cursor to resume from after iterateChanges() finished. */
|
|
200
|
+
lastCursor?: number;
|
|
175
201
|
catalog(params?: ChinaCatalogParams): Promise<ChinaCatalogResponse>;
|
|
176
|
-
|
|
177
|
-
|
|
202
|
+
/** All pages up to the API's 10,000-result depth limit. */
|
|
203
|
+
iterateCatalog(params?: ChinaCatalogParams): AsyncGenerator<Json, void, unknown>;
|
|
204
|
+
vehicle(id: string, params?: { source?: "dongchedi" | "che168"; lang?: "en" | "zh" }): Promise<Json>;
|
|
205
|
+
inspection(id: string, params?: { source?: "dongchedi" | "che168"; lang?: "en" | "zh" }): Promise<Json>;
|
|
178
206
|
bulkVehicles(ids: string[], params?: { lang?: "en" | "zh" }): Promise<Json>;
|
|
179
207
|
/** Alias of bulkVehicles. */
|
|
180
208
|
bulk(ids: string[], params?: { lang?: "en" | "zh" }): Promise<Json>;
|
|
181
|
-
changes(params:
|
|
209
|
+
changes(params: ChinaChangesParams): Promise<ChinaChangesResponse>;
|
|
210
|
+
iterateChanges(params: ChinaChangesParams): AsyncGenerator<ChinaChange, void, unknown>;
|
|
182
211
|
exportCsv(params?: Json): Promise<string>;
|
|
183
212
|
enums(params?: Json): Promise<Json>;
|
|
184
213
|
models(make: string | number): Promise<Json>;
|