@finbheara/archive-client 0.1.2 → 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 +65 -6
- package/dist/chunks/{index-zr8tfjk0.js → index-kp811fb0.js} +255 -18
- package/dist/cli.js +34 -10
- package/dist/index.js +11 -1
- package/dist/types/assets.d.ts +1 -0
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/comps.d.ts +9 -1
- package/dist/types/dancers.d.ts +18 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/registrations.d.ts +8 -0
- package/dist/types/release.d.ts +10 -0
- package/dist/types/schema.d.ts +20 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ type declarations (`.d.ts`), so a TypeScript project needs Bun's types (`"types"
|
|
|
20
20
|
`bunx idq <verb>`.
|
|
21
21
|
|
|
22
22
|
The version is 0.x while the API settles: a minor bump may break, so pin a minor
|
|
23
|
-
(`"@finbheara/archive-client": "0.
|
|
23
|
+
(`"@finbheara/archive-client": "0.2.x"`).
|
|
24
24
|
|
|
25
25
|
## Configuration
|
|
26
26
|
|
|
@@ -67,6 +67,25 @@ rel.path("school-registry.jsonl"); // its cach
|
|
|
67
67
|
rel.close();
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
An older release that lacks what a call reads (a column, a table or a side file a newer release
|
|
71
|
+
added) is REFUSED BY NAME, never read in part: the call throws, naming the feature, what the
|
|
72
|
+
release lacks and the first cut that publishes it. What a release carries is read from its own
|
|
73
|
+
schema and assets, never from its tag:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const c = await rel.capabilities();
|
|
77
|
+
c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...], since: 260, calls }]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| feature | the calls | from cut |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `results.level` | `results.query`, `majors.results` | 215 |
|
|
83
|
+
| `dancers` | `dancers.resolve`, `search`, `get` | 260 |
|
|
84
|
+
| `schools` | `schools.resolve`, a dancer question with a school | 260 |
|
|
85
|
+
| `staff` | `staff.teacher`, `staff.adjudicator` | 260 |
|
|
86
|
+
| `majors.order` | `majors.competitions` | 260 |
|
|
87
|
+
| `feises`, `comps` | `feises.find`, `feises.series`, `comps.*`, `registrations.resolve` | 265 |
|
|
88
|
+
|
|
70
89
|
A spec is `pinned` (the default: the user-local pin, else the latest), `latest`, a cut number
|
|
71
90
|
(`42`, or `42.1` for a fraction) or a tag (`archive-YYYY.MM.DD-N`).
|
|
72
91
|
|
|
@@ -182,10 +201,11 @@ const r = await rel.dancers.resolve({
|
|
|
182
201
|
school: "Ardvarna Academy", // a print or an id, through rel.schools
|
|
183
202
|
age: { band: "U10", year: 2026 }, // or { yob: 2016 }, or { comp: "<competition>" }
|
|
184
203
|
asOf: "2026-10-17", // the school as of then
|
|
204
|
+
population: "girls", // or "boys": only a contradiction excludes
|
|
185
205
|
});
|
|
186
206
|
r.arm; // "unique" | "ambiguous" | "none"
|
|
187
207
|
r.candidates; // [{ dancer_id, display, scope, score, evidence: { nameLevel, age, school }, prints, ... }]
|
|
188
|
-
r.excluded; // the candidates
|
|
208
|
+
r.excluded; // the candidates a contradiction set aside (why: "age" | "population")
|
|
189
209
|
r.preferred; // NON-BINDING: { dancer_id, why } where the school singles one out; never moves arm
|
|
190
210
|
r.unlinked; // rows printing a matched name that the release linked to no dancer
|
|
191
211
|
|
|
@@ -202,8 +222,21 @@ meets the asked age (`Age.fitsIn`, `overlaps`): `fits`, `overlaps`, `contradicts
|
|
|
202
222
|
`school` is how the dancer's schools meet the asked one: `same` (the latest school as of `asOf`),
|
|
203
223
|
`moved` (an earlier one), `none`, `unresolved` (the print reached no school).
|
|
204
224
|
|
|
225
|
+
`population` is how the dancer's published majors population meets the asked one: `same`,
|
|
226
|
+
`contradicts`, `both`, `unknown`, `unasked`. A dancer's population is read only from its rows at
|
|
227
|
+
the majors (tiers T1 to T3) in competitions whose published `gender` reads girls or boys
|
|
228
|
+
(`@finbheara/domain`'s `populationIn`), whatever the competition's category (a solo championship
|
|
229
|
+
often publishes none), counted on the candidate (`population: { girls, boys }`). The results hold
|
|
230
|
+
individual dancers' rows only (a team is published apart), so every such row is a solo row. A feis
|
|
231
|
+
competition is no evidence either way (below open a feis runs its competitions mixed and may merge
|
|
232
|
+
its few boys into the girls' group), and a mixed competition reads `mixed`.
|
|
233
|
+
|
|
205
234
|
NO SILENT GUESS. Only a contradiction sets a candidate aside: a birth window that shares no birth
|
|
206
|
-
year with the asked age
|
|
235
|
+
year with the asked age, or a dancer the release publishes in the other population's gendered
|
|
236
|
+
majors solo competitions and in none of the asked one's (listed in `excluded`, `why: "age"` or
|
|
237
|
+
`"population"`, and a candidate contradicting both says `age`). A dancer published in both, or in
|
|
238
|
+
neither, stays. A population word that names no one population (`mixed`) narrows nothing, and the
|
|
239
|
+
answer says so (`r.population.note`). The school is evidence and order, never a reason
|
|
207
240
|
to drop anyone: candidates whose latest school is the asked one come first, then those that ever
|
|
208
241
|
danced for it, then the rest. A school row meets the asked school by its id, else by its print
|
|
209
242
|
(one `rel.schools` resolves to the asked ids), so a latest print the release
|
|
@@ -216,6 +249,10 @@ A FEIS-LOCAL ID (`FEIS_LOCAL_ID_FLOOR`, 2^34, and up) is valid in its release on
|
|
|
216
249
|
carrying one say `scope: "per-release"` (`dancer_scope` on a row), so a caller never stores one
|
|
217
250
|
across releases. Every answer carries the release tag.
|
|
218
251
|
|
|
252
|
+
A MAJORS ID (under the floor) is kept across releases on a best-effort basis only: a merge
|
|
253
|
+
retires one, a split mints one, and a renumbered source page can hand one to another dancer. A
|
|
254
|
+
caller that joins ids across releases checks the name and the rows it expects.
|
|
255
|
+
|
|
219
256
|
## Results and the majors
|
|
220
257
|
|
|
221
258
|
```ts
|
|
@@ -251,6 +288,8 @@ c.source; // "syllabus" | "prior-year" | "title"
|
|
|
251
288
|
c.kind; // "championship" | "grade" | "unrated"
|
|
252
289
|
c.level; // "OC" (FF, BG, AB, NV, PW, PC, OC), c.feis_level 700
|
|
253
290
|
c.age; // "U10", c.population "mixed", c.canonical "710OC", c.title, c.competition
|
|
291
|
+
c.age_source; // "printed" | "title" | "code" | "merged" | null, c.age_printed "U10" as printed
|
|
292
|
+
c.second; // true for a second chance or a supplementary comp beside the main one
|
|
254
293
|
c.from; // { feis_ids: ["fweb:ABC25"], year: 2025 } where an earlier year answered
|
|
255
294
|
c.check; // "unchecked", or what the caller's `title` says about the earlier comp
|
|
256
295
|
c.syllabus_source; // "results": what the syllabus the answer read is read from
|
|
@@ -293,6 +332,21 @@ QuickFeis numbers its own and-over championships (`15 & over` prelim `616PC`, `1
|
|
|
293
332
|
`719OC`). An and-over code is then the same string as an `Under N + 1` one, and `age` (`O18`)
|
|
294
333
|
tells them apart. An adult band, a range and a grade render none.
|
|
295
334
|
|
|
335
|
+
`age` is the release's reading, and `age_source` says where it was read. A `merged` comp (a
|
|
336
|
+
platform's comps merged into one, `710OCM` with no `710OC` beside it) prints one member's band,
|
|
337
|
+
so its `age` is the union of its members' (`U12` for `710OCM - U10 Open Champion` where the next
|
|
338
|
+
open the feis prints is `713OCM`), and `age_printed` keeps the platform's own field (`U10`). A
|
|
339
|
+
joined code at the bottom of its series stays open below (`710OC/711OC` with no open under 10
|
|
340
|
+
beside it is `U11/11yr`, every age under 11), as a younger dancer dances up into it.
|
|
341
|
+
|
|
342
|
+
`population` is the sex the release states, else a population word in the title, else the
|
|
343
|
+
code's sex suffix read with every code its feis printed (`codePopulation` from
|
|
344
|
+
`@finbheara/domain/title`: `720OCB` beside `720OCG` is boys and girls, `512M` beside `512` is
|
|
345
|
+
boys, a lone `713OCM` says nothing), else the release's `mixed` for a grade or a preliminary
|
|
346
|
+
championship. `second` is read from the words of the title and the platform's level and dance
|
|
347
|
+
fields (`secondIn`: `2nd Chance`, `2nd Chc`, `Second Chance`, `Supplementary Prelim`, `2nd Open
|
|
348
|
+
Champ`), and never from a code's digit, which marks a round's half at some hosts (`610PC2H`).
|
|
349
|
+
|
|
296
350
|
## Registrations
|
|
297
351
|
|
|
298
352
|
```ts
|
|
@@ -301,10 +355,13 @@ const rows = await rel.registrations.resolve([
|
|
|
301
355
|
]);
|
|
302
356
|
rows[0].comps; // one competition answer per code
|
|
303
357
|
rows[0].age; // { band: "U10", year: 2026, code: "610", from: "comp" }: the band the dancer is asked at
|
|
358
|
+
rows[0].population; // { asked: "girls", from: "comp", codes: ["610"] }: who the dancer is asked as
|
|
304
359
|
rows[0].dancer; // the dancer resolver's answer
|
|
305
360
|
```
|
|
306
361
|
|
|
307
|
-
Each code is resolved as above. The dancer is asked at the
|
|
362
|
+
Each code is resolved as above. The dancer is asked at the row's own `population` where it gives
|
|
363
|
+
one, else at the one population its unique comps read (`from: "comp"`, with the codes). Comps
|
|
364
|
+
reading both populations give none, with the reason. The dancer is asked at the YOUNGEST band the row's competitions
|
|
308
365
|
read (the lowest ceiling, then the highest floor): a dancer may dance up into an older band and
|
|
309
366
|
never down, so the youngest bounds the dancer. Every band a row read is listed (`bands`) where
|
|
310
367
|
there are several. Where no comp answers with a band (a syllabus read from results that lacks the
|
|
@@ -323,6 +380,7 @@ and the competitions still resolve.
|
|
|
323
380
|
idq add <dir> --tag <tag>
|
|
324
381
|
idq index [--release <spec>] [<name>...] [--force]
|
|
325
382
|
idq verify [--release <spec>]
|
|
383
|
+
idq capabilities [--release <spec>]
|
|
326
384
|
idq prune [--keep N] [--dry-run]
|
|
327
385
|
idq rm <tag> [--dry-run]
|
|
328
386
|
idq drop-indexes [<tag>] [--dry-run]
|
|
@@ -331,7 +389,8 @@ and the competitions still resolve.
|
|
|
331
389
|
idq school [--release <spec>] <print|school_id> [--region <code>]
|
|
332
390
|
idq dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
|
|
333
391
|
[--order surname-first|given-first] [--school <print|id>]
|
|
334
|
-
[--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
392
|
+
[--yob <y> | --band <band> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
393
|
+
[--population girls|boys] [--rows]
|
|
335
394
|
idq results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <c>] [--school <s>] [--year <y>] [--limit <n>]
|
|
336
395
|
idq majors [--release <spec>] [--event <e>] [--year <y>] [--comp <c>] [--dancer <id>] [--rows]
|
|
337
396
|
idq teacher [--release <spec>] <name|TD number>
|
|
@@ -348,7 +407,7 @@ and the competitions still resolve.
|
|
|
348
407
|
|
|
349
408
|
`resolve` speaks JSON lines: one answer per line out, and with no positional question the
|
|
350
409
|
questions are JSON lines on stdin, one per line (`{feis, code, year?, title?}`,
|
|
351
|
-
`{name, school?, age?}`, `{name, school?, codes, feis, year?}`). A line that is no JSON object,
|
|
410
|
+
`{name, school?, age?, population?}`, `{name, school?, codes, feis, year?, population?}`). A line that is no JSON object,
|
|
352
411
|
or no question of its kind, answers `{tag, line, error}` and the other lines still answer. The
|
|
353
412
|
exit is non-zero where any line erred. A dancer's age may be given as a comp
|
|
354
413
|
(`{"comp": "<feis>/<code>"}`, or `--comp` on the command line): `rel.registrations.dancer` reads
|
|
@@ -71,6 +71,7 @@ import { Age } from "@finbheara/domain/age";
|
|
|
71
71
|
import { createIndex, indexSchema, personEncoder } from "@finbheara/domain/noun-index";
|
|
72
72
|
import domainPkg from "@finbheara/domain/package.json";
|
|
73
73
|
import { dancerName } from "@finbheara/domain/person";
|
|
74
|
+
import { populationIn, populationRelation } from "@finbheara/domain/title";
|
|
74
75
|
import {
|
|
75
76
|
foldNameKey,
|
|
76
77
|
surnameBlock
|
|
@@ -160,6 +161,7 @@ class ResultFinder2 {
|
|
|
160
161
|
}
|
|
161
162
|
async query(q) {
|
|
162
163
|
await this.rel.fetch("results");
|
|
164
|
+
await this.rel.require("results.level");
|
|
163
165
|
return queryResults(this.rel, "results", q);
|
|
164
166
|
}
|
|
165
167
|
}
|
|
@@ -188,6 +190,7 @@ class MajorsFinder2 {
|
|
|
188
190
|
}
|
|
189
191
|
async competitions(q) {
|
|
190
192
|
const db = await this.db();
|
|
193
|
+
await this.rel.require("majors.order");
|
|
191
194
|
const args = [q.event];
|
|
192
195
|
let year = "";
|
|
193
196
|
if (q.year !== undefined) {
|
|
@@ -207,6 +210,7 @@ class MajorsFinder2 {
|
|
|
207
210
|
}
|
|
208
211
|
async results(q) {
|
|
209
212
|
await this.db();
|
|
213
|
+
await this.rel.require("results.level");
|
|
210
214
|
return queryResults(this.rel, "majors", q);
|
|
211
215
|
}
|
|
212
216
|
}
|
|
@@ -223,7 +227,7 @@ var PERSON = { table: "person", value: "id", valueType: "INTEGER PRIMARY KEY" };
|
|
|
223
227
|
var ASKED = ["surname_key", "nn4", "blk_surname"];
|
|
224
228
|
var dancersIndex = {
|
|
225
229
|
name: "dancers",
|
|
226
|
-
version: "
|
|
230
|
+
version: "2",
|
|
227
231
|
inputs: ["results"],
|
|
228
232
|
keyParts: () => [...PACKAGE_KEY_PARTS],
|
|
229
233
|
says: "every published dancer print at the names package's keys, its dancers, schools and years",
|
|
@@ -233,6 +237,7 @@ var dancersIndex = {
|
|
|
233
237
|
out.run("CREATE TABLE print_dancer(person INTEGER NOT NULL, dancer_id INTEGER, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
|
|
234
238
|
out.run("CREATE TABLE dancer(dancer_id INTEGER PRIMARY KEY, display TEXT NOT NULL, names_printed TEXT NOT NULL, genders TEXT NOT NULL, appearances INTEGER NOT NULL, kind TEXT, birth_lo INTEGER, birth_hi INTEGER, birth_source TEXT NOT NULL, birth_conflict INTEGER NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
|
|
235
239
|
out.run("CREATE TABLE dancer_school(dancer_id INTEGER NOT NULL, school_id TEXT, print TEXT NOT NULL, rows INTEGER NOT NULL, year_lo INTEGER, year_hi INTEGER)");
|
|
240
|
+
out.run("CREATE TABLE dancer_population(dancer_id INTEGER NOT NULL, population TEXT NOT NULL, rows INTEGER NOT NULL, PRIMARY KEY(dancer_id, population)) WITHOUT ROWID");
|
|
236
241
|
const src = rel.db("results");
|
|
237
242
|
const groups = src.query(`SELECT r.name_printed AS p, r.name_order_resolved AS o, r.dancer_id AS d,
|
|
238
243
|
r.school_printed AS sp, r.school_id AS s,
|
|
@@ -298,6 +303,15 @@ var dancersIndex = {
|
|
|
298
303
|
const t = dt.get(d.dancer_id);
|
|
299
304
|
putD.run(d.dancer_id, d.display, d.names_printed, d.genders, d.appearances, d.scope, d.birth_lo, d.birth_hi, d.birth_source, d.birth_conflict, t?.rows ?? 0, t?.lo ?? null, t?.hi ?? null);
|
|
300
305
|
}
|
|
306
|
+
const putPop = out.prepare("INSERT INTO dancer_population VALUES (?, ?, ?) ON CONFLICT(dancer_id, population) DO UPDATE SET rows = rows + excluded.rows");
|
|
307
|
+
for (const r of src.query(`SELECT r.dancer_id AS d, c.gender AS g, count(*) AS n
|
|
308
|
+
FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
|
|
309
|
+
WHERE r.dancer_id IS NOT NULL AND c.tier IN ('T1', 'T2', 'T3') AND c.gender IS NOT NULL
|
|
310
|
+
GROUP BY 1, 2`).all()) {
|
|
311
|
+
const pop = populationIn(r.g);
|
|
312
|
+
if (pop === "girls" || pop === "boys")
|
|
313
|
+
putPop.run(r.d, pop, r.n);
|
|
314
|
+
}
|
|
301
315
|
for (const s of schema.indexes)
|
|
302
316
|
if (ASKED.some((c) => s.endsWith(`(${c})`)))
|
|
303
317
|
out.run(s);
|
|
@@ -337,6 +351,13 @@ var SCHOOL_RANK = {
|
|
|
337
351
|
unresolved: 2,
|
|
338
352
|
unasked: 2
|
|
339
353
|
};
|
|
354
|
+
var POPULATION_WEIGHT = {
|
|
355
|
+
same: 1,
|
|
356
|
+
both: 0,
|
|
357
|
+
unknown: 0,
|
|
358
|
+
unasked: 0,
|
|
359
|
+
contradicts: -10
|
|
360
|
+
};
|
|
340
361
|
var SCHOOL_WEIGHT = {
|
|
341
362
|
same: 2,
|
|
342
363
|
moved: 1,
|
|
@@ -418,6 +439,7 @@ class DancerResolver2 {
|
|
|
418
439
|
year_lo: d.year_lo,
|
|
419
440
|
year_hi: d.year_hi,
|
|
420
441
|
birth: birthOf(d),
|
|
442
|
+
population: populationOf(db, dancerId),
|
|
421
443
|
prints,
|
|
422
444
|
schools: schoolsOf(db, dancerId)
|
|
423
445
|
};
|
|
@@ -425,6 +447,7 @@ class DancerResolver2 {
|
|
|
425
447
|
async answer(db, q, hits, narrow = true) {
|
|
426
448
|
const asOf = yearOf(q.asOf);
|
|
427
449
|
const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
|
|
450
|
+
const population = q.population === undefined ? undefined : readPopulation2(q.population);
|
|
428
451
|
let schoolIds = null;
|
|
429
452
|
let school;
|
|
430
453
|
if (q.school !== undefined) {
|
|
@@ -455,19 +478,22 @@ class DancerResolver2 {
|
|
|
455
478
|
const schools = schoolsOf(db, id);
|
|
456
479
|
const latest = latestSchool(schools, asOf);
|
|
457
480
|
const ageEv = ageEvidence(d, age?.age ?? null);
|
|
481
|
+
const pop = populationOf(db, id);
|
|
482
|
+
const popEv = populationEvidence(pop, population?.read ?? null);
|
|
458
483
|
const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, asOf);
|
|
459
484
|
all.push({
|
|
460
485
|
dancer_id: id,
|
|
461
486
|
display: d.display,
|
|
462
487
|
scope: id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable",
|
|
463
488
|
kind: d.kind,
|
|
464
|
-
score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv],
|
|
465
|
-
evidence: { nameLevel: e.level, age: ageEv, school: schoolEv },
|
|
489
|
+
score: NAME_WEIGHT[e.level] + AGE_WEIGHT[ageEv] + SCHOOL_WEIGHT[schoolEv] + POPULATION_WEIGHT[popEv],
|
|
490
|
+
evidence: { nameLevel: e.level, age: ageEv, school: schoolEv, population: popEv },
|
|
466
491
|
prints: e.prints.sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
|
|
467
492
|
rows: d.rows,
|
|
468
493
|
year_lo: d.year_lo,
|
|
469
494
|
year_hi: d.year_hi,
|
|
470
495
|
birth: birthOf(d),
|
|
496
|
+
population: pop,
|
|
471
497
|
school: latest
|
|
472
498
|
});
|
|
473
499
|
}
|
|
@@ -476,9 +502,12 @@ class DancerResolver2 {
|
|
|
476
502
|
let kept = all;
|
|
477
503
|
if (narrow)
|
|
478
504
|
kept = all.filter((c) => {
|
|
479
|
-
if (c.evidence.age
|
|
505
|
+
if (c.evidence.age === "contradicts")
|
|
506
|
+
excluded.push({ candidate: c, why: "age" });
|
|
507
|
+
else if (c.evidence.population === "contradicts")
|
|
508
|
+
excluded.push({ candidate: c, why: "population" });
|
|
509
|
+
else
|
|
480
510
|
return true;
|
|
481
|
-
excluded.push({ candidate: c, why: "age" });
|
|
482
511
|
return false;
|
|
483
512
|
});
|
|
484
513
|
let preferred;
|
|
@@ -503,6 +532,7 @@ class DancerResolver2 {
|
|
|
503
532
|
...preferred !== undefined ? { preferred } : {},
|
|
504
533
|
unlinked: [...unlinked].map(([print, rows]) => ({ print, rows })).sort((a, b) => b.rows - a.rows || (a.print < b.print ? -1 : 1)),
|
|
505
534
|
...age !== undefined ? { age: age.answer } : {},
|
|
535
|
+
...population !== undefined ? { population } : {},
|
|
506
536
|
...school !== undefined ? { school } : {}
|
|
507
537
|
};
|
|
508
538
|
}
|
|
@@ -580,6 +610,34 @@ function latestSchool(schools, asOf) {
|
|
|
580
610
|
}
|
|
581
611
|
return best;
|
|
582
612
|
}
|
|
613
|
+
function populationOf(db, dancerId) {
|
|
614
|
+
const out = { girls: 0, boys: 0 };
|
|
615
|
+
for (const r of db.query("SELECT population, rows FROM dancer_population WHERE dancer_id = ?").all(dancerId))
|
|
616
|
+
if (r.population === "girls" || r.population === "boys")
|
|
617
|
+
out[r.population] = r.rows;
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
function readPopulation2(asked) {
|
|
621
|
+
const w = asked.trim().toLowerCase();
|
|
622
|
+
const read = w === "girls" || w === "boys" ? w : populationIn(w);
|
|
623
|
+
if (read === "girls" || read === "boys")
|
|
624
|
+
return { asked, read };
|
|
625
|
+
return {
|
|
626
|
+
asked,
|
|
627
|
+
read: null,
|
|
628
|
+
note: read === "mixed" ? `"${asked}" reads as mixed: it names no one population, so it narrows nothing` : `"${asked}" reads as no population (girls or boys): it narrows nothing`
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function populationEvidence(own, asked) {
|
|
632
|
+
if (asked === null)
|
|
633
|
+
return "unasked";
|
|
634
|
+
if (own.girls > 0 && own.boys > 0)
|
|
635
|
+
return "both";
|
|
636
|
+
const mine = own.girls > 0 ? "girls" : own.boys > 0 ? "boys" : "";
|
|
637
|
+
if (mine === "")
|
|
638
|
+
return "unknown";
|
|
639
|
+
return populationRelation(asked, mine) === "disjoint" ? "contradicts" : "same";
|
|
640
|
+
}
|
|
583
641
|
function ageEvidence(d, asked) {
|
|
584
642
|
if (asked === null)
|
|
585
643
|
return "unasked";
|
|
@@ -597,9 +655,11 @@ function ageEvidence(d, asked) {
|
|
|
597
655
|
|
|
598
656
|
import { Age as Age2 } from "@finbheara/domain/age";
|
|
599
657
|
import {
|
|
658
|
+
codePopulation,
|
|
600
659
|
compTitle,
|
|
601
660
|
matchTitle,
|
|
602
|
-
populationIn,
|
|
661
|
+
populationIn as populationIn2,
|
|
662
|
+
secondIn,
|
|
603
663
|
titleRelation
|
|
604
664
|
} from "@finbheara/domain/title";
|
|
605
665
|
var LEVEL = Object.freeze({
|
|
@@ -616,17 +676,25 @@ CREATE TABLE comp(
|
|
|
616
676
|
competition TEXT PRIMARY KEY, feis_id TEXT NOT NULL, brand_id TEXT, year INTEGER,
|
|
617
677
|
code TEXT NOT NULL, code_fold TEXT NOT NULL, title TEXT, age TEXT, gender TEXT,
|
|
618
678
|
feis_level INTEGER, round TEXT, platform TEXT NOT NULL, captures INTEGER NOT NULL,
|
|
619
|
-
file TEXT NOT NULL, line INTEGER NOT NULL
|
|
679
|
+
file TEXT NOT NULL, line INTEGER NOT NULL, age_source TEXT, age_printed TEXT,
|
|
680
|
+
gender_source TEXT, level_printed TEXT, dance_printed TEXT
|
|
620
681
|
);
|
|
621
682
|
`;
|
|
622
683
|
var INDEXES = [
|
|
623
684
|
"CREATE INDEX comp_feis_code ON comp(feis_id, code_fold)",
|
|
624
685
|
"CREATE INDEX comp_brand_year ON comp(brand_id, year)"
|
|
625
686
|
];
|
|
687
|
+
var OPTIONAL = [
|
|
688
|
+
"age_source",
|
|
689
|
+
"age_printed",
|
|
690
|
+
"gender_source",
|
|
691
|
+
"level_printed",
|
|
692
|
+
"dance_printed"
|
|
693
|
+
];
|
|
626
694
|
var fold = (code) => code.trim().toLowerCase();
|
|
627
695
|
var compsIndex = {
|
|
628
696
|
name: "comps",
|
|
629
|
-
version: "
|
|
697
|
+
version: "2",
|
|
630
698
|
inputs: ["results"],
|
|
631
699
|
says: "every competition by its feis and its own code, with its series and year",
|
|
632
700
|
build(rel, out) {
|
|
@@ -639,19 +707,23 @@ var compsIndex = {
|
|
|
639
707
|
const captures = new Map;
|
|
640
708
|
for (const r of src.query("SELECT competition, count(*) AS n FROM competitions GROUP BY competition").all())
|
|
641
709
|
captures.set(r.competition, r.n);
|
|
642
|
-
const put = out.prepare("INSERT OR IGNORE INTO comp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
643
|
-
|
|
710
|
+
const put = out.prepare("INSERT OR IGNORE INTO comp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
711
|
+
const has = new Set(src.query("PRAGMA table_info(competitions)").all().map((r) => r.name));
|
|
712
|
+
const opt = (col) => has.has(col) ? col : `NULL AS ${col}`;
|
|
713
|
+
for (const c of src.query(`SELECT competition, event, year, comp_no, title, age, gender, feis_level, round, source, file, line, ${OPTIONAL.map(opt).join(", ")} FROM competitions WHERE comp_no IS NOT NULL AND trim(comp_no) != '' ORDER BY file, line`).iterate()) {
|
|
644
714
|
const f = brand.get(c.event);
|
|
645
|
-
put.run(c.competition, c.event, f?.brand ?? null, c.year ?? f?.year ?? null, c.comp_no.trim(), fold(c.comp_no), c.title, c.age, c.gender, c.feis_level, c.round, c.source, captures.get(c.competition) ?? 1, c.file, c.line);
|
|
715
|
+
put.run(c.competition, c.event, f?.brand ?? null, c.year ?? f?.year ?? null, c.comp_no.trim(), fold(c.comp_no), c.title, c.age, c.gender, c.feis_level, c.round, c.source, captures.get(c.competition) ?? 1, c.file, c.line, c.age_source, c.age_printed, c.gender_source, c.level_printed, c.dance_printed);
|
|
646
716
|
}
|
|
647
717
|
for (const s of INDEXES)
|
|
648
718
|
out.run(s);
|
|
649
719
|
}
|
|
650
720
|
};
|
|
651
|
-
function compOf(r) {
|
|
721
|
+
function compOf(r, codes = []) {
|
|
652
722
|
const lv = r.feis_level;
|
|
653
723
|
const kind = lv === null ? "unrated" : lv >= 600 ? "championship" : "grade";
|
|
654
|
-
const
|
|
724
|
+
const stated = r.gender_source === "level" ? "" : populationIn2(r.gender ?? "");
|
|
725
|
+
const pop = stated || populationIn2(r.title ?? "") || (r.platform === "ifeis" && codes.length > 0 ? codePopulation(r.code, codes) : "") || populationIn2(r.gender ?? "");
|
|
726
|
+
const second = [r.title, r.level_printed, r.dance_printed].some((t) => t != null && secondIn(t));
|
|
655
727
|
const round = r.round ?? (r.title === null ? null : compTitle(r.title).round?.toString() ?? null);
|
|
656
728
|
return {
|
|
657
729
|
competition: r.competition,
|
|
@@ -663,7 +735,10 @@ function compOf(r) {
|
|
|
663
735
|
feis_level: lv,
|
|
664
736
|
level: lv === null ? null : LEVEL[lv] ?? null,
|
|
665
737
|
age: r.age,
|
|
738
|
+
age_source: r.age_source ?? null,
|
|
739
|
+
age_printed: r.age_printed ?? null,
|
|
666
740
|
population: pop === "" ? null : pop,
|
|
741
|
+
second,
|
|
667
742
|
round,
|
|
668
743
|
canonical: canonicalOf2(lv, r.age),
|
|
669
744
|
platform: r.platform,
|
|
@@ -770,7 +845,12 @@ class CompResolver2 {
|
|
|
770
845
|
const marks = feisIds.map(() => "?").join(", ");
|
|
771
846
|
const sql = `SELECT * FROM comp WHERE feis_id IN (${marks})${code !== undefined ? " AND code_fold = ?" : ""} ORDER BY file, line`;
|
|
772
847
|
const args = code !== undefined ? [...feisIds, fold(code)] : [...feisIds];
|
|
773
|
-
|
|
848
|
+
const rows = db.query(sql).all(...args);
|
|
849
|
+
const all = code === undefined ? rows : db.query(`SELECT feis_id, code FROM comp WHERE feis_id IN (${marks})`).all(...feisIds);
|
|
850
|
+
const codes = new Map;
|
|
851
|
+
for (const r of all)
|
|
852
|
+
codes.set(r.feis_id, [...codes.get(r.feis_id) ?? [], r.code]);
|
|
853
|
+
return rows.map((r) => compOf(r, codes.get(r.feis_id) ?? []));
|
|
774
854
|
}
|
|
775
855
|
async idsOf(brand, year) {
|
|
776
856
|
return (await this.ofSeries("", brand, year, "series")).feis_ids;
|
|
@@ -1724,13 +1804,31 @@ class RegistrationResolver2 {
|
|
|
1724
1804
|
from: "comp"
|
|
1725
1805
|
} : byTitle !== undefined ? { band: byTitle.age, year: byTitle.year, code: byTitle.code, from: "title" } : null;
|
|
1726
1806
|
const bands = [...new Set((first !== undefined ? read : printed).map((c) => c.age))];
|
|
1807
|
+
let population = null;
|
|
1808
|
+
if (row.population !== undefined)
|
|
1809
|
+
population = { asked: row.population, from: "row" };
|
|
1810
|
+
else {
|
|
1811
|
+
const gendered = comps.flatMap((c, i) => c.arm === "unique" && (c.population === "girls" || c.population === "boys") ? [{ pop: c.population, code: row.codes[i] }] : []);
|
|
1812
|
+
const pops = [...new Set(gendered.map((g) => g.pop))];
|
|
1813
|
+
if (pops.length === 1)
|
|
1814
|
+
population = {
|
|
1815
|
+
asked: pops[0],
|
|
1816
|
+
from: "comp",
|
|
1817
|
+
codes: gendered.map((g) => g.code)
|
|
1818
|
+
};
|
|
1819
|
+
else if (pops.length > 1)
|
|
1820
|
+
population = {
|
|
1821
|
+
why: `the row's comps read both populations (${gendered.map((g) => `${g.code}: ${g.pop}`).join(", ")}): none asked`
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1727
1824
|
const order = nameOrderOf(row.order);
|
|
1728
1825
|
const asked = {
|
|
1729
1826
|
name: row.name,
|
|
1730
1827
|
...order !== undefined ? { order } : {},
|
|
1731
1828
|
...row.school !== undefined ? { school: row.school } : {},
|
|
1732
1829
|
...age !== null ? { age: { band: age.band, year: age.year } } : {},
|
|
1733
|
-
...row.asOf !== undefined ? { asOf: row.asOf } : {}
|
|
1830
|
+
...row.asOf !== undefined ? { asOf: row.asOf } : {},
|
|
1831
|
+
...population !== null && "asked" in population ? { population: population.asked } : {}
|
|
1734
1832
|
};
|
|
1735
1833
|
const { dancer } = await this.dancer(asked);
|
|
1736
1834
|
out.push({
|
|
@@ -1739,6 +1837,7 @@ class RegistrationResolver2 {
|
|
|
1739
1837
|
comps,
|
|
1740
1838
|
age,
|
|
1741
1839
|
...bands.length > 1 ? { bands } : {},
|
|
1840
|
+
population,
|
|
1742
1841
|
dancer,
|
|
1743
1842
|
asked
|
|
1744
1843
|
});
|
|
@@ -1903,6 +2002,88 @@ function defaultRemote() {
|
|
|
1903
2002
|
return installed ?? fallbackRemote2(ghRemote2, httpsRemote2());
|
|
1904
2003
|
}
|
|
1905
2004
|
|
|
2005
|
+
function columnsOf2(db, table) {
|
|
2006
|
+
return new Set(db.query("SELECT name FROM pragma_table_info(?)").all(table).map((r) => r.name));
|
|
2007
|
+
}
|
|
2008
|
+
var FEATURES2 = [
|
|
2009
|
+
{
|
|
2010
|
+
feature: "results.level",
|
|
2011
|
+
needs: ["results:competitions.feis_level"],
|
|
2012
|
+
since: 215,
|
|
2013
|
+
calls: "results.query, majors.results"
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
feature: "majors.order",
|
|
2017
|
+
needs: ["majors:competitions.seq"],
|
|
2018
|
+
since: 260,
|
|
2019
|
+
calls: "majors.competitions"
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
feature: "dancers",
|
|
2023
|
+
needs: ["results:results.name_order_resolved"],
|
|
2024
|
+
since: 260,
|
|
2025
|
+
calls: "dancers.resolve, dancers.search, dancers.get (the dancers index)"
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
feature: "schools",
|
|
2029
|
+
needs: ["school-registry.jsonl", "school-redirects.jsonl"],
|
|
2030
|
+
since: 260,
|
|
2031
|
+
calls: "schools.resolve, a dancer question with a school (the schools index)"
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
feature: "feises",
|
|
2035
|
+
needs: [
|
|
2036
|
+
"results:feises.date_lo",
|
|
2037
|
+
"results:feises.date_hi",
|
|
2038
|
+
"results:feises.feis_instance_id",
|
|
2039
|
+
"results:feises.feis_brand_id",
|
|
2040
|
+
"results:competitions.feis_region",
|
|
2041
|
+
"feis-redirects.jsonl"
|
|
2042
|
+
],
|
|
2043
|
+
since: 265,
|
|
2044
|
+
calls: "feises.find, feises.series, and comps and registrations through them (the feises index)"
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
feature: "comps",
|
|
2048
|
+
needs: [
|
|
2049
|
+
"results:competitions.feis_level",
|
|
2050
|
+
"results:competitions.round",
|
|
2051
|
+
"results:feises.feis_brand_id"
|
|
2052
|
+
],
|
|
2053
|
+
since: 265,
|
|
2054
|
+
calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
|
|
2055
|
+
},
|
|
2056
|
+
{
|
|
2057
|
+
feature: "staff",
|
|
2058
|
+
needs: [
|
|
2059
|
+
"results:adjudicators",
|
|
2060
|
+
"results:feis_panels",
|
|
2061
|
+
"school-staff.jsonl",
|
|
2062
|
+
"school-teachers.jsonl",
|
|
2063
|
+
"school-registry.jsonl"
|
|
2064
|
+
],
|
|
2065
|
+
since: 260,
|
|
2066
|
+
calls: "staff.teacher, staff.adjudicator (the staff index)"
|
|
2067
|
+
}
|
|
2068
|
+
];
|
|
2069
|
+
async function hasNeed(probe, n) {
|
|
2070
|
+
const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
|
|
2071
|
+
if (m === null)
|
|
2072
|
+
return probe.publishes(n);
|
|
2073
|
+
const cols = columnsOf2(await probe.db(m[1]), m[2]);
|
|
2074
|
+
return m[3] === undefined ? cols.size > 0 : cols.has(m[3]);
|
|
2075
|
+
}
|
|
2076
|
+
async function capabilityOf2(probe, f) {
|
|
2077
|
+
const missing = [];
|
|
2078
|
+
for (const n of f.needs)
|
|
2079
|
+
if (!await hasNeed(probe, n))
|
|
2080
|
+
missing.push(n);
|
|
2081
|
+
return { ...f, state: missing.length === 0 ? "full" : "missing", missing };
|
|
2082
|
+
}
|
|
2083
|
+
function needsRelease2(tag, c) {
|
|
2084
|
+
return new Error(`archive-client: ${tag} is too old for ${c.feature} (${c.calls}): it lacks ${c.missing.join(", ")}. Open a release of cut ${c.since} or later`);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
1906
2087
|
function tagDir(root, tag) {
|
|
1907
2088
|
if (parseArchiveTag2(tag) === null)
|
|
1908
2089
|
throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
|
|
@@ -1999,9 +2180,62 @@ class Release2 {
|
|
|
1999
2180
|
out.push(await this.fetchOne(a));
|
|
2000
2181
|
return out;
|
|
2001
2182
|
}
|
|
2183
|
+
async publishes(asset) {
|
|
2184
|
+
const plan = this.plan(asset);
|
|
2185
|
+
const r = this.receipt();
|
|
2186
|
+
if (r.files[plan.local] !== undefined)
|
|
2187
|
+
return true;
|
|
2188
|
+
if (r.source.startsWith("local:"))
|
|
2189
|
+
return this.has(asset);
|
|
2190
|
+
if (r.published !== undefined)
|
|
2191
|
+
return r.published.some((n) => plan.published.includes(n));
|
|
2192
|
+
let listed;
|
|
2193
|
+
try {
|
|
2194
|
+
listed = await this.listed();
|
|
2195
|
+
} catch (e) {
|
|
2196
|
+
throw new Error(`archive-client: cannot tell whether ${this.tag} publishes ${asset}: its cache records no asset listing and the host did not answer (${String(e)}). Open it once with the network`);
|
|
2197
|
+
}
|
|
2198
|
+
return listed.some((a) => plan.published.includes(a.name));
|
|
2199
|
+
}
|
|
2200
|
+
probe = {
|
|
2201
|
+
db: async (name) => {
|
|
2202
|
+
await this.fetch(name);
|
|
2203
|
+
return this.db(name);
|
|
2204
|
+
},
|
|
2205
|
+
publishes: (asset) => this.publishes(asset)
|
|
2206
|
+
};
|
|
2207
|
+
checked = new Map;
|
|
2208
|
+
async capability(feature) {
|
|
2209
|
+
let c = this.checked.get(feature);
|
|
2210
|
+
if (c === undefined) {
|
|
2211
|
+
const f = FEATURES2.find((x) => x.feature === feature);
|
|
2212
|
+
if (f === undefined)
|
|
2213
|
+
throw new Error(`archive-client: no feature "${feature}"`);
|
|
2214
|
+
c = await capabilityOf2(this.probe, f);
|
|
2215
|
+
this.checked.set(feature, c);
|
|
2216
|
+
}
|
|
2217
|
+
return c;
|
|
2218
|
+
}
|
|
2219
|
+
async capabilities() {
|
|
2220
|
+
const features = [];
|
|
2221
|
+
for (const f of FEATURES2)
|
|
2222
|
+
features.push(await this.capability(f.feature));
|
|
2223
|
+
return { tag: this.tag, features };
|
|
2224
|
+
}
|
|
2225
|
+
async require(feature) {
|
|
2226
|
+
const c = await this.capability(feature);
|
|
2227
|
+
if (c.state === "missing")
|
|
2228
|
+
throw needsRelease2(this.tag, c);
|
|
2229
|
+
}
|
|
2002
2230
|
async listed() {
|
|
2003
|
-
if (this.remoteAssets === null)
|
|
2231
|
+
if (this.remoteAssets === null) {
|
|
2004
2232
|
this.remoteAssets = await (this.opts.remote ?? defaultRemote()).assets(requireRepo2(this.opts), this.tag);
|
|
2233
|
+
if (existsSync4(this.dir)) {
|
|
2234
|
+
const r = this.receipt();
|
|
2235
|
+
r.published = this.remoteAssets.map((a) => a.name).sort();
|
|
2236
|
+
writeJsonAtomic2(receiptPath(this.dir), r);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2005
2239
|
return this.remoteAssets;
|
|
2006
2240
|
}
|
|
2007
2241
|
async fetchOne(asset) {
|
|
@@ -2062,6 +2296,8 @@ class Release2 {
|
|
|
2062
2296
|
const b = this.builders().find((x) => x.name === name);
|
|
2063
2297
|
if (b === undefined)
|
|
2064
2298
|
throw new Error(`archive-client: no index "${name}" (known: ${this.builders().map((x) => x.name).join(", ")})`);
|
|
2299
|
+
if (FEATURES2.some((f) => f.feature === b.name))
|
|
2300
|
+
await this.require(b.name);
|
|
2065
2301
|
await this.fetch(...b.inputs);
|
|
2066
2302
|
return buildIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
|
|
2067
2303
|
}
|
|
@@ -2142,7 +2378,8 @@ async function openRelease2(spec = "pinned", opts = {}) {
|
|
|
2142
2378
|
writeJsonAtomic2(receiptPath(dir), {
|
|
2143
2379
|
tag,
|
|
2144
2380
|
source: repo,
|
|
2145
|
-
files: { "MANIFEST.json": landed }
|
|
2381
|
+
files: { "MANIFEST.json": landed },
|
|
2382
|
+
published: listed.map((a) => a.name).sort()
|
|
2146
2383
|
});
|
|
2147
2384
|
dropIfEmpty(part);
|
|
2148
2385
|
}
|
|
@@ -2354,4 +2591,4 @@ function releases2(opts = {}) {
|
|
|
2354
2591
|
return new ReleaseManager2(opts);
|
|
2355
2592
|
}
|
|
2356
2593
|
|
|
2357
|
-
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, canonicalOf2, CompResolver2, NO_CALENDAR2, FeisFinder2, StaffFinder2, SchoolResolver2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
|
|
2594
|
+
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, NO_CALENDAR2, FeisFinder2, StaffFinder2, SchoolResolver2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
nameOrderOf,
|
|
5
5
|
openRelease2,
|
|
6
6
|
releases2
|
|
7
|
-
} from "./chunks/index-
|
|
7
|
+
} from "./chunks/index-kp811fb0.js";
|
|
8
8
|
import"./chunks/index-hne1wadw.js";
|
|
9
9
|
import"./chunks/index-hghdy980.js";
|
|
10
10
|
|
|
@@ -23,6 +23,9 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
23
23
|
index [--release <spec>] [<name>...] [--force]
|
|
24
24
|
build the per-tag indexes (default: every one)
|
|
25
25
|
verify [--release <spec>] re-hash every cached file against its receipt
|
|
26
|
+
capabilities [--release <spec>] what the release carries of what the client reads, from
|
|
27
|
+
its own schema: each feature full or missing (its calls
|
|
28
|
+
refuse, naming the first cut that publishes it)
|
|
26
29
|
prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)
|
|
27
30
|
and the pinned one
|
|
28
31
|
rm <tag> [--dry-run] remove one cached release (not the pinned one)
|
|
@@ -37,8 +40,9 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
37
40
|
dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>
|
|
38
41
|
[--order surname-first|given-first] [--school <print|id>]
|
|
39
42
|
[--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]
|
|
40
|
-
[--rows]
|
|
41
|
-
|
|
43
|
+
[--population girls|boys] [--rows]
|
|
44
|
+
the dancers a name reaches, each with its evidence
|
|
45
|
+
(name level, age, school, population) and the arm; the results of
|
|
42
46
|
the one dancer where one answers (or --rows for each)
|
|
43
47
|
results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]
|
|
44
48
|
[--school <print|id>] [--year <y>] [--limit <n>]
|
|
@@ -61,12 +65,13 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
|
|
|
61
65
|
else the newest, saying which and what it is read from
|
|
62
66
|
resolve comp [<feis> <code>] [--year <y>] [--title <t>]
|
|
63
67
|
resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]
|
|
64
|
-
[--order <o>] [--as-of <date>]
|
|
68
|
+
[--order <o>] [--as-of <date>] [--population girls|boys]
|
|
65
69
|
resolve registrations
|
|
66
70
|
JSONL out, one answer per line; with no positional the
|
|
67
71
|
questions are JSONL on stdin, one per line (a comp:
|
|
68
|
-
{feis, code, year?, title?}, a dancer: {name, school?, age
|
|
69
|
-
a registration: {name, school?, codes[], feis,
|
|
72
|
+
{feis, code, year?, title?}, a dancer: {name, school?, age?,
|
|
73
|
+
population?}, a registration: {name, school?, codes[], feis,
|
|
74
|
+
year?, population?})
|
|
70
75
|
|
|
71
76
|
<spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)`;
|
|
72
77
|
var VALUE = new Set([
|
|
@@ -90,6 +95,7 @@ var VALUE = new Set([
|
|
|
90
95
|
"--band",
|
|
91
96
|
"--comp",
|
|
92
97
|
"--as-of",
|
|
98
|
+
"--population",
|
|
93
99
|
"--dancer",
|
|
94
100
|
"--feis",
|
|
95
101
|
"--limit",
|
|
@@ -137,8 +143,8 @@ function printCached(c, say) {
|
|
|
137
143
|
}
|
|
138
144
|
var span = (lo, hi) => lo === null ? "-" : lo === hi ? `${lo}` : `${lo}-${hi ?? "?"}`;
|
|
139
145
|
function printCandidate(c, say, pad = " ") {
|
|
140
|
-
say(`${pad}${c.dancer_id} ${c.display}${c.scope === "per-release" ? " [per-release id]" : ""} score ${c.score} name ${c.evidence.nameLevel} age ${c.evidence.age} school ${c.evidence.school}`);
|
|
141
|
-
say(`${pad} ${c.rows} rows ${span(c.year_lo, c.year_hi)} born ${span(c.birth.lo, c.birth.hi)} (${c.birth.source}) school ${c.school === null ? "-" : `${c.school.school_id ?? "(no id)"} "${c.school.print}" ${span(c.school.year_lo, c.school.year_hi)}`}`);
|
|
146
|
+
say(`${pad}${c.dancer_id} ${c.display}${c.scope === "per-release" ? " [per-release id]" : ""} score ${c.score} name ${c.evidence.nameLevel} age ${c.evidence.age} school ${c.evidence.school} population ${c.evidence.population}`);
|
|
147
|
+
say(`${pad} ${c.rows} rows ${span(c.year_lo, c.year_hi)} born ${span(c.birth.lo, c.birth.hi)} (${c.birth.source}) majors girls ${c.population.girls} boys ${c.population.boys} school ${c.school === null ? "-" : `${c.school.school_id ?? "(no id)"} "${c.school.print}" ${span(c.school.year_lo, c.school.year_hi)}`}`);
|
|
142
148
|
say(`${pad} prints ${c.prints.slice(0, 5).map((p) => `"${p.print}" (${p.rows}, ${p.level})`).join(", ")}${c.prints.length > 5 ? `, +${c.prints.length - 5} more` : ""}`);
|
|
143
149
|
}
|
|
144
150
|
function printDancers(a, say) {
|
|
@@ -147,6 +153,8 @@ function printDancers(a, say) {
|
|
|
147
153
|
say(` age asked: ${a.age.read ?? "(unread)"}${a.age.birth !== null ? ` born ${a.age.birth.from ?? "?"}-${a.age.birth.to ?? "?"}` : ""}${a.age.note !== undefined ? ` (${a.age.note})` : ""}`);
|
|
148
154
|
if (a.school !== undefined)
|
|
149
155
|
say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}`);
|
|
156
|
+
if (a.population !== undefined)
|
|
157
|
+
say(` population asked: ${a.population.read ?? "(unread)"}${a.population.note !== undefined ? ` (${a.population.note})` : ""}`);
|
|
150
158
|
for (const c of a.candidates)
|
|
151
159
|
printCandidate(c, say);
|
|
152
160
|
if (a.preferred !== undefined)
|
|
@@ -277,7 +285,8 @@ async function resolveVerb(rel, what, args, p, say, stdin) {
|
|
|
277
285
|
...nameOrderOf(p.flags.get("--order")) !== undefined ? { order: nameOrderOf(p.flags.get("--order")) } : {},
|
|
278
286
|
...p.flags.has("--school") ? { school: p.flags.get("--school") } : {},
|
|
279
287
|
...age !== undefined ? { age } : {},
|
|
280
|
-
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {}
|
|
288
|
+
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
|
|
289
|
+
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {}
|
|
281
290
|
}
|
|
282
291
|
}
|
|
283
292
|
];
|
|
@@ -404,6 +413,20 @@ async function main(argv, io = {}) {
|
|
|
404
413
|
});
|
|
405
414
|
return 0;
|
|
406
415
|
}
|
|
416
|
+
case "capabilities": {
|
|
417
|
+
const rel = await openRelease2(spec, { ...opts, assets: [] });
|
|
418
|
+
try {
|
|
419
|
+
const c = await rel.capabilities();
|
|
420
|
+
out(c, () => {
|
|
421
|
+
say(`${c.tag}: what it carries of what the client reads`);
|
|
422
|
+
for (const f of c.features)
|
|
423
|
+
say(` ${f.state.padEnd(8)} ${f.feature}${f.state === "full" ? "" : ` lacks ${f.missing.join(", ")}: a release of cut ${f.since} or later answers ${f.calls}`}`);
|
|
424
|
+
});
|
|
425
|
+
return 0;
|
|
426
|
+
} finally {
|
|
427
|
+
rel.close();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
407
430
|
case "verify": {
|
|
408
431
|
const tag = (await m.fetch(spec, { assets: [] })).tag;
|
|
409
432
|
const bad = await m.verify(tag);
|
|
@@ -522,6 +545,7 @@ async function main(argv, io = {}) {
|
|
|
522
545
|
...p.flags.has("--school") ? { school: p.flags.get("--school") } : {},
|
|
523
546
|
...age !== undefined ? { age } : {},
|
|
524
547
|
...p.flags.has("--as-of") ? { asOf: p.flags.get("--as-of") } : {},
|
|
548
|
+
...p.flags.has("--population") ? { population: p.flags.get("--population") } : {},
|
|
525
549
|
...p.flags.has("--limit") ? { limit: Number(p.flags.get("--limit")) } : {}
|
|
526
550
|
};
|
|
527
551
|
const surname = p.flags.get("--surname");
|
|
@@ -666,7 +690,7 @@ async function main(argv, io = {}) {
|
|
|
666
690
|
out(a, () => {
|
|
667
691
|
say(`${a.feis.asked} ${a.year ?? "?"} ${a.held ? `held: ${a.feis.feis_ids.join(", ")}` : "not held"} series ${a.feis.brand_id ?? "-"} (years ${a.years.join(", ") || "-"}) in ${a.tag}`);
|
|
668
692
|
for (const c of a.entries)
|
|
669
|
-
say(` ${c.code} ${c.kind} ${c.level ?? "-"} ${c.age ?? "-"} ${c.population ?? "-"} "${c.title ?? ""}"`);
|
|
693
|
+
say(` ${c.code} ${c.kind} ${c.level ?? "-"} ${c.age ?? "-"} ${c.population ?? "-"}${c.second ? " second" : ""} "${c.title ?? ""}"`);
|
|
670
694
|
say(`${a.entries.length} comp(s)`);
|
|
671
695
|
});
|
|
672
696
|
rel.close();
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ResultFinder2,
|
|
11
11
|
MajorsFinder2,
|
|
12
12
|
DancerResolver2,
|
|
13
|
+
readPopulation2,
|
|
13
14
|
canonicalOf2,
|
|
14
15
|
CompResolver2,
|
|
15
16
|
NO_CALENDAR2,
|
|
@@ -30,13 +31,17 @@ import {
|
|
|
30
31
|
httpsRemote2,
|
|
31
32
|
fallbackRemote2,
|
|
32
33
|
setRemote2,
|
|
34
|
+
columnsOf2,
|
|
35
|
+
FEATURES2,
|
|
36
|
+
capabilityOf2,
|
|
37
|
+
needsRelease2,
|
|
33
38
|
resolveTag2,
|
|
34
39
|
Release2,
|
|
35
40
|
openRelease2,
|
|
36
41
|
openCached2,
|
|
37
42
|
ReleaseManager2,
|
|
38
43
|
releases2
|
|
39
|
-
} from "./chunks/index-
|
|
44
|
+
} from "./chunks/index-kp811fb0.js";
|
|
40
45
|
import {
|
|
41
46
|
cacheRoot2,
|
|
42
47
|
repoOf2
|
|
@@ -55,6 +60,7 @@ export {
|
|
|
55
60
|
CompResolver2 as CompResolver,
|
|
56
61
|
DATABASES2 as DATABASES,
|
|
57
62
|
DancerResolver2 as DancerResolver,
|
|
63
|
+
FEATURES2 as FEATURES,
|
|
58
64
|
FEIS_LOCAL_ID_FLOOR2 as FEIS_LOCAL_ID_FLOOR,
|
|
59
65
|
FeisFinder2 as FeisFinder,
|
|
60
66
|
MajorsFinder2 as MajorsFinder,
|
|
@@ -69,6 +75,8 @@ export {
|
|
|
69
75
|
buildIndex2 as buildIndex,
|
|
70
76
|
cacheRoot2 as cacheRoot,
|
|
71
77
|
canonicalOf2 as canonicalOf,
|
|
78
|
+
capabilityOf2 as capabilityOf,
|
|
79
|
+
columnsOf2 as columnsOf,
|
|
72
80
|
compareTagsNewestFirst2 as compareTagsNewestFirst,
|
|
73
81
|
cutOf2 as cutOf,
|
|
74
82
|
dancerResolverOf2 as dancerResolverOf,
|
|
@@ -81,6 +89,7 @@ export {
|
|
|
81
89
|
isCurrent2 as isCurrent,
|
|
82
90
|
listIndexes2 as listIndexes,
|
|
83
91
|
nameLevel2 as nameLevel,
|
|
92
|
+
needsRelease2 as needsRelease,
|
|
84
93
|
openCached2 as openCached,
|
|
85
94
|
openRelease2 as openRelease,
|
|
86
95
|
parseArchiveTag2 as parseArchiveTag,
|
|
@@ -88,6 +97,7 @@ export {
|
|
|
88
97
|
pickTag2 as pickTag,
|
|
89
98
|
planAsset2 as planAsset,
|
|
90
99
|
readPin2 as readPin,
|
|
100
|
+
readPopulation2 as readPopulation,
|
|
91
101
|
releases2 as releases,
|
|
92
102
|
renderArchiveTag2 as renderArchiveTag,
|
|
93
103
|
repoOf2 as repoOf,
|
package/dist/types/assets.d.ts
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import type { OpenOptions } from "./release.js";
|
|
3
|
-
export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--rows]
|
|
3
|
+
export declare const USAGE = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]\n\n config repo <owner/name> set the user-local repository the releases come from\n list [--remote] the cached releases (size, files, indexes, the pin), and\n with --remote the published tags\n fetch [--release <spec>] [--assets results,majors,<file>...]\n download, verify and cache a release (default: results)\n pin <spec> the user-local pin: what a bare open (spec \"pinned\") opens\n unpin remove the pin (a bare open is then \"latest\")\n current the pin, and whether it is cached\n add <dir> --tag <tag> add a release built locally (its MANIFEST.json and files)\n to the cache under its tag\n index [--release <spec>] [<name>...] [--force]\n build the per-tag indexes (default: every one)\n verify [--release <spec>] re-hash every cached file against its receipt\n capabilities [--release <spec>] what the release carries of what the client reads, from\n its own schema: each feature full or missing (its calls\n refuse, naming the first cut that publishes it)\n prune [--keep N] [--dry-run] remove all but the N newest cached releases (default 1)\n and the pinned one\n rm <tag> [--dry-run] remove one cached release (not the pinned one)\n drop-indexes [<tag>] [--dry-run] remove the index files (of one release, or of every one)\n feis [--release <spec>] [--platform <p>] [--code <c>] [--id <id>] [--name <glob>] [--year <y>]\n the feises a query names\n series [--release <spec>] <name|host code|feis id>\n the series a name, a host code (fweb:ABC) or any feis id\n names: its years, hosts, codes and next instance\n school [--release <spec>] <print|school_id> [--region <code>]\n a school print or id to the published schools\n dancer [--release <spec>] <name> | --surname <s> | --like <glob> | --id <dancer_id>\n [--order surname-first|given-first] [--school <print|id>]\n [--yob <y> | --band <U10> [--year <y>] | --comp <competition>] [--as-of <date>]\n [--population girls|boys] [--rows]\n the dancers a name reaches, each with its evidence\n (name level, age, school, population) and the arm; the results of\n the one dancer where one answers (or --rows for each)\n results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <competition>]\n [--school <print|id>] [--year <y>] [--limit <n>]\n the published result rows, prints and ids\n majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]\n the majors: every event and its years; an event's\n competitions; a competition's, an event's or a dancer's rows\n teacher [--release <spec>] <name|TD number>\n the staff lists' people: where they taught, the register,\n where they sat\n adjudicator [--release <spec>] <name|TD number>\n the register's and the panels' people: the same answer\n comp [--release <spec>] <feis> <code> [--year <y>] [--title <t>]\n a feis's own comp code to the comp it names, through that\n feis's syllabus (or the prior year's)\n syllabus [--release <spec>] <feis> [year]\n one feis-year's comps, in printed order\n syllabus [--release <spec>] --for <series> [year]\n a series' syllabus for a year: that year's where held,\n else the newest, saying which and what it is read from\n resolve comp [<feis> <code>] [--year <y>] [--title <t>]\n resolve dancer [<name>] [--school <s>] [--comp <feis>/<code> | --yob <y> | --band <b> --year <y>]\n [--order <o>] [--as-of <date>] [--population girls|boys]\n resolve registrations\n JSONL out, one answer per line; with no positional the\n questions are JSONL on stdin, one per line (a comp:\n {feis, code, year?, title?}, a dancer: {name, school?, age?,\n population?}, a registration: {name, school?, codes[], feis,\n year?, population?})\n\n <spec>: pinned (the default) | latest | a cut (42, 42.1) | a tag (archive-YYYY.MM.DD-N)";
|
|
4
4
|
export interface Parsed {
|
|
5
5
|
positional: string[];
|
|
6
6
|
flags: Map<string, string>;
|
package/dist/types/comps.d.ts
CHANGED
|
@@ -15,7 +15,10 @@ export interface Comp {
|
|
|
15
15
|
feis_level: number | null;
|
|
16
16
|
level: string | null;
|
|
17
17
|
age: string | null;
|
|
18
|
+
age_source: string | null;
|
|
19
|
+
age_printed: string | null;
|
|
18
20
|
population: Population | null;
|
|
21
|
+
second: boolean;
|
|
19
22
|
round: string | null;
|
|
20
23
|
canonical: string | null;
|
|
21
24
|
platform: string;
|
|
@@ -107,8 +110,13 @@ interface CompRow {
|
|
|
107
110
|
round: string | null;
|
|
108
111
|
platform: string;
|
|
109
112
|
captures: number;
|
|
113
|
+
age_source?: string | null;
|
|
114
|
+
age_printed?: string | null;
|
|
115
|
+
gender_source?: string | null;
|
|
116
|
+
level_printed?: string | null;
|
|
117
|
+
dance_printed?: string | null;
|
|
110
118
|
}
|
|
111
|
-
export declare function compOf(r: CompRow): Comp;
|
|
119
|
+
export declare function compOf(r: CompRow, codes?: readonly string[]): Comp;
|
|
112
120
|
export declare function canonicalOf(level: number | null, age: string | null): string | null;
|
|
113
121
|
export declare class CompResolver {
|
|
114
122
|
private readonly rel;
|
package/dist/types/dancers.d.ts
CHANGED
|
@@ -20,8 +20,10 @@ export interface DancerQuery {
|
|
|
20
20
|
school?: string;
|
|
21
21
|
age?: AgeAsked;
|
|
22
22
|
asOf?: string;
|
|
23
|
+
population?: string;
|
|
23
24
|
}
|
|
24
25
|
export type AgeEvidence = "fits" | "overlaps" | "contradicts" | "unknown" | "unasked";
|
|
26
|
+
export type PopulationEvidence = "same" | "contradicts" | "both" | "unknown" | "unasked";
|
|
25
27
|
export type SchoolEvidence = "same" | "moved" | "none" | "unresolved" | "unasked";
|
|
26
28
|
export interface DancerSchool {
|
|
27
29
|
school_id: string | null;
|
|
@@ -40,6 +42,7 @@ export interface DancerCandidate {
|
|
|
40
42
|
nameLevel: NameLevel;
|
|
41
43
|
age: AgeEvidence;
|
|
42
44
|
school: SchoolEvidence;
|
|
45
|
+
population: PopulationEvidence;
|
|
43
46
|
};
|
|
44
47
|
prints: {
|
|
45
48
|
print: string;
|
|
@@ -55,11 +58,15 @@ export interface DancerCandidate {
|
|
|
55
58
|
source: string;
|
|
56
59
|
conflict: boolean;
|
|
57
60
|
};
|
|
61
|
+
population: {
|
|
62
|
+
girls: number;
|
|
63
|
+
boys: number;
|
|
64
|
+
};
|
|
58
65
|
school: DancerSchool | null;
|
|
59
66
|
}
|
|
60
67
|
export interface ExcludedCandidate {
|
|
61
68
|
candidate: DancerCandidate;
|
|
62
|
-
why: "age";
|
|
69
|
+
why: "age" | "population";
|
|
63
70
|
}
|
|
64
71
|
export interface DancerAnswer {
|
|
65
72
|
tag: string;
|
|
@@ -83,6 +90,11 @@ export interface DancerAnswer {
|
|
|
83
90
|
} | null;
|
|
84
91
|
note?: string;
|
|
85
92
|
};
|
|
93
|
+
population?: {
|
|
94
|
+
asked: string;
|
|
95
|
+
read: "girls" | "boys" | null;
|
|
96
|
+
note?: string;
|
|
97
|
+
};
|
|
86
98
|
school?: {
|
|
87
99
|
arm: string;
|
|
88
100
|
ids: string[];
|
|
@@ -113,6 +125,10 @@ export interface DancerRecord {
|
|
|
113
125
|
source: string;
|
|
114
126
|
conflict: boolean;
|
|
115
127
|
};
|
|
128
|
+
population: {
|
|
129
|
+
girls: number;
|
|
130
|
+
boys: number;
|
|
131
|
+
};
|
|
116
132
|
prints: {
|
|
117
133
|
print: string;
|
|
118
134
|
rows: number;
|
|
@@ -136,3 +152,4 @@ export declare class DancerResolver {
|
|
|
136
152
|
private schoolEvidence;
|
|
137
153
|
private readAge;
|
|
138
154
|
}
|
|
155
|
+
export declare function readPopulation(asked: string): NonNullable<DancerAnswer["population"]>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { type AssetPlan, DATABASES, type DatabaseName, type LandedFile, type Receipt, type ReleaseManifest, planAsset, } from "./assets.js";
|
|
2
2
|
export { sha256File, unzstdTo } from "./bytes.js";
|
|
3
|
-
export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerSchool, type DancerSearchQuery, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type SchoolEvidence, } from "./dancers.js";
|
|
3
|
+
export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerSchool, type DancerSearchQuery, type ExcludedCandidate, FEIS_LOCAL_ID_FLOOR, type PopulationEvidence, type SchoolEvidence, readPopulation, } from "./dancers.js";
|
|
4
4
|
export { NAME_LEVELS, type NameLevel, nameLevel } from "./dancers-names.js";
|
|
5
5
|
export { type Comp, type CompAnswer, type CompKind, type CompQuery, CompResolver, type CompSource, type FeisPlace, type SyllabusAnswer, type SyllabusForAnswer, type SyllabusSource, type TitleCheck, canonicalOf, } from "./comps.js";
|
|
6
6
|
export { type ClientOptions, cacheRoot, repoOf } from "./config.js";
|
|
@@ -14,4 +14,5 @@ export { type OpenOptions, Release, openCached, openRelease, resolveTag, } from
|
|
|
14
14
|
export { type MajorCompetition, type MajorEvent, MajorsFinder, type ResultAnswer, ResultFinder, type ResultQuery, type ResultRow, } from "./results.js";
|
|
15
15
|
export { type DancerAnswerLike, type DancerQueryLike, type DancerResolverLike, type DancerUnavailable, type RegistrationRow, type ResolvedDancer, RegistrationResolver, type ResolvedRegistration, dancerResolverOf, youngest, } from "./registrations.js";
|
|
16
16
|
export { type SchoolAnswer, type SchoolCandidate, type SchoolQuery, SchoolResolver, type SchoolRung, } from "./schools.js";
|
|
17
|
+
export { type Capability, FEATURES, type Feature, type SchemaProbe, capabilityOf, columnsOf, needsRelease, } from "./schema.js";
|
|
17
18
|
export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
|
|
@@ -10,6 +10,7 @@ export interface RegistrationRow {
|
|
|
10
10
|
year?: number;
|
|
11
11
|
titles?: Readonly<Record<string, string>>;
|
|
12
12
|
asOf?: string;
|
|
13
|
+
population?: string;
|
|
13
14
|
[k: string]: unknown;
|
|
14
15
|
}
|
|
15
16
|
export type DancerQueryLike = DancerQuery;
|
|
@@ -36,6 +37,13 @@ export interface ResolvedRegistration {
|
|
|
36
37
|
from: "comp" | "title";
|
|
37
38
|
} | null;
|
|
38
39
|
bands?: string[];
|
|
40
|
+
population: {
|
|
41
|
+
asked: string;
|
|
42
|
+
from: "row" | "comp";
|
|
43
|
+
codes?: string[];
|
|
44
|
+
} | {
|
|
45
|
+
why: string;
|
|
46
|
+
} | null;
|
|
39
47
|
dancer: DancerAnswer | DancerAnswerLike | DancerUnavailable;
|
|
40
48
|
asked?: DancerQueryLike;
|
|
41
49
|
}
|
package/dist/types/release.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { StaffFinder } from "./people.js";
|
|
|
9
9
|
import { type DancerResolverLike, RegistrationResolver } from "./registrations.js";
|
|
10
10
|
import { type Remote } from "./remote.js";
|
|
11
11
|
import { MajorsFinder, ResultFinder } from "./results.js";
|
|
12
|
+
import { type Capability } from "./schema.js";
|
|
12
13
|
import { type SchoolReaderChoice, SchoolResolver } from "./schools.js";
|
|
13
14
|
import { type ReleaseSpec } from "./tag.js";
|
|
14
15
|
export interface OpenOptions extends ClientOptions {
|
|
@@ -49,6 +50,15 @@ export declare class Release {
|
|
|
49
50
|
has(asset: string): boolean;
|
|
50
51
|
path(asset: string): string;
|
|
51
52
|
fetch(...assets: string[]): Promise<LandedFile[]>;
|
|
53
|
+
publishes(asset: string): Promise<boolean>;
|
|
54
|
+
private readonly probe;
|
|
55
|
+
private readonly checked;
|
|
56
|
+
capability(feature: string): Promise<Capability>;
|
|
57
|
+
capabilities(): Promise<{
|
|
58
|
+
tag: string;
|
|
59
|
+
features: Capability[];
|
|
60
|
+
}>;
|
|
61
|
+
require(feature: string): Promise<void>;
|
|
52
62
|
private listed;
|
|
53
63
|
private fetchOne;
|
|
54
64
|
private record;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
export declare function columnsOf(db: Database, table: string): Set<string>;
|
|
3
|
+
export interface Feature {
|
|
4
|
+
feature: string;
|
|
5
|
+
needs: readonly string[];
|
|
6
|
+
since: number;
|
|
7
|
+
calls: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const FEATURES: readonly Feature[];
|
|
10
|
+
export interface Capability extends Feature {
|
|
11
|
+
state: "full" | "missing";
|
|
12
|
+
missing: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface SchemaProbe {
|
|
15
|
+
db(name: string): Promise<Database>;
|
|
16
|
+
publishes(asset: string): Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
export declare function hasNeed(probe: SchemaProbe, n: string): Promise<boolean>;
|
|
19
|
+
export declare function capabilityOf(probe: SchemaProbe, f: Feature): Promise<Capability>;
|
|
20
|
+
export declare function needsRelease(tag: string, c: Capability): Error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/archive-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Open a versioned release of an Irish dance results archive: download, verify, decompress and cache it by tag, keep the cache, and resolve feises and schools against it",
|
|
5
5
|
"keywords": ["irish-dance", "results", "release", "cache", "sqlite"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prepublishOnly": "bun scripts/build.ts && bun ../../tools/checks/archive-client-tarball.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@finbheara/domain": "0.
|
|
47
|
+
"@finbheara/domain": "0.49.0",
|
|
48
48
|
"@finbheara/names": "0.22.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|