wenmar-open 0.2.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 +163 -9
- package/dist/client.d.ts +153 -2
- package/dist/client.js +233 -1
- package/dist/errors.d.ts +9 -2
- package/dist/index.d.ts +2 -2
- package/dist/offline/client.d.ts +123 -3
- package/dist/offline/client.js +241 -4
- package/dist/offline/index.d.ts +1 -1
- package/dist/offline/node.d.ts +6 -0
- package/dist/offline/node.js +27 -0
- package/dist/offline/store.d.ts +6 -0
- package/dist/offline/wasm-inline.js +1 -1
- package/dist/offline/wenmar_open.wasm +0 -0
- package/dist/schema.d.ts +5151 -470
- package/dist/types.d.ts +124 -0
- package/package.json +10 -3
package/dist/offline/client.js
CHANGED
|
@@ -8,7 +8,18 @@ import { cleanRows } from "./store.js";
|
|
|
8
8
|
* at most 7 and a search at most 4. Reaching this means something is wrong.
|
|
9
9
|
*/
|
|
10
10
|
export const MAX_STEPS = 32;
|
|
11
|
-
const keyOf = (statement) => `${statement.sql}\u0000${JSON.stringify(statement.params)}`;
|
|
11
|
+
const keyOf = (statement) => `${statement.db ?? ""}\u0000${statement.sql}\u0000${JSON.stringify(statement.params)}`;
|
|
12
|
+
/**
|
|
13
|
+
* The operations that are not about recalls alone and still read the shop
|
|
14
|
+
* file when there is one. Without a shop store they answer all the same.
|
|
15
|
+
*/
|
|
16
|
+
const READS_SHOP = new Set(["meta", "overview_vehicle", "overview_vin", "model_years"]);
|
|
17
|
+
/** The operations about trouble codes. Each needs the shop file. */
|
|
18
|
+
const CODE_OPS = new Set(["code", "codes", "vehicle_code", "jobs", "job", "labor_vehicle", "labor_vin"]);
|
|
19
|
+
/** Whether a question reads the shop file, when there is one. */
|
|
20
|
+
const needsShop = (question) => READS_SHOP.has(question.op) || question.op.startsWith("recall") || question.op === "safety" || question.op === "complaints" || FACTS.test(question.op) || CODE_OPS.has(question.op);
|
|
21
|
+
/** The operations for fuel, specifications, fluids and tire fitments, such as `fuel_vin`. */
|
|
22
|
+
const FACTS = /^(fuel|specs|fluids|tires)_(vehicle|vin)$/;
|
|
12
23
|
function thrown(failure) {
|
|
13
24
|
return new WenmarOpenError({ code: failure.code, message: failure.message, details: failure.details });
|
|
14
25
|
}
|
|
@@ -47,12 +58,16 @@ async function read(store, statements) {
|
|
|
47
58
|
*/
|
|
48
59
|
export class WenmarOpenOffline {
|
|
49
60
|
#store;
|
|
61
|
+
#shop;
|
|
62
|
+
/** The copy of the decoder the shop file was opened on, and that opening. */
|
|
63
|
+
#shopOpened;
|
|
50
64
|
#wasm;
|
|
51
65
|
#currentYear;
|
|
52
66
|
#module;
|
|
53
67
|
#engine;
|
|
54
68
|
constructor(options) {
|
|
55
69
|
this.#store = options.store;
|
|
70
|
+
this.#shop = options.shop;
|
|
56
71
|
this.#wasm = options.wasm;
|
|
57
72
|
this.#currentYear = options.currentYear;
|
|
58
73
|
}
|
|
@@ -114,16 +129,178 @@ export class WenmarOpenOffline {
|
|
|
114
129
|
async vehicle(id, options = {}) {
|
|
115
130
|
return this.#one({ op: "vehicle", args: { id: String(id) } }, options);
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* NHTSA's crash test ratings for a model year, by its id such as
|
|
134
|
+
* `2018_ford_f-150`. NHTSA rates by body style and drive, so `ratings`
|
|
135
|
+
* often has several entries: each names the vehicle it is for. Needs a
|
|
136
|
+
* shop file that holds the ratings: without one it fails with
|
|
137
|
+
* `dataset_unavailable`.
|
|
138
|
+
*/
|
|
139
|
+
async safety(id, options = {}) {
|
|
140
|
+
return this.#one({ op: "safety", args: { id: String(id) } }, options);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* What owners reported to NHTSA about a model year, as counts by component
|
|
144
|
+
* with the median mileage they gave. Without a shop file that holds the
|
|
145
|
+
* complaints it fails with `dataset_unavailable`.
|
|
146
|
+
*/
|
|
147
|
+
async complaints(id, options = {}) {
|
|
148
|
+
return this.#one({ op: "complaints", args: { id: String(id) } }, options);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Fuel consumption ratings of a model year, from EPA and Natural
|
|
152
|
+
* Resources Canada. Each says in `confidence` how far it was matched:
|
|
153
|
+
* `model`, `engine` or `submodel`. Needs a shop file that holds them:
|
|
154
|
+
* without one it fails with `dataset_unavailable`.
|
|
155
|
+
*/
|
|
156
|
+
async fuel(id, query = {}, options = {}) {
|
|
157
|
+
return this.#one({ op: "fuel_vehicle", args: { ...query, id: String(id) } }, options);
|
|
158
|
+
}
|
|
159
|
+
/** Fuel ratings for the vehicle a VIN decodes to: one engine's when the decode settles it. */
|
|
160
|
+
async vinFuel(vin, query = {}, options = {}) {
|
|
161
|
+
return this.#one({ op: "fuel_vin", args: { ...query, vin: String(vin) } }, options);
|
|
162
|
+
}
|
|
163
|
+
/** Specifications of a model year from EPA's files, one entry per EPA rating. */
|
|
164
|
+
async specs(id, options = {}) {
|
|
165
|
+
return this.#one({ op: "specs_vehicle", args: { id: String(id) } }, options);
|
|
166
|
+
}
|
|
167
|
+
/** Specifications for the vehicle a VIN decodes to: one engine's when the decode settles it. */
|
|
168
|
+
async vinSpecs(vin, query = {}, options = {}) {
|
|
169
|
+
return this.#one({ op: "specs_vin", args: { ...query, vin: String(vin) } }, options);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Fluids of a model year: the engine oil viscosity grade the maker
|
|
173
|
+
* certified each engine with, per EPA, and the records a person read from
|
|
174
|
+
* the maker's own documents, each with its source and the day it was checked.
|
|
175
|
+
*/
|
|
176
|
+
async fluids(id, options = {}) {
|
|
177
|
+
return this.#one({ op: "fluids_vehicle", args: { id: String(id) } }, options);
|
|
178
|
+
}
|
|
179
|
+
/** Fluids for the vehicle a VIN decodes to: one engine's when the decode settles it. */
|
|
180
|
+
async vinFluids(vin, query = {}, options = {}) {
|
|
181
|
+
return this.#one({ op: "fluids_vin", args: { ...query, vin: String(vin) } }, options);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Original-equipment tire fitments of a model year. Every size on record
|
|
185
|
+
* is listed with the trims it covers; none is picked.
|
|
186
|
+
*/
|
|
187
|
+
async tires(id, options = {}) {
|
|
188
|
+
return this.#one({ op: "tires_vehicle", args: { id: String(id) } }, options);
|
|
189
|
+
}
|
|
190
|
+
/** Tire fitments of the model year a VIN decodes to. Never narrowed: a VIN does not name the wheels. */
|
|
191
|
+
async vinTires(vin, query = {}, options = {}) {
|
|
192
|
+
return this.#one({ op: "tires_vin", args: { ...query, vin: String(vin) } }, options);
|
|
193
|
+
}
|
|
194
|
+
/** The repair jobs a labour estimate can be about. Needs a shop file that holds the job list. */
|
|
195
|
+
async jobs(options = {}) {
|
|
196
|
+
return this.#one({ op: "jobs", args: {} }, options);
|
|
197
|
+
}
|
|
198
|
+
/** One repair job: what it covers, and what it typically takes by class of vehicle. */
|
|
199
|
+
async job(job, options = {}) {
|
|
200
|
+
return this.#one({ op: "job", args: { job: String(job) } }, options);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Wenmar's labour estimates for a model year, or for one `job` of it.
|
|
204
|
+
* An estimate, not a quote; credit it as `attribution` says. Needs a
|
|
205
|
+
* shop file that holds the estimates.
|
|
206
|
+
*/
|
|
207
|
+
async labor(id, job, options = {}) {
|
|
208
|
+
return this.#one({ op: "labor_vehicle", args: { id: String(id), job } }, options);
|
|
209
|
+
}
|
|
210
|
+
/** Labour estimates for the vehicle a VIN decodes to, or for one `job` of it. */
|
|
211
|
+
async vinLabor(vin, job, options = {}) {
|
|
212
|
+
return this.#one({ op: "labor_vin", args: { vin: String(vin), job, year: options.year } }, options);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* One generic trouble code, such as `P0420`, described in this project's
|
|
216
|
+
* own words. Needs a shop file that holds the codes: without one it
|
|
217
|
+
* fails with `dataset_unavailable`.
|
|
218
|
+
*/
|
|
219
|
+
async code(code, options = {}) {
|
|
220
|
+
return this.#one({ op: "code", args: { code: String(code) } }, options);
|
|
221
|
+
}
|
|
222
|
+
/** The trouble codes that are described, by code, narrowed by text or system. */
|
|
223
|
+
async codes(query = {}, options = {}) {
|
|
224
|
+
return this.#one({ op: "codes", args: { ...query } }, options);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A trouble code on one model year: its description and the recalls
|
|
228
|
+
* about the same components. `bulletins` and `related.bulletins` are
|
|
229
|
+
* always `null` offline: bulletins are on the hosted service only.
|
|
230
|
+
*/
|
|
231
|
+
async vehicleCode(id, code, options = {}) {
|
|
232
|
+
return this.#one({ op: "vehicle_code", args: { id: String(id), code: String(code) } }, options);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Recalls that may apply to a model year, by its id such as
|
|
236
|
+
* `2018_ford_f-150`. The list is for the year, make and model: it is not
|
|
237
|
+
* a lookup by VIN, and `lookup` says where one vehicle can be checked.
|
|
238
|
+
* Needs a shop file: without one it fails with `dataset_unavailable`.
|
|
239
|
+
*/
|
|
240
|
+
async recalls(id, query = {}, options = {}) {
|
|
241
|
+
return this.#one({ op: "recalls_vehicle", args: { ...query, id: String(id) } }, options);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Recalls that may apply to the model year a VIN decodes to. The VIN only
|
|
245
|
+
* names the model year: no public data says a recall applies to one VIN.
|
|
246
|
+
*/
|
|
247
|
+
async vinRecalls(vin, query = {}, options = {}) {
|
|
248
|
+
return this.#one({ op: "recalls_vin", args: { ...query, vin: String(vin) } }, options);
|
|
249
|
+
}
|
|
250
|
+
/** One recall campaign: `source` is `nhtsa` or `tc`. */
|
|
251
|
+
async recall(source, campaign, options = {}) {
|
|
252
|
+
return this.#one({ op: "recall", args: { source: String(source), campaign: String(campaign) } }, options);
|
|
253
|
+
}
|
|
254
|
+
/** Recall campaigns, newest first, narrowed by what is given. */
|
|
255
|
+
async searchRecalls(query = {}, options = {}) {
|
|
256
|
+
return this.#one({ op: "recalls_search", args: query }, options);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Everything held for a model year in one answer, by its id such as
|
|
260
|
+
* `2018_ford_f-150`. A block the files do not hold is `null`. `recalls`
|
|
261
|
+
* counts the campaigns that may apply to the year, make and model: it is
|
|
262
|
+
* not a lookup by VIN. With no shop file `recalls` is `null`, and the
|
|
263
|
+
* method still answers.
|
|
264
|
+
*/
|
|
265
|
+
async overview(id, options = {}) {
|
|
266
|
+
return this.#one({ op: "overview_vehicle", args: { id: String(id) } }, options);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The overview of the model year a VIN decodes to, with `decode`. The VIN
|
|
270
|
+
* only names the model year: no public data says a recall applies to one
|
|
271
|
+
* VIN.
|
|
272
|
+
*/
|
|
273
|
+
async vinOverview(vin, options = {}) {
|
|
274
|
+
return this.#one({ op: "overview_vin", args: { vin: String(vin), year: options.year } }, options);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Every model year of one model, newest first, each with its vehicle id
|
|
278
|
+
* and the number of recall campaigns that may apply to it: `null` with no
|
|
279
|
+
* shop file.
|
|
280
|
+
*/
|
|
281
|
+
async modelYears(make, model, options = {}) {
|
|
282
|
+
return this.#one({ op: "model_years", args: { make: String(make), model: String(model) } }, options);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Recall campaigns reported on or after `since` (a day such as
|
|
286
|
+
* `2026-09-01`), newest first, each with the vehicles it may apply to.
|
|
287
|
+
* Needs a shop file: without one it fails with `dataset_unavailable`.
|
|
288
|
+
*/
|
|
289
|
+
async recentRecalls(query, options = {}) {
|
|
290
|
+
return this.#one({ op: "recalls_recent", args: query }, options);
|
|
291
|
+
}
|
|
117
292
|
/**
|
|
118
293
|
* The data version, the vPIC release it came from, and the build time.
|
|
119
|
-
* `server_version` is the version of this package.
|
|
294
|
+
* `server_version` is the version of this package. `datasets` lists what
|
|
295
|
+
* the shop file holds, and is empty when no shop store was given.
|
|
120
296
|
*/
|
|
121
297
|
async meta(options = {}) {
|
|
122
298
|
return this.#one({ op: "meta", args: {} }, options);
|
|
123
299
|
}
|
|
124
|
-
/** Closes the
|
|
300
|
+
/** Closes the stores, if they were opened for this client. */
|
|
125
301
|
close() {
|
|
126
302
|
this.#store.close?.();
|
|
303
|
+
this.#shop?.close?.();
|
|
127
304
|
}
|
|
128
305
|
async #one(question, options) {
|
|
129
306
|
const [outcome] = await this.#ask([question], options);
|
|
@@ -178,6 +355,64 @@ export class WenmarOpenOffline {
|
|
|
178
355
|
}
|
|
179
356
|
throw new WenmarOpenError({ code: "internal_error", message: "Opening the data did not finish." });
|
|
180
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* The rows of every statement, each from the store its file is in: a
|
|
360
|
+
* statement marked `db: "shop"` is for the shop file.
|
|
361
|
+
*/
|
|
362
|
+
async #read(statements) {
|
|
363
|
+
const ofShop = statements.map((statement) => statement.db === "shop");
|
|
364
|
+
if (!ofShop.includes(true))
|
|
365
|
+
return read(this.#store, statements);
|
|
366
|
+
const shop = this.#shop;
|
|
367
|
+
if (shop === undefined) {
|
|
368
|
+
throw new WenmarOpenError({
|
|
369
|
+
code: "internal_error",
|
|
370
|
+
message: "The decoder asked for the shop file, and no shop store was given.",
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const forData = statements.filter((_, index) => !ofShop[index]);
|
|
374
|
+
const forShop = statements.filter((_, index) => ofShop[index]);
|
|
375
|
+
const [fromData, fromShop] = await Promise.all([
|
|
376
|
+
forData.length === 0 ? [] : read(this.#store, forData),
|
|
377
|
+
read(shop, forShop),
|
|
378
|
+
]);
|
|
379
|
+
let [data, other] = [0, 0];
|
|
380
|
+
return statements.map((_, index) => (ofShop[index] ? fromShop[other++] : fromData[data++]) ?? []);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Opens the shop file on `engine`, once for each copy of the decoder. It
|
|
384
|
+
* is opened when a question first needs it, so a shop store that fails
|
|
385
|
+
* does not stop a VIN from being decoded.
|
|
386
|
+
*/
|
|
387
|
+
async #withShop(engine) {
|
|
388
|
+
if (this.#shopOpened?.engine !== engine) {
|
|
389
|
+
this.#shopOpened = { engine, done: this.#openShop(engine) };
|
|
390
|
+
}
|
|
391
|
+
const opening = this.#shopOpened;
|
|
392
|
+
try {
|
|
393
|
+
await opening.done;
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
if (this.#shopOpened === opening)
|
|
397
|
+
this.#shopOpened = undefined;
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async #openShop(engine) {
|
|
402
|
+
const answers = [];
|
|
403
|
+
for (let step = 0; step < MAX_STEPS; step += 1) {
|
|
404
|
+
const answer = engine.call({ op: "open_shop", answers });
|
|
405
|
+
if ("ok" in answer)
|
|
406
|
+
return;
|
|
407
|
+
if ("error" in answer)
|
|
408
|
+
throw thrown(answer.error);
|
|
409
|
+
const rows = await this.#read(answer.need);
|
|
410
|
+
answer.need.forEach((statement, index) => {
|
|
411
|
+
answers.push({ ...statement, rows: cleanRows(rows[index], statement.sql) });
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
throw new WenmarOpenError({ code: "internal_error", message: "Opening the shop file did not finish." });
|
|
415
|
+
}
|
|
181
416
|
/**
|
|
182
417
|
* Answers every question, going back to the store as often as the
|
|
183
418
|
* decoder asks. What several questions need in the same step is read
|
|
@@ -202,6 +437,8 @@ export class WenmarOpenOffline {
|
|
|
202
437
|
});
|
|
203
438
|
}
|
|
204
439
|
const engine = await this.#ready();
|
|
440
|
+
if (this.#shop !== undefined && questions.some(needsShop))
|
|
441
|
+
await this.#withShop(engine);
|
|
205
442
|
const wanted = new Map();
|
|
206
443
|
const waiting = [];
|
|
207
444
|
questions.forEach((question, index) => {
|
|
@@ -228,7 +465,7 @@ export class WenmarOpenOffline {
|
|
|
228
465
|
throw new WenmarOpenError({ code: "internal_error", message: "The decoder kept asking for more data." });
|
|
229
466
|
}
|
|
230
467
|
const statements = [...wanted.values()];
|
|
231
|
-
const rows = await read(
|
|
468
|
+
const rows = await this.#read(statements);
|
|
232
469
|
const found = new Map();
|
|
233
470
|
statements.forEach((statement, index) => {
|
|
234
471
|
found.set(keyOf(statement), { ...statement, rows: cleanRows(rows[index], statement.sql) });
|
package/dist/offline/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { MAX_BATCH, isBatchError } from "../batch.js";
|
|
|
2
2
|
export { WenmarOpenError } from "../errors.js";
|
|
3
3
|
export type { ApiErrorCode, ClientErrorCode, ErrorCode, OfflineErrorCode, WenmarOpenErrorInit, } from "../errors.js";
|
|
4
4
|
export type { DecodeOptions, RequestOptions } from "../client.js";
|
|
5
|
-
export type { BatchItem, CheckDigit, Engine, EngineOption, EnginesQuery, Entry, ErrorBody, Make, MakesQuery, Manufacturer, Meta, Model, ModelsQuery, Plant, Safety, SearchQuery, Selection, Submodel, SubmodelsQuery, VinDecode, Warning, YearsQuery, } from "../types.js";
|
|
5
|
+
export type { BatchItem, CheckDigit, Engine, EngineOil, EngineOption, EnginesQuery, Entry, FluidRecord, FuelRating, ErrorBody, Make, MakesQuery, Manufacturer, Meta, Model, ModelsQuery, ModelYears, Overview, OverviewRecalls, Plant, Recall, RecallCampaign, RecallCount, RecallList, RecallLookup, RecallSearchQuery, RecentRecalls, RecentRecallsQuery, Safety, SafetyRating, SearchQuery, Selection, Spec, Submodel, SubmodelsQuery, TireFitment, VehicleFluids, VehicleFuel, VehicleComplaints, VehicleFuelQuery, VehicleRecalls, VehicleRecallsQuery, Code, CodeLink, CodeList, CodeSearchQuery, VehicleCode, JobDetail, JobList, LaborFigure, VehicleLabor, VinLabor, VehicleSafety, VehicleSpecs, VehicleTires, VinDecode, VinFluids, VinFuel, VinFuelQuery, VinRecalls, VinRecallsQuery, VinSpecs, VinTires, VinYearQuery, Warning, YearsQuery, } from "../types.js";
|
|
6
6
|
export { MAX_STEPS, WenmarOpenOffline } from "./client.js";
|
|
7
7
|
export type { OfflineOptions } from "./client.js";
|
|
8
8
|
export type { CompiledWasm, WasmSource } from "./engine.js";
|
package/dist/offline/node.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export interface NodeOptions {
|
|
|
6
6
|
* which must then be installed.
|
|
7
7
|
*/
|
|
8
8
|
path?: string;
|
|
9
|
+
/**
|
|
10
|
+
* The shop file, which holds the recalls. Default: the one in the
|
|
11
|
+
* `wenmar-open-data-shop` package, if that package is installed. `false`
|
|
12
|
+
* opens none even then.
|
|
13
|
+
*/
|
|
14
|
+
shopPath?: string | false;
|
|
9
15
|
wasm?: WasmSource;
|
|
10
16
|
currentYear?: number;
|
|
11
17
|
}
|
package/dist/offline/node.js
CHANGED
|
@@ -50,17 +50,44 @@ export async function openOffline(options = {}) {
|
|
|
50
50
|
cause,
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
+
// The shop file: the one named, or the one of the wenmar-open-data-shop
|
|
54
|
+
// package when it is installed. With neither, recalls are
|
|
55
|
+
// `dataset_unavailable` and everything else works.
|
|
56
|
+
let shopPath = options.shopPath;
|
|
57
|
+
if (shopPath === undefined) {
|
|
58
|
+
shopPath = await load("wenmar-open-data-shop").then((module) => module.path, () => false);
|
|
59
|
+
}
|
|
60
|
+
let shopDatabase;
|
|
61
|
+
if (shopPath !== false) {
|
|
62
|
+
try {
|
|
63
|
+
shopDatabase = new sqlite.DatabaseSync(shopPath, { readOnly: true });
|
|
64
|
+
}
|
|
65
|
+
catch (cause) {
|
|
66
|
+
database.close();
|
|
67
|
+
throw new WenmarOpenError({
|
|
68
|
+
code: "no_data",
|
|
69
|
+
message: `The shop file at ${shopPath} could not be opened.`,
|
|
70
|
+
details: { path: shopPath },
|
|
71
|
+
cause,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const shop = shopDatabase;
|
|
53
76
|
const store = { ...syncStore(database), close: () => database.close() };
|
|
54
77
|
const client = new WenmarOpenOffline({
|
|
55
78
|
store,
|
|
79
|
+
...(shop === undefined ? {} : { shop: { ...syncStore(shop), close: () => shop.close() } }),
|
|
56
80
|
...(options.wasm === undefined ? {} : { wasm: options.wasm }),
|
|
57
81
|
...(options.currentYear === undefined ? {} : { currentYear: options.currentYear }),
|
|
58
82
|
});
|
|
59
83
|
try {
|
|
84
|
+
// `meta` opens the data file and, when there is one, the shop file,
|
|
85
|
+
// which must go with it.
|
|
60
86
|
await client.meta();
|
|
61
87
|
}
|
|
62
88
|
catch (error) {
|
|
63
89
|
database.close();
|
|
90
|
+
shop?.close();
|
|
64
91
|
// SQLite opens a file that is not a database without complaint and
|
|
65
92
|
// fails on the first read; for a file on disk that means the file is
|
|
66
93
|
// not a data file, not that a connection dropped.
|
package/dist/offline/store.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export type Param = string | number | null;
|
|
|
4
4
|
export interface Statement {
|
|
5
5
|
readonly sql: string;
|
|
6
6
|
readonly params: readonly Param[];
|
|
7
|
+
/**
|
|
8
|
+
* Which file the statement is for. Left out: the data file. `"shop"`:
|
|
9
|
+
* the shop file, which holds the recalls. The client sends each statement
|
|
10
|
+
* to the store of its file, so a store never has to look at this.
|
|
11
|
+
*/
|
|
12
|
+
readonly db?: "shop";
|
|
7
13
|
}
|
|
8
14
|
/**
|
|
9
15
|
* A database holding a Wenmar Open data file. It is asked to run a list of
|