@finbheara/archive-client 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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.6.x"`).
23
+ (`"@finbheara/archive-client": "0.7.x"`).
24
24
 
25
25
  ## Configuration
26
26
 
@@ -85,6 +85,7 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
85
85
  | `staff` | `staff.teacher`, `staff.adjudicator` | 260 |
86
86
  | `majors.order` | `majors.competitions` | 260 |
87
87
  | `feises`, `comps` | `feises.find`, `feises.series`, `comps.*`, `registrations.resolve` | 265 |
88
+ | `comps.dance` | the `dance` a comp of `comps.*` carries: null, never refused, without it | 270 |
88
89
  | `results.stream`, `majors.stream` | `results.stream`, `results.entries` | 255 |
89
90
  | `feises.dates` | `feises.dates` | 245 |
90
91
  | `dancers.records` | `dancers.records` | 215 |
@@ -233,7 +234,17 @@ level of the names package: the print, the key, the folded key, the tokens in an
233
234
  (a middle name one print omits: `nameRelation`). `age` is how the dancer's published birth window
234
235
  meets the asked age (`Age.fitsIn`, `overlaps`): `fits`, `overlaps`, `contradicts`, `unknown`.
235
236
  `school` is how the dancer's schools meet the asked one: `same` (the latest school as of `asOf`),
236
- `moved` (an earlier one), `none`, `unresolved` (the print reached no school).
237
+ `lineage` (the latest school is one the release's lineage joins to the asked one), `moved` (an
238
+ earlier one is the asked one), `none`, `unresolved` (the print reached no school).
239
+
240
+ THE LINEAGE is the release's `school-lineage.jsonl`: every line joins its `id` and its `other`,
241
+ whatever its direction (`precedes`, `succeeds`) or shape (a rename, a split, a merger, an absorb,
242
+ a relation), so a file carrying one end of an edge reads as one carrying both. The whole line
243
+ joins: every school reached through any number of edges, in either direction (the two halves of a
244
+ split are joined through their predecessor, the schools of a merger through their successor). An
245
+ earlier row at a joined school alone is no match. `r.school.lineage` lists the ids joined to the
246
+ asked ones. A release that publishes no lineage (feature `schools.lineage`) joins
247
+ nothing and its answer carries no `lineage`, never refused.
237
248
 
238
249
  `population` is how the dancer's published majors population meets the asked one: `same`,
239
250
  `contradicts`, `both`, `unknown`, `unasked`. A dancer's population is read only from its rows at
@@ -250,12 +261,13 @@ majors solo competitions and in none of the asked one's (listed in `excluded`, `
250
261
  `"population"`, and a candidate contradicting both says `age`). A dancer published in both, or in
251
262
  neither, stays. A population word that names no one population (`mixed`) narrows nothing, and the
252
263
  answer says so (`r.population.note`). The school is evidence and order, never a reason
253
- to drop anyone: candidates whose latest school is the asked one come first, then those that ever
254
- danced for it, then the rest. A school row meets the asked school by its id, else by its print
264
+ to drop anyone: candidates whose latest school is the asked one, or one the lineage joins to it,
265
+ come first, then those that ever danced for it, then the rest. A school row meets the asked school by its id, else by its print
255
266
  (one `rel.schools` resolves to the asked ids), so a latest print the release
256
267
  filed under no id is still `same`. `unique` is one candidate left after the age, never a best
257
268
  guess promoted. Where the school singles out one candidate, `preferred` names it with the reason,
258
- for a caller to weigh, and `arm` stays as counted. The `score` orders candidates and is no
269
+ for a caller to weigh, and `arm` stays as counted. A `lineage` match ranks with `same`: one of
270
+ each singles out neither. The `score` orders candidates and is no
259
271
  probability.
260
272
 
261
273
  A FEIS-LOCAL ID (`FEIS_LOCAL_ID_FLOOR`, 2^34, and up) is valid in its release only: a candidate and a result row
@@ -388,6 +400,8 @@ c.level; // "OC" (FF, BG, AB, NV, PW, PC, OC), c.feis_level 700
388
400
  c.age; // "U10", c.population "mixed", c.canonical "710OC", c.title, c.competition
389
401
  c.age_source; // "printed" | "title" | "code" | "merged" | null, c.age_printed "U10" as printed
390
402
  c.second; // true for a second chance or a supplementary comp beside the main one
403
+ c.dance; // "reel", "light-jig", "single-jig", "slip-jig", "treble-jig", "hornpipe",
404
+ // "treble-reel", "traditional-set", "set-dance", or null where the release names none
391
405
  c.from; // { feis_ids: ["fweb:ABC25"], year: 2025 } where an earlier year answered
392
406
  c.check; // "unchecked", or what the caller's `title` says about the earlier comp
393
407
  c.syllabus_source; // "results": what the syllabus the answer read is read from
