@finbheara/archive-client 0.10.0 → 0.11.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.
@@ -339,6 +339,269 @@ function notOnTheFloor(placePrinted, atts, withdrawn) {
339
339
  return withdrawn !== null && DID_NOT_TABULATE.has(withdrawn);
340
340
  }
341
341
 
342
+ function columnsOf2(db, table) {
343
+ return new Set(db.query("SELECT name FROM pragma_table_info(?)").all(table).map((r) => r.name));
344
+ }
345
+ var FEATURES2 = [
346
+ {
347
+ feature: "results.level",
348
+ needs: ["results:competitions.feis_level"],
349
+ since: 215,
350
+ calls: "results.query, majors.results"
351
+ },
352
+ {
353
+ feature: "majors.order",
354
+ needs: ["majors:competitions.seq"],
355
+ since: 260,
356
+ calls: "majors.competitions"
357
+ },
358
+ {
359
+ feature: "dancers",
360
+ needs: ["results:results.name_order_resolved"],
361
+ since: 260,
362
+ calls: "dancers.resolve, dancers.search, dancers.get (the dancers index)"
363
+ },
364
+ {
365
+ feature: "schools",
366
+ needs: ["school-registry.jsonl", "school-redirects.jsonl"],
367
+ since: 260,
368
+ calls: "schools.resolve, a dancer question with a school (the schools index)"
369
+ },
370
+ {
371
+ feature: "feises",
372
+ needs: [
373
+ "results:feises.date_lo",
374
+ "results:feises.date_hi",
375
+ "results:feises.feis_instance_id",
376
+ "results:feises.feis_brand_id",
377
+ "results:competitions.feis_region",
378
+ "feis-redirects.jsonl"
379
+ ],
380
+ since: 265,
381
+ calls: "feises.find, feises.series, and comps and registrations through them (the feises index)"
382
+ },
383
+ {
384
+ feature: "comps",
385
+ needs: [
386
+ "results:competitions.feis_level",
387
+ "results:competitions.round",
388
+ "results:feises.feis_brand_id"
389
+ ],
390
+ since: 265,
391
+ calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
392
+ },
393
+ {
394
+ feature: "comps.dance",
395
+ needs: ["results:competitions.dance"],
396
+ since: 270,
397
+ calls: "the `dance` a comp of comps.resolve, comps.syllabus and comps.syllabusFor carries: null, never refused, on a release without it"
398
+ },
399
+ {
400
+ feature: "schools.lineage",
401
+ needs: ["school-lineage.jsonl"],
402
+ since: 184,
403
+ calls: "the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
404
+ },
405
+ {
406
+ feature: "results.stream",
407
+ needs: [
408
+ "results:competitions.round",
409
+ "results:competitions.feis_level",
410
+ "results:competitions.level_printed",
411
+ "results:competitions.age_printed",
412
+ "results:results.yob_printed",
413
+ "results:results.primary_source",
414
+ "results:results.platform_dancer_id",
415
+ "results:results.relative_place",
416
+ "results:feises.date_lo",
417
+ "results:feises.date_hi"
418
+ ],
419
+ since: 255,
420
+ calls: "results.stream, results.entries (scopes transcribed, platform, all)"
421
+ },
422
+ {
423
+ feature: "majors.stream",
424
+ needs: [
425
+ "majors:competitions.round",
426
+ "majors:competitions.feis_level",
427
+ "majors:results.yob_printed",
428
+ "majors:results.primary_source",
429
+ "majors:results.relative_place",
430
+ "majors:feises.date_lo"
431
+ ],
432
+ since: 255,
433
+ calls: "results.stream, results.entries (scope majors)"
434
+ },
435
+ {
436
+ feature: "results.printed_age",
437
+ needs: ["results:results.printed_age", "majors:results.printed_age"],
438
+ since: 271,
439
+ calls: "the `printed_age` a row of results.stream and results.entries carries: null, never refused, on a release without it"
440
+ },
441
+ {
442
+ feature: "majors.tabs",
443
+ needs: ["majors:tabs.address", "majors:tabs.true_round"],
444
+ since: 215,
445
+ calls: "majors.tabulated"
446
+ },
447
+ {
448
+ feature: "feises.dates",
449
+ needs: ["results:feises.feis_id", "results:feises.date_lo", "results:feises.date_hi"],
450
+ since: 245,
451
+ calls: "feises.dates"
452
+ },
453
+ {
454
+ feature: "dancers.records",
455
+ needs: [
456
+ "results:dancers.display",
457
+ "results:dancers.appearances",
458
+ "results:dancers.birth_lo",
459
+ "results:dancers.birth_hi",
460
+ "results:dancers.birth_source",
461
+ "results:dancers.birth_conflict",
462
+ "results:dancers.genders"
463
+ ],
464
+ since: 215,
465
+ calls: "dancers.records"
466
+ },
467
+ {
468
+ feature: "dancers.years",
469
+ needs: [
470
+ "results:dancer_years.region",
471
+ "results:dancer_years.region_source",
472
+ "results:dancer_years.nationals",
473
+ "results:dancer_years.nationals_source",
474
+ "results:dancer_years.geohash"
475
+ ],
476
+ since: 245,
477
+ calls: "dancers.years"
478
+ },
479
+ {
480
+ feature: "teams",
481
+ needs: [
482
+ ...[
483
+ "slug",
484
+ "address",
485
+ "event",
486
+ "year",
487
+ "card",
488
+ "comp_no",
489
+ "title",
490
+ "category",
491
+ "hands",
492
+ "letter",
493
+ "label",
494
+ "name_printed",
495
+ "school_printed",
496
+ "school_loc",
497
+ "school_id",
498
+ "place",
499
+ "place_printed",
500
+ "tie",
501
+ "competitors",
502
+ "atts"
503
+ ].map((c) => `table:team_results.${c}`),
504
+ ...[
505
+ "team",
506
+ "kind",
507
+ "event_year",
508
+ "year",
509
+ "comp_no",
510
+ "title",
511
+ "team_index",
512
+ "seq",
513
+ "school_id",
514
+ "school_print_materialized"
515
+ ].map((c) => `table:roster_teams.${c}`),
516
+ ...["team", "seq", "name_printed", "name", "yob", "dancer_id"].map((c) => `table:roster_members.${c}`),
517
+ ...[
518
+ "event_year",
519
+ "comp_no",
520
+ "team",
521
+ "result_address",
522
+ "school_id",
523
+ "join_kind",
524
+ "via_split",
525
+ "roster_teams",
526
+ "result_teams",
527
+ "roster_index",
528
+ "result_letter"
529
+ ].map((c) => `table:team_result_rosters.${c}`),
530
+ "table:event_years.event_year",
531
+ "table:event_years.event",
532
+ "table:event_years.year"
533
+ ],
534
+ since: 260,
535
+ calls: "teams.results, teams.rosters, teams.resultRosters (the teams index)"
536
+ },
537
+ {
538
+ feature: "teams.fields",
539
+ needs: [
540
+ "table:team_results.age",
541
+ "table:team_results.gender",
542
+ "table:team_results.printed_status",
543
+ "table:roster_teams.dance",
544
+ "table:roster_teams.school_print_as_of",
545
+ "table:roster_members.gender"
546
+ ],
547
+ since: 266,
548
+ calls: "the fields teams.results (age, gender, printed_status) and teams.rosters (dance, school_printed_as_of, a member's gender) carry where present: absent, never refused, on a release without them"
549
+ },
550
+ {
551
+ feature: "events",
552
+ needs: [
553
+ "event_year",
554
+ "event",
555
+ "year",
556
+ "date_lo",
557
+ "date_hi",
558
+ "resolution",
559
+ "source",
560
+ "date_printed",
561
+ "place",
562
+ "place_printed"
563
+ ].map((c) => `table:event_years.${c}`),
564
+ since: 260,
565
+ calls: "events.years (the events index)"
566
+ },
567
+ {
568
+ feature: "staff",
569
+ needs: [
570
+ "results:adjudicators",
571
+ "results:feis_panels",
572
+ "school-staff.jsonl",
573
+ "school-teachers.jsonl",
574
+ "school-registry.jsonl"
575
+ ],
576
+ since: 260,
577
+ calls: "staff.teacher, staff.adjudicator (the staff index)"
578
+ }
579
+ ];
580
+ async function hasNeed(probe, n) {
581
+ const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
582
+ if (t !== null) {
583
+ if (probe.tableColumns === undefined)
584
+ return false;
585
+ const cols = await probe.tableColumns(t[1]);
586
+ return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
587
+ }
588
+ const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
589
+ if (m === null)
590
+ return probe.publishes(n);
591
+ const cols = columnsOf2(await probe.db(m[1]), m[2]);
592
+ return m[3] === undefined ? cols.size > 0 : cols.has(m[3]);
593
+ }
594
+ async function capabilityOf2(probe, f) {
595
+ const missing = [];
596
+ for (const n of f.needs)
597
+ if (!await hasNeed(probe, n))
598
+ missing.push(n);
599
+ return { ...f, state: missing.length === 0 ? "full" : "missing", missing };
600
+ }
601
+ function needsRelease2(tag, c) {
602
+ return new Error(`archive-client: ${tag} is too old for ${c.feature} (${c.calls}): it lacks ${c.missing.join(", ")}. Open a release of cut ${c.since} or later`);
603
+ }
604
+
342
605
  var ENTRY_RANK = new Map([
343
606
  ["final", 0],
344
607
  ["", 1],
@@ -369,9 +632,13 @@ var COLUMNS = `r.address AS address, r.competition AS competition, c.event AS ev
369
632
  r.yob_printed AS yob_printed, r.points_printed AS points_printed,
370
633
  r.platform_dancer_id AS platform_dancer_id, r.primary_source AS primary_source,
371
634
  r.file AS file, r.line AS line`;
635
+ var printedAgeColumn = (carried) => carried ? "r.printed_age AS printed_age" : "NULL AS printed_age";
636
+ var streamColumns = (db) => ({
637
+ printedAge: columnsOf2(db, "results").has("printed_age")
638
+ });
372
639
  var TRANSCRIBED = "('transcript', 'manifested')";
373
640
  var scopeDb = (s) => s === "majors" ? "majors" : "results";
374
- function streamSql(q) {
641
+ function streamSql(q, carried = { printedAge: false }) {
375
642
  const where = [];
376
643
  const args = [];
377
644
  const list = (col, v, nocase = false) => {
@@ -412,7 +679,7 @@ function streamSql(q) {
412
679
  if (q.primary === true)
413
680
  where.push("(r.primary_source IS NULL OR r.primary_source = '')");
414
681
  return {
415
- 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`,
682
+ sql: `SELECT ${COLUMNS}, ${printedAgeColumn(carried.printedAge)} 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`,
416
683
  args
417
684
  };
418
685
  }
@@ -435,12 +702,13 @@ function rowOf(r, dates) {
435
702
  withdrawn: text(withdrawn),
436
703
  qualifier: text(rest.qualifier),
437
704
  primary_source: text(rest.primary_source),
705
+ printed_age: text(rest.printed_age),
438
706
  dancer_scope: r.dancer_id === null ? null : r.dancer_id >= FEIS_LOCAL_ID_FLOOR2 ? "per-release" : "stable"
439
707
  };
440
708
  }
441
709
  async function* streamRows(db, q) {
442
710
  const dates = feisDates(db);
443
- const { sql, args } = streamSql(q);
711
+ const { sql, args } = streamSql(q, streamColumns(db));
444
712
  const stmt = db.prepare(sql);
445
713
  try {
446
714
  for (const r of stmt.iterate(...args))
@@ -451,7 +719,7 @@ async function* streamRows(db, q) {
451
719
  }
452
720
  async function* entryRows(db, q) {
453
721
  const dates = feisDates(db);
454
- const { sql, args } = streamSql(q);
722
+ const { sql, args } = streamSql(q, streamColumns(db));
455
723
  const stmt = db.prepare(sql);
456
724
  let file = null;
457
725
  let held = new Map;
@@ -2364,651 +2632,394 @@ function buildIndex2(rel, b, o) {
2364
2632
  if (b.load !== undefined && o.loaded === undefined)
2365
2633
  throw new Error(`archive-client: index "${b.name}" reads its inputs first: build it with ensureIndex (or rel.index)`);
2366
2634
  mkdirSync(indexDir(rel), { recursive: true });
2367
- const tmp = `${out}.building.${process.pid}`;
2368
- rmSync(tmp, { force: true });
2369
- const t0 = performance.now();
2370
- const db = new Database(tmp, { create: true });
2371
- try {
2372
- db.run("PRAGMA journal_mode = OFF");
2373
- db.run("PRAGMA synchronous = OFF");
2374
- db.run("CREATE TABLE meta(k TEXT PRIMARY KEY, v TEXT NOT NULL)");
2375
- db.transaction(() => b.build(rel, db, o.loaded))();
2376
- const put = db.prepare("INSERT INTO meta(k, v) VALUES (?, ?)");
2377
- put.run("key", key);
2378
- put.run("builder", b.name);
2379
- put.run("version", b.version);
2380
- put.run("tag", rel.tag);
2381
- db.run("VACUUM");
2382
- } catch (e) {
2383
- db.close();
2384
- rmSync(tmp, { force: true });
2385
- throw e;
2386
- }
2387
- db.close();
2388
- renameSync(tmp, out);
2389
- o.log?.(`index ${rel.tag} ${b.name}: built in ${((performance.now() - t0) / 1000).toFixed(1)} s`);
2390
- return out;
2391
- }
2392
- async function ensureIndex2(rel, b, o) {
2393
- if (!o.force && isCurrent2(rel, b))
2394
- return indexPath2(rel, b.name);
2395
- const loaded = b.load === undefined ? undefined : await b.load(rel);
2396
- return buildIndex2(rel, b, { ...o, loaded });
2397
- }
2398
- function listIndexes2(rel) {
2399
- const dir = indexDir(rel);
2400
- if (!existsSync(dir))
2401
- return [];
2402
- return readdirSync(dir).filter((f) => f.endsWith(".sqlite")).sort().map((f) => {
2403
- const name = f.slice(0, -".sqlite".length);
2404
- const path = join(dir, f);
2405
- const b = rel.builders().find((x) => x.name === name);
2406
- return {
2407
- name,
2408
- path,
2409
- bytes: statSync(path).size,
2410
- current: b === undefined ? null : keyOf(path) === indexKey2(rel, b)
2411
- };
2412
- });
2413
- }
2414
-
2415
- import {
2416
- copyFileSync,
2417
- existsSync as existsSync5,
2418
- linkSync,
2419
- mkdirSync as mkdirSync4,
2420
- readFileSync as readFileSync4,
2421
- readdirSync as readdirSync3,
2422
- rmSync as rmSync4,
2423
- statSync as statSync3
2424
- } from "fs";
2425
- import { isAbsolute, join as join4, relative, resolve, sep } from "path";
2426
-
2427
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
2428
- import { join as join2 } from "path";
2429
- var pinPath = (root) => join2(root, "PINNED.json");
2430
- function readPin2(root) {
2431
- const p = pinPath(root);
2432
- if (!existsSync2(p))
2433
- return null;
2434
- try {
2435
- const v = JSON.parse(readFileSync2(p, "utf8"));
2436
- return typeof v.tag === "string" && parseArchiveTag2(v.tag) !== null ? { tag: v.tag, source: typeof v.source === "string" ? v.source : "unknown" } : null;
2437
- } catch {
2438
- return null;
2439
- }
2440
- }
2441
- function writePin(root, pin) {
2442
- if (parseArchiveTag2(pin.tag) === null)
2443
- throw new Error(`pin: "${pin.tag}" is not a release tag`);
2444
- writeJsonAtomic2(pinPath(root), pin);
2445
- }
2446
- function clearPin(root) {
2447
- const p = pinPath(root);
2448
- if (!existsSync2(p))
2449
- return false;
2450
- rmSync2(p);
2451
- return true;
2452
- }
2453
-
2454
- import { Database as Database2 } from "bun:sqlite";
2455
- import {
2456
- existsSync as existsSync4,
2457
- mkdirSync as mkdirSync3,
2458
- readFileSync as readFileSync3,
2459
- readdirSync as readdirSync2,
2460
- renameSync as renameSync2,
2461
- rmSync as rmSync3,
2462
- statSync as statSync2
2463
- } from "fs";
2464
- import { join as join3 } from "path";
2465
-
2466
- import { Age as Age3 } from "@finbheara/domain/age";
2467
- import { compTitle as compTitle2 } from "@finbheara/domain/title";
2468
- function youngest2(answers) {
2469
- const key = (a) => {
2470
- const iv = Age3.from(a.age, { year: null }).interval();
2471
- return iv === null ? [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY] : [iv.hi, iv.lo];
2472
- };
2473
- let best;
2474
- let bk = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
2475
- for (const a of answers) {
2476
- const k = key(a);
2477
- if (best === undefined || k[0] < bk[0] || k[0] === bk[0] && k[1] > bk[1]) {
2478
- best = a;
2479
- bk = k;
2480
- }
2481
- }
2482
- return best;
2483
- }
2484
- function dancerResolverOf2(rel) {
2485
- return rel.dancers;
2486
- }
2487
-
2488
- class RegistrationResolver2 {
2489
- rel;
2490
- comps;
2491
- dancers;
2492
- constructor(rel, comps, dancers) {
2493
- this.rel = rel;
2494
- this.comps = comps;
2495
- this.dancers = dancers;
2496
- }
2497
- async dancer(q) {
2498
- const dancers = this.dancers === undefined ? dancerResolverOf2(this.rel) : this.dancers;
2499
- let put = q;
2500
- let comp;
2501
- const age = q.age;
2502
- if (age !== undefined && "comp" in age) {
2503
- const slash = age.comp.lastIndexOf("/");
2504
- if (slash > 0) {
2505
- comp = await this.comps.resolve({
2506
- feis: age.comp.slice(0, slash),
2507
- code: age.comp.slice(slash + 1),
2508
- ...age.year !== undefined ? { year: age.year } : {}
2509
- });
2510
- if (comp.arm === "unique" && comp.age !== null)
2511
- put = { ...q, age: { band: comp.age, year: comp.feis.year } };
2512
- }
2513
- }
2514
- return {
2515
- tag: this.rel.tag,
2516
- asked: q,
2517
- put,
2518
- ...comp !== undefined ? { comp } : {},
2519
- dancer: dancers === null ? { arm: "unavailable", why: "this release opens no dancer resolver" } : await dancers.resolve(put)
2520
- };
2521
- }
2522
- async resolve(rows) {
2523
- const seen = new Map;
2524
- const out = [];
2525
- for (const row of rows) {
2526
- const comps = [];
2527
- for (const code of row.codes) {
2528
- const title = row.titles?.[code];
2529
- const key = JSON.stringify([row.feis, code, row.year ?? null, title ?? null]);
2530
- let a = seen.get(key);
2531
- if (a === undefined) {
2532
- a = await this.comps.resolve({
2533
- feis: row.feis,
2534
- code,
2535
- ...row.year !== undefined ? { year: row.year } : {},
2536
- ...title !== undefined ? { title } : {}
2537
- });
2538
- seen.set(key, a);
2539
- }
2540
- comps.push(a);
2541
- }
2542
- const read = comps.filter((c) => c.arm === "unique" && c.age !== null);
2543
- const first = youngest2(read);
2544
- const printed = first !== undefined ? [] : comps.flatMap((c, i) => {
2545
- const code = row.codes[i];
2546
- const t = row.titles?.[code];
2547
- const a = t === undefined ? null : compTitle2(t).age;
2548
- return a?.isAge() === true && a.kind !== "yob" ? [{ age: a.ag1, year: c.feis.year, code }] : [];
2549
- });
2550
- const byTitle = youngest2(printed);
2551
- const age = first !== undefined ? {
2552
- band: first.age,
2553
- year: first.feis.year,
2554
- code: first.asked.code,
2555
- from: "comp"
2556
- } : byTitle !== undefined ? { band: byTitle.age, year: byTitle.year, code: byTitle.code, from: "title" } : null;
2557
- const bands = [...new Set((first !== undefined ? read : printed).map((c) => c.age))];
2558
- let population = null;
2559
- if (row.population !== undefined)
2560
- population = { asked: row.population, from: "row" };
2561
- else {
2562
- const gendered = comps.flatMap((c, i) => c.arm === "unique" && (c.population === "girls" || c.population === "boys") ? [{ pop: c.population, code: row.codes[i] }] : []);
2563
- const pops = [...new Set(gendered.map((g) => g.pop))];
2564
- if (pops.length === 1)
2565
- population = {
2566
- asked: pops[0],
2567
- from: "comp",
2568
- codes: gendered.map((g) => g.code)
2569
- };
2570
- else if (pops.length > 1)
2571
- population = {
2572
- why: `the row's comps read both populations (${gendered.map((g) => `${g.code}: ${g.pop}`).join(", ")}): none asked`
2573
- };
2574
- }
2575
- const order = nameOrderOf(row.order);
2576
- const asked = {
2577
- name: row.name,
2578
- ...order !== undefined ? { order } : {},
2579
- ...row.school !== undefined ? { school: row.school } : {},
2580
- ...age !== null ? { age: { band: age.band, year: age.year } } : {},
2581
- ...row.asOf !== undefined ? { asOf: row.asOf } : {},
2582
- ...population !== null && "asked" in population ? { population: population.asked } : {},
2583
- ...row.oireachtas !== undefined ? { oireachtas: row.oireachtas } : {}
2584
- };
2585
- const { dancer } = await this.dancer(asked);
2586
- out.push({
2587
- tag: this.rel.tag,
2588
- row,
2589
- comps,
2590
- age,
2591
- ...bands.length > 1 ? { bands } : {},
2592
- population,
2593
- dancer,
2594
- asked
2595
- });
2596
- }
2597
- return out;
2635
+ const tmp = `${out}.building.${process.pid}`;
2636
+ rmSync(tmp, { force: true });
2637
+ const t0 = performance.now();
2638
+ const db = new Database(tmp, { create: true });
2639
+ try {
2640
+ db.run("PRAGMA journal_mode = OFF");
2641
+ db.run("PRAGMA synchronous = OFF");
2642
+ db.run("CREATE TABLE meta(k TEXT PRIMARY KEY, v TEXT NOT NULL)");
2643
+ db.transaction(() => b.build(rel, db, o.loaded))();
2644
+ const put = db.prepare("INSERT INTO meta(k, v) VALUES (?, ?)");
2645
+ put.run("key", key);
2646
+ put.run("builder", b.name);
2647
+ put.run("version", b.version);
2648
+ put.run("tag", rel.tag);
2649
+ db.run("VACUUM");
2650
+ } catch (e) {
2651
+ db.close();
2652
+ rmSync(tmp, { force: true });
2653
+ throw e;
2598
2654
  }
2655
+ db.close();
2656
+ renameSync(tmp, out);
2657
+ o.log?.(`index ${rel.tag} ${b.name}: built in ${((performance.now() - t0) / 1000).toFixed(1)} s`);
2658
+ return out;
2599
2659
  }
2600
-
2601
- import { spawn } from "child_process";
2602
- import { createWriteStream as createWriteStream2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
2603
- import { dirname } from "path";
2604
- import { Readable } from "stream";
2605
- import { pipeline as pipeline2 } from "stream/promises";
2606
- function run(cmd, args) {
2607
- return new Promise((resolve) => {
2608
- let stdout = "";
2609
- let stderr = "";
2610
- let child;
2611
- try {
2612
- child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2613
- } catch (e) {
2614
- resolve({ status: null, stdout, stderr: e.message });
2615
- return;
2616
- }
2617
- child.stdout?.on("data", (d) => {
2618
- stdout += String(d);
2619
- });
2620
- child.stderr?.on("data", (d) => {
2621
- stderr += String(d);
2622
- });
2623
- child.on("error", (e) => resolve({ status: null, stdout, stderr: e.message }));
2624
- child.on("close", (status) => resolve({ status, stdout, stderr }));
2625
- });
2660
+ async function ensureIndex2(rel, b, o) {
2661
+ if (!o.force && isCurrent2(rel, b))
2662
+ return indexPath2(rel, b.name);
2663
+ const loaded = b.load === undefined ? undefined : await b.load(rel);
2664
+ return buildIndex2(rel, b, { ...o, loaded });
2626
2665
  }
2627
- var firstLine = (s) => s.trim().split(`
2628
- `)[0] ?? "";
2629
- var ghRemote2 = {
2630
- name: "gh",
2631
- async tags(repo) {
2632
- const r = await run("gh", [
2633
- "release",
2634
- "list",
2635
- "-R",
2636
- repo,
2637
- "--limit",
2638
- "500",
2639
- "--exclude-drafts",
2640
- "--json",
2641
- "tagName",
2642
- "--jq",
2643
- ".[].tagName"
2644
- ]);
2645
- if (r.status !== 0)
2646
- throw new Error(`gh release list -R ${repo}: ${firstLine(r.stderr)}`);
2647
- return r.stdout.split(`
2648
- `).map((t) => t.trim()).filter((t) => t !== "");
2649
- },
2650
- async assets(repo, tag) {
2651
- const r = await run("gh", ["release", "view", tag, "-R", repo, "--json", "assets"]);
2652
- if (r.status !== 0)
2653
- throw new Error(`gh release view ${tag} -R ${repo}: ${firstLine(r.stderr)}`);
2654
- const v = JSON.parse(r.stdout);
2655
- return (v.assets ?? []).map((a) => ({ name: a.name, size: a.size, digest: a.digest ?? null }));
2656
- },
2657
- async download(repo, tag, name, dest) {
2658
- const r = await run("gh", [
2659
- "release",
2660
- "download",
2661
- tag,
2662
- "-R",
2663
- repo,
2664
- "-p",
2666
+ function listIndexes2(rel) {
2667
+ const dir = indexDir(rel);
2668
+ if (!existsSync(dir))
2669
+ return [];
2670
+ return readdirSync(dir).filter((f) => f.endsWith(".sqlite")).sort().map((f) => {
2671
+ const name = f.slice(0, -".sqlite".length);
2672
+ const path = join(dir, f);
2673
+ const b = rel.builders().find((x) => x.name === name);
2674
+ return {
2665
2675
  name,
2666
- "-O",
2667
- dest,
2668
- "--clobber"
2669
- ]);
2670
- if (r.status !== 0 || !existsSync3(dest))
2671
- throw new Error(`gh release download ${tag} -p ${name}: ${firstLine(r.stderr)}`);
2676
+ path,
2677
+ bytes: statSync(path).size,
2678
+ current: b === undefined ? null : keyOf(path) === indexKey2(rel, b)
2679
+ };
2680
+ });
2681
+ }
2682
+
2683
+ import {
2684
+ copyFileSync,
2685
+ existsSync as existsSync5,
2686
+ linkSync,
2687
+ mkdirSync as mkdirSync4,
2688
+ readFileSync as readFileSync4,
2689
+ readdirSync as readdirSync3,
2690
+ rmSync as rmSync4,
2691
+ statSync as statSync3
2692
+ } from "fs";
2693
+ import { isAbsolute, join as join4, relative, resolve, sep } from "path";
2694
+
2695
+ import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
2696
+ import { join as join2 } from "path";
2697
+ var pinPath = (root) => join2(root, "PINNED.json");
2698
+ function readPin2(root) {
2699
+ const p = pinPath(root);
2700
+ if (!existsSync2(p))
2701
+ return null;
2702
+ try {
2703
+ const v = JSON.parse(readFileSync2(p, "utf8"));
2704
+ return typeof v.tag === "string" && parseArchiveTag2(v.tag) !== null ? { tag: v.tag, source: typeof v.source === "string" ? v.source : "unknown" } : null;
2705
+ } catch {
2706
+ return null;
2672
2707
  }
2673
- };
2674
- var API = "https://api.github.com";
2675
- function nodeWebStream(body) {
2676
- return body;
2677
2708
  }
2678
- function headers(env, accept) {
2679
- const token = env.GITHUB_TOKEN ?? env.GH_TOKEN;
2680
- return {
2681
- accept,
2682
- "user-agent": "archive-client",
2683
- "x-github-api-version": "2022-11-28",
2684
- ...token !== undefined && token !== "" ? { authorization: `Bearer ${token}` } : {}
2685
- };
2709
+ function writePin(root, pin) {
2710
+ if (parseArchiveTag2(pin.tag) === null)
2711
+ throw new Error(`pin: "${pin.tag}" is not a release tag`);
2712
+ writeJsonAtomic2(pinPath(root), pin);
2686
2713
  }
2687
- function httpsRemote2(env = process.env, fetchImpl = fetch) {
2688
- const json = async (url) => {
2689
- const res = await fetchImpl(url, { headers: headers(env, "application/vnd.github+json") });
2690
- if (!res.ok)
2691
- throw new Error(`GET ${url}: ${res.status} ${res.statusText}`);
2692
- return await res.json();
2693
- };
2694
- const release = (repo, tag) => json(`${API}/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`);
2695
- return {
2696
- name: "https",
2697
- async tags(repo) {
2698
- const out = [];
2699
- for (let page = 1;page <= 10; page++) {
2700
- const rows = await json(`${API}/repos/${repo}/releases?per_page=100&page=${page}`);
2701
- for (const r of rows)
2702
- if (!r.draft)
2703
- out.push(r.tag_name);
2704
- if (rows.length < 100)
2705
- break;
2706
- }
2707
- return out;
2708
- },
2709
- async assets(repo, tag) {
2710
- return (await release(repo, tag)).assets.map((a) => ({
2711
- name: a.name,
2712
- size: a.size,
2713
- digest: a.digest ?? null
2714
- }));
2715
- },
2716
- async download(repo, tag, name, dest) {
2717
- const a = (await release(repo, tag)).assets.find((x) => x.name === name);
2718
- if (a === undefined)
2719
- throw new Error(`${tag} has no asset ${name}`);
2720
- const res = await fetchImpl(a.url, { headers: headers(env, "application/octet-stream") });
2721
- if (!res.ok || res.body === null)
2722
- throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
2723
- mkdirSync2(dirname(dest), { recursive: true });
2724
- await pipeline2(Readable.fromWeb(nodeWebStream(res.body)), createWriteStream2(dest));
2725
- }
2726
- };
2714
+ function clearPin(root) {
2715
+ const p = pinPath(root);
2716
+ if (!existsSync2(p))
2717
+ return false;
2718
+ rmSync2(p);
2719
+ return true;
2727
2720
  }
2728
- function fallbackRemote2(first, second) {
2729
- const both = (f) => async (...a) => {
2730
- try {
2731
- return await f(first)(...a);
2732
- } catch (e1) {
2733
- try {
2734
- return await f(second)(...a);
2735
- } catch (e2) {
2736
- throw new Error(`${first.name}: ${e1.message}; ${second.name}: ${e2.message}`);
2737
- }
2738
- }
2739
- };
2740
- return {
2741
- name: `${first.name}+${second.name}`,
2742
- tags: both((r) => r.tags.bind(r)),
2743
- assets: both((r) => r.assets.bind(r)),
2744
- download: both((r) => r.download.bind(r))
2721
+
2722
+ import { Database as Database2 } from "bun:sqlite";
2723
+ import {
2724
+ existsSync as existsSync4,
2725
+ mkdirSync as mkdirSync3,
2726
+ readFileSync as readFileSync3,
2727
+ readdirSync as readdirSync2,
2728
+ renameSync as renameSync2,
2729
+ rmSync as rmSync3,
2730
+ statSync as statSync2
2731
+ } from "fs";
2732
+ import { join as join3 } from "path";
2733
+
2734
+ import { Age as Age3 } from "@finbheara/domain/age";
2735
+ import { compTitle as compTitle2 } from "@finbheara/domain/title";
2736
+ function youngest2(answers) {
2737
+ const key = (a) => {
2738
+ const iv = Age3.from(a.age, { year: null }).interval();
2739
+ return iv === null ? [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY] : [iv.hi, iv.lo];
2745
2740
  };
2741
+ let best;
2742
+ let bk = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
2743
+ for (const a of answers) {
2744
+ const k = key(a);
2745
+ if (best === undefined || k[0] < bk[0] || k[0] === bk[0] && k[1] > bk[1]) {
2746
+ best = a;
2747
+ bk = k;
2748
+ }
2749
+ }
2750
+ return best;
2746
2751
  }
2747
- var installed = null;
2748
- function setRemote2(r) {
2749
- const was = installed;
2750
- installed = r;
2751
- return was;
2752
- }
2753
- function defaultRemote() {
2754
- return installed ?? fallbackRemote2(ghRemote2, httpsRemote2());
2752
+ function dancerResolverOf2(rel) {
2753
+ return rel.dancers;
2755
2754
  }
2756
2755
 
2757
- function columnsOf2(db, table) {
2758
- return new Set(db.query("SELECT name FROM pragma_table_info(?)").all(table).map((r) => r.name));
2759
- }
2760
- var FEATURES2 = [
2761
- {
2762
- feature: "results.level",
2763
- needs: ["results:competitions.feis_level"],
2764
- since: 215,
2765
- calls: "results.query, majors.results"
2766
- },
2767
- {
2768
- feature: "majors.order",
2769
- needs: ["majors:competitions.seq"],
2770
- since: 260,
2771
- calls: "majors.competitions"
2772
- },
2773
- {
2774
- feature: "dancers",
2775
- needs: ["results:results.name_order_resolved"],
2776
- since: 260,
2777
- calls: "dancers.resolve, dancers.search, dancers.get (the dancers index)"
2778
- },
2779
- {
2780
- feature: "schools",
2781
- needs: ["school-registry.jsonl", "school-redirects.jsonl"],
2782
- since: 260,
2783
- calls: "schools.resolve, a dancer question with a school (the schools index)"
2784
- },
2785
- {
2786
- feature: "feises",
2787
- needs: [
2788
- "results:feises.date_lo",
2789
- "results:feises.date_hi",
2790
- "results:feises.feis_instance_id",
2791
- "results:feises.feis_brand_id",
2792
- "results:competitions.feis_region",
2793
- "feis-redirects.jsonl"
2794
- ],
2795
- since: 265,
2796
- calls: "feises.find, feises.series, and comps and registrations through them (the feises index)"
2797
- },
2798
- {
2799
- feature: "comps",
2800
- needs: [
2801
- "results:competitions.feis_level",
2802
- "results:competitions.round",
2803
- "results:feises.feis_brand_id"
2804
- ],
2805
- since: 265,
2806
- calls: "comps.resolve, comps.syllabus, comps.syllabusFor, registrations.resolve (the comps index)"
2807
- },
2808
- {
2809
- feature: "comps.dance",
2810
- needs: ["results:competitions.dance"],
2811
- since: 270,
2812
- calls: "the `dance` a comp of comps.resolve, comps.syllabus and comps.syllabusFor carries: null, never refused, on a release without it"
2813
- },
2814
- {
2815
- feature: "schools.lineage",
2816
- needs: ["school-lineage.jsonl"],
2817
- since: 184,
2818
- calls: "the `lineage` school evidence of dancers.resolve and dancers.search: no school is joined by lineage, never refused, on a release without it"
2819
- },
2820
- {
2821
- feature: "results.stream",
2822
- needs: [
2823
- "results:competitions.round",
2824
- "results:competitions.feis_level",
2825
- "results:competitions.level_printed",
2826
- "results:competitions.age_printed",
2827
- "results:results.yob_printed",
2828
- "results:results.primary_source",
2829
- "results:results.platform_dancer_id",
2830
- "results:results.relative_place",
2831
- "results:feises.date_lo",
2832
- "results:feises.date_hi"
2833
- ],
2834
- since: 255,
2835
- calls: "results.stream, results.entries (scopes transcribed, platform, all)"
2836
- },
2837
- {
2838
- feature: "majors.stream",
2839
- needs: [
2840
- "majors:competitions.round",
2841
- "majors:competitions.feis_level",
2842
- "majors:results.yob_printed",
2843
- "majors:results.primary_source",
2844
- "majors:results.relative_place",
2845
- "majors:feises.date_lo"
2846
- ],
2847
- since: 255,
2848
- calls: "results.stream, results.entries (scope majors)"
2849
- },
2850
- {
2851
- feature: "majors.tabs",
2852
- needs: ["majors:tabs.address", "majors:tabs.true_round"],
2853
- since: 215,
2854
- calls: "majors.tabulated"
2855
- },
2856
- {
2857
- feature: "feises.dates",
2858
- needs: ["results:feises.feis_id", "results:feises.date_lo", "results:feises.date_hi"],
2859
- since: 245,
2860
- calls: "feises.dates"
2861
- },
2862
- {
2863
- feature: "dancers.records",
2864
- needs: [
2865
- "results:dancers.display",
2866
- "results:dancers.appearances",
2867
- "results:dancers.birth_lo",
2868
- "results:dancers.birth_hi",
2869
- "results:dancers.birth_source",
2870
- "results:dancers.birth_conflict",
2871
- "results:dancers.genders"
2872
- ],
2873
- since: 215,
2874
- calls: "dancers.records"
2875
- },
2876
- {
2877
- feature: "dancers.years",
2878
- needs: [
2879
- "results:dancer_years.region",
2880
- "results:dancer_years.region_source",
2881
- "results:dancer_years.nationals",
2882
- "results:dancer_years.nationals_source",
2883
- "results:dancer_years.geohash"
2884
- ],
2885
- since: 245,
2886
- calls: "dancers.years"
2887
- },
2888
- {
2889
- feature: "teams",
2890
- needs: [
2891
- ...[
2892
- "slug",
2893
- "address",
2894
- "event",
2895
- "year",
2896
- "card",
2897
- "comp_no",
2898
- "title",
2899
- "category",
2900
- "hands",
2901
- "letter",
2902
- "label",
2903
- "name_printed",
2904
- "school_printed",
2905
- "school_loc",
2906
- "school_id",
2907
- "place",
2908
- "place_printed",
2909
- "tie",
2910
- "competitors",
2911
- "atts"
2912
- ].map((c) => `table:team_results.${c}`),
2913
- ...[
2914
- "team",
2915
- "kind",
2916
- "event_year",
2917
- "year",
2918
- "comp_no",
2919
- "title",
2920
- "team_index",
2921
- "seq",
2922
- "school_id",
2923
- "school_print_materialized"
2924
- ].map((c) => `table:roster_teams.${c}`),
2925
- ...["team", "seq", "name_printed", "name", "yob", "dancer_id"].map((c) => `table:roster_members.${c}`),
2926
- ...[
2927
- "event_year",
2928
- "comp_no",
2929
- "team",
2930
- "result_address",
2931
- "school_id",
2932
- "join_kind",
2933
- "via_split",
2934
- "roster_teams",
2935
- "result_teams",
2936
- "roster_index",
2937
- "result_letter"
2938
- ].map((c) => `table:team_result_rosters.${c}`),
2939
- "table:event_years.event_year",
2940
- "table:event_years.event",
2941
- "table:event_years.year"
2942
- ],
2943
- since: 260,
2944
- calls: "teams.results, teams.rosters, teams.resultRosters (the teams index)"
2945
- },
2946
- {
2947
- feature: "teams.fields",
2948
- needs: [
2949
- "table:team_results.age",
2950
- "table:team_results.gender",
2951
- "table:team_results.printed_status",
2952
- "table:roster_teams.dance",
2953
- "table:roster_teams.school_print_as_of",
2954
- "table:roster_members.gender"
2955
- ],
2956
- since: 266,
2957
- calls: "the fields teams.results (age, gender, printed_status) and teams.rosters (dance, school_printed_as_of, a member's gender) carry where present: absent, never refused, on a release without them"
2756
+ class RegistrationResolver2 {
2757
+ rel;
2758
+ comps;
2759
+ dancers;
2760
+ constructor(rel, comps, dancers) {
2761
+ this.rel = rel;
2762
+ this.comps = comps;
2763
+ this.dancers = dancers;
2764
+ }
2765
+ async dancer(q) {
2766
+ const dancers = this.dancers === undefined ? dancerResolverOf2(this.rel) : this.dancers;
2767
+ let put = q;
2768
+ let comp;
2769
+ const age = q.age;
2770
+ if (age !== undefined && "comp" in age) {
2771
+ const slash = age.comp.lastIndexOf("/");
2772
+ if (slash > 0) {
2773
+ comp = await this.comps.resolve({
2774
+ feis: age.comp.slice(0, slash),
2775
+ code: age.comp.slice(slash + 1),
2776
+ ...age.year !== undefined ? { year: age.year } : {}
2777
+ });
2778
+ if (comp.arm === "unique" && comp.age !== null)
2779
+ put = { ...q, age: { band: comp.age, year: comp.feis.year } };
2780
+ }
2781
+ }
2782
+ return {
2783
+ tag: this.rel.tag,
2784
+ asked: q,
2785
+ put,
2786
+ ...comp !== undefined ? { comp } : {},
2787
+ dancer: dancers === null ? { arm: "unavailable", why: "this release opens no dancer resolver" } : await dancers.resolve(put)
2788
+ };
2789
+ }
2790
+ async resolve(rows) {
2791
+ const seen = new Map;
2792
+ const out = [];
2793
+ for (const row of rows) {
2794
+ const comps = [];
2795
+ for (const code of row.codes) {
2796
+ const title = row.titles?.[code];
2797
+ const key = JSON.stringify([row.feis, code, row.year ?? null, title ?? null]);
2798
+ let a = seen.get(key);
2799
+ if (a === undefined) {
2800
+ a = await this.comps.resolve({
2801
+ feis: row.feis,
2802
+ code,
2803
+ ...row.year !== undefined ? { year: row.year } : {},
2804
+ ...title !== undefined ? { title } : {}
2805
+ });
2806
+ seen.set(key, a);
2807
+ }
2808
+ comps.push(a);
2809
+ }
2810
+ const read = comps.filter((c) => c.arm === "unique" && c.age !== null);
2811
+ const first = youngest2(read);
2812
+ const printed = first !== undefined ? [] : comps.flatMap((c, i) => {
2813
+ const code = row.codes[i];
2814
+ const t = row.titles?.[code];
2815
+ const a = t === undefined ? null : compTitle2(t).age;
2816
+ return a?.isAge() === true && a.kind !== "yob" ? [{ age: a.ag1, year: c.feis.year, code }] : [];
2817
+ });
2818
+ const byTitle = youngest2(printed);
2819
+ const age = first !== undefined ? {
2820
+ band: first.age,
2821
+ year: first.feis.year,
2822
+ code: first.asked.code,
2823
+ from: "comp"
2824
+ } : byTitle !== undefined ? { band: byTitle.age, year: byTitle.year, code: byTitle.code, from: "title" } : null;
2825
+ const bands = [...new Set((first !== undefined ? read : printed).map((c) => c.age))];
2826
+ let population = null;
2827
+ if (row.population !== undefined)
2828
+ population = { asked: row.population, from: "row" };
2829
+ else {
2830
+ const gendered = comps.flatMap((c, i) => c.arm === "unique" && (c.population === "girls" || c.population === "boys") ? [{ pop: c.population, code: row.codes[i] }] : []);
2831
+ const pops = [...new Set(gendered.map((g) => g.pop))];
2832
+ if (pops.length === 1)
2833
+ population = {
2834
+ asked: pops[0],
2835
+ from: "comp",
2836
+ codes: gendered.map((g) => g.code)
2837
+ };
2838
+ else if (pops.length > 1)
2839
+ population = {
2840
+ why: `the row's comps read both populations (${gendered.map((g) => `${g.code}: ${g.pop}`).join(", ")}): none asked`
2841
+ };
2842
+ }
2843
+ const order = nameOrderOf(row.order);
2844
+ const asked = {
2845
+ name: row.name,
2846
+ ...order !== undefined ? { order } : {},
2847
+ ...row.school !== undefined ? { school: row.school } : {},
2848
+ ...age !== null ? { age: { band: age.band, year: age.year } } : {},
2849
+ ...row.asOf !== undefined ? { asOf: row.asOf } : {},
2850
+ ...population !== null && "asked" in population ? { population: population.asked } : {},
2851
+ ...row.oireachtas !== undefined ? { oireachtas: row.oireachtas } : {}
2852
+ };
2853
+ const { dancer } = await this.dancer(asked);
2854
+ out.push({
2855
+ tag: this.rel.tag,
2856
+ row,
2857
+ comps,
2858
+ age,
2859
+ ...bands.length > 1 ? { bands } : {},
2860
+ population,
2861
+ dancer,
2862
+ asked
2863
+ });
2864
+ }
2865
+ return out;
2866
+ }
2867
+ }
2868
+
2869
+ import { spawn } from "child_process";
2870
+ import { createWriteStream as createWriteStream2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
2871
+ import { dirname } from "path";
2872
+ import { Readable } from "stream";
2873
+ import { pipeline as pipeline2 } from "stream/promises";
2874
+ function run(cmd, args) {
2875
+ return new Promise((resolve) => {
2876
+ let stdout = "";
2877
+ let stderr = "";
2878
+ let child;
2879
+ try {
2880
+ child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2881
+ } catch (e) {
2882
+ resolve({ status: null, stdout, stderr: e.message });
2883
+ return;
2884
+ }
2885
+ child.stdout?.on("data", (d) => {
2886
+ stdout += String(d);
2887
+ });
2888
+ child.stderr?.on("data", (d) => {
2889
+ stderr += String(d);
2890
+ });
2891
+ child.on("error", (e) => resolve({ status: null, stdout, stderr: e.message }));
2892
+ child.on("close", (status) => resolve({ status, stdout, stderr }));
2893
+ });
2894
+ }
2895
+ var firstLine = (s) => s.trim().split(`
2896
+ `)[0] ?? "";
2897
+ var ghRemote2 = {
2898
+ name: "gh",
2899
+ async tags(repo) {
2900
+ const r = await run("gh", [
2901
+ "release",
2902
+ "list",
2903
+ "-R",
2904
+ repo,
2905
+ "--limit",
2906
+ "500",
2907
+ "--exclude-drafts",
2908
+ "--json",
2909
+ "tagName",
2910
+ "--jq",
2911
+ ".[].tagName"
2912
+ ]);
2913
+ if (r.status !== 0)
2914
+ throw new Error(`gh release list -R ${repo}: ${firstLine(r.stderr)}`);
2915
+ return r.stdout.split(`
2916
+ `).map((t) => t.trim()).filter((t) => t !== "");
2958
2917
  },
2959
- {
2960
- feature: "events",
2961
- needs: [
2962
- "event_year",
2963
- "event",
2964
- "year",
2965
- "date_lo",
2966
- "date_hi",
2967
- "resolution",
2968
- "source",
2969
- "date_printed",
2970
- "place",
2971
- "place_printed"
2972
- ].map((c) => `table:event_years.${c}`),
2973
- since: 260,
2974
- calls: "events.years (the events index)"
2918
+ async assets(repo, tag) {
2919
+ const r = await run("gh", ["release", "view", tag, "-R", repo, "--json", "assets"]);
2920
+ if (r.status !== 0)
2921
+ throw new Error(`gh release view ${tag} -R ${repo}: ${firstLine(r.stderr)}`);
2922
+ const v = JSON.parse(r.stdout);
2923
+ return (v.assets ?? []).map((a) => ({ name: a.name, size: a.size, digest: a.digest ?? null }));
2975
2924
  },
2976
- {
2977
- feature: "staff",
2978
- needs: [
2979
- "results:adjudicators",
2980
- "results:feis_panels",
2981
- "school-staff.jsonl",
2982
- "school-teachers.jsonl",
2983
- "school-registry.jsonl"
2984
- ],
2985
- since: 260,
2986
- calls: "staff.teacher, staff.adjudicator (the staff index)"
2987
- }
2988
- ];
2989
- async function hasNeed(probe, n) {
2990
- const t = /^table:([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
2991
- if (t !== null) {
2992
- if (probe.tableColumns === undefined)
2993
- return false;
2994
- const cols = await probe.tableColumns(t[1]);
2995
- return t[2] === undefined ? cols.size > 0 : cols.has(t[2]);
2925
+ async download(repo, tag, name, dest) {
2926
+ const r = await run("gh", [
2927
+ "release",
2928
+ "download",
2929
+ tag,
2930
+ "-R",
2931
+ repo,
2932
+ "-p",
2933
+ name,
2934
+ "-O",
2935
+ dest,
2936
+ "--clobber"
2937
+ ]);
2938
+ if (r.status !== 0 || !existsSync3(dest))
2939
+ throw new Error(`gh release download ${tag} -p ${name}: ${firstLine(r.stderr)}`);
2996
2940
  }
2997
- const m = /^(results|majors):([A-Za-z_]+)(?:\.([A-Za-z_]+))?$/.exec(n);
2998
- if (m === null)
2999
- return probe.publishes(n);
3000
- const cols = columnsOf2(await probe.db(m[1]), m[2]);
3001
- return m[3] === undefined ? cols.size > 0 : cols.has(m[3]);
2941
+ };
2942
+ var API = "https://api.github.com";
2943
+ function nodeWebStream(body) {
2944
+ return body;
3002
2945
  }
3003
- async function capabilityOf2(probe, f) {
3004
- const missing = [];
3005
- for (const n of f.needs)
3006
- if (!await hasNeed(probe, n))
3007
- missing.push(n);
3008
- return { ...f, state: missing.length === 0 ? "full" : "missing", missing };
2946
+ function headers(env, accept) {
2947
+ const token = env.GITHUB_TOKEN ?? env.GH_TOKEN;
2948
+ return {
2949
+ accept,
2950
+ "user-agent": "archive-client",
2951
+ "x-github-api-version": "2022-11-28",
2952
+ ...token !== undefined && token !== "" ? { authorization: `Bearer ${token}` } : {}
2953
+ };
3009
2954
  }
3010
- function needsRelease2(tag, c) {
3011
- return new Error(`archive-client: ${tag} is too old for ${c.feature} (${c.calls}): it lacks ${c.missing.join(", ")}. Open a release of cut ${c.since} or later`);
2955
+ function httpsRemote2(env = process.env, fetchImpl = fetch) {
2956
+ const json = async (url) => {
2957
+ const res = await fetchImpl(url, { headers: headers(env, "application/vnd.github+json") });
2958
+ if (!res.ok)
2959
+ throw new Error(`GET ${url}: ${res.status} ${res.statusText}`);
2960
+ return await res.json();
2961
+ };
2962
+ const release = (repo, tag) => json(`${API}/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`);
2963
+ return {
2964
+ name: "https",
2965
+ async tags(repo) {
2966
+ const out = [];
2967
+ for (let page = 1;page <= 10; page++) {
2968
+ const rows = await json(`${API}/repos/${repo}/releases?per_page=100&page=${page}`);
2969
+ for (const r of rows)
2970
+ if (!r.draft)
2971
+ out.push(r.tag_name);
2972
+ if (rows.length < 100)
2973
+ break;
2974
+ }
2975
+ return out;
2976
+ },
2977
+ async assets(repo, tag) {
2978
+ return (await release(repo, tag)).assets.map((a) => ({
2979
+ name: a.name,
2980
+ size: a.size,
2981
+ digest: a.digest ?? null
2982
+ }));
2983
+ },
2984
+ async download(repo, tag, name, dest) {
2985
+ const a = (await release(repo, tag)).assets.find((x) => x.name === name);
2986
+ if (a === undefined)
2987
+ throw new Error(`${tag} has no asset ${name}`);
2988
+ const res = await fetchImpl(a.url, { headers: headers(env, "application/octet-stream") });
2989
+ if (!res.ok || res.body === null)
2990
+ throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
2991
+ mkdirSync2(dirname(dest), { recursive: true });
2992
+ await pipeline2(Readable.fromWeb(nodeWebStream(res.body)), createWriteStream2(dest));
2993
+ }
2994
+ };
2995
+ }
2996
+ function fallbackRemote2(first, second) {
2997
+ const both = (f) => async (...a) => {
2998
+ try {
2999
+ return await f(first)(...a);
3000
+ } catch (e1) {
3001
+ try {
3002
+ return await f(second)(...a);
3003
+ } catch (e2) {
3004
+ throw new Error(`${first.name}: ${e1.message}; ${second.name}: ${e2.message}`);
3005
+ }
3006
+ }
3007
+ };
3008
+ return {
3009
+ name: `${first.name}+${second.name}`,
3010
+ tags: both((r) => r.tags.bind(r)),
3011
+ assets: both((r) => r.assets.bind(r)),
3012
+ download: both((r) => r.download.bind(r))
3013
+ };
3014
+ }
3015
+ var installed = null;
3016
+ function setRemote2(r) {
3017
+ const was = installed;
3018
+ installed = r;
3019
+ return was;
3020
+ }
3021
+ function defaultRemote() {
3022
+ return installed ?? fallbackRemote2(ghRemote2, httpsRemote2());
3012
3023
  }
3013
3024
 
3014
3025
  function tagDir(root, tag) {
@@ -3529,4 +3540,4 @@ function releases2(opts = {}) {
3529
3540
  return new ReleaseManager2(opts);
3530
3541
  }
3531
3542
 
3532
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, initialsOnly2, 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 };
3543
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, NO_CALENDAR2, FeisFinder2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, canonicalOf2, CompResolver2, StaffFinder2, SchoolResolver2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, resolveTag2, Release2, openRelease2, openCached2, ReleaseManager2, releases2 };