@finbheara/archive-client 0.3.1 → 0.4.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
@@ -2,8 +2,8 @@
2
2
 
3
3
  Open a versioned release of an Irish dance results dataset and work with it locally: download,
4
4
  verify, decompress and cache it by tag, keep a user-local cache of releases, and resolve feises,
5
- schools, competitions, registration lists, dancers, results, majors, teachers and
6
- adjudicators against one. A command line, `idq`, is a thin wrapper over the same calls.
5
+ schools, competitions, registration lists, dancers, results, majors, teams, event dates, teachers
6
+ and adjudicators against one. A command line, `idq`, is a thin wrapper over the same calls.
7
7
 
8
8
  Bun 1.4.2 or later (the databases open through `bun:sqlite`). MIT.
9
9
 
@@ -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.2.x"`).
23
+ (`"@finbheara/archive-client": "0.4.x"`).
24
24
 
25
25
  ## Configuration
26
26
 
@@ -85,6 +85,8 @@ 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
+ | `teams` | `teams.results`, `teams.rosters`, `teams.resultRosters` | 260 |
89
+ | `events` | `events.years` | 260 |
88
90
 
89
91
  A spec is `pinned` (the default: the user-local pin, else the latest), `latest`, a cut number
90
92
  (`42`, or `42.1` for a fraction) or a tag (`archive-YYYY.MM.DD-N`).
@@ -138,8 +140,9 @@ outside the cache root.
138
140
 
139
141
  The release databases carry a primary key per table and nothing more, so lookups build side
140
142
  indexes, one SQLite file per index per tag, each keyed on its builder's version and the sha256 of
141
- every input it read. A file whose key is stale is rebuilt, never served. Four ship here, and a
142
- caller may hand its own (`builders` option, an `IndexBuilder`):
143
+ every input it read. A file whose key is stale is rebuilt, never served. These ship here, and a
144
+ caller may hand its own (`builders` option, an `IndexBuilder`, with an optional async `load` step
145
+ whose answer its `build` receives):
143
146
 
144
147
  - `schools`: every published school print with the ids its rows carry, filed into a
145
148
  `@finbheara/domain` `schoolEncoder` noun index, with the registry and the retired ids
@@ -151,6 +154,9 @@ caller may hand its own (`builders` option, an `IndexBuilder`):
151
154
  - `staff` (keyed the same way): teachers and adjudicators by name (a `roleEncoder` noun index) and TD number: the
152
155
  schools' staff lists and TD links, the adjudicator register, and the seats each sat
153
156
  - `comps`: every competition by its feis and the feis's own code, with its series and year
157
+ - `teams`: the team results, the roster teams with their members, and how they join, by
158
+ event-year
159
+ - `events`: every event-year's dates and place
154
160
 
155
161
  ## Feises
156
162
 
@@ -270,6 +276,32 @@ A row carries the prints and the ids that stand for them: the address, the compe
270
276
  and age, the place, the name and school as printed with the dancer and school ids. The majors are
271
277
  read from `majors.sqlite`, fetched on first use.
272
278
 
279
+ ## Teams and event-years
280
+
281
+ ```ts
282
+ const r = await rel.teams.results({ event: "<event>", year: 2024 });
283
+ r.results; // [{ address, competition, event, year, comp_no, title, category, hands, letter, label,
284
+ // school_printed, school_id, place, place_printed, tie, competitors, atts, ... }]
285
+ const t = await rel.teams.rosters({ event: "<event>", year: 2024 });
286
+ t.teams; // [{ team, kind, event, year, comp_no, title, team_index, seq, school_id, school_printed,
287
+ // members: [{ seq, name_printed, name, yob, dancer_id, dancer_scope }] }]
288
+ const j = await rel.teams.resultRosters({ event: "<event>", year: 2024 });
289
+ j.joins; // [{ team, result_address, join_kind, via_split, roster_index, result_letter, ... }]
290
+ const e = await rel.events.years({ event: "<event>" });
291
+ e.years; // [{ event, year, date_lo, date_hi, resolution, source, date_printed, place, place_printed }]
292
+ ```
293
+
294
+ A query names an event (case-blind), a year, both, or neither (every row). Everything comes from
295
+ ONE release: a roster member's `dancer_id` is the same release's id as the results', so a caller
296
+ never joins team records of one release to dancer ids of another. A roster is a sheet of a school's
297
+ teams (`kind: "rosterTeam"`) or a result card's own line-up (`lineup`). `join_kind` says how a
298
+ roster team met the results (`exact`, `card`, `branch`) or why it did not (`no-result`,
299
+ `no-school`, `no-roster`, `multi-team-unjoined`), and `result_address` is null where none joined.
300
+ An event-year's dates are a window (`date_lo`, `date_hi`) with how fine it is (`resolution`:
301
+ `day`, `month`, `calendar`) and what it is read from (`source`), and `place` is a place-tree node
302
+ where the release knows one. A field the source left empty is null. The rows are read once per
303
+ release into the `teams` and `events` indexes.
304
+
273
305
  ## Teachers and adjudicators
274
306
 
