wenmar-open 0.1.0 → 0.3.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 +292 -2
- package/dist/batch.d.ts +5 -0
- package/dist/batch.js +7 -0
- package/dist/client.d.ts +153 -6
- package/dist/client.js +234 -7
- package/dist/errors.d.ts +21 -3
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -1
- package/dist/offline/client.d.ts +190 -0
- package/dist/offline/client.js +482 -0
- package/dist/offline/engine.d.ts +53 -0
- package/dist/offline/engine.js +129 -0
- package/dist/offline/index.d.ts +10 -0
- package/dist/offline/index.js +4 -0
- package/dist/offline/node.d.ts +26 -0
- package/dist/offline/node.js +105 -0
- package/dist/offline/store.d.ts +78 -0
- package/dist/offline/store.js +156 -0
- package/dist/offline/wasm-inline.js +2 -0
- package/dist/offline/wenmar_open.wasm +0 -0
- package/dist/schema.d.ts +5152 -471
- package/dist/types.d.ts +124 -0
- package/package.json +30 -4
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# wenmar-open
|
|
2
2
|
|
|
3
|
-
A typed client for the [Wenmar Open](https://open.wenmarpro.com) API: VIN decoding
|
|
3
|
+
A typed client for the [Wenmar Open](https://open.wenmarpro.com) API: VIN decoding, a year, make, model, trim and engine catalog, and the recall campaigns that may apply to a model year, for auto repair shops. The API needs no key and no account.
|
|
4
4
|
|
|
5
5
|
- No dependencies. It uses the platform's `fetch`.
|
|
6
|
+
- An [offline mode](#offline) that decodes from a data file with no network.
|
|
6
7
|
- Runs in Node 20 and later, browsers, Cloudflare Workers, Deno and Bun.
|
|
7
8
|
- Types are generated from the API's OpenAPI description.
|
|
8
9
|
|
|
@@ -71,6 +72,7 @@ try {
|
|
|
71
72
|
| `invalid_vin` | 400 | Wrong length or characters. `details.suggestions` lists likely corrections. |
|
|
72
73
|
| `validation_failed` | 400 | A parameter is missing or wrong. `details.field` names it. |
|
|
73
74
|
| `not_found` | 404 | No manufacturer for the VIN, or no vehicle with that id. |
|
|
75
|
+
| `dataset_unavailable` | 404 | The server holds no data of that kind, such as recalls. `details.dataset` names it. |
|
|
74
76
|
| `rate_limited` | 429 | Over 600 requests a minute from one address. `retryAfter` is in seconds. |
|
|
75
77
|
| `unavailable` | 503 | The server is busy. `retryAfter` is in seconds. |
|
|
76
78
|
| `internal_error` | 500 | Something went wrong on the server. |
|
|
@@ -139,6 +141,65 @@ console.log(same.summary);
|
|
|
139
141
|
|
|
140
142
|
A decoded VIN carries the same ids in `catalog`, so a VIN can fill in a vehicle form: `vehicle.catalog?.entry.id`.
|
|
141
143
|
|
|
144
|
+
## Recalls
|
|
145
|
+
|
|
146
|
+
Recall campaigns from NHTSA (United States) and Transport Canada. A campaign lists a year, a make and a model, so an answer is about a model year and never about one vehicle. Every answer has `may_apply: true`, and `lookup` gives the official addresses where a VIN can be checked. Neither government publishes recalls by VIN as data: say "may apply" where you show these, and send people to `lookup` to confirm.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { WenmarOpen } from "wenmar-open";
|
|
150
|
+
|
|
151
|
+
const open = new WenmarOpen();
|
|
152
|
+
|
|
153
|
+
// By vehicle id, or by VIN. The VIN only names the model year.
|
|
154
|
+
const byId = await open.recalls("2018_ford_f-150", { country: "us" });
|
|
155
|
+
const byVin = await open.vinRecalls("KM8K2CAB4PU001140");
|
|
156
|
+
|
|
157
|
+
for (const recall of byVin.recalls) {
|
|
158
|
+
console.log(recall.date, recall.source, recall.campaign, recall.component);
|
|
159
|
+
if (recall.do_not_drive) console.log("NHTSA advises not driving it until it is repaired.");
|
|
160
|
+
}
|
|
161
|
+
console.log(`${byVin.recalls.length} campaigns may apply. Check the VIN: ${byVin.lookup.nhtsa}`);
|
|
162
|
+
console.log(byId.as_of.us, byId.attribution);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`country` is `us` or `ca`; left out, both. `as_of` is the day each country's data was current, so an empty list means none was found as of that day. `attribution` lists the credit lines the data's licences ask for: show them where you show the data. A VIN that decodes to no vehicle in the catalog is not an error: `vehicle` is `null`, the list is empty, and a warning has the code `no_catalog_vehicle`.
|
|
166
|
+
|
|
167
|
+
One campaign with every vehicle it lists, and the list of campaigns, a page at a time:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { WenmarOpen } from "wenmar-open";
|
|
171
|
+
|
|
172
|
+
const open = new WenmarOpen();
|
|
173
|
+
|
|
174
|
+
const campaign = await open.recall("nhtsa", "19V888000");
|
|
175
|
+
console.log(campaign.summary, campaign.remedy, campaign.vehicles.length);
|
|
176
|
+
|
|
177
|
+
let page = await open.searchRecalls({ make: "ford", year: 2018, q: "tailgate", limit: 25 });
|
|
178
|
+
console.log(page.recalls.map((recall) => recall.campaign));
|
|
179
|
+
if (page.next_cursor) {
|
|
180
|
+
page = await open.searchRecalls({ make: "ford", year: 2018, q: "tailgate", limit: 25, cursor: page.next_cursor });
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A server run without recall data answers these four methods with `dataset_unavailable`, and `details.dataset` names what is missing. `open.meta()` lists what a server holds in `datasets`.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { WenmarOpen, WenmarOpenError } from "wenmar-open";
|
|
188
|
+
|
|
189
|
+
const open = new WenmarOpen({ baseUrl: "http://localhost:3000" });
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const answer = await open.recalls("2018_ford_f-150");
|
|
193
|
+
console.log(answer.recalls.length, "campaigns may apply");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error instanceof WenmarOpenError && error.code === "dataset_unavailable") {
|
|
196
|
+
console.log(`This server does not hold ${String(error.details["dataset"])}.`); // recalls_us or recalls_ca
|
|
197
|
+
} else {
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
142
203
|
## Options
|
|
143
204
|
|
|
144
205
|
```ts
|
|
@@ -175,13 +236,242 @@ Answers change only when the data does, about once a month. `open.meta()` return
|
|
|
175
236
|
|
|
176
237
|
One address may make 600 requests a minute. A server that calls the API for many users shares one address, so cache catalog answers there. To decode a list of VINs, use `decodeVins`: a batch of 50 counts as one request.
|
|
177
238
|
|
|
239
|
+
## Offline
|
|
240
|
+
|
|
241
|
+
`wenmar-open/offline` answers the same questions with no network. It runs the same decoder the API runs, compiled to WebAssembly and shipped inside this package, over a Wenmar Open data file: a plain SQLite database of about 167 MB. Recalls come from a second, smaller file ([below](#recalls-offline)). The methods, their answers and the error codes are those of `WenmarOpen`, with three more codes for the data ([below](#switching-between-the-api-and-the-data-file)), so code written for one works with the other.
|
|
242
|
+
|
|
243
|
+
The hosted client does not load any of this. An application that imports only `wenmar-open` gets the small client and nothing else.
|
|
244
|
+
|
|
245
|
+
### In Node
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
npm install wenmar-open wenmar-open-data
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`wenmar-open-data` is the data file as an npm package. It is 49 MB to download and 167 MB on disk, and a new version is published each month.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { WenmarOpenError } from "wenmar-open/offline";
|
|
255
|
+
import { openOffline } from "wenmar-open/offline/node";
|
|
256
|
+
|
|
257
|
+
const open = await openOffline();
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const vehicle = await open.decodeVin("KM8K2CAB4PU001140");
|
|
261
|
+
console.log(vehicle.year, vehicle.make, vehicle.model);
|
|
262
|
+
console.log(await open.models({ make: "honda", year: 2019 }));
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (error instanceof WenmarOpenError) console.error(error.code, error.message);
|
|
265
|
+
else throw error;
|
|
266
|
+
} finally {
|
|
267
|
+
open.close();
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`openOffline()` uses Node's own SQLite, so it needs Node 22.16 or later. It opens the file read-only and checks it before it returns. To read a file from somewhere else, such as one downloaded with `wenmar-open data pull`, pass its path:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { openOffline } from "wenmar-open/offline/node";
|
|
275
|
+
|
|
276
|
+
const open = await openOffline({ path: "/var/lib/wenmar-open/wenmar-open-2026.09.sqlite3" });
|
|
277
|
+
console.log((await open.meta()).data_version);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
On Node 20, or to use a database you opened yourself, give the client a store. `syncStore` takes a `DatabaseSync` of `node:sqlite` or a database of `better-sqlite3`:
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
import Database from "better-sqlite3";
|
|
284
|
+
import { path } from "wenmar-open-data";
|
|
285
|
+
import { WenmarOpenOffline, syncStore } from "wenmar-open/offline";
|
|
286
|
+
|
|
287
|
+
const database = new Database(path, { readonly: true });
|
|
288
|
+
const open = new WenmarOpenOffline({ store: syncStore(database) });
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Recalls offline
|
|
292
|
+
|
|
293
|
+
The recalls are in a second file, the shop file, published as `wenmar-open-data-shop` (17 MB to download, 77 MB on disk, a new version each week):
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm install wenmar-open wenmar-open-data wenmar-open-data-shop
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { openOffline } from "wenmar-open/offline/node";
|
|
301
|
+
|
|
302
|
+
const open = await openOffline();
|
|
303
|
+
|
|
304
|
+
const answer = await open.vinRecalls("KM8K2CAB4PU001140");
|
|
305
|
+
console.log(`${answer.recalls.length} recall campaigns may apply.`);
|
|
306
|
+
console.log("Check the VIN:", answer.lookup.nhtsa, answer.lookup.tc);
|
|
307
|
+
console.log((await open.meta()).datasets);
|
|
308
|
+
open.close();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`openOffline()` uses the shop file of `wenmar-open-data-shop` when that package is installed. Pass `{ shopPath }` to read one from somewhere else, such as the file `wenmar-open data pull --shop` downloads, or `{ shopPath: false }` to open none. The four recall methods are those of `WenmarOpen` and give the same answers, with one difference listed below. Without a shop file they fail with `dataset_unavailable`, and everything else works.
|
|
312
|
+
|
|
313
|
+
A shop file holds the ids of one data file's catalog, so it is read only with the data file it was built against: `wenmar-open-data-shop` names that exact version of `wenmar-open-data` as its peer. A pair that does not go together is refused with `data_invalid`, by `openOffline()` before it returns and otherwise by the first recall question. Update the two packages together.
|
|
314
|
+
|
|
315
|
+
With a store of your own, the shop file is a second store:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { WenmarOpenOffline } from "wenmar-open/offline";
|
|
319
|
+
import type { Store } from "wenmar-open/offline";
|
|
320
|
+
|
|
321
|
+
declare const data: Store;
|
|
322
|
+
declare const shop: Store;
|
|
323
|
+
|
|
324
|
+
export const open = new WenmarOpenOffline({ store: data, shop });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Each statement goes to the store of its file; a statement for the shop file is marked `db: "shop"`, which a store may ignore. The shop file is opened when a question first needs it, so a decode never reads it.
|
|
328
|
+
|
|
329
|
+
### One vehicle in one answer, offline
|
|
330
|
+
|
|
331
|
+
`overview(id)`, `vinOverview(vin)`, `modelYears(make, model)` and `recentRecalls({ since })` are those of `WenmarOpen`:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { openOffline } from "wenmar-open/offline/node";
|
|
335
|
+
|
|
336
|
+
const open = await openOffline();
|
|
337
|
+
|
|
338
|
+
const overview = await open.vinOverview("KM8K2CAB4PU001140");
|
|
339
|
+
console.log(overview.vehicle?.id, overview.links.page);
|
|
340
|
+
if (overview.recalls) {
|
|
341
|
+
const { us, ca } = overview.recalls.count;
|
|
342
|
+
console.log(`${(us ?? 0) + (ca ?? 0)} recall campaigns may apply. Check the VIN: ${overview.recalls.lookup.nhtsa}`);
|
|
343
|
+
} else {
|
|
344
|
+
console.log("No shop file: no recalls to count.");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const years = await open.modelYears("hyundai", "kona");
|
|
348
|
+
console.log(years.years.map((year) => [year.id, year.recalls]));
|
|
349
|
+
open.close();
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`overview`, `vinOverview` and `modelYears` answer with no shop file too: `recalls` is then `null`, in the overview and in each year of `modelYears`. `recentRecalls` needs the shop file, as the other recall methods do. A block the files do not give is `null`, as it is from a server that does not hold it: see the list below.
|
|
353
|
+
|
|
354
|
+
### Fuel, specifications, fluids and tires offline
|
|
355
|
+
|
|
356
|
+
`fuel`, `specs`, `fluids` and `tires`, and `vinFuel`, `vinSpecs`, `vinFluids` and `vinTires`, are those of `WenmarOpen`, read from the shop file.
|
|
357
|
+
|
|
358
|
+
`code(code)`, `codes(query)` and `vehicleCode(id, code)` are those of `WenmarOpen`: generic OBD-II trouble codes described in this project's own words, read from the shop file. Offline, `vehicleCode` gives the description and the recalls about the same components; its `bulletins` and `related.bulletins` are always `null`, because bulletins are on the hosted service only.
|
|
359
|
+
|
|
360
|
+
`jobs()`, `job(job)`, `labor(id, job?)` and `vinLabor(vin, job?)` are those of `WenmarOpen`: the job list and Wenmar's labour estimates, read from the shop file. Each figure has `hours` (`low`, `typical`, `high`), a `basis` and the `scope` it is for. An estimate, not a quote; credit it as `attribution` says: `Labour estimates by Wenmar Pro, CC BY 4.0`.
|
|
361
|
+
|
|
362
|
+
`safety(id)` is that of `WenmarOpen`: NHTSA's crash test ratings for a model year, read from the shop file.
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import { openOffline } from "wenmar-open/offline/node";
|
|
366
|
+
|
|
367
|
+
const open = await openOffline();
|
|
368
|
+
const safety = await open.safety("2023_hyundai_kona");
|
|
369
|
+
for (const rating of safety.ratings) {
|
|
370
|
+
console.log(rating.description, rating.stars.overall ?? "no overall rating");
|
|
371
|
+
}
|
|
372
|
+
console.log(`NHTSA's file of ${safety.as_of}: ${safety.source_url}`);
|
|
373
|
+
open.close();
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
NHTSA rates a vehicle by body style and drive, so `ratings` often has several entries and their stars can differ: `description` says which vehicle each is for, in NHTSA's words. Show them all, or the one whose body style and drive you know. A rating with every star `null` is a vehicle NHTSA lists and has not tested, and `ratings` is empty when NHTSA lists none. Without a shop file, or with one that holds no ratings, `safety` fails with `dataset_unavailable` and `details.dataset` is `ratings`. The answer is the hosted API's, field for field.
|
|
377
|
+
|
|
378
|
+
`complaints(id)` is that of `WenmarOpen` too: what owners reported to NHTSA about a model year, as counts by component with the median mileage they gave. These are owners' reports and are not verified, and a count is not a rate. Without a shop file that holds the complaints, it fails with `dataset_unavailable` and `details.dataset` is `complaints`.
|
|
379
|
+
|
|
380
|
+
### Switching between the API and the data file
|
|
381
|
+
|
|
382
|
+
Both clients have the same methods, so the choice can be one line:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { WenmarOpen } from "wenmar-open";
|
|
386
|
+
import type { VinDecode } from "wenmar-open";
|
|
387
|
+
import type { WenmarOpenOffline } from "wenmar-open/offline";
|
|
388
|
+
import { openOffline } from "wenmar-open/offline/node";
|
|
389
|
+
|
|
390
|
+
async function client(offline: boolean): Promise<WenmarOpen | WenmarOpenOffline> {
|
|
391
|
+
return offline ? openOffline() : new WenmarOpen();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const open = await client(true);
|
|
395
|
+
const vehicle: VinDecode = await open.decodeVin("KM8K2CAB4PU001140");
|
|
396
|
+
console.log(vehicle.catalog?.vehicle_id);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
What differs offline:
|
|
400
|
+
|
|
401
|
+
- `error.status` is always `undefined`: there is no HTTP answer. Branch on `error.code`.
|
|
402
|
+
- Three more codes: `no_data` (no data file where one was looked for), `data_invalid` (the database is not a data file this version reads) and `store_error` (the database failed while it was read, including while it was first opened, so a retry may succeed; `data_invalid` is for rows that were read and are not a data file, and for a schema version this client does not read).
|
|
403
|
+
- `search` reads the text as a year, a make, a model and a submodel, in that order. The API also finds a model from its words in any order, with an index the data file does not have. `search({ q: "civic type r" })` finds the Civic Type R in both; `search({ q: "type r" })` finds it only through the API.
|
|
404
|
+
- `meta().server_version` is the version of this package.
|
|
405
|
+
- `meta().datasets` lists what the shop file holds, and is `{}` when no shop file is open: the data file holds the VIN decoder and the catalog only.
|
|
406
|
+
- `lookup.maker` in a recall answer is always `null`. The makers' own recall lookups are part of the hosted service and are in neither file. `lookup.nhtsa` and `lookup.tc` are always there.
|
|
407
|
+
- `links.maker` in an overview is always `null`, for the same reason. `specs`, `fuel`, `fluids` and `tires` in an overview are what `specs(id)`, `fuel(id)`, `fluids(id)` and `tires(id)` give when the shop file holds them, and `null` otherwise. `safety` is what `safety(id)` gives when the shop file holds the ratings, and `null` otherwise.
|
|
408
|
+
- A recall's `url` is its page on `https://open.wenmarpro.com`, whichever server you would otherwise ask. So are `links.page` and every other address in an overview or in `modelYears`.
|
|
409
|
+
- `decodeVins` takes no `include`: the recall count of a batch is the hosted API's alone. Ask `vinOverview` for each VIN instead.
|
|
410
|
+
- `timeoutMs` and `signal` are checked each time the client goes back to the database, not while the database is working. There is no time limit unless you set one.
|
|
411
|
+
- The current year, which bounds the model years a VIN can have, comes from the device's clock. Pass `currentYear` to set it.
|
|
412
|
+
|
|
413
|
+
### In Cloudflare Workers
|
|
414
|
+
|
|
415
|
+
A Worker cannot carry a 167 MB file. Import the data into a D1 database once (see the [`wenmar-open-data` README](https://www.npmjs.com/package/wenmar-open-data)), or use the hosted API. For recalls, import the shop file into a second D1 database (see the [`wenmar-open-data-shop` README](https://www.npmjs.com/package/wenmar-open-data-shop)) and pass it as `shop: d1Store(env.SHOP_DB)`.
|
|
416
|
+
|
|
417
|
+
```js
|
|
418
|
+
import { WenmarOpenOffline, d1Store } from "wenmar-open/offline";
|
|
419
|
+
import wasm from "wenmar-open/offline.wasm";
|
|
420
|
+
|
|
421
|
+
let open;
|
|
422
|
+
|
|
423
|
+
export default {
|
|
424
|
+
async fetch(request, env) {
|
|
425
|
+
open ??= new WenmarOpenOffline({ store: d1Store(env.DB), wasm });
|
|
426
|
+
const vin = new URL(request.url).pathname.slice(1);
|
|
427
|
+
return Response.json(await open.decodeVin(vin));
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Workers do not compile WebAssembly from bytes, so the module is imported as a file and passed as `wasm`. Everywhere else the option is left out.
|
|
433
|
+
|
|
434
|
+
A decode reads D1 6 or 7 times and runs 10 to 15 statements, each of which counts as one D1 query. The first question an isolate answers also reads about 12,000 rows of makes, once. `years()` reads every model-year row of the catalog, about 490,000, on every call: keep its answer.
|
|
435
|
+
|
|
436
|
+
### A store of your own
|
|
437
|
+
|
|
438
|
+
Anything that can run a `SELECT` on the data file can be a store: an object with one method, which is given a list of statements and returns the rows of each, in order, as arrays of values. It may return them at once or as a promise.
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
import { WenmarOpenOffline } from "wenmar-open/offline";
|
|
442
|
+
import type { Statement, Store } from "wenmar-open/offline";
|
|
443
|
+
|
|
444
|
+
declare function run(sql: string, params: readonly (string | number | null)[]): Promise<unknown[][]>;
|
|
445
|
+
|
|
446
|
+
const store: Store = {
|
|
447
|
+
query: (statements: readonly Statement[]) => Promise.all(statements.map(({ sql, params }) => run(sql, params))),
|
|
448
|
+
};
|
|
449
|
+
export const open = new WenmarOpenOffline({ store });
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Rows must be arrays, not objects keyed by column name: two columns of one statement may share a name. Integers may be numbers or BigInts.
|
|
453
|
+
|
|
454
|
+
### Data versions
|
|
455
|
+
|
|
456
|
+
A version of `wenmar-open` reads data files of one schema version, and `wenmar-open-data`'s major version is that schema version: `wenmar-open-data@3.202609.0` is the data of September 2026 in schema version 3. A file of another schema version is refused with `data_invalid` and a message naming both versions. The shop file has a schema version of its own, and `wenmar-open-data-shop@1.20261012.0` is the shop file of 12 October 2026 in schema version 1.
|
|
457
|
+
|
|
458
|
+
| `wenmar-open` | reads `wenmar-open-data` | reads `wenmar-open-data-shop` |
|
|
459
|
+
|---|---|---|
|
|
460
|
+
| 0.2 | 3.x | 1.x |
|
|
461
|
+
|
|
462
|
+
### Size
|
|
463
|
+
|
|
464
|
+
The decoder is 703 kB of WebAssembly (238 kB compressed). The package holds it twice, as a file and as text inside a module, so the package is 1.9 MB unpacked. None of it is loaded by `import "wenmar-open"`.
|
|
465
|
+
|
|
466
|
+
In a browser the page's content security policy must allow WebAssembly (`'wasm-unsafe-eval'`). The data file is too large to send to a browser; use the hosted API there.
|
|
467
|
+
|
|
178
468
|
## CommonJS
|
|
179
469
|
|
|
180
470
|
The package is an ES module. `require("wenmar-open")` works in Node 20.19 and later and in Node 22.12 and later. In older Node 20 releases use `await import("wenmar-open")`.
|
|
181
471
|
|
|
182
472
|
## Types
|
|
183
473
|
|
|
184
|
-
Every response type is exported: `VinDecode`, `Entry`, `Make`, `Model`, `Submodel`, `EngineOption`, `Meta`, `BatchItem`, `ErrorBody`. The generated `paths`, `operations` and `components` types are exported as well.
|
|
474
|
+
Every response type is exported: `VinDecode`, `Entry`, `Make`, `Model`, `Submodel`, `EngineOption`, `Meta`, `BatchItem`, `ErrorBody`, and for recalls `VehicleRecalls`, `VinRecalls`, `RecallCampaign`, `RecallList`, `Recall` and `RecallLookup`. So are the query types, such as `RecallSearchQuery`. The generated `paths`, `operations` and `components` types are exported as well.
|
|
185
475
|
|
|
186
476
|
The API only ever adds fields and endpoints. A response may carry fields this version's types do not name; they are passed through untouched.
|
|
187
477
|
|
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BatchItem, ErrorBody } from "./types.js";
|
|
2
|
+
/** The most VINs decoded in one batch. */
|
|
3
|
+
export declare const MAX_BATCH = 50;
|
|
4
|
+
/** Whether an item of a batch is the error for its VIN and not a decode. */
|
|
5
|
+
export declare function isBatchError(item: BatchItem): item is ErrorBody;
|
package/dist/batch.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The most VINs decoded in one batch. */
|
|
2
|
+
export const MAX_BATCH = 50;
|
|
3
|
+
/** Whether an item of a batch is the error for its VIN and not a decode. */
|
|
4
|
+
export function isBatchError(item) {
|
|
5
|
+
const error = item.error;
|
|
6
|
+
return typeof error === "object" && error !== null && !Array.isArray(error);
|
|
7
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { BatchItem, EngineOption, EnginesQuery, Entry,
|
|
1
|
+
import type { BatchItem, BulletinDetail, BulletinFilterQuery, BulletinList, BulletinSearchQuery, Code, CodeList, CodeSearchQuery, EngineOption, EnginesQuery, Entry, Make, MakeDetail, MakesQuery, Meta, Model, ModelYears, ModelsQuery, Overview, PayrollDeductions, PayrollDeductionsQuery, PartsCard, RecallCampaign, RecallInvestigations, RecallList, RecallSearchQuery, RecentRecalls, RecentRecallsQuery, SearchQuery, Submodel, SubmodelsQuery, VehicleFluids, VehicleFuel, VehicleFuelQuery, VehicleInvestigations, VehicleInvestigationsQuery, JobDetail, JobList, VehicleLabor, VinLabor, VehicleBulletins, VehicleCode, VehicleRecalls, VehicleRecallsQuery, VehicleComplaints, VehicleSafety, VehicleSpecs, VehicleTires, VinDecode, VinFluids, VinFuel, VinFuelQuery, VinBulletins, VinBulletinsQuery, VinRecalls, VinRecallsQuery, VinSpecs, VinTires, VinYearQuery, YearsQuery } from "./types.js";
|
|
2
2
|
/** The hosted API. */
|
|
3
3
|
export declare const DEFAULT_BASE_URL = "https://open.wenmarpro.com";
|
|
4
4
|
/** How long a request may take before it fails with `timeout`. */
|
|
5
5
|
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
6
|
-
/** The most VINs the API decodes in one batch. */
|
|
7
|
-
export declare const MAX_BATCH = 50;
|
|
8
6
|
/** What the client asks of a `fetch`: the part of the standard it uses. */
|
|
9
7
|
export interface FetchInit {
|
|
10
8
|
method: string;
|
|
@@ -47,8 +45,13 @@ export interface DecodeOptions extends RequestOptions {
|
|
|
47
45
|
/** Use this model year instead of working it out from the VIN. */
|
|
48
46
|
year?: number;
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
export interface BatchOptions extends RequestOptions {
|
|
49
|
+
/**
|
|
50
|
+
* `"recalls"` adds to each decode the number of recall campaigns that may
|
|
51
|
+
* apply to the model year it reaches, as `recalls`.
|
|
52
|
+
*/
|
|
53
|
+
include?: "recalls";
|
|
54
|
+
}
|
|
52
55
|
/** A client for the Wenmar Open API. It keeps no state between requests. */
|
|
53
56
|
export declare class WenmarOpen {
|
|
54
57
|
#private;
|
|
@@ -62,7 +65,7 @@ export declare class WenmarOpen {
|
|
|
62
65
|
* decode, or the error a single request for that VIN would have given.
|
|
63
66
|
* Tell them apart with `isBatchError`.
|
|
64
67
|
*/
|
|
65
|
-
decodeVins(vins: readonly string[], options?:
|
|
68
|
+
decodeVins(vins: readonly string[], options?: BatchOptions): Promise<BatchItem[]>;
|
|
66
69
|
/** Model years, newest first. */
|
|
67
70
|
years(query?: YearsQuery, options?: RequestOptions): Promise<number[]>;
|
|
68
71
|
/** Makes, popular ones first and the rest by name. */
|
|
@@ -79,6 +82,150 @@ export declare class WenmarOpen {
|
|
|
79
82
|
search(query: SearchQuery, options?: RequestOptions): Promise<Entry[]>;
|
|
80
83
|
/** One catalog entry by its id, such as `2019_honda_civic_si`. */
|
|
81
84
|
vehicle(id: string, options?: RequestOptions): Promise<Entry>;
|
|
85
|
+
/**
|
|
86
|
+
* NHTSA's crash test ratings for a model year, by its id such as
|
|
87
|
+
* `2018_ford_f-150`. NHTSA rates by body style and drive, so `ratings`
|
|
88
|
+
* often has several entries: each names the vehicle it is for.
|
|
89
|
+
*/
|
|
90
|
+
safety(id: string, options?: RequestOptions): Promise<VehicleSafety>;
|
|
91
|
+
/**
|
|
92
|
+
* What owners reported to NHTSA about a model year, as counts by
|
|
93
|
+
* component. These are owners' reports: not verified, and not a rate.
|
|
94
|
+
*/
|
|
95
|
+
complaints(id: string, options?: RequestOptions): Promise<VehicleComplaints>;
|
|
96
|
+
/**
|
|
97
|
+
* One generic trouble code, such as `P0420`, described in this project's
|
|
98
|
+
* own words. A manufacturer-specific code has no description: the call
|
|
99
|
+
* fails with `not_found`.
|
|
100
|
+
*/
|
|
101
|
+
code(code: string, options?: RequestOptions): Promise<Code>;
|
|
102
|
+
/** The trouble codes that are described, by code, narrowed by text or system. */
|
|
103
|
+
codes(query?: CodeSearchQuery, options?: RequestOptions): Promise<CodeList>;
|
|
104
|
+
/**
|
|
105
|
+
* A trouble code on one model year: its description, the bulletins of
|
|
106
|
+
* that model year that name it, and the bulletins and recalls about the
|
|
107
|
+
* same components. Any code answers, a manufacturer-specific one too.
|
|
108
|
+
*/
|
|
109
|
+
vehicleCode(id: string, code: string, options?: RequestOptions): Promise<VehicleCode>;
|
|
110
|
+
/** The repair jobs a labour estimate can be about. A job's id never changes. */
|
|
111
|
+
jobs(options?: RequestOptions): Promise<JobList>;
|
|
112
|
+
/** One repair job: what it covers, and what it typically takes by class of vehicle. */
|
|
113
|
+
job(job: string, options?: RequestOptions): Promise<JobDetail>;
|
|
114
|
+
/**
|
|
115
|
+
* Wenmar's labour estimates for a model year, or for one `job` of it:
|
|
116
|
+
* for each job, the hours of the narrowest scope that has a figure, and
|
|
117
|
+
* which scope that was. An estimate, not a quote. Credit it as
|
|
118
|
+
* `attribution` says.
|
|
119
|
+
*/
|
|
120
|
+
labor(id: string, job?: string, options?: RequestOptions): Promise<VehicleLabor>;
|
|
121
|
+
/** Labour estimates for the vehicle a VIN decodes to, or for one `job` of it. */
|
|
122
|
+
vinLabor(vin: string, job?: string, options?: RequestOptions & {
|
|
123
|
+
year?: number;
|
|
124
|
+
}): Promise<VinLabor>;
|
|
125
|
+
/**
|
|
126
|
+
* The income tax, CPP, CPP2 and EI on one regular pay in Ontario, by
|
|
127
|
+
* CRA's T4127 formulas, with the net pay, the employer's share and every
|
|
128
|
+
* factor used. Claims left out are the basic personal amounts (claim
|
|
129
|
+
* code 1). An estimate: the employer remains liable to CRA; check with
|
|
130
|
+
* CRA's PDOC at `pdoc_url`. Not available offline.
|
|
131
|
+
*/
|
|
132
|
+
payrollDeductions(query: PayrollDeductionsQuery, options?: RequestOptions): Promise<PayrollDeductions>;
|
|
133
|
+
/** One make by its id, name or alias, such as `honda`, with the maker's own links. */
|
|
134
|
+
make(make: string, options?: RequestOptions): Promise<MakeDetail>;
|
|
135
|
+
/**
|
|
136
|
+
* Recalls that may apply to a model year, by its id such as
|
|
137
|
+
* `2018_ford_f-150`. The list is for the year, make and model: it is not
|
|
138
|
+
* a lookup by VIN, and `lookup` says where one vehicle can be checked.
|
|
139
|
+
*/
|
|
140
|
+
recalls(id: string, query?: VehicleRecallsQuery, options?: RequestOptions): Promise<VehicleRecalls>;
|
|
141
|
+
/**
|
|
142
|
+
* Recalls that may apply to the model year a VIN decodes to. The VIN only
|
|
143
|
+
* names the model year: no public data says a recall applies to one VIN.
|
|
144
|
+
*/
|
|
145
|
+
vinRecalls(vin: string, query?: VinRecallsQuery, options?: RequestOptions): Promise<VinRecalls>;
|
|
146
|
+
/** One recall campaign: `source` is `nhtsa` or `tc`. */
|
|
147
|
+
recall(source: string, campaign: string, options?: RequestOptions): Promise<RecallCampaign>;
|
|
148
|
+
/** Recall campaigns, newest first, narrowed by what is given. */
|
|
149
|
+
searchRecalls(query?: RecallSearchQuery, options?: RequestOptions): Promise<RecallList>;
|
|
150
|
+
/**
|
|
151
|
+
* Manufacturer communications that list a model year: service bulletins,
|
|
152
|
+
* service campaigns, warranty extensions and over-the-air updates, one
|
|
153
|
+
* page, newest first. `counts` says how many there are of each kind.
|
|
154
|
+
*/
|
|
155
|
+
bulletins(id: string, query?: BulletinFilterQuery, options?: RequestOptions): Promise<VehicleBulletins>;
|
|
156
|
+
/** Manufacturer communications for the model year a VIN decodes to. */
|
|
157
|
+
vinBulletins(vin: string, query?: VinBulletinsQuery, options?: RequestOptions): Promise<VinBulletins>;
|
|
158
|
+
/** One manufacturer communication, by NHTSA's id number. */
|
|
159
|
+
bulletin(nhtsaId: number | string, options?: RequestOptions): Promise<BulletinDetail>;
|
|
160
|
+
/** Manufacturer communications, newest first, narrowed by what is given. */
|
|
161
|
+
searchBulletins(query?: BulletinSearchQuery, options?: RequestOptions): Promise<BulletinList>;
|
|
162
|
+
/**
|
|
163
|
+
* Fuel consumption ratings of a model year, from EPA and Natural Resources
|
|
164
|
+
* Canada. Each rating says how far it was matched in `confidence`, and
|
|
165
|
+
* names a catalog engine only when the match settled one.
|
|
166
|
+
*/
|
|
167
|
+
fuel(id: string, query?: VehicleFuelQuery, options?: RequestOptions): Promise<VehicleFuel>;
|
|
168
|
+
/**
|
|
169
|
+
* Fuel consumption ratings for the vehicle a VIN decodes to: narrowed to
|
|
170
|
+
* its engine when `engine_settled` is `true`, the model year's otherwise.
|
|
171
|
+
*/
|
|
172
|
+
vinFuel(vin: string, query?: VinFuelQuery, options?: RequestOptions): Promise<VinFuel>;
|
|
173
|
+
/** Specifications of a model year from EPA's files, one entry per rating. */
|
|
174
|
+
specs(id: string, options?: RequestOptions): Promise<VehicleSpecs>;
|
|
175
|
+
/** Specifications for the vehicle a VIN decodes to. */
|
|
176
|
+
vinSpecs(vin: string, query?: VinYearQuery, options?: RequestOptions): Promise<VinSpecs>;
|
|
177
|
+
/**
|
|
178
|
+
* Fluids of a model year: the engine oil viscosity grade the maker
|
|
179
|
+
* certified each engine with, per EPA, and the records a person read from
|
|
180
|
+
* the maker's own documents, each with its source and the day it was checked.
|
|
181
|
+
*/
|
|
182
|
+
fluids(id: string, options?: RequestOptions): Promise<VehicleFluids>;
|
|
183
|
+
/** Fluids for the vehicle a VIN decodes to: one engine's when the decode settles it. */
|
|
184
|
+
vinFluids(vin: string, query?: VinYearQuery, options?: RequestOptions): Promise<VinFluids>;
|
|
185
|
+
/**
|
|
186
|
+
* Original-equipment tire fitments of a model year, read by a person from
|
|
187
|
+
* the door placard or the owner's manual. Every size on record is listed
|
|
188
|
+
* with the trims it covers; none is picked.
|
|
189
|
+
*/
|
|
190
|
+
tires(id: string, options?: RequestOptions): Promise<VehicleTires>;
|
|
191
|
+
/** Tire fitments of the model year a VIN decodes to. Never narrowed: a VIN does not name the wheels. */
|
|
192
|
+
vinTires(vin: string, query?: VinYearQuery, options?: RequestOptions): Promise<VinTires>;
|
|
193
|
+
/**
|
|
194
|
+
* The parts-ordering card for a VIN: what a parts counter asks for, each
|
|
195
|
+
* line with where it came from. `text` is the card ready to paste.
|
|
196
|
+
*/
|
|
197
|
+
partsCard(vin: string, query?: VinYearQuery, options?: RequestOptions): Promise<PartsCard>;
|
|
198
|
+
/**
|
|
199
|
+
* Everything held for a model year in one answer, by its id such as
|
|
200
|
+
* `2018_ford_f-150`. A block the server has no data for is `null`.
|
|
201
|
+
* `recalls` counts the campaigns that may apply to the year, make and
|
|
202
|
+
* model: it is not a lookup by VIN.
|
|
203
|
+
*/
|
|
204
|
+
overview(id: string, options?: RequestOptions): Promise<Overview>;
|
|
205
|
+
/**
|
|
206
|
+
* The overview of the model year a VIN decodes to, with `decode`. The VIN
|
|
207
|
+
* only names the model year: no public data says a recall applies to one
|
|
208
|
+
* VIN.
|
|
209
|
+
*/
|
|
210
|
+
vinOverview(vin: string, options?: DecodeOptions): Promise<Overview>;
|
|
211
|
+
/**
|
|
212
|
+
* Every model year of one model, newest first, each with its vehicle id
|
|
213
|
+
* and the number of recall campaigns that may apply to it.
|
|
214
|
+
*/
|
|
215
|
+
modelYears(make: string, model: string, options?: RequestOptions): Promise<ModelYears>;
|
|
216
|
+
/**
|
|
217
|
+
* Recall campaigns reported on or after `since` (a day such as
|
|
218
|
+
* `2026-09-01`), newest first, each with the vehicles it may apply to.
|
|
219
|
+
*/
|
|
220
|
+
recentRecalls(query: RecentRecallsQuery, options?: RequestOptions): Promise<RecentRecalls>;
|
|
221
|
+
/**
|
|
222
|
+
* The open NHTSA investigations that list a model year, by its id such as
|
|
223
|
+
* `2019_ram_1500`; `{ status: "all" }` adds the closed ones. An
|
|
224
|
+
* investigation is not a finding that a defect exists: show `note`.
|
|
225
|
+
*/
|
|
226
|
+
investigations(id: string, query?: VehicleInvestigationsQuery, options?: RequestOptions): Promise<VehicleInvestigations>;
|
|
227
|
+
/** The NHTSA investigations that led to a recall campaign: `source` is `nhtsa` or `tc`. */
|
|
228
|
+
recallInvestigations(source: string, campaign: string, options?: RequestOptions): Promise<RecallInvestigations>;
|
|
82
229
|
/** The data version, the vPIC release it came from, and the build time. */
|
|
83
230
|
meta(options?: RequestOptions): Promise<Meta>;
|
|
84
231
|
}
|