@finbheara/archive-client 0.3.1 → 0.5.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.
@@ -106,8 +106,149 @@ function nameLevel2(q, c) {
106
106
  return null;
107
107
  }
108
108
 
109
+ var ENTRY_RANK = new Map([
110
+ ["final", 0],
111
+ ["", 1],
112
+ ["recall", 2],
113
+ ["standings", 3],
114
+ ["unplaced", 4],
115
+ ["qual", 5],
116
+ ["qual-a", 5],
117
+ ["qual-b", 5]
118
+ ]);
119
+ function entryRank2(section) {
120
+ const known = ENTRY_RANK.get(section);
121
+ if (known !== undefined)
122
+ return known;
123
+ if (/^solo(?:-r\d+)?$/.test(section))
124
+ return 6;
125
+ return 9;
126
+ }
127
+ var COLUMNS = `r.address AS address, r.competition AS competition, c.event AS event,
128
+ c.year AS year, c.tier AS tier, c.comp_no AS comp_no, c.title AS title, c.category AS category,
129
+ c.age AS age, c.age_printed AS age_printed, c.gender AS gender, c.competitors AS competitors,
130
+ c.feis_level AS feis_level, c.level_printed AS level_printed, c.date_printed AS date_printed,
131
+ c.source AS source, c.round AS round, r.section AS section, r.card AS card, r.place AS place,
132
+ r.place_printed AS place_printed, r.tie AS tie, r.relative_place AS relative_place,
133
+ r.atts AS atts, r.withdrawn AS withdrawn, r.qualifier AS qualifier,
134
+ r.name_printed AS name_printed, r.name AS name, r.school_printed AS school_printed,
135
+ r.school_loc AS school_loc, r.school_id AS school_id, r.dancer_id AS dancer_id, r.yob AS yob,
136
+ r.yob_printed AS yob_printed, r.points_printed AS points_printed,
137
+ r.platform_dancer_id AS platform_dancer_id, r.primary_source AS primary_source,
138
+ r.file AS file`;
139
+ var TRANSCRIBED = "('transcript', 'manifested')";
140
+ var scopeDb = (s) => s === "majors" ? "majors" : "results";
141
+ function streamSql(q) {
142
+ const where = [];
143
+ const args = [];
144
+ const list = (col, v, nocase = false) => {
145
+ if (v.length === 0) {
146
+ where.push("0");
147
+ return;
148
+ }
149
+ where.push(`${col}${nocase ? " COLLATE NOCASE" : ""} IN (${v.map(() => "?").join(", ")})`);
150
+ args.push(...v);
151
+ };
152
+ if (q.scope === "transcribed")
153
+ where.push(`r.source IN ${TRANSCRIBED}`);
154
+ else if (q.scope === "platform")
155
+ where.push(`r.source NOT IN ${TRANSCRIBED}`);
156
+ else if (q.scope !== "majors" && q.scope !== "all")
157
+ throw new Error(`archive-client: no stream scope "${String(q.scope)}"`);
158
+ if (q.event !== undefined)
159
+ list("c.event", typeof q.event === "string" ? [q.event] : q.event, true);
160
+ if (typeof q.year === "number") {
161
+ where.push("c.year = ?");
162
+ args.push(q.year);
163
+ } else if (Array.isArray(q.year)) {
164
+ where.push("c.year BETWEEN ? AND ?");
165
+ args.push(q.year[0], q.year[1]);
166
+ }
167
+ if (q.tier !== undefined)
168
+ list("c.tier", q.tier);
169
+ if (q.category !== undefined)
170
+ list("c.category", q.category);
171
+ if (q.feis_level !== undefined)
172
+ list("c.feis_level", q.feis_level);
173
+ if (q.dancer_id !== undefined)
174
+ list("r.dancer_id", q.dancer_id);
175
+ if (q.school_id !== undefined) {
176
+ where.push("r.school_id = ?");
177
+ args.push(q.school_id);
178
+ }
179
+ if (q.primary === true)
180
+ where.push("(r.primary_source IS NULL OR r.primary_source = '')");
181
+ return {
182
+ sql: `SELECT ${COLUMNS} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`} ORDER BY r.file, r.line`,
183
+ args
184
+ };
185
+ }
186
+ function feisDates(db) {
187
+ const out = new Map;
188
+ for (const f of db.query("SELECT feis_id, min(date_lo) AS lo, max(date_hi) AS hi FROM feises GROUP BY feis_id").all())
189
+ out.set(f.feis_id, { lo: f.lo, hi: f.hi });
190
+ return out;
191
+ }
192
+ var text = (v) => v === null || v === "" ? null : v;
193
+ function rowOf(r, dates) {
194
+ const { file: _file, atts, withdrawn, ...rest } = r;
195
+ const d = dates.get(r.event);
196
+ return {
197
+ ...rest,
198
+ feis_id: d === undefined ? null : r.event,
199
+ feis_date_lo: d?.lo ?? null,
200
+ feis_date_hi: d?.hi ?? null,
201
+ atts: atts === "" ? [] : atts.split(","),
202
+ withdrawn: text(withdrawn),
203
+ qualifier: text(rest.qualifier),
204
+ primary_source: text(rest.primary_source),
205
+ dancer_scope: r.dancer_id === null ? null : r.dancer_id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable"
206
+ };
207
+ }
208
+ async function* streamRows(db, q) {
209
+ const dates = feisDates(db);
210
+ const { sql, args } = streamSql(q);
211
+ const stmt = db.prepare(sql);
212
+ try {
213
+ for (const r of stmt.iterate(...args))
214
+ yield rowOf(r, dates);
215
+ } finally {
216
+ stmt.finalize();
217
+ }
218
+ }
219
+ async function* entryRows(db, q) {
220
+ const dates = feisDates(db);
221
+ const { sql, args } = streamSql(q);
222
+ const stmt = db.prepare(sql);
223
+ let file = null;
224
+ let held = new Map;
225
+ const flush = function* () {
226
+ for (const h of held.values())
227
+ yield { ...rowOf(h.raw, dates), recall_place: h.recall };
228
+ held = new Map;
229
+ };
230
+ try {
231
+ for (const r of stmt.iterate(...args)) {
232
+ if (r.file !== file) {
233
+ yield* flush();
234
+ file = r.file;
235
+ }
236
+ const rank = entryRank2(r.section);
237
+ const h = held.get(r.address);
238
+ const recall = r.section === "recall" ? r.place : h?.recall ?? null;
239
+ if (h === undefined || rank <= h.rank)
240
+ held.set(r.address, { raw: r, rank, recall });
241
+ else
242
+ h.recall = recall;
243
+ }
244
+ yield* flush();
245
+ } finally {
246
+ stmt.finalize();
247
+ }
248
+ }
249
+
109
250
  var FEIS_LOCAL_ID_FLOOR2 = 17179869184;
110
- var COLUMNS = `r.address AS address, r.competition AS competition, c.event AS event, c.year AS year,
251
+ var COLUMNS2 = `r.address AS address, r.competition AS competition, c.event AS event, c.year AS year,
111
252
  c.tier AS tier, c.title AS title, c.age AS age, c.category AS category, c.gender AS gender,
112
253
  c.feis_level AS feis_level, c.comp_no AS comp_no, r.place AS place,
113
254
  r.place_printed AS place_printed, c.competitors AS competitors, r.name_printed AS name_printed,
@@ -139,7 +280,7 @@ function queryResults(rel, db, q) {
139
280
  args.push(q.year);
140
281
  }
141
282
  const limit = q.limit ?? 500;
142
- const all = rel.db(db).query(`SELECT ${COLUMNS} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
283
+ const all = rel.db(db).query(`SELECT ${COLUMNS2} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
143
284
  WHERE ${where.join(" AND ")}
144
285
  ORDER BY c.year, c.event, r.competition, r.place IS NULL, r.place, r.address`).all(...args);
145
286
  return {
@@ -164,6 +305,18 @@ class ResultFinder2 {
164
305
  await this.rel.require("results.level");
165
306
  return queryResults(this.rel, "results", q);
166
307
  }
308
+ async streamDb(q) {
309
+ const db = scopeDb(q.scope);
310
+ await this.rel.fetch(db);
311
+ await this.rel.require(db === "majors" ? "majors.stream" : "results.stream");
312
+ return this.rel.db(db);
313
+ }
314
+ async* stream(q) {
315
+ yield* streamRows(await this.streamDb(q), q);
316
+ }
317
+ async* entries(q) {
318
+ yield* entryRows(await this.streamDb(q), q);
319
+ }
167
320
  }
168
321
 
169
322
  class MajorsFinder2 {
@@ -208,6 +361,13 @@ class MajorsFinder2 {
208
361
  ORDER BY c.year, c.seq, c.competition`).all(...args).map((c) => ({ ...c, rows: rows.get(c.competition) ?? 0 }));
209
362
  return { tag: this.rel.tag, competitions };
210
363
  }
364
+ async tabulated(q = {}) {
365
+ const db = await this.db();
366
+ await this.rel.require("majors.tabs");
367
+ const min = q.min_true_round ?? 3;
368
+ const addresses = db.query("SELECT DISTINCT address FROM tabs WHERE true_round >= ? ORDER BY address").all(min).map((r) => r.address);
369
+ return { tag: this.rel.tag, min_true_round: min, addresses };
370
+ }
211
371
  async results(q) {
212
372
  await this.db();
213
373
  await this.rel.require("results.level");
@@ -365,6 +525,12 @@ var SCHOOL_WEIGHT = {
365
525
  unresolved: 0,
366
526
  unasked: 0
367
527
  };
528
+ function idChunks(ids) {
529
+ const out = [];
530
+ for (let i = 0;i < ids.length; i += 500)
531
+ out.push(ids.slice(i, i + 500));
532
+ return out;
533
+ }
368
534
 
369
535
  class DancerResolver2 {
370
536
  rel;
@@ -372,6 +538,34 @@ class DancerResolver2 {
372
538
  constructor(rel) {
373
539
  this.rel = rel;
374
540
  }
541
+ async records(q = {}) {
542
+ await this.rel.fetch("results");
543
+ await this.rel.require("dancers.records");
544
+ const db = this.rel.db("results");
545
+ const sql = "SELECT dancer_id, display, appearances, birth_lo, birth_hi, birth_source, birth_conflict, genders FROM dancers";
546
+ const rows = q.ids === undefined ? db.query(`${sql} ORDER BY dancer_id`).all() : idChunks(q.ids).flatMap((c) => db.query(`${sql} WHERE dancer_id IN (${c.map(() => "?").join(",")})`).all(...c));
547
+ rows.sort((a, b) => a.dancer_id - b.dancer_id);
548
+ return {
549
+ tag: this.rel.tag,
550
+ records: rows.map((r) => ({
551
+ ...r,
552
+ scope: r.dancer_id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable",
553
+ birth_conflict: r.birth_conflict === 1,
554
+ genders: r.genders === "" ? [] : r.genders.split(",")
555
+ }))
556
+ };
557
+ }
558
+ async years(q = {}) {
559
+ await this.rel.fetch("results");
560
+ await this.rel.require("dancers.years");
561
+ const db = this.rel.db("results");
562
+ const sql = "SELECT dancer_id, year, region, region_source, nationals, nationals_source, geohash FROM dancer_years";
563
+ const yearSql = q.year === undefined ? "" : " AND year = ?";
564
+ const yearArgs = q.year === undefined ? [] : [q.year];
565
+ const rows = q.ids === undefined ? db.query(`${sql} WHERE 1${yearSql} ORDER BY dancer_id, year`).all(...yearArgs) : idChunks(q.ids).flatMap((c) => db.query(`${sql} WHERE dancer_id IN (${c.map(() => "?").join(",")})${yearSql}`).all(...c, ...yearArgs));
566
+ rows.sort((a, b) => a.dancer_id - b.dancer_id || a.year - b.year);
567
+ return { tag: this.rel.tag, years: rows };
568
+ }
375
569
  async resolve(q) {
376
570
  const db = await this.rel.indexDb("dancers");
377
571
  const asked = dancerName(q.name, q.order === "unknown" ? undefined : q.order);
@@ -1067,6 +1261,12 @@ class FeisFinder2 {
1067
1261
  constructor(rel) {
1068
1262
  this.rel = rel;
1069
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
+ }
1070
1270
  async find(q) {
1071
1271
  const db = await this.rel.indexDb("feises");
1072
1272
  const where = [];
@@ -1586,8 +1786,240 @@ class SchoolResolver2 {
1586
1786
  }
1587
1787
  }
1588
1788
 
1789
+ import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
1790
+ var COMPRESSORS = {
1791
+ ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
1792
+ };
1793
+ var tableAsset = (table) => `${table}.parquet`;
1794
+ function plain(v) {
1795
+ if (typeof v === "bigint")
1796
+ return v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v;
1797
+ if (Array.isArray(v))
1798
+ return v.map(plain);
1799
+ if (v !== null && typeof v === "object" && !(v instanceof Date)) {
1800
+ const o = {};
1801
+ for (const [k, x] of Object.entries(v))
1802
+ o[k] = plain(x);
1803
+ return o;
1804
+ }
1805
+ return v;
1806
+ }
1807
+ async function readParquet(path, columns) {
1808
+ const file = await Bun.file(path).arrayBuffer();
1809
+ const rows = await parquetReadObjects({
1810
+ file,
1811
+ compressors: COMPRESSORS,
1812
+ ...columns !== undefined ? { columns: [...columns] } : {}
1813
+ });
1814
+ return rows.map((r) => plain(r));
1815
+ }
1816
+ async function parquetColumns(path) {
1817
+ const meta = parquetMetadata(await Bun.file(path).arrayBuffer());
1818
+ return new Set(parquetSchema(meta).children.map((c) => c.element.name));
1819
+ }
1820
+
1821
+ var str = (v) => typeof v === "string" && v !== "" ? v : null;
1822
+ var num = (v) => typeof v === "number" ? v : null;
1823
+ var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
1824
+ var scope = (id) => id === null ? null : id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable";
1825
+ var TEAM_TABLES = [
1826
+ "team_results",
1827
+ "roster_teams",
1828
+ "roster_members",
1829
+ "team_result_rosters",
1830
+ "event_years"
1831
+ ];
1832
+ var TEAMS_SCHEMA = `
1833
+ CREATE TABLE event_year(event_year TEXT PRIMARY KEY, event TEXT NOT NULL, year INTEGER NOT NULL);
1834
+ CREATE TABLE result(address TEXT PRIMARY KEY, competition TEXT, event TEXT, year INTEGER,
1835
+ card TEXT, comp_no TEXT, title TEXT, category TEXT, hands INTEGER, letter TEXT, label TEXT,
1836
+ name_printed TEXT, school_printed TEXT, school_loc TEXT, school_id TEXT, place INTEGER,
1837
+ place_printed TEXT, tie INTEGER, competitors INTEGER, atts TEXT, ord INTEGER);
1838
+ CREATE TABLE roster_team(team TEXT PRIMARY KEY, kind TEXT, event TEXT, year INTEGER,
1839
+ comp_no TEXT, title TEXT, team_index TEXT, seq INTEGER, school_id TEXT, school_printed TEXT,
1840
+ ord INTEGER);
1841
+ CREATE TABLE roster_member(team TEXT, seq INTEGER, name_printed TEXT, name TEXT, yob INTEGER,
1842
+ dancer_id INTEGER, ord INTEGER);
1843
+ CREATE TABLE result_roster(team TEXT, event TEXT, year INTEGER, comp_no TEXT, school_id TEXT,
1844
+ result_address TEXT, join_kind TEXT, via_split INTEGER, roster_teams INTEGER,
1845
+ result_teams INTEGER, roster_index TEXT, result_letter TEXT, ord INTEGER);
1846
+ `;
1847
+ var TEAMS_INDEXES = [
1848
+ "CREATE INDEX result_ey ON result(event COLLATE NOCASE, year)",
1849
+ "CREATE INDEX result_year ON result(year)",
1850
+ "CREATE INDEX roster_team_ey ON roster_team(event COLLATE NOCASE, year)",
1851
+ "CREATE INDEX roster_team_year ON roster_team(year)",
1852
+ "CREATE INDEX roster_member_team ON roster_member(team)",
1853
+ "CREATE INDEX result_roster_ey ON result_roster(event COLLATE NOCASE, year)",
1854
+ "CREATE INDEX result_roster_year ON result_roster(year)"
1855
+ ];
1856
+ async function readTables(rel, tables) {
1857
+ const out = {};
1858
+ for (const t of tables) {
1859
+ await rel.fetch(tableAsset(t));
1860
+ out[t] = await readParquet(rel.path(tableAsset(t)));
1861
+ }
1862
+ return out;
1863
+ }
1864
+ var teamsIndex = {
1865
+ name: "teams",
1866
+ version: "1",
1867
+ inputs: TEAM_TABLES.map(tableAsset),
1868
+ says: "the team results, the roster teams with their members, and how they join, by event-year",
1869
+ load: (rel) => readTables(rel, TEAM_TABLES),
1870
+ build(_rel, out, loaded) {
1871
+ const t = loaded;
1872
+ out.run(TEAMS_SCHEMA);
1873
+ const ey = new Map;
1874
+ const putEy = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?)");
1875
+ for (const r of t.event_years) {
1876
+ const k = str(r.event_year);
1877
+ const event = str(r.event);
1878
+ const year = num(r.year);
1879
+ if (k === null || event === null || year === null)
1880
+ continue;
1881
+ ey.set(k, { event, year });
1882
+ putEy.run(k, event, year);
1883
+ }
1884
+ const putR = out.prepare("INSERT OR IGNORE INTO result VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1885
+ let ord = 0;
1886
+ for (const r of t.team_results)
1887
+ 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++);
1888
+ const putT = out.prepare("INSERT OR IGNORE INTO roster_team VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1889
+ for (const r of t.roster_teams) {
1890
+ const e = ey.get(str(r.event_year) ?? "");
1891
+ 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++);
1892
+ }
1893
+ const putM = out.prepare("INSERT INTO roster_member VALUES (?, ?, ?, ?, ?, ?, ?)");
1894
+ for (const r of t.roster_members)
1895
+ 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++);
1896
+ const putJ = out.prepare("INSERT INTO result_roster VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1897
+ for (const r of t.team_result_rosters) {
1898
+ const e = ey.get(str(r.event_year) ?? "");
1899
+ 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++);
1900
+ }
1901
+ for (const s of TEAMS_INDEXES)
1902
+ out.run(s);
1903
+ }
1904
+ };
1905
+ var EVENTS_SCHEMA = `CREATE TABLE event_year(event TEXT NOT NULL, year INTEGER NOT NULL,
1906
+ date_lo TEXT, date_hi TEXT, resolution TEXT, source TEXT, date_printed TEXT, place TEXT,
1907
+ place_printed TEXT, PRIMARY KEY(event, year))`;
1908
+ var eventsIndex = {
1909
+ name: "events",
1910
+ version: "1",
1911
+ inputs: [tableAsset("event_years")],
1912
+ says: "every event-year: its dates (and how fine they are) and its place",
1913
+ load: (rel) => readTables(rel, ["event_years"]),
1914
+ build(_rel, out, loaded) {
1915
+ const rows = loaded.event_years ?? [];
1916
+ out.run(EVENTS_SCHEMA);
1917
+ const put = out.prepare("INSERT OR IGNORE INTO event_year VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
1918
+ for (const r of rows) {
1919
+ const event = str(r.event);
1920
+ const year = num(r.year);
1921
+ if (event === null || year === null)
1922
+ continue;
1923
+ 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)));
1924
+ }
1925
+ out.run("CREATE INDEX event_year_event ON event_year(event COLLATE NOCASE)");
1926
+ }
1927
+ };
1928
+ function whereOf(q, prefix = "") {
1929
+ const where = [];
1930
+ const args = [];
1931
+ if (q.event !== undefined) {
1932
+ where.push(`${prefix}event = ? COLLATE NOCASE`);
1933
+ args.push(q.event);
1934
+ }
1935
+ if (q.year !== undefined) {
1936
+ where.push(`${prefix}year = ?`);
1937
+ args.push(q.year);
1938
+ }
1939
+ return { sql: where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`, args };
1940
+ }
1941
+
1942
+ class TeamFinder2 {
1943
+ rel;
1944
+ constructor(rel) {
1945
+ this.rel = rel;
1946
+ }
1947
+ db() {
1948
+ return this.rel.indexDb("teams");
1949
+ }
1950
+ async results(q = {}) {
1951
+ const db = await this.db();
1952
+ const w = whereOf(q);
1953
+ const rows = db.query(`SELECT address, competition, event, year, card, comp_no, title, category, hands, letter,
1954
+ label, name_printed, school_printed, school_loc, school_id, place, place_printed, tie,
1955
+ competitors, atts
1956
+ FROM result${w.sql} ORDER BY year, event, competition, place IS NULL, place, ord`).all(...w.args);
1957
+ return {
1958
+ tag: this.rel.tag,
1959
+ asked: q,
1960
+ results: rows.map((r) => ({ ...r, atts: JSON.parse(r.atts) }))
1961
+ };
1962
+ }
1963
+ async rosters(q = {}) {
1964
+ const db = await this.db();
1965
+ const w = whereOf(q, "t.");
1966
+ const teams = db.query(`SELECT team, kind, event, year, comp_no, title, team_index, seq, school_id, school_printed
1967
+ FROM roster_team t${w.sql} ORDER BY year, event, ord`).all(...w.args);
1968
+ const members = new Map;
1969
+ 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,
1970
+ m.yob AS yob, m.dancer_id AS dancer_id
1971
+ FROM roster_member m JOIN roster_team t ON t.team = m.team${w.sql}
1972
+ ORDER BY m.team, m.seq, m.ord`).all(...w.args)) {
1973
+ const { team, ...rest } = m;
1974
+ let list = members.get(team);
1975
+ if (list === undefined)
1976
+ members.set(team, list = []);
1977
+ list.push({ ...rest, dancer_scope: scope(rest.dancer_id) });
1978
+ }
1979
+ return {
1980
+ tag: this.rel.tag,
1981
+ asked: q,
1982
+ teams: teams.map((t) => ({ ...t, members: members.get(t.team) ?? [] }))
1983
+ };
1984
+ }
1985
+ async resultRosters(q = {}) {
1986
+ const db = await this.db();
1987
+ const w = whereOf(q);
1988
+ const rows = db.query(`SELECT team, event, year, comp_no, school_id, result_address, join_kind, via_split,
1989
+ roster_teams, result_teams, roster_index, result_letter
1990
+ FROM result_roster${w.sql} ORDER BY year, event, ord`).all(...w.args);
1991
+ return {
1992
+ tag: this.rel.tag,
1993
+ asked: q,
1994
+ joins: rows.map((r) => ({ ...r, via_split: r.via_split === 1 }))
1995
+ };
1996
+ }
1997
+ }
1998
+
1999
+ class EventFinder2 {
2000
+ rel;
2001
+ constructor(rel) {
2002
+ this.rel = rel;
2003
+ }
2004
+ async years(q = {}) {
2005
+ const db = await this.rel.indexDb("events");
2006
+ const w = whereOf(q);
2007
+ const rows = db.query(`SELECT event, year, date_lo, date_hi, resolution, source, date_printed, place, place_printed
2008
+ FROM event_year${w.sql} ORDER BY year, event`).all(...w.args);
2009
+ return {
2010
+ tag: this.rel.tag,
2011
+ asked: q,
2012
+ years: rows.map((r) => ({
2013
+ ...r,
2014
+ date_printed: JSON.parse(r.date_printed),
2015
+ place_printed: JSON.parse(r.place_printed)
2016
+ }))
2017
+ };
2018
+ }
2019
+ }
2020
+
1589
2021
  function defaultBuilders2() {
1590
- return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex];
2022
+ return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex, teamsIndex, eventsIndex];
1591
2023
  }
1592
2024
  var indexDir = (rel) => join(rel.dir, "index");
1593
2025
  var indexPath2 = (rel, name) => join(indexDir(rel), `${name}.sqlite`);
@@ -1623,6 +2055,8 @@ function buildIndex2(rel, b, o) {
1623
2055
  const key = indexKey2(rel, b);
1624
2056
  if (!o.force && keyOf(out) === key)
1625
2057
  return out;
2058
+ if (b.load !== undefined && o.loaded === undefined)
2059
+ throw new Error(`archive-client: index "${b.name}" reads its inputs first: build it with ensureIndex (or rel.index)`);
1626
2060
  mkdirSync(indexDir(rel), { recursive: true });
1627
2061
  const tmp = `${out}.building.${process.pid}`;
1628
2062
  rmSync(tmp, { force: true });
@@ -1632,7 +2066,7 @@ function buildIndex2(rel, b, o) {
1632
2066
  db.run("PRAGMA journal_mode = OFF");
1633
2067
  db.run("PRAGMA synchronous = OFF");
1634
2068
  db.run("CREATE TABLE meta(k TEXT PRIMARY KEY, v TEXT NOT NULL)");
1635
- db.transaction(() => b.build(rel, db))();
2069
+ db.transaction(() => b.build(rel, db, o.loaded))();
1636
2070
  const put = db.prepare("INSERT INTO meta(k, v) VALUES (?, ?)");
1637
2071
  put.run("key", key);
1638
2072
  put.run("builder", b.name);
@@ -1649,6 +2083,12 @@ function buildIndex2(rel, b, o) {
1649
2083
  o.log?.(`index ${rel.tag} ${b.name}: built in ${((performance.now() - t0) / 1000).toFixed(1)} s`);
1650
2084
  return out;
1651
2085
  }
2086
+ async function ensureIndex2(rel, b, o) {
2087
+ if (!o.force && isCurrent2(rel, b))
2088
+ return indexPath2(rel, b.name);
2089
+ const loaded = b.load === undefined ? undefined : await b.load(rel);
2090
+ return buildIndex2(rel, b, { ...o, loaded });
2091
+ }
1652
2092
  function listIndexes2(rel) {
1653
2093
  const dir = indexDir(rel);
1654
2094
  if (!existsSync(dir))
@@ -2058,6 +2498,149 @@ var FEATURES2 = [
2058
2498
  since: 265,
2059
2499
  calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
2060
2500
  },
2501
+ {
2502
+ feature: "results.stream",
2503
+ needs: [
2504
+ "results:competitions.round",
2505
+ "results:competitions.feis_level",
2506
+ "results:competitions.level_printed",
2507
+ "results:competitions.age_printed",
2508
+ "results:results.yob_printed",
2509
+ "results:results.primary_source",
2510
+ "results:results.platform_dancer_id",
2511
+ "results:results.relative_place",
2512
+ "results:feises.date_lo",
2513
+ "results:feises.date_hi"
2514
+ ],
2515
+ since: 255,
2516
+ calls: "results.stream, results.entries (scopes transcribed, platform, all)"
2517
+ },
2518
+ {
2519
+ feature: "majors.stream",
2520
+ needs: [
2521
+ "majors:competitions.round",
2522
+ "majors:competitions.feis_level",
2523
+ "majors:results.yob_printed",
2524
+ "majors:results.primary_source",
2525
+ "majors:results.relative_place",
2526
+ "majors:feises.date_lo"
2527
+ ],
2528
+ since: 255,
2529
+ calls: "results.stream, results.entries (scope majors)"
2530
+ },
2531
+ {
2532
+ feature: "majors.tabs",
2533
+ needs: ["majors:tabs.address", "majors:tabs.true_round"],
2534
+ since: 215,
2535
+ calls: "majors.tabulated"
2536
+ },
2537
+ {
2538
+ feature: "feises.dates",
2539
+ needs: ["results:feises.feis_id", "results:feises.date_lo", "results:feises.date_hi"],
2540
+ since: 245,
2541
+ calls: "feises.dates"
2542
+ },
2543
+ {
2544
+ feature: "dancers.records",
2545
+ needs: [
2546
+ "results:dancers.display",
2547
+ "results:dancers.appearances",
2548
+ "results:dancers.birth_lo",
2549
+ "results:dancers.birth_hi",
2550
+ "results:dancers.birth_source",
2551
+ "results:dancers.birth_conflict",
2552
+ "results:dancers.genders"
2553
+ ],
2554
+ since: 215,
2555
+ calls: "dancers.records"
2556
+ },
2557
+ {
2558
+ feature: "dancers.years",
2559
+ needs: [
2560
+ "results:dancer_years.region",
2561
+ "results:dancer_years.region_source",
2562
+ "results:dancer_years.nationals",
2563
+ "results:dancer_years.nationals_source",
2564
+ "results:dancer_years.geohash"
2565
+ ],
2566
+ since: 245,
2567
+ calls: "dancers.years"
2568
+ },
2569
+ {
2570
+ feature: "teams",
2571
+ needs: [
2572
+ ...[
2573
+ "slug",
2574
+ "address",
2575
+ "event",
2576
+ "year",
2577
+ "card",
2578
+ "comp_no",
2579
+ "title",
2580
+ "category",
2581
+ "hands",
2582
+ "letter",
2583
+ "label",
2584
+ "name_printed",
2585
+ "school_printed",
2586
+ "school_loc",
2587
+ "school_id",
2588
+ "place",
2589
+ "place_printed",
2590
+ "tie",
2591
+ "competitors",
2592
+ "atts"
2593
+ ].map((c) => `table:team_results.${c}`),
2594
+ ...[
2595
+ "team",
2596
+ "kind",
2597
+ "event_year",
2598
+ "year",
2599
+ "comp_no",
2600
+ "title",
2601
+ "team_index",
2602
+ "seq",
2603
+ "school_id",
2604
+ "school_print_materialized"
2605
+ ].map((c) => `table:roster_teams.${c}`),
2606
+ ...["team", "seq", "name_printed", "name", "yob", "dancer_id"].map((c) => `table:roster_members.${c}`),
2607
+ ...[
2608
+ "event_year",
2609
+ "comp_no",
2610
+ "team",
2611
+ "result_address",
2612
+ "school_id",
2613
+ "join_kind",
2614
+ "via_split",
2615
+ "roster_teams",
2616
+ "result_teams",
2617
+ "roster_index",
2618
+ "result_letter"
2619
+ ].map((c) => `table:team_result_rosters.${c}`),
2620
+ "table:event_years.event_year",
2621
+ "table:event_years.event",
2622
+ "table:event_years.year"
2623
+ ],
2624
+ since: 260,
2625
+ calls: "teams.results, teams.rosters, teams.resultRosters (the teams index)"
2626
+ },
2627
+ {
2628
+ feature: "events",
2629
+ needs: [
2630
+ "event_year",
2631
+ "event",
2632
+ "year",
2633
+ "date_lo",
2634
+ "date_hi",
2635
+ "resolution",
2636
+ "source",
2637
+ "date_printed",
2638
+ "place",
2639
+ "place_printed"
2640
+ ].map((c) => `table:event_years.${c}`),
2641
+ since: 260,
2642
+ calls: "events.years (the events index)"
2643
+ },
2061
2644
  {
2062
2645
  feature: "staff",
2063
2646
  needs: [
@@ -2072,6 +2655,13 @@ var FEATURES2 = [
2072
2655
  }
2073
2656
  ];
2074
2657
  async function hasNeed(probe, n) {
2658
+ const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
2659
+ if (t !== null) {
2660
+ if (probe.tableColumns === undefined)
2661
+ return false;
2662
+ const cols = await probe.tableColumns(t[1]);
2663
+ return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
2664
+ }
2075
2665
  const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
2076
2666
  if (m === null)
2077
2667
  return probe.publishes(n);
@@ -2143,6 +2733,8 @@ class Release2 {
2143
2733
  staff;
2144
2734
  comps;
2145
2735
  registrations;
2736
+ teams;
2737
+ events;
2146
2738
  opts;
2147
2739
  handles = new Map;
2148
2740
  remoteAssets = null;
@@ -2163,6 +2755,8 @@ class Release2 {
2163
2755
  this.staff = new StaffFinder2(this);
2164
2756
  this.comps = new CompResolver2(this);
2165
2757
  this.registrations = new RegistrationResolver2(this, this.comps, opts.dancerResolver);
2758
+ this.teams = new TeamFinder2(this);
2759
+ this.events = new EventFinder2(this);
2166
2760
  }
2167
2761
  receipt() {
2168
2762
  return readReceipt(this.dir) ?? { tag: this.tag, source: "unknown", files: {} };
@@ -2207,7 +2801,14 @@ class Release2 {
2207
2801
  await this.fetch(name);
2208
2802
  return this.db(name);
2209
2803
  },
2210
- publishes: (asset) => this.publishes(asset)
2804
+ publishes: (asset) => this.publishes(asset),
2805
+ tableColumns: async (table) => {
2806
+ const asset = tableAsset(table);
2807
+ if (!await this.publishes(asset))
2808
+ return new Set;
2809
+ await this.fetch(asset);
2810
+ return parquetColumns(this.path(asset));
2811
+ }
2211
2812
  };
2212
2813
  checked = new Map;
2213
2814
  async capability(feature) {
@@ -2304,7 +2905,7 @@ class Release2 {
2304
2905
  if (FEATURES2.some((f) => f.feature === b.name))
2305
2906
  await this.require(b.name);
2306
2907
  await this.fetch(...b.inputs);
2307
- return buildIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
2908
+ return ensureIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
2308
2909
  }
2309
2910
  async indexDb(name) {
2310
2911
  const key = `index:${name}`;
@@ -2523,7 +3124,7 @@ class ReleaseManager2 {
2523
3124
  await rel.fetch(...b.inputs);
2524
3125
  built.push({
2525
3126
  name,
2526
- path: buildIndex2(rel, b, { force: o.force ?? false, log: this.opts.log })
3127
+ path: await ensureIndex2(rel, b, { force: o.force ?? false, log: this.opts.log })
2527
3128
  });
2528
3129
  }
2529
3130
  rel.close();
@@ -2596,4 +3197,4 @@ function releases2(opts = {}) {
2596
3197
  return new ReleaseManager2(opts);
2597
3198
  }
2598
3199
 
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 };
3200
+ 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 };