@@ -430,6 +444,10 @@ QuickFeis numbers its own and-over championships (`15 & over` prelim `616PC`, `1
430
444
  `719OC`). An and-over code is then the same string as an `Under N + 1` one, and `age` (`O18`)
431
445
  tells them apart. An adult band, a range and a grade render none.
432
446
 
447
+ `dance` is the release's own `competitions.dance`, read there off the platform's dance field, its
448
+ level field and the title, in that order: one dance or none, never a word read here. A release
449
+ older than the column answers null for every comp, and `rel.capability("comps.dance")` says which.
450
+
433
451
  `age` is the release's reading, and `age_source` says where it was read. A `merged` comp (a
434
452
  platform's comps merged into one, `710OCM` with no `710OC` beside it) prints one member's band,
435
453
  so its `age` is the union of its members' (`U12` for `710OCM - U10 Open Champion` where the next
@@ -106,6 +106,215 @@ function nameLevel2(q, c) {
106
106
  return null;
107
107
  }
108
108
 
109
+ import { readFileSync } from "fs";
110
+ var SCHEMA = `
111
+ CREATE TABLE feis(
112
+ feis_id TEXT PRIMARY KEY, source TEXT NOT NULL, token TEXT NOT NULL, host_id TEXT NOT NULL,
113
+ name TEXT NOT NULL, date_lo TEXT, date_hi TEXT, date_printed TEXT, year INTEGER,
114
+ location TEXT, instance_id TEXT, brand_id TEXT, regions TEXT NOT NULL,
115
+ competitions INTEGER NOT NULL, rows INTEGER NOT NULL, captures INTEGER NOT NULL
116
+ );
117
+ CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL);
118
+ `;
119
+ var INDEXES = [
120
+ "CREATE INDEX feis_host ON feis(host_id COLLATE NOCASE)",
121
+ "CREATE INDEX feis_token ON feis(token)",
122
+ "CREATE INDEX feis_source ON feis(source)",
123
+ "CREATE INDEX feis_instance ON feis(instance_id)",
124
+ "CREATE INDEX feis_brand ON feis(brand_id)",
125
+ "CREATE INDEX feis_year ON feis(year)"
126
+ ];
127
+ function readJsonl(path) {
128
+ return readFileSync(path, "utf8").split(`
129
+ `).filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
130
+ }
131
+ var feisesIndex = {
132
+ name: "feises",
133
+ version: "2",
134
+ inputs: ["results", "feis-redirects.jsonl"],
135
+ says: "every feis by id, platform, code, series and name, with its counts and regions",
136
+ build(rel, out) {
137
+ out.run(SCHEMA);
138
+ const src = rel.db("results");
139
+ const counts = new Map;
140
+ for (const r of src.query("SELECT event, count(*) AS n, json_group_array(DISTINCT feis_region) AS regions FROM competitions GROUP BY event").all())
141
+ counts.set(r.event, {
142
+ comps: r.n,
143
+ regions: new Set(JSON.parse(r.regions).filter((x) => x !== null))
144
+ });
145
+ const rows = new Map;
146
+ for (const r of src.query("SELECT c.event AS event, sum(r.n) AS n FROM (SELECT competition, count(*) AS n FROM results GROUP BY competition) r JOIN (SELECT DISTINCT competition, event FROM competitions) c ON c.competition = r.competition GROUP BY c.event").all())
147
+ rows.set(r.event, r.n);
148
+ const put = out.prepare("INSERT OR IGNORE INTO feis VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
149
+ const captures = new Map;
150
+ const feisRows = src.query("SELECT feis_id, source, host_id, name_printed, date_lo, date_hi, date_printed, year, location_printed, feis_instance_id, feis_brand_id FROM feises ORDER BY file, line").all();
151
+ for (const f of feisRows)
152
+ captures.set(f.feis_id, (captures.get(f.feis_id) ?? 0) + 1);
153
+ for (const f of feisRows) {
154
+ const c = counts.get(f.feis_id);
155
+ const colon = f.feis_id.indexOf(":");
156
+ put.run(f.feis_id, f.source, colon === -1 ? f.source : f.feis_id.slice(0, colon), f.host_id, f.name_printed, f.date_lo, f.date_hi, f.date_printed, f.year, f.location_printed, f.feis_instance_id, f.feis_brand_id, JSON.stringify([...c?.regions ?? []].sort()), c?.comps ?? 0, rows.get(f.feis_id) ?? 0, captures.get(f.feis_id) ?? 0);
157
+ }
158
+ const red = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
159
+ for (const r of readJsonl(rel.path("feis-redirects.jsonl")))
160
+ red.run(r.old_id, r.new_id, r.kind);
161
+ for (const s of INDEXES)
162
+ out.run(s);
163
+ }
164
+ };
165
+ var NO_CALENDAR2 = "the release holds a feis-year once its results are captured and carries no calendar of scheduled feiseanna";
166
+ var globToLike = (g) => g.replace(/[\\%_]/g, (c) => `\\${c}`).replace(/\*/g, "%").replace(/\?/g, "_");
167
+
168
+ class FeisFinder2 {
169
+ rel;
170
+ constructor(rel) {
171
+ this.rel = rel;
172
+ }
173
+ async dates() {
174
+ await this.rel.fetch("results");
175
+ await this.rel.require("feises.dates");
176
+ const dates = this.rel.db("results").query("SELECT feis_id, min(date_lo) AS date_lo, max(date_hi) AS date_hi FROM feises GROUP BY feis_id ORDER BY feis_id").all();
177
+ return { tag: this.rel.tag, dates };
178
+ }
179
+ async find(q) {
180
+ const db = await this.rel.indexDb("feises");
181
+ const where = [];
182
+ const args = [];
183
+ let redirected;
184
+ if (q.platform !== undefined) {
185
+ where.push("(source = ? OR token = ?)");
186
+ args.push(q.platform, q.platform);
187
+ }
188
+ if (q.code !== undefined) {
189
+ where.push("host_id = ? COLLATE NOCASE");
190
+ args.push(q.code);
191
+ }
192
+ if (q.id !== undefined) {
193
+ let id = q.id;
194
+ const seen = new Set;
195
+ for (;; ) {
196
+ const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(id);
197
+ if (r === null || seen.has(r.new_id))
198
+ break;
199
+ seen.add(id);
200
+ id = r.new_id;
201
+ }
202
+ if (id !== q.id)
203
+ redirected = { from: q.id, to: id };
204
+ where.push("(feis_id = ? OR instance_id = ? OR brand_id = ?)");
205
+ args.push(id, id, id);
206
+ }
207
+ if (q.name !== undefined) {
208
+ where.push("name LIKE ? ESCAPE '\\'");
209
+ args.push(globToLike(q.name));
210
+ }
211
+ if (q.year !== undefined) {
212
+ where.push("year = ?");
213
+ args.push(q.year);
214
+ }
215
+ const sql = `SELECT * FROM feis${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo, feis_id`;
216
+ const feises = db.query(sql).all(...args).map((r) => ({
217
+ ...r,
218
+ regions: JSON.parse(r.regions)
219
+ }));
220
+ return { tag: this.rel.tag, feises, ...redirected !== undefined ? { redirected } : {} };
221
+ }
222
+ async codeStem(token, stem) {
223
+ const db = await this.rel.indexDb("feises");
224
+ const like = db.query("SELECT host_id, year, brand_id FROM feis WHERE token = ? AND host_id LIKE ? ESCAPE '\\' COLLATE NOCASE").all(token, `${stem.replace(/[\\%_]/g, (c) => `\\${c}`)}__`);
225
+ const fits = like.filter((f) => {
226
+ const d = /(\d{2})$/.exec(f.host_id);
227
+ return d !== null && f.host_id.slice(0, -2).toLowerCase() === stem.toLowerCase() && f.year !== null && f.year % 100 === Number(d[1]) && f.brand_id !== null;
228
+ });
229
+ return [...new Set(fits.map((f) => f.brand_id))].sort();
230
+ }
231
+ async seriesOf(brand) {
232
+ const db = await this.rel.indexDb("feises");
233
+ const rows = db.query("SELECT * FROM feis WHERE brand_id = ? ORDER BY year, date_lo, feis_id").all(brand);
234
+ const byYear = new Map;
235
+ for (const r of rows) {
236
+ const i = byYear.get(r.year);
237
+ if (i === undefined)
238
+ byYear.set(r.year, {
239
+ year: r.year,
240
+ feis_ids: [r.feis_id],
241
+ date_lo: r.date_lo,
242
+ name: r.name
243
+ });
244
+ else
245
+ i.feis_ids.push(r.feis_id);
246
+ }
247
+ const instances = [...byYear.values()];
248
+ const newest = rows[rows.length - 1];
249
+ return {
250
+ series_id: brand,
251
+ name: newest?.name ?? "",
252
+ host: newest?.source ?? "",
253
+ hosts: [...new Set(rows.map((r) => r.source))].sort(),
254
+ codes: [...new Set(rows.map((r) => r.host_id))].sort(),
255
+ years: instances.map((i) => i.year).filter((y) => y !== null),
256
+ instances
257
+ };
258
+ }
259
+ async series(q) {
260
+ const asked = q.trim();
261
+ const answer = async (brands, via, more = {}) => {
262
+ const candidates = [];
263
+ for (const b of brands)
264
+ candidates.push(await this.seriesOf(b));
265
+ const arm = candidates.length === 0 ? "none" : candidates.length === 1 ? "unique" : "ambiguous";
266
+ const u = arm === "unique" ? candidates[0] : null;
267
+ return {
268
+ tag: this.rel.tag,
269
+ asked,
270
+ arm,
271
+ via: candidates.length === 0 ? null : via,
272
+ series_id: u?.series_id ?? null,
273
+ name: u?.name ?? null,
274
+ host: u?.host ?? null,
275
+ years: u?.years ?? [],
276
+ instances: u?.instances ?? [],
277
+ next: null,
278
+ ...u !== null ? { next_why: NO_CALENDAR2 } : {},
279
+ candidates,
280
+ ...arm === "ambiguous" ? { why: `${candidates.length} series answer "${asked}": ${brands.join(", ")}` } : {},
281
+ ...more
282
+ };
283
+ };
284
+ if (asked === "")
285
+ return answer([], null, { why: "no series was asked" });
286
+ const byId = await this.find({ id: asked });
287
+ if (byId.feises.length > 0) {
288
+ const brands = [...new Set(byId.feises.map((f) => f.brand_id))];
289
+ const filed = brands.filter((b) => b !== null).sort();
290
+ const redirected = byId.redirected !== undefined ? { redirected: byId.redirected } : {};
291
+ if (filed.length === 0)
292
+ return answer([], null, {
293
+ ...redirected,
294
+ why: `the release files ${byId.feises.map((f) => f.feis_id).join(", ")} under no series`
295
+ });
296
+ return answer(filed, "id", redirected);
297
+ }
298
+ const code = /^([^:\s]+):(.*?)(\d{2})?$/.exec(asked);
299
+ if (code !== null) {
300
+ const [, token, stem] = code;
301
+ const brands = stem === "" ? [] : await this.codeStem(token, stem);
302
+ if (brands.length > 0)
303
+ return answer(brands, "code-stem");
304
+ }
305
+ const glob = /[*?]/.test(asked) ? asked : `*${asked}*`;
306
+ const byName = await this.find({ name: glob });
307
+ const brands = [
308
+ ...new Set(byName.feises.map((f) => f.brand_id).filter((b) => b !== null))
309
+ ].sort();
310
+ if (brands.length > 0)
311
+ return answer(brands, "name");
312
+ return answer([], null, {
313
+ why: byName.feises.length > 0 ? `the feises named "${asked}" are filed under no series` : `the release holds no feis or series "${asked}"`
314
+ });
315
+ }
316
+ }
317
+
109
318
  var ENTRY_RANK = new Map([
110
319
  ["final", 0],
111
320
  ["", 1],
@@ -375,6 +584,7 @@ class MajorsFinder2 {
375
584
  }
376
585
  }
377
586
 
587
+ var LINEAGE_FILE = "school-lineage.jsonl";
378
588
  function nameOrderOf(s) {
379
589
  return s === "surname-first" || s === "given-first" || s === "unknown" ? s : undefined;
380
590
  }
@@ -506,11 +716,17 @@ var AGE_WEIGHT = {
506
716
  };
507
717
  var SCHOOL_RANK = {
508
718
  same: 0,
719
+ lineage: 0,
509
720
  moved: 1,
510
721
  none: 2,
511
722
  unresolved: 2,
512
723
  unasked: 2
513
724
  };
725
+ var PREFERRED_WHY = {
726
+ same: "whose latest school is",
727
+ lineage: "whose latest school the release's lineage joins to",
728
+ moved: "that ever danced for"
729
+ };
514
730
  var POPULATION_WEIGHT = {
515
731
  same: 1,
516
732
  both: 0,
@@ -520,6 +736,7 @@ var POPULATION_WEIGHT = {
520
736
  };
521
737
  var SCHOOL_WEIGHT = {
522
738
  same: 2,
739
+ lineage: 2,
523
740
  moved: 1,
524
741
  none: 0,
525
742
  unresolved: 0,
@@ -535,9 +752,30 @@ function idChunks(ids) {
535
752
  class DancerResolver2 {
536
753
  rel;
537
754
  printIds = new Map;
755
+ lineageOf;
538
756
  constructor(rel) {
539
757
  this.rel = rel;
540
758
  }
759
+ lineage() {
760
+ this.lineageOf ??= (async () => {
761
+ if ((await this.rel.capability("schools.lineage")).state !== "full")
762
+ return null;
763
+ await this.rel.fetch(LINEAGE_FILE);
764
+ const out = new Map;
765
+ const join = (a, b) => {
766
+ const s = out.get(a) ?? new Set;
767
+ s.add(b);
768
+ out.set(a, s);
769
+ };
770
+ for (const r of readJsonl(this.rel.path(LINEAGE_FILE)))
771
+ if (r.id !== r.other) {
772
+ join(r.id, r.other);
773
+ join(r.other, r.id);
774
+ }
775
+ return out;
776
+ })();
777
+ return this.lineageOf;
778
+ }
541
779
  async records(q = {}) {
542
780
  await this.rel.fetch("results");
543
781
  await this.rel.require("dancers.records");
@@ -643,11 +881,27 @@ class DancerResolver2 {
643
881
  const age = q.age === undefined ? undefined : this.readAge(q.age, asOf);
644
882
  const population = q.population === undefined ? undefined : readPopulation2(q.population);
645
883
  let schoolIds = null;
884
+ let lineageIds = [];
646
885
  let school;
647
886
  if (q.school !== undefined) {
648
887
  const s = await this.rel.schools.resolve(q.school);
649
888
  schoolIds = s.candidates.map((c) => c.school_id);
650
- school = { arm: s.arm, ids: schoolIds };
889
+ const lineage = await this.lineage();
890
+ if (lineage === null)
891
+ school = { arm: s.arm, ids: schoolIds };
892
+ else {
893
+ const asked = new Set(schoolIds);
894
+ const reached = new Set(schoolIds);
895
+ const todo = [...schoolIds];
896
+ for (let id = todo.pop();id !== undefined; id = todo.pop())
897
+ for (const o of lineage.get(id) ?? [])
898
+ if (!reached.has(o)) {
899
+ reached.add(o);
900
+ todo.push(o);
901
+ }
902
+ lineageIds = [...reached].filter((id) => !asked.has(id)).sort();
903
+ school = { arm: s.arm, ids: schoolIds, lineage: lineageIds };
904
+ }
651
905
  }
652
906
  const byDancer = new Map;
653
907
  const unlinked = new Map;
@@ -674,7 +928,7 @@ class DancerResolver2 {
674
928
  const ageEv = ageEvidence(d, age?.age ?? null);
675
929
  const pop = populationOf(db, id);
676
930
  const popEv = populationEvidence(pop, population?.read ?? null);
677
- const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, asOf);
931
+ const schoolEv = schoolIds === null ? "unasked" : schoolIds.length === 0 ? "unresolved" : await this.schoolEvidence(schools, schoolIds, lineageIds, asOf);
678
932
  all.push({
679
933
  dancer_id: id,
680
934
  display: d.display,
@@ -706,15 +960,17 @@ class DancerResolver2 {
706
960
  });
707
961
  let preferred;
708
962
  if (kept.length > 1 && school !== undefined)
709
- for (const ev of ["same", "moved"]) {
710
- const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] === SCHOOL_RANK[ev]);
963
+ for (const rank of [SCHOOL_RANK.same, SCHOOL_RANK.moved]) {
964
+ const hit = kept.filter((c) => SCHOOL_RANK[c.evidence.school] === rank);
711
965
  if (hit.length === 0)
712
966
  continue;
713
- if (hit.length === 1)
967
+ if (hit.length === 1) {
968
+ const c = hit[0];
714
969
  preferred = {
715
- dancer_id: hit[0].dancer_id,
716
- why: `the only candidate ${ev === "same" ? "whose latest school is" : "that ever danced for"} ${q.school}`
970
+ dancer_id: c.dancer_id,
971
+ why: `the only candidate ${PREFERRED_WHY[c.evidence.school]} ${q.school}`
717
972
  };
973
+ }
718
974
  break;
719
975
  }
720
976
  return {
@@ -730,29 +986,33 @@ class DancerResolver2 {
730
986
  ...school !== undefined ? { school } : {}
731
987
  };
732
988
  }
733
- async schoolEvidence(schools, ids, asOf) {
989
+ async schoolEvidence(schools, ids, lineage, asOf) {
734
990
  const rows = schools.filter((s) => asOf === null || s.year_lo === null || s.year_lo <= asOf);
735
- const meets = async (s) => {
991
+ const idsOf = async (s) => {
736
992
  if (s.school_id !== null)
737
- return ids.includes(s.school_id);
993
+ return [s.school_id];
738
994
  let read = this.printIds.get(s.print);
739
995
  if (read === undefined) {
740
996
  read = (await this.rel.schools.resolve(s.print)).candidates.map((c) => c.school_id);
741
997
  this.printIds.set(s.print, read);
742
998
  }
743
- return read.some((id) => ids.includes(id));
999
+ return read;
744
1000
  };
745
1001
  const top = (s) => asOf !== null && s.year_hi !== null ? Math.min(s.year_hi, asOf) : s.year_hi ?? -1;
746
1002
  const latest = Math.max(-1, ...rows.map(top));
747
1003
  let earlier = false;
1004
+ let joined = false;
748
1005
  for (const s of rows) {
749
- if (!await meets(s))
750
- continue;
751
- if (top(s) === latest)
752
- return "same";
753
- earlier = true;
1006
+ const own = await idsOf(s);
1007
+ const isLatest = top(s) === latest;
1008
+ if (own.some((id) => ids.includes(id))) {
1009
+ if (isLatest)
1010
+ return "same";
1011
+ earlier = true;
1012
+ } else if (isLatest && own.some((id) => lineage.includes(id)))
1013
+ joined = true;
754
1014
  }
755
- return earlier ? "moved" : "none";
1015
+ return joined ? "lineage" : earlier ? "moved" : "none";
756
1016
  }
757
1017
  readAge(a, asOf) {
758
1018
  const done = (age, note) => {
@@ -865,16 +1125,16 @@ var LEVEL = Object.freeze({
865
1125
  600: "PC",
866
1126
  700: "OC"
867
1127
  });
868
- var SCHEMA = `
1128
+ var SCHEMA2 = `
869
1129
  CREATE TABLE comp(
870
1130
  competition TEXT PRIMARY KEY, feis_id TEXT NOT NULL, brand_id TEXT, year INTEGER,
871
1131
  code TEXT NOT NULL, code_fold TEXT NOT NULL, title TEXT, age TEXT, gender TEXT,
872
1132
  feis_level INTEGER, round TEXT, platform TEXT NOT NULL, captures INTEGER NOT NULL,
873
1133
  file TEXT NOT NULL, line INTEGER NOT NULL, age_source TEXT, age_printed TEXT,
874
- gender_source TEXT, level_printed TEXT, dance_printed TEXT
1134
+ gender_source TEXT, level_printed TEXT, dance_printed TEXT, dance TEXT
875
1135
  );
876
1136
  `;
877
- var INDEXES = [
1137
+ var INDEXES2 = [
878
1138
  "CREATE INDEX comp_feis_code ON comp(feis_id, code_fold)",
879
1139
  "CREATE INDEX comp_brand_year ON comp(brand_id, year)"
880
1140
  ];
@@ -883,16 +1143,17 @@ var OPTIONAL = [
883
1143
  "age_printed",
884
1144
  "gender_source",
885
1145
  "level_printed",
886
- "dance_printed"
1146
+ "dance_printed",
1147
+ "dance"
887
1148
  ];
888
1149
  var fold = (code) => code.trim().toLowerCase();
889
1150
  var compsIndex = {
890
1151
  name: "comps",
891
- version: "2",
1152
+ version: "3",
892
1153
  inputs: ["results"],
893
1154
  says: "every competition by its feis and its own code, with its series and year",
894
1155
  build(rel, out) {
895
- out.run(SCHEMA);
1156
+ out.run(SCHEMA2);
896
1157
  const src = rel.db("results");
897
1158
  const brand = new Map;
898
1159
  for (const f of src.query("SELECT feis_id, feis_brand_id, year FROM feises ORDER BY file, line").all())
@@ -901,14 +1162,14 @@ var compsIndex = {
901
1162
  const captures = new Map;
902
1163
  for (const r of src.query("SELECT competition, count(*) AS n FROM competitions GROUP BY competition").all())
903
1164
  captures.set(r.competition, r.n);
904
- const put = out.prepare("INSERT OR IGNORE INTO comp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1165
+ const put = out.prepare("INSERT OR IGNORE INTO comp VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
905
1166
  const has = new Set(src.query("PRAGMA table_info(competitions)").all().map((r) => r.name));
906
1167
  const opt = (col) => has.has(col) ? col : `NULL AS ${col}`;
907
1168
  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()) {
908
1169
  const f = brand.get(c.event);
909
- 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);
1170
+ 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, c.dance);
910
1171
  }
911
- for (const s of INDEXES)
1172
+ for (const s of INDEXES2)
912
1173
  out.run(s);
913
1174
  }
914
1175
  };
@@ -933,6 +1194,7 @@ function compOf(r, codes = []) {
933
1194
  age_printed: r.age_printed ?? null,
934
1195
  population: pop === "" ? null : pop,
935
1196
  second,
1197
+ dance: r.dance ?? null,
936
1198
  round,
937
1199
  canonical: canonicalOf2(lv, r.age),
938
1200
  platform: r.platform,
@@ -1197,215 +1459,6 @@ class CompResolver2 {
1197
1459
  }
1198
1460
  }
1199
1461
 
1200
- import { readFileSync } from "fs";
1201
- var SCHEMA2 = `
1202
- CREATE TABLE feis(
1203
- feis_id TEXT PRIMARY KEY, source TEXT NOT NULL, token TEXT NOT NULL, host_id TEXT NOT NULL,
1204
- name TEXT NOT NULL, date_lo TEXT, date_hi TEXT, date_printed TEXT, year INTEGER,
1205
- location TEXT, instance_id TEXT, brand_id TEXT, regions TEXT NOT NULL,
1206
- competitions INTEGER NOT NULL, rows INTEGER NOT NULL, captures INTEGER NOT NULL
1207
- );
1208
- CREATE TABLE redirect(old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL, kind TEXT NOT NULL);
1209
- `;
1210
- var INDEXES2 = [
1211
- "CREATE INDEX feis_host ON feis(host_id COLLATE NOCASE)",
1212
- "CREATE INDEX feis_token ON feis(token)",
1213
- "CREATE INDEX feis_source ON feis(source)",
1214
- "CREATE INDEX feis_instance ON feis(instance_id)",
1215
- "CREATE INDEX feis_brand ON feis(brand_id)",
1216
- "CREATE INDEX feis_year ON feis(year)"
1217
- ];
1218
- function readJsonl(path) {
1219
- return readFileSync(path, "utf8").split(`
1220
- `).filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
1221
- }
1222
- var feisesIndex = {
1223
- name: "feises",
1224
- version: "2",
1225
- inputs: ["results", "feis-redirects.jsonl"],
1226
- says: "every feis by id, platform, code, series and name, with its counts and regions",
1227
- build(rel, out) {
1228
- out.run(SCHEMA2);
1229
- const src = rel.db("results");
1230
- const counts = new Map;
1231
- for (const r of src.query("SELECT event, count(*) AS n, json_group_array(DISTINCT feis_region) AS regions FROM competitions GROUP BY event").all())
1232
- counts.set(r.event, {
1233
- comps: r.n,
1234
- regions: new Set(JSON.parse(r.regions).filter((x) => x !== null))
1235
- });
1236
- const rows = new Map;
1237
- for (const r of src.query("SELECT c.event AS event, sum(r.n) AS n FROM (SELECT competition, count(*) AS n FROM results GROUP BY competition) r JOIN (SELECT DISTINCT competition, event FROM competitions) c ON c.competition = r.competition GROUP BY c.event").all())
1238
- rows.set(r.event, r.n);
1239
- const put = out.prepare("INSERT OR IGNORE INTO feis VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1240
- const captures = new Map;
1241
- const feisRows = src.query("SELECT feis_id, source, host_id, name_printed, date_lo, date_hi, date_printed, year, location_printed, feis_instance_id, feis_brand_id FROM feises ORDER BY file, line").all();
1242
- for (const f of feisRows)
1243
- captures.set(f.feis_id, (captures.get(f.feis_id) ?? 0) + 1);
1244
- for (const f of feisRows) {
1245
- const c = counts.get(f.feis_id);
1246
- const colon = f.feis_id.indexOf(":");
1247
- put.run(f.feis_id, f.source, colon === -1 ? f.source : f.feis_id.slice(0, colon), f.host_id, f.name_printed, f.date_lo, f.date_hi, f.date_printed, f.year, f.location_printed, f.feis_instance_id, f.feis_brand_id, JSON.stringify([...c?.regions ?? []].sort()), c?.comps ?? 0, rows.get(f.feis_id) ?? 0, captures.get(f.feis_id) ?? 0);
1248
- }
1249
- const red = out.prepare("INSERT OR REPLACE INTO redirect VALUES (?, ?, ?)");
1250
- for (const r of readJsonl(rel.path("feis-redirects.jsonl")))
1251
- red.run(r.old_id, r.new_id, r.kind);
1252
- for (const s of INDEXES2)
1253
- out.run(s);
1254
- }
1255
- };
1256
- var NO_CALENDAR2 = "the release holds a feis-year once its results are captured and carries no calendar of scheduled feiseanna";
1257
- var globToLike = (g) => g.replace(/[\\%_]/g, (c) => `\\${c}`).replace(/\*/g, "%").replace(/\?/g, "_");
1258
-
1259
- class FeisFinder2 {
1260
- rel;
1261
- constructor(rel) {
1262
- this.rel = rel;
1263
- }
1264
- async dates() {
1265
- await this.rel.fetch("results");
1266
- await this.rel.require("feises.dates");
1267
- const dates = this.rel.db("results").query("SELECT feis_id, min(date_lo) AS date_lo, max(date_hi) AS date_hi FROM feises GROUP BY feis_id ORDER BY feis_id").all();
1268
- return { tag: this.rel.tag, dates };
1269
- }
1270
- async find(q) {
1271
- const db = await this.rel.indexDb("feises");
1272
- const where = [];
1273
- const args = [];
1274
- let redirected;
1275
- if (q.platform !== undefined) {
1276
- where.push("(source = ? OR token = ?)");
1277
- args.push(q.platform, q.platform);
1278
- }
1279
- if (q.code !== undefined) {
1280
- where.push("host_id = ? COLLATE NOCASE");
1281
- args.push(q.code);
1282
- }
1283
- if (q.id !== undefined) {
1284
- let id = q.id;
1285
- const seen = new Set;
1286
- for (;; ) {
1287
- const r = db.query("SELECT new_id FROM redirect WHERE old_id = ?").get(id);
1288
- if (r === null || seen.has(r.new_id))
1289
- break;
1290
- seen.add(id);
1291
- id = r.new_id;
1292
- }
1293
- if (id !== q.id)
1294
- redirected = { from: q.id, to: id };
1295
- where.push("(feis_id = ? OR instance_id = ? OR brand_id = ?)");
1296
- args.push(id, id, id);
1297
- }
1298
- if (q.name !== undefined) {
1299
- where.push("name LIKE ? ESCAPE '\\'");
1300
- args.push(globToLike(q.name));
1301
- }
1302
- if (q.year !== undefined) {
1303
- where.push("year = ?");
1304
- args.push(q.year);
1305
- }
1306
- const sql = `SELECT * FROM feis${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo, feis_id`;
1307
- const feises = db.query(sql).all(...args).map((r) => ({
1308
- ...r,
1309
- regions: JSON.parse(r.regions)
1310
- }));
1311
- return { tag: this.rel.tag, feises, ...redirected !== undefined ? { redirected } : {} };
1312
- }
1313
- async codeStem(token, stem) {
1314
- const db = await this.rel.indexDb("feises");
1315
- const like = db.query("SELECT host_id, year, brand_id FROM feis WHERE token = ? AND host_id LIKE ? ESCAPE '\\' COLLATE NOCASE").all(token, `${stem.replace(/[\\%_]/g, (c) => `\\${c}`)}__`);
1316
- const fits = like.filter((f) => {
1317
- const d = /(\d{2})$/.exec(f.host_id);
1318
- return d !== null && f.host_id.slice(0, -2).toLowerCase() === stem.toLowerCase() && f.year !== null && f.year % 100 === Number(d[1]) && f.brand_id !== null;
1319
- });
1320
- return [...new Set(fits.map((f) => f.brand_id))].sort();
1321
- }
1322
- async seriesOf(brand) {
1323
- const db = await this.rel.indexDb("feises");
1324
- const rows = db.query("SELECT * FROM feis WHERE brand_id = ? ORDER BY year, date_lo, feis_id").all(brand);
1325
- const byYear = new Map;
1326
- for (const r of rows) {
1327
- const i = byYear.get(r.year);
1328
- if (i === undefined)
1329
- byYear.set(r.year, {
1330
- year: r.year,
1331
- feis_ids: [r.feis_id],
1332
- date_lo: r.date_lo,
1333
- name: r.name
1334
- });
1335
- else
1336
- i.feis_ids.push(r.feis_id);
1337
- }
1338
- const instances = [...byYear.values()];
1339
- const newest = rows[rows.length - 1];
1340
- return {
1341
- series_id: brand,
1342
- name: newest?.name ?? "",
1343
- host: newest?.source ?? "",
1344
- hosts: [...new Set(rows.map((r) => r.source))].sort(),
1345
- codes: [...new Set(rows.map((r) => r.host_id))].sort(),
1346
- years: instances.map((i) => i.year).filter((y) => y !== null),
1347
- instances
1348
- };
1349
- }
1350
- async series(q) {
1351
- const asked = q.trim();
1352
- const answer = async (brands, via, more = {}) => {
1353
- const candidates = [];
1354
- for (const b of brands)
1355
- candidates.push(await this.seriesOf(b));
1356
- const arm = candidates.length === 0 ? "none" : candidates.length === 1 ? "unique" : "ambiguous";
1357
- const u = arm === "unique" ? candidates[0] : null;
1358
- return {
1359
- tag: this.rel.tag,
1360
- asked,
1361
- arm,
1362
- via: candidates.length === 0 ? null : via,
1363
- series_id: u?.series_id ?? null,
1364
- name: u?.name ?? null,
1365
- host: u?.host ?? null,
1366
- years: u?.years ?? [],
1367
- instances: u?.instances ?? [],
1368
- next: null,
1369
- ...u !== null ? { next_why: NO_CALENDAR2 } : {},
1370
- candidates,
1371
- ...arm === "ambiguous" ? { why: `${candidates.length} series answer "${asked}": ${brands.join(", ")}` } : {},
1372
- ...more
1373
- };
1374
- };
1375
- if (asked === "")
1376
- return answer([], null, { why: "no series was asked" });
1377
- const byId = await this.find({ id: asked });
1378
- if (byId.feises.length > 0) {
1379
- const brands = [...new Set(byId.feises.map((f) => f.brand_id))];
1380
- const filed = brands.filter((b) => b !== null).sort();
1381
- const redirected = byId.redirected !== undefined ? { redirected: byId.redirected } : {};
1382
- if (filed.length === 0)
1383
- return answer([], null, {
1384
- ...redirected,
1385
- why: `the release files ${byId.feises.map((f) => f.feis_id).join(", ")} under no series`
1386
- });
1387
- return answer(filed, "id", redirected);
1388
- }
1389
- const code = /^([^:\s]+):(.*?)(\d{2})?$/.exec(asked);
1390
- if (code !== null) {
1391
- const [, token, stem] = code;
1392
- const brands = stem === "" ? [] : await this.codeStem(token, stem);
1393
- if (brands.length > 0)
1394
- return answer(brands, "code-stem");
1395
- }
1396
- const glob = /[*?]/.test(asked) ? asked : `*${asked}*`;
1397
- const byName = await this.find({ name: glob });
1398
- const brands = [
1399
- ...new Set(byName.feises.map((f) => f.brand_id).filter((b) => b !== null))
1400
- ].sort();
1401
- if (brands.length > 0)
1402
- return answer(brands, "name");
1403
- return answer([], null, {
1404
- why: byName.feises.length > 0 ? `the feises named "${asked}" are filed under no series` : `the release holds no feis or series "${asked}"`
1405
- });
1406
- }
1407
- }
1408
-
1409
1462
  import { Database } from "bun:sqlite";
1410
1463
  import { createHash as createHash2 } from "crypto";
1411
1464
  import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
@@ -2509,6 +2562,18 @@ var FEATURES2 = [
2509
2562
  since: 265,
2510
2563
  calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
2511
2564
  },
2565
+ {
2566
+ feature: "comps.dance",
2567
+ needs: ["results:competitions.dance"],
2568
+ since: 270,
2569
+ calls: "the `dance` a comp of comps.resolve, comps.syllabus and comps.syllabusFor carries: null, never refused, on a release without it"
2570
+ },
2571
+ {
2572
+ feature: "schools.lineage",
2573
+ needs: ["school-lineage.jsonl"],
2574
+ since: 184,
2575
+ calls: "the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
2576
+ },
2512
2577
  {
2513
2578
  feature: "results.stream",
2514
2579
  needs: [
@@ -3221,4 +3286,4 @@ function releases2(opts = {}) {
3221
3286
  return new ReleaseManager2(opts);
3222
3287
  }
3223
3288
 
3224
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, NO_CALENDAR2, FeisFinder2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };
3289
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, 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-gy5xye67.js";
7
+ } from "./chunks/index-mg6dw0y5.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
@@ -178,7 +178,7 @@ function printDancers(a, say) {
178
178
  if (a.age !== undefined)
179
179
  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})` : ""}`);
180
180
  if (a.school !== undefined)
181
- say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}`);
181
+ say(` school asked: ${a.school.arm} ${a.school.ids.join(", ") || "-"}${a.school.lineage !== undefined && a.school.lineage.length > 0 ? ` (lineage: ${a.school.lineage.join(", ")})` : ""}`);
182
182
  if (a.population !== undefined)
183
183
  say(` population asked: ${a.population.read ?? "(unread)"}${a.population.note !== undefined ? ` (${a.population.note})` : ""}`);
184
184
  for (const c of a.candidates)
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  unzstdTo2,
7
7
  NAME_LEVELS2,
8
8
  nameLevel2,
9
+ NO_CALENDAR2,
10
+ FeisFinder2,
9
11
  entryRank2,
10
12
  FEIS_LOCAL_ID_FLOOR2,
11
13
  ResultFinder2,
@@ -14,8 +16,6 @@ import {
14
16
  readPopulation2,
15
17
  canonicalOf2,
16
18
  CompResolver2,
17
- NO_CALENDAR2,
18
- FeisFinder2,
19
19
  StaffFinder2,
20
20
  SchoolResolver2,
21
21
  TeamFinder2,
@@ -45,7 +45,7 @@ import {
45
45
  openCached2,
46
46
  ReleaseManager2,
47
47
  releases2
48
- } from "./chunks/index-gy5xye67.js";
48
+ } from "./chunks/index-mg6dw0y5.js";
49
49
  import {
50
50
  cacheRoot2,
51
51
  repoOf2
@@ -2,6 +2,7 @@ import { type AgeRelation } from "@finbheara/domain/age";
2
2
  import { type Population, type PopulationRelation } from "@finbheara/domain/title";
3
3
  import type { IndexBuilder } from "./indexes.js";
4
4
  import type { Release } from "./release.js";
5
+ export type CompDance = "reel" | "light-jig" | "single-jig" | "slip-jig" | "treble-jig" | "hornpipe" | "treble-reel" | "traditional-set" | "set-dance";
5
6
  export type CompKind = "championship" | "grade" | "unrated";
6
7
  export type CompSource = "syllabus" | "prior-year" | "title";
7
8
  export type SyllabusSource = "syllabus-document" | "results";
@@ -19,6 +20,7 @@ export interface Comp {
19
20
  age_printed: string | null;
20
21
  population: Population | null;
21
22
  second: boolean;
23
+ dance: CompDance | null;
22
24
  round: string | null;
23
25
  canonical: string | null;
24
26
  platform: string;
@@ -115,6 +117,7 @@ interface CompRow {
115
117
  gender_source?: string | null;
116
118
  level_printed?: string | null;
117
119
  dance_printed?: string | null;
120
+ dance?: string | null;
118
121
  }
119
122
  export declare function compOf(r: CompRow, codes?: readonly string[]): Comp;
120
123
  export declare function canonicalOf(level: number | null, age: string | null): string | null;
@@ -24,7 +24,7 @@ export interface DancerQuery {
24
24
  }
25
25
  export type AgeEvidence = "fits" | "overlaps" | "contradicts" | "unknown" | "unasked";
26
26
  export type PopulationEvidence = "same" | "contradicts" | "both" | "unknown" | "unasked";
27
- export type SchoolEvidence = "same" | "moved" | "none" | "unresolved" | "unasked";
27
+ export type SchoolEvidence = "same" | "lineage" | "moved" | "none" | "unresolved" | "unasked";
28
28
  export interface DancerSchool {
29
29
  school_id: string | null;
30
30
  print: string;
@@ -98,6 +98,7 @@ export interface DancerAnswer {
98
98
  school?: {
99
99
  arm: string;
100
100
  ids: string[];
101
+ lineage?: string[];
101
102
  };
102
103
  refused?: string;
103
104
  }
@@ -163,7 +164,9 @@ export interface DancerYear {
163
164
  export declare class DancerResolver {
164
165
  private readonly rel;
165
166
  private readonly printIds;
167
+ private lineageOf;
166
168
  constructor(rel: Release);
169
+ private lineage;
167
170
  records(q?: {
168
171
  ids?: readonly number[];
169
172
  }): Promise<{
@@ -2,7 +2,7 @@ export { type AssetPlan, DATABASES, type DatabaseName, type LandedFile, type Rec
2
2
  export { sha256File, unzstdTo } from "./bytes.js";
3
3
  export { type AgeAsked, type AgeEvidence, type DancerAnswer, type DancerBulkRecord, type DancerCandidate, type DancerQuery, type DancerRecord, DancerResolver, type DancerSchool, type DancerSearchQuery, type DancerYear, 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
- 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";
5
+ export { type Comp, type CompAnswer, type CompDance, 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";
7
7
  export { type Feis, type FeisAnswer, FeisFinder, type FeisQuery, NO_CALENDAR, type Series, type SeriesAnswer, type SeriesInstance, type SeriesNext, } from "./feises.js";
8
8
  export { type IndexBuilder, type IndexFile, buildIndex, defaultBuilders, ensureIndex, indexKey, indexPath, isCurrent, listIndexes, } from "./indexes.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.6.0",
3
+ "version": "0.8.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,8 +44,8 @@
44
44
  "prepublishOnly": "bun scripts/build.ts && bun ../../tools/checks/archive-client-tarball.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@finbheara/domain": "0.49.0",
48
- "@finbheara/names": "0.22.0",
47
+ "@finbheara/domain": "0.50.0",
48
+ "@finbheara/names": "0.23.0",
49
49
  "hyparquet": "1.31.2"
50
50
  },
51
51
  "devDependencies": {