@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.
- package/README.md +7 -2
- package/dist/chunks/{index-hs72zekf.js → index-7hw19tw1.js} +644 -633
- package/dist/cli.js +1 -1
- package/dist/index.js +5 -5
- package/dist/types/stream.d.ts +6 -1
- package/package.json +2 -2
|
@@ -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
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
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
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
const
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
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
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
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
|
|
2688
|
-
const
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
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
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
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
|
-
|
|
2748
|
-
|
|
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
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
{
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
"
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
{
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
].
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
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
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
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
|
-
|
|
2978
|
-
|
|
2979
|
-
"
|
|
2980
|
-
|
|
2981
|
-
"
|
|
2982
|
-
|
|
2983
|
-
"
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
];
|
|
2989
|
-
|
|
2990
|
-
|
|
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
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
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
|
-
|
|
3004
|
-
const
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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
|
|
3011
|
-
|
|
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,
|
|
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 };
|