275
307
  ```ts
@@ -394,6 +426,8 @@ and the competitions still resolve.
394
426
  [--population girls|boys] [--rows]
395
427
  idq results [--release <spec>] [--dancer <id>] [--feis <id>] [--comp <c>] [--school <s>] [--year <y>] [--limit <n>]
396
428
  idq majors [--release <spec>] [--event <e>] [--year <y>] [--comp <c>] [--dancer <id>] [--rows]
429
+ idq teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]
430
+ idq events [--release <spec>] [--event <e>] [--year <y>]
397
431
  idq teacher [--release <spec>] <name|TD number>
398
432
  idq adjudicator [--release <spec>] <name|TD number>
399
433
  idq comp [--release <spec>] <feis> <code> [--year y] [--title t]
@@ -1586,8 +1586,240 @@ class SchoolResolver2 {
1586
1586
  }
1587
1587
  }
1588
1588
 
1589
+ import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
1590
+ var COMPRESSORS = {
1591
+ ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
1592
+ };
1593
+ var tableAsset = (table) => `${table}.parquet`;
1594
+ function plain(v) {
1595
+ if (typeof v === "bigint")
1596
+ return v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v;
1597
+ if (Array.isArray(v))
1598
+ return v.map(plain);
1599
+ if (v !== null && typeof v === "object" && !(v instanceof Date)) {
1600
+ const o = {};
1601
+ for (const [k, x] of Object.entries(v))
1602
+ o[k] = plain(x);
1603
+ return o;
1604
+ }
1605
+ return v;
1606
+ }
1607
+ async function readParquet(path, columns) {
1608
+ const file = await Bun.file(path).arrayBuffer();
1609
+ const rows = await parquetReadObjects({
1610
+ file,
1611
+ compressors: COMPRESSORS,
1612
+ ...columns !== undefined ? { columns: [...columns] } : {}
1613
+ });
1614
+ return rows.map((r) => plain(r));
1615
+ }
1616
+ async function parquetColumns(path) {
1617
+ const meta = parquetMetadata(await Bun.file(path).arrayBuffer());
1618
+ return new Set(parquetSchema(meta).children.map((c) => c.element.name));
1619
+ }
1620
+
1621
+ var str = (v) => typeof v === "string" && v !== "" ? v : null;
1622
+ var num = (v) => typeof v === "number" ? v : null;
1623
+ var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
1624
+ var scope = (id) => id === null ? null : id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable";
1625
+ var TEAM_TABLES = [
1626
+ "team_results",
1627
+ "roster_teams",
1628
+ "roster_members",
1629
+ "team_result_rosters",
1630
+ "event_years"
1631
+ ];
1632
+ var TEAMS_SCHEMA = `
1633
+ CREATE TABLE event_year(event_year TEXT PRIMARY KEY, event TEXT NOT NULL, year INTEGER NOT NULL);
1634
+ CREATE TABLE result(address TEXT PRIMARY KEY, competition TEXT, event TEXT, year INTEGER,
1635
+ card TEXT, comp_no TEXT, title TEXT, category TEXT, hands INTEGER, letter TEXT, label TEXT,
1636
+ name_printed TEXT, school_printed TEXT, school_loc TEXT, school_id TEXT, place INTEGER,
1637
+ place_printed TEXT, tie INTEGER, competitors INTEGER, atts TEXT, ord INTEGER);
1638
+ CREATE TABLE roster_team(team TEXT PRIMARY KEY, kind TEXT, event TEXT, year INTEGER,
1639
+ comp_no TEXT, title TEXT, team_index TEXT, seq INTEGER, school_id TEXT, school_printed TEXT,
1640
+ ord INTEGER);
1641
+ CREATE TABLE roster_member(team TEXT, seq INTEGER, name_printed TEXT, name TEXT, yob INTEGER,
1642
+ dancer_id INTEGER, ord INTEGER);
1643
+ CREATE TABLE result_roster(team TEXT, event TEXT, year INTEGER, comp_no TEXT, school_id TEXT,
1644
+ result_address TEXT, join_kind TEXT, via_split INTEGER, roster_teams INTEGER,
1645
+ result_teams INTEGER, roster_index TEXT, result_letter TEXT, ord INTEGER);
1646
+ `;
1647
+ var TEAMS_INDEXES = [
1648
+ "CREATE INDEX result_ey ON result(event COLLATE NOCASE, year)",
1649
+ "CREATE INDEX result_year ON result(year)",
1650
+ "CREATE INDEX roster_team_ey ON roster_team(event COLLATE NOCASE, year)",
1651
+ "CREATE INDEX roster_team_year ON roster_team(year)",
1652
+ "CREATE INDEX roster_member_team ON roster_member(team)",
1653
+ "CREATE INDEX result_roster_ey ON result_roster(event COLLATE NOCASE, year)",
1654
+ "CREATE INDEX result_roster_year ON result_roster(year)"
1655
+ ];
1656
+ async function readTables(rel, tables) {
1657
+ const out = {};
1658
+ for (const t of tables) {
1659
+ await rel.fetch(tableAsset(t));
1660
+ out[t] = await readParquet(rel.path(tableAsset(t)));
1661
+ }
1662
+ return out;
1663
+ }
1664
+ var teamsIndex = {
1665
+ name: "teams",
1666
+ version: "1",
1667
+ inputs: TEAM_TABLES.map(tableAsset),
1668
+ says: "the team results, the roster teams with their members, and how they join, by event-year",
1669
+ load: (rel) => readTables(rel, TEAM_TABLES),
1670
+ build(_rel, out, loaded) {
1671
+ const t = loaded;
1672
+ out.run(TEAMS_SCHEMA);
1673
+ const ey = new Map;
1674
+ const putEy = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?)");
1675
+ for (const r of t.event_years) {
1676
+ const k = str(r.event_year);
1677
+ const event = str(r.event);
1678
+ const year = num(r.year);
1679
+ if (k === null || event === null || year === null)
1680
+ continue;
1681
+ ey.set(k, { event, year });
1682
+ putEy.run(k, event, year);
1683
+ }
1684
+ const putR = out.prepare("INSERT OR IGNORE INTO result VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1685
+ let ord = 0;
1686
+ for (const r of t.team_results)
1687
+ putR.run(str(r.address), str(r.slug), str(r.event), num(r.year), str(r.card), str(r.comp_no), str(r.title), str(r.category), num(r.hands), str(r.letter), str(r.label), str(r.name_printed), str(r.school_printed), str(r.school_loc), str(r.school_id), num(r.place), str(r.place_printed), num(r.tie), num(r.competitors), JSON.stringify(strs(r.atts)), ord++);
1688
+ const putT = out.prepare("INSERT OR IGNORE INTO roster_team VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1689
+ for (const r of t.roster_teams) {
1690
+ const e = ey.get(str(r.event_year) ?? "");
1691
+ putT.run(str(r.team), str(r.kind), e?.event ?? null, num(r.year) ?? e?.year ?? null, str(r.comp_no), str(r.title), str(r.team_index), num(r.seq), str(r.school_id), str(r.school_print_materialized), ord++);
1692
+ }
1693
+ const putM = out.prepare("INSERT INTO roster_member VALUES (?, ?, ?, ?, ?, ?, ?)");
1694
+ for (const r of t.roster_members)
1695
+ putM.run(str(r.team), num(r.seq), typeof r.name_printed === "string" ? r.name_printed : "", str(r.name), num(r.yob), num(r.dancer_id), ord++);
1696
+ const putJ = out.prepare("INSERT INTO result_roster VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1697
+ for (const r of t.team_result_rosters) {
1698
+ const e = ey.get(str(r.event_year) ?? "");
1699
+ putJ.run(str(r.team), e?.event ?? null, e?.year ?? null, str(r.comp_no), str(r.school_id), str(r.result_address), str(r.join_kind), num(r.via_split) === 1 ? 1 : 0, num(r.roster_teams), num(r.result_teams), str(r.roster_index), str(r.result_letter), ord++);
1700
+ }
1701
+ for (const s of TEAMS_INDEXES)
1702
+ out.run(s);
1703
+ }
1704
+ };
1705
+ var EVENTS_SCHEMA = `CREATE TABLE event_year(event TEXT NOT NULL, year INTEGER NOT NULL,
1706
+ date_lo TEXT, date_hi TEXT, resolution TEXT, source TEXT, date_printed TEXT, place TEXT,
1707
+ place_printed TEXT, PRIMARY KEY(event, year))`;
1708
+ var eventsIndex = {
1709
+ name: "events",
1710
+ version: "1",
1711
+ inputs: [tableAsset("event_years")],
1712
+ says: "every event-year: its dates (and how fine they are) and its place",
1713
+ load: (rel) => readTables(rel, ["event_years"]),
1714
+ build(_rel, out, loaded) {
1715
+ const rows = loaded.event_years ?? [];
1716
+ out.run(EVENTS_SCHEMA);
1717
+ const put = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
1718
+ for (const r of rows) {
1719
+ const event = str(r.event);
1720
+ const year = num(r.year);
1721
+ if (event === null || year === null)
1722
+ continue;
1723
+ put.run(event, year, str(r.date_lo), str(r.date_hi), str(r.resolution), str(r.source), JSON.stringify(strs(r.date_printed)), str(r.place), JSON.stringify(strs(r.place_printed)));
1724
+ }
1725
+ out.run("CREATE INDEX event_year_event ON event_year(event COLLATE NOCASE)");
1726
+ }
1727
+ };
1728
+ function whereOf(q, prefix = "") {
1729
+ const where = [];
1730
+ const args = [];
1731
+ if (q.event !== undefined) {
1732
+ where.push(`${prefix}event = ? COLLATE NOCASE`);
1733
+ args.push(q.event);
1734
+ }
1735
+ if (q.year !== undefined) {
1736
+ where.push(`${prefix}year = ?`);
1737
+ args.push(q.year);
1738
+ }
1739
+ return { sql: where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`, args };
1740
+ }
1741
+
1742
+ class TeamFinder2 {
1743
+ rel;
1744
+ constructor(rel) {
1745
+ this.rel = rel;
1746
+ }
1747
+ db() {
1748
+ return this.rel.indexDb("teams");
1749
+ }
1750
+ async results(q = {}) {
1751
+ const db = await this.db();
1752
+ const w = whereOf(q);
1753
+ const rows = db.query(`SELECT address, competition, event, year, card, comp_no, title, category, hands, letter,
1754
+ label, name_printed, school_printed, school_loc, school_id, place, place_printed, tie,
1755
+ competitors, atts
1756
+ FROM result${w.sql} ORDER BY year, event, competition, place IS NULL, place, ord`).all(...w.args);
1757
+ return {
1758
+ tag: this.rel.tag,
1759
+ asked: q,
1760
+ results: rows.map((r) => ({ ...r, atts: JSON.parse(r.atts) }))
1761
+ };
1762
+ }
1763
+ async rosters(q = {}) {
1764
+ const db = await this.db();
1765
+ const w = whereOf(q, "t.");
1766
+ const teams = db.query(`SELECT team, kind, event, year, comp_no, title, team_index, seq, school_id, school_printed
1767
+ FROM roster_team t${w.sql} ORDER BY year, event, ord`).all(...w.args);
1768
+ const members = new Map;
1769
+ for (const m of db.query(`SELECT m.team AS team, m.seq AS seq, m.name_printed AS name_printed, m.name AS name,
1770
+ m.yob AS yob, m.dancer_id AS dancer_id
1771
+ FROM roster_member m JOIN roster_team t ON t.team = m.team${w.sql}
1772
+ ORDER BY m.team, m.seq, m.ord`).all(...w.args)) {
1773
+ const { team, ...rest } = m;
1774
+ let list = members.get(team);
1775
+ if (list === undefined)
1776
+ members.set(team, list = []);
1777
+ list.push({ ...rest, dancer_scope: scope(rest.dancer_id) });
1778
+ }
1779
+ return {
1780
+ tag: this.rel.tag,
1781
+ asked: q,
1782
+ teams: teams.map((t) => ({ ...t, members: members.get(t.team) ?? [] }))
1783
+ };
1784
+ }
1785
+ async resultRosters(q = {}) {
1786
+ const db = await this.db();
1787
+ const w = whereOf(q);
1788
+ const rows = db.query(`SELECT team, event, year, comp_no, school_id, result_address, join_kind, via_split,
1789
+ roster_teams, result_teams, roster_index, result_letter
1790
+ FROM result_roster${w.sql} ORDER BY year, event, ord`).all(...w.args);
1791
+ return {
1792
+ tag: this.rel.tag,
1793
+ asked: q,
1794
+ joins: rows.map((r) => ({ ...r, via_split: r.via_split === 1 }))
1795
+ };
1796
+ }
1797
+ }
1798
+
1799
+ class EventFinder2 {
1800
+ rel;
1801
+ constructor(rel) {
1802
+ this.rel = rel;
1803
+ }
1804
+ async years(q = {}) {
1805
+ const db = await this.rel.indexDb("events");
1806
+ const w = whereOf(q);
1807
+ const rows = db.query(`SELECT event, year, date_lo, date_hi, resolution, source, date_printed, place, place_printed
1808
+ FROM event_year${w.sql} ORDER BY year, event`).all(...w.args);
1809
+ return {
1810
+ tag: this.rel.tag,
1811
+ asked: q,
1812
+ years: rows.map((r) => ({
1813
+ ...r,
1814
+ date_printed: JSON.parse(r.date_printed),
1815
+ place_printed: JSON.parse(r.place_printed)
1816
+ }))
1817
+ };
1818
+ }
1819
+ }
1820
+
1589
1821
  function defaultBuilders2() {
1590
- return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex];
1822
+ return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex, teamsIndex, eventsIndex];
1591
1823
  }
1592
1824
  var indexDir = (rel) => join(rel.dir, "index");
1593
1825
  var indexPath2 = (rel, name) => join(indexDir(rel), `${name}.sqlite`);
@@ -1623,6 +1855,8 @@ function buildIndex2(rel, b, o) {
1623
1855
  const key = indexKey2(rel, b);
1624
1856
  if (!o.force && keyOf(out) === key)
1625
1857
  return out;
1858
+ if (b.load !== undefined && o.loaded === undefined)
1859
+ throw new Error(`archive-client: index "${b.name}" reads its inputs first: build it with ensureIndex (or rel.index)`);
1626
1860
  mkdirSync(indexDir(rel), { recursive: true });
1627
1861
  const tmp = `${out}.building.${process.pid}`;
1628
1862
  rmSync(tmp, { force: true });
@@ -1632,7 +1866,7 @@ function buildIndex2(rel, b, o) {
1632
1866
  db.run("PRAGMA journal_mode = OFF");
1633
1867
  db.run("PRAGMA synchronous = OFF");
1634
1868
  db.run("CREATE TABLE meta(k TEXT PRIMARY KEY, v TEXT NOT NULL)");
1635
- db.transaction(() => b.build(rel, db))();
1869
+ db.transaction(() => b.build(rel, db, o.loaded))();
1636
1870
  const put = db.prepare("INSERT INTO meta(k, v) VALUES (?, ?)");
1637
1871
  put.run("key", key);
1638
1872
  put.run("builder", b.name);
@@ -1649,6 +1883,12 @@ function buildIndex2(rel, b, o) {
1649
1883
  o.log?.(`index ${rel.tag} ${b.name}: built in ${((performance.now() - t0) / 1000).toFixed(1)} s`);
1650
1884
  return out;
1651
1885
  }
1886
+ async function ensureIndex2(rel, b, o) {
1887
+ if (!o.force && isCurrent2(rel, b))
1888
+ return indexPath2(rel, b.name);
1889
+ const loaded = b.load === undefined ? undefined : await b.load(rel);
1890
+ return buildIndex2(rel, b, { ...o, loaded });
1891
+ }
1652
1892
  function listIndexes2(rel) {
1653
1893
  const dir = indexDir(rel);
1654
1894
  if (!existsSync(dir))
@@ -2058,6 +2298,81 @@ var FEATURES2 = [
2058
2298
  since: 265,
2059
2299
  calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
2060
2300
  },
2301
+ {
2302
+ feature: "teams",
2303
+ needs: [
2304
+ ...[
2305
+ "slug",
2306
+ "address",
2307
+ "event",
2308
+ "year",
2309
+ "card",
2310
+ "comp_no",
2311
+ "title",
2312
+ "category",
2313
+ "hands",
2314
+ "letter",
2315
+ "label",
2316
+ "name_printed",
2317
+ "school_printed",
2318
+ "school_loc",
2319
+ "school_id",
2320
+ "place",
2321
+ "place_printed",
2322
+ "tie",
2323
+ "competitors",
2324
+ "atts"
2325
+ ].map((c) => `table:team_results.${c}`),
2326
+ ...[
2327
+ "team",
2328
+ "kind",
2329
+ "event_year",
2330
+ "year",
2331
+ "comp_no",
2332
+ "title",
2333
+ "team_index",
2334
+ "seq",
2335
+ "school_id",
2336
+ "school_print_materialized"
2337
+ ].map((c) => `table:roster_teams.${c}`),
2338
+ ...["team", "seq", "name_printed", "name", "yob", "dancer_id"].map((c) => `table:roster_members.${c}`),
2339
+ ...[
2340
+ "event_year",
2341
+ "comp_no",
2342
+ "team",
2343
+ "result_address",
2344
+ "school_id",
2345
+ "join_kind",
2346
+ "via_split",
2347
+ "roster_teams",
2348
+ "result_teams",
2349
+ "roster_index",
2350
+ "result_letter"
2351
+ ].map((c) => `table:team_result_rosters.${c}`),
2352
+ "table:event_years.event_year",
2353
+ "table:event_years.event",
2354
+ "table:event_years.year"
2355
+ ],
2356
+ since: 260,
2357
+ calls: "teams.results, teams.rosters, teams.resultRosters (the teams index)"
2358
+ },
2359
+ {
2360
+ feature: "events",
2361
+ needs: [
2362
+ "event_year",
2363
+ "event",
2364
+ "year",
2365
+ "date_lo",
2366
+ "date_hi",
2367
+ "resolution",
2368
+ "source",
2369
+ "date_printed",
2370
+ "place",
2371
+ "place_printed"
2372
+ ].map((c) => `table:event_years.${c}`),
2373
+ since: 260,
2374
+ calls: "events.years (the events index)"
2375
+ },
2061
2376
  {
2062
2377
  feature: "staff",
2063
2378
  needs: [
@@ -2072,6 +2387,13 @@ var FEATURES2 = [
2072
2387
  }
2073
2388
  ];
2074
2389
  async function hasNeed(probe, n) {
2390
+ const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
2391
+ if (t !== null) {
2392
+ if (probe.tableColumns === undefined)
2393
+ return false;
2394
+ const cols = await probe.tableColumns(t[1]);
2395
+ return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
2396
+ }
2075
2397
  const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
2076
2398
  if (m === null)
2077
2399
  return probe.publishes(n);
@@ -2143,6 +2465,8 @@ class Release2 {
2143
2465
  staff;
2144
2466
  comps;
2145
2467
  registrations;
2468
+ teams;
2469
+ events;
2146
2470
  opts;
2147
2471
  handles = new Map;
2148
2472
  remoteAssets = null;
@@ -2163,6 +2487,8 @@ class Release2 {
2163
2487
  this.staff = new StaffFinder2(this);
2164
2488
  this.comps = new CompResolver2(this);
2165
2489
  this.registrations = new RegistrationResolver2(this, this.comps, opts.dancerResolver);
2490
+ this.teams = new TeamFinder2(this);
2491
+ this.events = new EventFinder2(this);
2166
2492
  }
2167
2493
  receipt() {
2168
2494
  return readReceipt(this.dir) ?? { tag: this.tag, source: "unknown", files: {} };
@@ -2207,7 +2533,14 @@ class Release2 {
2207
2533
  await this.fetch(name);
2208
2534
  return this.db(name);
2209
2535
  },
2210
- publishes: (asset) => this.publishes(asset)
2536
+ publishes: (asset) => this.publishes(asset),
2537
+ tableColumns: async (table) => {
2538
+ const asset = tableAsset(table);
2539
+ if (!await this.publishes(asset))
2540
+ return new Set;
2541
+ await this.fetch(asset);
2542
+ return parquetColumns(this.path(asset));
2543
+ }
2211
2544
  };
2212
2545
  checked = new Map;
2213
2546
  async capability(feature) {
@@ -2304,7 +2637,7 @@ class Release2 {
2304
2637
  if (FEATURES2.some((f) => f.feature === b.name))
2305
2638
  await this.require(b.name);
2306
2639
  await this.fetch(...b.inputs);
2307
- return buildIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
2640
+ return ensureIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
2308
2641
  }
2309
2642
  async indexDb(name) {
2310
2643
  const key = `index:${name}`;
@@ -2523,7 +2856,7 @@ class ReleaseManager2 {
2523
2856
  await rel.fetch(...b.inputs);
2524
2857
  built.push({
2525
2858
  name,
2526
- path: buildIndex2(rel, b, { force: o.force ?? false, log: this.opts.log })
2859
+ path: await ensureIndex2(rel, b, { force: o.force ?? false, log: this.opts.log })
2527
2860
  });
2528
2861
  }
2529
2862
  rel.close();
@@ -2596,4 +2929,4 @@ function releases2(opts = {}) {
2596
2929
  return new ReleaseManager2(opts);
2597
2930
  }
2598
2931
 
2599
- 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 };
2932
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, 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 };
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  nameOrderOf,
5
5
  openRelease2,
6
6
  releases2
7
- } from "./chunks/index-3va2jzse.js";
7
+ } from "./chunks/index-v8e0qe9f.js";
8
8
  import"./chunks/index-hne1wadw.js";
9
9
  import"./chunks/index-hghdy980.js";
10
10
 
@@ -50,6 +50,12 @@ var USAGE = `usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json]
50
50
  majors [--release <spec>] [--event <e>] [--year <y>] [--comp <competition>] [--dancer <id>]
51
51
  the majors: every event and its years; an event's
52
52
  competitions; a competition's, an event's or a dancer's rows
53
+ teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]
54
+ the team result rows of an event-year; with --rosters its
55
+ roster teams and their dancers; with --joins how each roster
56
+ team meets the results
57
+ events [--release <spec>] [--event <e>] [--year <y>]
58
+ each event-year's dates and place
53
59
  teacher [--release <spec>] <name|TD number>
54
60
  the staff lists' people: where they taught, the register,
55
61
  where they sat
@@ -313,6 +319,14 @@ async function resolveVerb(rel, what, args, p, say, stdin) {
313
319
  throw new Error("resolve comp|dancer|registrations");
314
320
  }
315
321
  }
322
+ function eventYearOf(p) {
323
+ const event = p.flags.get("--event");
324
+ const year = p.flags.get("--year");
325
+ return {
326
+ ...event !== undefined ? { event } : {},
327
+ ...year !== undefined ? { year: Number(year) } : {}
328
+ };
329
+ }
316
330
  async function main(argv, io = {}) {
317
331
  const say = io.say ?? ((s) => console.log(s));
318
332
  const log = io.log ?? ((s) => console.error(s));
@@ -630,6 +644,56 @@ async function main(argv, io = {}) {
630
644
  rel.close();
631
645
  }
632
646
  }
647
+ case "teams": {
648
+ const rel = await openRelease2(spec, { ...opts, assets: [] });
649
+ try {
650
+ const q = eventYearOf(p);
651
+ if (p.bools.has("--rosters")) {
652
+ const a = await rel.teams.rosters(q);
653
+ out(a, () => {
654
+ for (const t of a.teams) {
655
+ say(` ${t.year} ${t.event ?? "?"} ${t.comp_no ?? "-"} ${t.team_index ?? "-"} ${t.school_id ?? "(no id)"} "${t.school_printed ?? ""}" ${t.title ?? ""} [${t.kind}]`);
656
+ for (const m of t.members)
657
+ say(` ${m.seq} ${m.dancer_id ?? "-"} ${m.name_printed}${m.yob === null ? "" : ` (${m.yob})`}`);
658
+ }
659
+ say(`${a.teams.length} roster team(s) in ${a.tag}`);
660
+ });
661
+ return a.teams.length === 0 ? 1 : 0;
662
+ }
663
+ if (p.bools.has("--joins")) {
664
+ const a = await rel.teams.resultRosters(q);
665
+ out(a, () => {
666
+ for (const j of a.joins)
667
+ say(` ${j.year ?? "?"} ${j.event ?? "?"} ${j.comp_no ?? "-"} ${j.team} ${j.join_kind} ${j.result_address ?? "-"} roster ${j.roster_index ?? "-"} result ${j.result_letter ?? "-"}`);
668
+ say(`${a.joins.length} join(s) in ${a.tag}`);
669
+ });
670
+ return a.joins.length === 0 ? 1 : 0;
671
+ }
672
+ const a = await rel.teams.results(q);
673
+ out(a, () => {
674
+ for (const r of a.results)
675
+ say(` ${r.address} ${r.place_printed ?? "-"} ${r.label ?? "-"} ${r.school_id ?? "(no id)"} "${r.school_printed ?? ""}" ${r.title ?? ""}${r.atts.length > 0 ? ` [${r.atts.join(",")}]` : ""}`);
676
+ say(`${a.results.length} team result(s) in ${a.tag}`);
677
+ });
678
+ return a.results.length === 0 ? 1 : 0;
679
+ } finally {
680
+ rel.close();
681
+ }
682
+ }
683
+ case "events": {
684
+ const rel = await openRelease2(spec, { ...opts, assets: [] });
685
+ try {
686
+ const a = await rel.events.years(eventYearOf(p));
687
+ out(a, () => {
688
+ for (const y of a.years)
689
+ say(` ${y.event} ${y.year} ${y.date_lo ?? "?"}..${y.date_hi ?? "?"} (${y.resolution ?? "-"}, ${y.source ?? "-"}) ${y.place ?? "-"}${y.place_printed.length > 0 ? ` "${y.place_printed.join("; ")}"` : ""}`);
690
+ say(`${a.years.length} event-year(s) in ${a.tag}`);
691
+ });
692
+ return a.years.length === 0 ? 1 : 0;
693
+ } finally {
694
+ rel.close();
695
+ }
696
+ }
633
697
  case "teacher":
634
698
  case "adjudicator": {
635
699
  if (rest.length === 0) {
package/dist/index.js CHANGED
@@ -17,11 +17,14 @@ import {
17
17
  FeisFinder2,
18
18
  StaffFinder2,
19
19
  SchoolResolver2,
20
+ TeamFinder2,
21
+ EventFinder2,
20
22
  defaultBuilders2,
21
23
  indexPath2,
22
24
  indexKey2,
23
25
  isCurrent2,
24
26
  buildIndex2,
27
+ ensureIndex2,
25
28
  listIndexes2,
26
29
  readPin2,
27
30
  youngest2,
@@ -41,7 +44,7 @@ import {
41
44
  openCached2,
42
45
  ReleaseManager2,
43
46
  releases2
44
- } from "./chunks/index-3va2jzse.js";
47
+ } from "./chunks/index-v8e0qe9f.js";
45
48
  import {
46
49
  cacheRoot2,
47
50
  repoOf2
@@ -60,6 +63,7 @@ export {
60
63
  CompResolver2 as CompResolver,
61
64
  DATABASES2 as DATABASES,
62
65
  DancerResolver2 as DancerResolver,
66
+ EventFinder2 as EventFinder,
63
67
  FEATURES2 as FEATURES,
64
68
  FEIS_LOCAL_ID_FLOOR2 as FEIS_LOCAL_ID_FLOOR,
65
69
  FeisFinder2 as FeisFinder,
@@ -72,6 +76,7 @@ export {
72
76
  ResultFinder2 as ResultFinder,
73
77
  SchoolResolver2 as SchoolResolver,
74
78
  StaffFinder2 as StaffFinder,
79
+ TeamFinder2 as TeamFinder,
75
80
  buildIndex2 as buildIndex,
76
81
  cacheRoot2 as cacheRoot,
77
82
  canonicalOf2 as canonicalOf,
@@ -81,6 +86,7 @@ export {
81
86
  cutOf2 as cutOf,
82
87
  dancerResolverOf2 as dancerResolverOf,
83
88
  defaultBuilders2 as defaultBuilders,
89
+ ensureIndex2 as ensureIndex,
84
90
  fallbackRemote2 as fallbackRemote,
85
91
  ghRemote2 as ghRemote,
86
92
  httpsRemote2 as httpsRemote,
@@ -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 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)";
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 teams [--release <spec>] [--event <e>] [--year <y>] [--rosters | --joins]\n the team result rows of an event-year; with --rosters its\n roster teams and their dancers; with --joins how each roster\n team meets the results\n events [--release <spec>] [--event <e>] [--year <y>]\n each event-year's dates and place\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>;
@@ -5,7 +5,7 @@ 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";
7
7
  export { type Feis, type FeisAnswer, FeisFinder, type FeisQuery, NO_CALENDAR, type Series, type SeriesAnswer, type SeriesInstance, type SeriesNext, } from "./feises.js";
8
- export { type IndexBuilder, type IndexFile, buildIndex, defaultBuilders, indexKey, indexPath, isCurrent, listIndexes, } from "./indexes.js";
8
+ export { type IndexBuilder, type IndexFile, buildIndex, defaultBuilders, ensureIndex, indexKey, indexPath, isCurrent, listIndexes, } from "./indexes.js";
9
9
  export { type CachedRelease, type ReleaseList, ReleaseManager, type RemovalPlan, releases, } from "./manager.js";
10
10
  export { type Registered, type Sat, type StaffAnswer, StaffFinder, type StaffPerson, type Taught, type TdLink, } from "./people.js";
11
11
  export { type Pin, readPin } from "./pin.js";
@@ -15,4 +15,5 @@ export { type MajorCompetition, type MajorEvent, MajorsFinder, type ResultAnswer
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
17
  export { type Capability, FEATURES, type Feature, type SchemaProbe, capabilityOf, columnsOf, needsRelease, } from "./schema.js";
18
+ export { EventFinder, type EventYear, type EventYearQuery, type ResultRoster, type RosterMember, type RosterTeam, TeamFinder, type TeamResult, } from "./teams.js";
18
19
  export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
@@ -6,7 +6,8 @@ export interface IndexBuilder {
6
6
  readonly inputs: readonly string[];
7
7
  readonly says: string;
8
8
  keyParts?(rel: Release): string[];
9
- build(rel: Release, out: Database): void;
9
+ load?(rel: Release): Promise<unknown>;
10
+ build(rel: Release, out: Database, loaded?: unknown): void;
10
11
  }
11
12
  export declare function defaultBuilders(): IndexBuilder[];
12
13
  export declare const indexDir: (rel: {
@@ -21,7 +22,12 @@ export declare function isCurrent(rel: Release, b: IndexBuilder): boolean;
21
22
  export declare function buildIndex(rel: Release, b: IndexBuilder, o: {
22
23
  force: boolean;
23
24
  log?: (line: string) => void;
25
+ loaded?: unknown;
24
26
  }): string;
27
+ export declare function ensureIndex(rel: Release, b: IndexBuilder, o: {
28
+ force: boolean;
29
+ log?: (line: string) => void;
30
+ }): Promise<string>;
25
31
  export interface IndexFile {
26
32
  name: string;
27
33
  path: string;
@@ -0,0 +1,4 @@
1
+ export declare const tableAsset: (table: string) => string;
2
+ export declare function plain(v: unknown): unknown;
3
+ export declare function readParquet(path: string, columns?: readonly string[]): Promise<Record<string, unknown>[]>;
4
+ export declare function parquetColumns(path: string): Promise<Set<string>>;
@@ -12,6 +12,7 @@ import { MajorsFinder, ResultFinder } from "./results.js";
12
12
  import { type Capability } from "./schema.js";
13
13
  import { type SchoolReaderChoice, SchoolResolver } from "./schools.js";
14
14
  import { type ReleaseSpec } from "./tag.js";
15
+ import { EventFinder, TeamFinder } from "./teams.js";
15
16
  export interface OpenOptions extends ClientOptions {
16
17
  assets?: readonly string[];
17
18
  remote?: Remote;
@@ -41,6 +42,8 @@ export declare class Release {
41
42
  readonly staff: StaffFinder;
42
43
  readonly comps: CompResolver;
43
44
  readonly registrations: RegistrationResolver;
45
+ readonly teams: TeamFinder;
46
+ readonly events: EventFinder;
44
47
  private readonly opts;
45
48
  private readonly handles;
46
49
  private remoteAssets;
@@ -14,6 +14,7 @@ export interface Capability extends Feature {
14
14
  export interface SchemaProbe {
15
15
  db(name: string): Promise<Database>;
16
16
  publishes(asset: string): Promise<boolean>;
17
+ tableColumns?(table: string): Promise<Set<string>>;
17
18
  }
18
19
  export declare function hasNeed(probe: SchemaProbe, n: string): Promise<boolean>;
19
20
  export declare function capabilityOf(probe: SchemaProbe, f: Feature): Promise<Capability>;
@@ -0,0 +1,105 @@
1
+ import type { IndexBuilder } from "./indexes.js";
2
+ import type { Release } from "./release.js";
3
+ export interface EventYearQuery {
4
+ event?: string;
5
+ year?: number;
6
+ }
7
+ export interface TeamResult {
8
+ address: string;
9
+ competition: string;
10
+ event: string;
11
+ year: number;
12
+ card: string | null;
13
+ comp_no: string | null;
14
+ title: string | null;
15
+ category: string | null;
16
+ hands: number | null;
17
+ letter: string | null;
18
+ label: string | null;
19
+ name_printed: string | null;
20
+ school_printed: string | null;
21
+ school_loc: string | null;
22
+ school_id: string | null;
23
+ place: number | null;
24
+ place_printed: string | null;
25
+ tie: number | null;
26
+ competitors: number | null;
27
+ atts: string[];
28
+ }
29
+ export interface RosterMember {
30
+ seq: number;
31
+ name_printed: string;
32
+ name: string | null;
33
+ yob: number | null;
34
+ dancer_id: number | null;
35
+ dancer_scope: "per-release" | "stable" | null;
36
+ }
37
+ export interface RosterTeam {
38
+ team: string;
39
+ kind: string;
40
+ event: string | null;
41
+ year: number;
42
+ comp_no: string | null;
43
+ title: string | null;
44
+ team_index: string | null;
45
+ seq: number | null;
46
+ school_id: string | null;
47
+ school_printed: string | null;
48
+ members: RosterMember[];
49
+ }
50
+ export interface ResultRoster {
51
+ team: string;
52
+ event: string | null;
53
+ year: number | null;
54
+ comp_no: string | null;
55
+ school_id: string | null;
56
+ result_address: string | null;
57
+ join_kind: string;
58
+ via_split: boolean;
59
+ roster_teams: number | null;
60
+ result_teams: number | null;
61
+ roster_index: string | null;
62
+ result_letter: string | null;
63
+ }
64
+ export interface EventYear {
65
+ event: string;
66
+ year: number;
67
+ date_lo: string | null;
68
+ date_hi: string | null;
69
+ resolution: string | null;
70
+ source: string | null;
71
+ date_printed: string[];
72
+ place: string | null;
73
+ place_printed: string[];
74
+ }
75
+ export declare const teamsIndex: IndexBuilder;
76
+ export declare const eventsIndex: IndexBuilder;
77
+ export declare class TeamFinder {
78
+ private readonly rel;
79
+ constructor(rel: Release);
80
+ private db;
81
+ results(q?: EventYearQuery): Promise<{
82
+ tag: string;
83
+ asked: EventYearQuery;
84
+ results: TeamResult[];
85
+ }>;
86
+ rosters(q?: EventYearQuery): Promise<{
87
+ tag: string;
88
+ asked: EventYearQuery;
89
+ teams: RosterTeam[];
90
+ }>;
91
+ resultRosters(q?: EventYearQuery): Promise<{
92
+ tag: string;
93
+ asked: EventYearQuery;
94
+ joins: ResultRoster[];
95
+ }>;
96
+ }
97
+ export declare class EventFinder {
98
+ private readonly rel;
99
+ constructor(rel: Release);
100
+ years(q?: EventYearQuery): Promise<{
101
+ tag: string;
102
+ asked: EventYearQuery;
103
+ years: EventYear[];
104
+ }>;
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.3.1",
3
+ "version": "0.4.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",
@@ -45,7 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@finbheara/domain": "0.49.0",
48
- "@finbheara/names": "0.22.0"
48
+ "@finbheara/names": "0.22.0",
49
+ "hyparquet": "1.31.2"
49
50
  },
50
51
  "devDependencies": {
51
52
  "typescript": "7.0.2"