@finbheara/archive-client 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -99,6 +99,8 @@ c.features; // [{ feature: "dancers", state: "full" | "missing", missing: [...
99
99
  | `events` | `events.years` | 260 |
100
100
  | `upcoming.feises` | `upcoming.feises`, and the `next` of `feises.series` (null with the reason without it) | 285 |
101
101
  | `upcoming.fields`, `upcoming.entrants` | `upcoming.fields`, `upcoming.entrants` (285 and 290 carry the tables empty) | 291 |
102
+ | `upcoming.fields.instance` | the `feis_instance_id` `upcoming.fields` carries; absent, never refused, without it | 320 |
103
+ | `calendar` | `calendar.get`, `calendar.list` | 320 |
102
104
  | `tabs`, `panels` | `tabs.query`, `tabs.stream`; `panels.query` | 184 |
103
105
  | `feis-panels` | `feisPanels.query` | 260 |
104
106
  | `staff.adjudicators` | `staff.adjudicators` | 230 |
@@ -697,7 +699,8 @@ o.feises; // [{ occurrence_id, occurrence_ids, name_printed, date_lo, date_hi
697
699
  // feis_brand_id, feis_instance_id, series_basis, brand_candidates, captured_id, state }]
698
700
  const f = await rel.upcoming.fields({ state: "pending" }); // state, feis, field_id, from, to
699
701
  f.fields; // [{ field_id, feis_id, source, host_id, competition_code, count_only, occurrence_id,
700
- // name_printed, date_lo, date_hi, year, feis_brand_id, comp, state, flags,
702
+ // name_printed, date_lo, date_hi, year, feis_brand_id, feis_instance_id, comp,
703
+ // state, flags,
701
704
  // results_competitions, results_feis_ids, held_out, merged_into, version, versions,
702
705
  // captured_date, sha256,
703
706
  // limits, entrants, stated_rows, printed_total }]
@@ -712,7 +715,10 @@ d.departures; // [{ field_id, feis_id, occurrence_id, feis_brand_id, competition
712
715
  `upcoming.feises` is every occurrence of the cut's feis-index the release holds no results of:
713
716
  `upcoming` (dated on or after the cut's day) or `no-results` (before it), each with every
714
717
  platform's or calendar's listing (`hosts`) and its feis series where one was named (`series_basis`
715
- says how; `none` leaves `feis_brand_id` null, the unjoined ones).
718
+ says how; `none` leaves `feis_brand_id` null, the unjoined ones). An occurrence whose series one
719
+ filed brand answers is given its `feis_instance_id` when it is ANNOUNCED, before any capture of it
720
+ is held (`series_basis` `announced` on every later release), and its captures land on that
721
+ instance when they arrive, so a field's `feis_instance_id` is the id its results will carry.
716
722
 
717
723
  A FIELD is one competition a competitor list names, `<feis_id>/<code>`, the same id on every cut. A later capture of the host is a new `version`
718
724
  (a capture id; `versions` lists them), never a new field. `state` says how it stands on the cut:
@@ -747,6 +753,33 @@ the corpus `entrants/` part: its rows are schools' counts (`level_counts`), with
747
753
  host with a registrant list publishes no such field. An unknown field or version answers no rows and says
748
754
  why. On a release without the tables each call refuses by name.
749
755
 
756
+ ## The feis calendar
757
+
758
+ ```ts
759
+ const g = await rel.calendar.get("plf.2026"); // an instance, an event-year (`<event><year>`), a retired
760
+ // id, a capture (`fweb:PLF26`) or an occurrence id
761
+ g.via; // "instance" | "formerly" | "feis_id" | "occurrence_id" | null
762
+ g.entries; // [{ feis_instance_id, feis_brand_id, tier, name_printed, date_lo, date_hi, date_source,
763
+ // occurrence_ids, feis_ids, stages: { announced, syllabus, entrants, run, results },
764
+ // terminal, carried, formerly }]
765
+ const l = await rel.calendar.list({ stage: "entrants", state: "observed", from: "2026-11-01" });
766
+ // brand, tier, from, to, stage, state, terminal ("none": still ahead)
767
+ ```
768
+
769
+ ONE ROW PER FEIS INSTANCE, on the id its results carry: a feis-series instance (`<brand>.<year>`),
770
+ announced, held or both, or a rated majors event-year. Each stage (`announced`, `syllabus`,
771
+ `entrants`, `run`, `results`) is `{ state, date, cite }`: `observed` (a capture showed it, `date`
772
+ the day it was first observed, null where no capture dates it, `cite` the capture), `pending` (the
773
+ release holds captures of that kind for the instance's platform, so a later one may show it) or
774
+ `not-captured` (it holds none). A day first observed never moves later from one release to the
775
+ next: a stage an earlier release observed keeps its day, named in `carried` (`["row"]` for a row
776
+ the cut no longer names, kept whole), and a retired id is listed in `formerly`. `terminal` is
777
+ `results`, `withdrawn` (every field withdrawn), `passed-no-results` (its first day is before the
778
+ cut's day), or null for a feis still ahead. Stage dates follow capture timing, not the feis's own
779
+ order: only results after the run is a belief. `get` tries the instance id, then `formerly`, then
780
+ a capture, then an occurrence id, and says which met (`via`). On a release without the table each
781
+ call refuses by name.
782
+
750
783
  ## Teachers and adjudicators
751
784
 
752
785
  ```ts
@@ -922,6 +955,9 @@ is passed to the dancer resolver as asked (`rel.dancers`, above).
922
955
  idq upcoming [--release <spec>] [--state <s,...>] [--feis <id>] [--from <date>] [--to <date>]
923
956
  idq upcoming --entrants <field_id> [--version <capture id>]
924
957
  idq upcoming --feises [--state upcoming|no-results] [--brand <series>] [--from <date>] [--to <date>]
958
+ idq calendar [--release <spec>] <instance|feis id|occurrence id>
959
+ idq calendar [--brand <series>] [--tier <t>] [--from <date>] [--to <date>] [--stage <s>] [--state <s>]
960
+ [--terminal results|withdrawn|passed-no-results|none]
925
961
  idq teacher [--release <spec>] <name|TD number>
926
962
  idq adjudicator [--release <spec>] <name|TD number>
927
963
  idq comp [--release <spec>] <feis> <code> [--year y] [--title t]
@@ -682,6 +682,46 @@ var FEATURES2 = [
682
682
  since: 291,
683
683
  calls: "upcoming.fields (285 and 290 carry the table empty)"
684
684
  },
685
+ {
686
+ feature: "upcoming.fields.instance",
687
+ needs: ["results:upcoming_fields.feis_instance_id"],
688
+ since: 320,
689
+ calls: "upcoming.fields' `feis_instance_id` (absent, never null, without it)"
690
+ },
691
+ {
692
+ feature: "calendar",
693
+ needs: [
694
+ "feis_instance_id",
695
+ "feis_brand_id",
696
+ "tier",
697
+ "name_printed",
698
+ "date_lo",
699
+ "date_hi",
700
+ "date_source",
701
+ "occurrence_ids",
702
+ "feis_ids",
703
+ "announced_state",
704
+ "announced_date",
705
+ "announced_cite",
706
+ "syllabus_state",
707
+ "syllabus_date",
708
+ "syllabus_cite",
709
+ "entrants_state",
710
+ "entrants_date",
711
+ "entrants_cite",
712
+ "run_state",
713
+ "run_date",
714
+ "run_cite",
715
+ "results_state",
716
+ "results_date",
717
+ "results_cite",
718
+ "terminal",
719
+ "carried",
720
+ "formerly"
721
+ ].map((c) => `results:feis_calendar.${c}`),
722
+ since: 320,
723
+ calls: "calendar.get, calendar.list"
724
+ },
685
725
  {
686
726
  feature: "upcoming.entrants",
687
727
  needs: [
@@ -3819,6 +3859,118 @@ import {
3819
3859
  } from "fs";
3820
3860
  import { join as join3 } from "path";
3821
3861
 
3862
+ var CALENDAR_STAGES2 = [
3863
+ "announced",
3864
+ "syllabus",
3865
+ "entrants",
3866
+ "run",
3867
+ "results"
3868
+ ];
3869
+ var CALENDAR_STAGE_STATES2 = [
3870
+ "observed",
3871
+ "pending",
3872
+ "not-captured"
3873
+ ];
3874
+ var CALENDAR_TERMINALS2 = [
3875
+ "results",
3876
+ "withdrawn",
3877
+ "passed-no-results"
3878
+ ];
3879
+ var json = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
3880
+ function entryOf2(r) {
3881
+ const stages = Object.fromEntries(CALENDAR_STAGES2.map((s) => [
3882
+ s,
3883
+ {
3884
+ state: r[`${s}_state`],
3885
+ date: r[`${s}_date`] ?? null,
3886
+ cite: r[`${s}_cite`] ?? null
3887
+ }
3888
+ ]));
3889
+ return {
3890
+ feis_instance_id: r.feis_instance_id,
3891
+ feis_brand_id: r.feis_brand_id ?? null,
3892
+ tier: r.tier,
3893
+ name_printed: r.name_printed ?? null,
3894
+ date_lo: r.date_lo ?? null,
3895
+ date_hi: r.date_hi ?? null,
3896
+ date_source: r.date_source ?? null,
3897
+ occurrence_ids: json(r.occurrence_ids, []),
3898
+ feis_ids: json(r.feis_ids, []),
3899
+ stages,
3900
+ terminal: r.terminal ?? null,
3901
+ carried: json(r.carried, []),
3902
+ formerly: json(r.formerly, [])
3903
+ };
3904
+ }
3905
+ var holds = (col) => `EXISTS (SELECT 1 FROM json_each(${col}) WHERE value = ?)`;
3906
+
3907
+ class CalendarFinder2 {
3908
+ rel;
3909
+ constructor(rel) {
3910
+ this.rel = rel;
3911
+ }
3912
+ async db() {
3913
+ await this.rel.fetch("results");
3914
+ await this.rel.require("calendar");
3915
+ return this.rel.db("results");
3916
+ }
3917
+ async get(id) {
3918
+ const db = await this.db();
3919
+ const ways = [
3920
+ ["instance", "feis_instance_id = ?"],
3921
+ ["formerly", holds("formerly")],
3922
+ ["feis_id", holds("feis_ids")],
3923
+ ["occurrence_id", holds("occurrence_ids")]
3924
+ ];
3925
+ for (const [via, where] of ways) {
3926
+ const rows = db.query(`SELECT * FROM feis_calendar WHERE ${where} ORDER BY feis_instance_id`).all(id);
3927
+ if (rows.length > 0)
3928
+ return { tag: this.rel.tag, asked: id, via, entries: rows.map(entryOf2) };
3929
+ }
3930
+ return {
3931
+ tag: this.rel.tag,
3932
+ asked: id,
3933
+ via: null,
3934
+ entries: [],
3935
+ why: `the calendar names no instance, capture or occurrence "${id}"`
3936
+ };
3937
+ }
3938
+ async list(q = {}) {
3939
+ if (q.stage !== undefined && !CALENDAR_STAGES2.includes(q.stage))
3940
+ throw new Error(`calendar.list: no stage "${q.stage}" (the stages are ${CALENDAR_STAGES2.join(", ")})`);
3941
+ if (q.state !== undefined && !CALENDAR_STAGE_STATES2.includes(q.state))
3942
+ throw new Error(`calendar.list: no state "${q.state}" (the states are ${CALENDAR_STAGE_STATES2.join(", ")})`);
3943
+ if (q.terminal !== undefined && q.terminal !== "none" && !CALENDAR_TERMINALS2.includes(q.terminal))
3944
+ throw new Error(`calendar.list: no terminal "${q.terminal}" (the terminals are ${CALENDAR_TERMINALS2.join(", ")}, none)`);
3945
+ const db = await this.db();
3946
+ const where = [];
3947
+ const args = [];
3948
+ const add = (sql, ...v) => {
3949
+ where.push(sql);
3950
+ args.push(...v);
3951
+ };
3952
+ if (q.brand !== undefined)
3953
+ add("feis_brand_id = ?", q.brand);
3954
+ if (q.tier !== undefined)
3955
+ add("tier = ?", q.tier);
3956
+ if (q.from !== undefined)
3957
+ add("date_lo >= ?", q.from);
3958
+ if (q.to !== undefined)
3959
+ add("date_lo <= ?", q.to);
3960
+ if (q.stage !== undefined)
3961
+ add(`${q.stage}_state = ?`, q.state ?? "observed");
3962
+ else if (q.state !== undefined)
3963
+ add(`(${CALENDAR_STAGES2.map((s) => `${s}_state = ?`).join(" OR ")})`, ...CALENDAR_STAGES2.map(() => q.state));
3964
+ if (q.terminal === "none")
3965
+ add("terminal IS NULL");
3966
+ else if (q.terminal !== undefined)
3967
+ add("terminal = ?", q.terminal);
3968
+ const sql = `SELECT * FROM feis_calendar${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY date_lo IS NULL, date_lo, feis_instance_id`;
3969
+ const rows = db.query(sql).all(...args);
3970
+ return { tag: this.rel.tag, entries: rows.map(entryOf2) };
3971
+ }
3972
+ }
3973
+
3822
3974
  import { Age as Age3 } from "@finbheara/domain/age";
3823
3975
  import { compTitle as compTitle3 } from "@finbheara/domain/title";
3824
3976
  function youngest2(answers) {
@@ -4119,7 +4271,7 @@ var FIELD_STATES2 = [
4119
4271
  "withdrawn"
4120
4272
  ];
4121
4273
  var DEPARTURE_REASONS2 = FIELD_STATES2.filter((s) => s !== "pending");
4122
- var json = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
4274
+ var json2 = (s, empty) => typeof s === "string" && s !== "" ? JSON.parse(s) : empty;
4123
4275
  function fieldOf(r) {
4124
4276
  const countOnly = r.count_only === 1;
4125
4277
  return {
@@ -4135,6 +4287,7 @@ function fieldOf(r) {
4135
4287
  date_hi: r.date_hi ?? null,
4136
4288
  year: r.year,
4137
4289
  feis_brand_id: r.feis_brand_id ?? null,
4290
+ ...r.feis_instance_id === undefined ? {} : { feis_instance_id: r.feis_instance_id ?? null },
4138
4291
  comp: r.comp_arm === null ? null : {
4139
4292
  arm: r.comp_arm,
4140
4293
  source: r.comp_source ?? null,
@@ -4147,19 +4300,19 @@ function fieldOf(r) {
4147
4300
  why: r.comp_why ?? null
4148
4301
  },
4149
4302
  state: r.state,
4150
- flags: json(r.flags, []),
4151
- results_competitions: json(r.results_competitions, []),
4152
- results_feis_ids: json(r.results_feis_ids, []),
4153
- held_out: json(r.held_out, []),
4303
+ flags: json2(r.flags, []),
4304
+ results_competitions: json2(r.results_competitions, []),
4305
+ results_feis_ids: json2(r.results_feis_ids, []),
4306
+ held_out: json2(r.held_out, []),
4154
4307
  merged_into: r.merged_into ?? null,
4155
4308
  version: r.version,
4156
- versions: json(r.versions, []),
4309
+ versions: json2(r.versions, []),
4157
4310
  captured_date: r.captured_date,
4158
4311
  sha256: r.sha256,
4159
- limits: json(r.limits, []),
4312
+ limits: json2(r.limits, []),
4160
4313
  entrants: r.entrants,
4161
4314
  stated_rows: r.stated_rows ?? null,
4162
- printed_total: json(r.printed_total, null)
4315
+ printed_total: json2(r.printed_total, null)
4163
4316
  };
4164
4317
  }
4165
4318
  function entrantOf(r) {
@@ -4176,30 +4329,30 @@ function entrantOf(r) {
4176
4329
  school_printed: r.school_printed ?? null,
4177
4330
  school_id: r.school_id ?? null,
4178
4331
  school_rung: r.school_rung,
4179
- school_candidates: json(r.school_candidates, []),
4180
- competitions: json(r.competitions, []),
4181
- field_ids: json(r.field_ids, []),
4182
- level_counts: json(r.level_counts, null),
4332
+ school_candidates: json2(r.school_candidates, []),
4333
+ competitions: json2(r.competitions, []),
4334
+ field_ids: json2(r.field_ids, []),
4335
+ level_counts: json2(r.level_counts, null),
4183
4336
  yob: r.yob ?? null,
4184
- asked: json(r.asked, null),
4337
+ asked: json2(r.asked, null),
4185
4338
  arm: r.arm ?? null,
4186
- candidates: json(r.candidates, []),
4187
- flags: json(r.flags, [])
4339
+ candidates: json2(r.candidates, []),
4340
+ flags: json2(r.flags, [])
4188
4341
  };
4189
4342
  }
4190
4343
  function feisOf(r) {
4191
4344
  return {
4192
4345
  occurrence_id: r.occurrence_id,
4193
- occurrence_ids: json(r.occurrence_ids, []),
4346
+ occurrence_ids: json2(r.occurrence_ids, []),
4194
4347
  name_printed: r.name_printed,
4195
4348
  date_lo: r.date_lo,
4196
4349
  date_hi: r.date_hi ?? null,
4197
4350
  year: r.year,
4198
- hosts: json(r.hosts, []),
4351
+ hosts: json2(r.hosts, []),
4199
4352
  feis_brand_id: r.feis_brand_id ?? null,
4200
4353
  feis_instance_id: r.feis_instance_id ?? null,
4201
4354
  series_basis: r.series_basis,
4202
- brand_candidates: json(r.brand_candidates, []),
4355
+ brand_candidates: json2(r.brand_candidates, []),
4203
4356
  captured_id: r.captured_id,
4204
4357
  state: r.results_state
4205
4358
  };
@@ -4389,6 +4542,7 @@ class Release2 {
4389
4542
  teams;
4390
4543
  events;
4391
4544
  upcoming;
4545
+ calendar;
4392
4546
  tabs;
4393
4547
  panels;
4394
4548
  feisPanels;
@@ -4415,6 +4569,7 @@ class Release2 {
4415
4569
  this.teams = new TeamFinder2(this);
4416
4570
  this.events = new EventFinder2(this);
4417
4571
  this.upcoming = new UpcomingFinder2(this);
4572
+ this.calendar = new CalendarFinder2(this);
4418
4573
  this.tabs = new TabFinder2(this);
4419
4574
  this.panels = new PanelFinder2(this);
4420
4575
  this.feisPanels = new FeisPanelFinder2(this);
@@ -5138,4 +5293,4 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
5138
5293
  return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
5139
5294
  }
5140
5295
 
5141
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, TabFinder2, PanelFinder2, FeisPanelFinder2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, rosterOf2, NO_CALENDAR2, NO_UPCOMING2, FeisFinder2, ADVANCEMENT_DANCES2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, LINEAGE_FILE2, linksOfLineage2, releaseSchoolGraph2, readsSpanishNames2, SchoolResolver2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, syllabusSourceOf2, canonicalOf2, CompResolver2, StaffFinder2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, defaultRemote, FIELD_STATES2, DEPARTURE_REASONS2, UpcomingFinder2, resolveTag2, Release2, openRelease2, openCached2, DEFAULT_KEEP2, ReleaseManager2, releases2, LOCAL_DIR2, localRoot2, newerThan2, incomplete2, releaseVersion2, pickBuild2, buildTag2, lacks, openBuild2, sweepBuilds2, LATEST_CHECK_MS2, LATEST_CHECK_BUDGET_MS2, latestCheckPath2, readLatestCheck2, latestCheck2, within, staleWarning2 };
5296
+ export { DATABASES2, planAsset2, sha256File2, unzstdTo2, NAME_LEVELS2, nameLevel2, TabFinder2, PanelFinder2, FeisPanelFinder2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, rosterOf2, NO_CALENDAR2, NO_UPCOMING2, FeisFinder2, ADVANCEMENT_DANCES2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, LINEAGE_FILE2, linksOfLineage2, releaseSchoolGraph2, readsSpanishNames2, SchoolResolver2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, syllabusSourceOf2, canonicalOf2, CompResolver2, StaffFinder2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, CALENDAR_STAGES2, CALENDAR_STAGE_STATES2, CALENDAR_TERMINALS2, CalendarFinder2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, defaultRemote, FIELD_STATES2, DEPARTURE_REASONS2, UpcomingFinder2, resolveTag2, Release2, openRelease2, openCached2, DEFAULT_KEEP2, ReleaseManager2, releases2, LOCAL_DIR2, localRoot2, newerThan2, incomplete2, releaseVersion2, pickBuild2, buildTag2, lacks, openBuild2, sweepBuilds2, LATEST_CHECK_MS2, LATEST_CHECK_BUDGET_MS2, latestCheckPath2, readLatestCheck2, latestCheck2, within, staleWarning2 };
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  latestCheck2,
22
22
  within,
23
23
  staleWarning2
24
- } from "./chunks/index-dy6yvpgk.js";
24
+ } from "./chunks/index-cwhyrcyx.js";
25
25
  import"./chunks/index-hghdy980.js";
26
26
 
27
27
  var USAGE_HEADER = "usage: idq <verb> [--cache <dir>] [--repo <owner/name>] [--json] [--live]";
@@ -392,6 +392,21 @@ var VERBS = [
392
392
  " held-out competitions)"
393
393
  ]
394
394
  },
395
+ {
396
+ verb: "calendar",
397
+ value: ["--release", "--brand", "--tier", "--from", "--to", "--stage", "--state", "--terminal"],
398
+ bool: [],
399
+ usage: [
400
+ " calendar [--release <spec>] <instance|feis id|occurrence id>",
401
+ " one feis instance's stages: announced, syllabus, entrants,",
402
+ " run and results, each observed, pending or not-captured,",
403
+ " the day first observed and the capture that observed it",
404
+ " calendar [--brand <series>] [--tier T1..T4] [--from <date>] [--to <date>]",
405
+ " [--stage announced|syllabus|entrants|run|results] [--state observed|pending|not-captured]",
406
+ " [--terminal results|withdrawn|passed-no-results|none]",
407
+ " the instances a query names, by first day"
408
+ ]
409
+ },
395
410
  {
396
411
  verb: "teacher",
397
412
  value: ["--release"],
@@ -799,6 +814,7 @@ var ANSWERING = new Set([
799
814
  "teams",
800
815
  "events",
801
816
  "upcoming",
817
+ "calendar",
802
818
  "teacher",
803
819
  "adjudicator",
804
820
  "comp",
@@ -1533,6 +1549,42 @@ async function main(argv, io = {}) {
1533
1549
  rel.close();
1534
1550
  }
1535
1551
  }
1552
+ case "calendar": {
1553
+ const rel = await open(spec, { ...opts, assets: [] });
1554
+ try {
1555
+ const line = (e) => ` ${e.date_lo ?? "?"} ${e.feis_instance_id} ${e.tier} ${["announced", "syllabus", "entrants", "run", "results"].map((s) => `${s}:${e.stages[s].state === "observed" ? e.stages[s].date ?? "seen" : e.stages[s].state}`).join(" ")} [${e.terminal ?? "ahead"}]${e.name_printed !== null ? ` "${e.name_printed}"` : ""}`;
1556
+ if (rest.length > 0) {
1557
+ const a = await rel.calendar.get(rest.join(" "));
1558
+ out(a, () => {
1559
+ for (const e of a.entries) {
1560
+ say(line(e));
1561
+ for (const s of ["announced", "syllabus", "entrants", "run", "results"])
1562
+ say(` ${s} ${e.stages[s].state} ${e.stages[s].date ?? "-"} ${e.stages[s].cite ?? "-"}${e.carried.includes(s) ? " (carried)" : ""}`);
1563
+ }
1564
+ say(`${a.entries.length} instance(s) for "${a.asked}"${a.via !== null ? ` by ${a.via}` : ""}${a.why !== undefined ? ` (${a.why})` : ""} in ${a.tag}`);
1565
+ });
1566
+ return a.entries.length === 0 ? 1 : 0;
1567
+ }
1568
+ const v = (f) => p.flags.get(f);
1569
+ const a = await rel.calendar.list({
1570
+ ...v("--brand") !== undefined ? { brand: v("--brand") } : {},
1571
+ ...v("--tier") !== undefined ? { tier: v("--tier") } : {},
1572
+ ...v("--from") !== undefined ? { from: v("--from") } : {},
1573
+ ...v("--to") !== undefined ? { to: v("--to") } : {},
1574
+ ...v("--stage") !== undefined ? { stage: v("--stage") } : {},
1575
+ ...v("--state") !== undefined ? { state: v("--state") } : {},
1576
+ ...v("--terminal") !== undefined ? { terminal: v("--terminal") } : {}
1577
+ });
1578
+ out(a, () => {
1579
+ for (const e of a.entries)
1580
+ say(line(e));
1581
+ say(`${a.entries.length} instance(s) in ${a.tag}`);
1582
+ });
1583
+ return a.entries.length === 0 ? 1 : 0;
1584
+ } finally {
1585
+ rel.close();
1586
+ }
1587
+ }
1536
1588
  case "teacher":
1537
1589
  case "adjudicator": {
1538
1590
  if (rest.length === 0) {
package/dist/index.js CHANGED
@@ -44,6 +44,10 @@ import {
44
44
  ensureIndex2,
45
45
  listIndexes2,
46
46
  readPin2,
47
+ CALENDAR_STAGES2,
48
+ CALENDAR_STAGE_STATES2,
49
+ CALENDAR_TERMINALS2,
50
+ CalendarFinder2,
47
51
  youngest2,
48
52
  dancerResolverOf2,
49
53
  RegistrationResolver2,
@@ -76,7 +80,7 @@ import {
76
80
  readLatestCheck2,
77
81
  latestCheck2,
78
82
  staleWarning2
79
- } from "./chunks/index-dy6yvpgk.js";
83
+ } from "./chunks/index-cwhyrcyx.js";
80
84
  import {
81
85
  cacheRoot2,
82
86
  repoOf2
@@ -93,6 +97,10 @@ import {
93
97
  export {
94
98
  ADVANCEMENT_DANCES2 as ADVANCEMENT_DANCES,
95
99
  ARCHIVE_TAG2 as ARCHIVE_TAG,
100
+ CALENDAR_STAGES2 as CALENDAR_STAGES,
101
+ CALENDAR_STAGE_STATES2 as CALENDAR_STAGE_STATES,
102
+ CALENDAR_TERMINALS2 as CALENDAR_TERMINALS,
103
+ CalendarFinder2 as CalendarFinder,
96
104
  CompResolver2 as CompResolver,
97
105
  DATABASES2 as DATABASES,
98
106
  DEFAULT_KEEP2 as DEFAULT_KEEP,
@@ -0,0 +1,54 @@
1
+ import type { Release } from "./release.js";
2
+ export type CalendarStage = "announced" | "syllabus" | "entrants" | "run" | "results";
3
+ export declare const CALENDAR_STAGES: readonly CalendarStage[];
4
+ export type CalendarStageState = "observed" | "pending" | "not-captured";
5
+ export declare const CALENDAR_STAGE_STATES: readonly CalendarStageState[];
6
+ export type CalendarTerminal = "results" | "withdrawn" | "passed-no-results";
7
+ export declare const CALENDAR_TERMINALS: readonly CalendarTerminal[];
8
+ export interface CalendarStageValue {
9
+ state: CalendarStageState;
10
+ date: string | null;
11
+ cite: string | null;
12
+ }
13
+ export interface CalendarEntry {
14
+ feis_instance_id: string;
15
+ feis_brand_id: string | null;
16
+ tier: string;
17
+ name_printed: string | null;
18
+ date_lo: string | null;
19
+ date_hi: string | null;
20
+ date_source: "run" | "announced" | null;
21
+ occurrence_ids: string[];
22
+ feis_ids: string[];
23
+ stages: Record<CalendarStage, CalendarStageValue>;
24
+ terminal: CalendarTerminal | null;
25
+ carried: string[];
26
+ formerly: string[];
27
+ }
28
+ export interface CalendarQuery {
29
+ brand?: string;
30
+ tier?: string;
31
+ from?: string;
32
+ to?: string;
33
+ stage?: CalendarStage;
34
+ state?: CalendarStageState;
35
+ terminal?: CalendarTerminal | "none";
36
+ }
37
+ export interface CalendarAnswer {
38
+ tag: string;
39
+ entries: CalendarEntry[];
40
+ }
41
+ export interface CalendarGetAnswer {
42
+ tag: string;
43
+ asked: string;
44
+ via: "instance" | "formerly" | "feis_id" | "occurrence_id" | null;
45
+ entries: CalendarEntry[];
46
+ why?: string;
47
+ }
48
+ export declare class CalendarFinder {
49
+ private readonly rel;
50
+ constructor(rel: Release);
51
+ private db;
52
+ get(id: string): Promise<CalendarGetAnswer>;
53
+ list(q?: CalendarQuery): Promise<CalendarAnswer>;
54
+ }
@@ -26,4 +26,5 @@ export { type EntryRow, type StreamCompetition, type StreamQuery, type StreamRow
26
26
  export { type FeisPanelSeat, FeisPanelFinder, PanelFinder, type PanelSeat, type RegisterRow, type TabAnswer, TabFinder, type TabQuery, type TabRow, type TabStreamQuery, } from "./tabs.js";
27
27
  export { EventFinder, type EventYear, type EventYearQuery, type ResultRoster, type RosterMember, type RosterTeam, TeamFinder, type TeamResult, } from "./teams.js";
28
28
  export { type EntrantCandidate, FIELD_STATES, type FieldComp, DEPARTURE_REASONS, type DepartureReason, type FieldState, type HeldOut, type UpcomingDeparture, type UpcomingDepartureAnswer, type UpcomingDepartureQuery, type UpcomingEntrant, type UpcomingEntrantAnswer, type UpcomingEntrantQuery, type UpcomingFeis, type UpcomingFeisAnswer, type UpcomingFeisQuery, type UpcomingFeisState, type UpcomingField, type UpcomingFieldAnswer, type UpcomingFieldQuery, UpcomingFinder, type UpcomingHost, } from "./upcoming.js";
29
+ export { CALENDAR_STAGES, CALENDAR_STAGE_STATES, CALENDAR_TERMINALS, type CalendarAnswer, type CalendarEntry, CalendarFinder, type CalendarGetAnswer, type CalendarQuery, type CalendarStage, type CalendarStageState, type CalendarStageValue, type CalendarTerminal, } from "./calendar.js";
29
30
  export { ARCHIVE_TAG, type ArchiveTag, type ReleaseSpec, compareTagsNewestFirst, cutOf, parseArchiveTag, parseReleaseSpec, pickTag, renderArchiveTag, } from "./tag.js";
@@ -1,5 +1,6 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { type AssetPlan, type LandedFile, type Receipt, type ReleaseManifest } from "./assets.js";
3
+ import { CalendarFinder } from "./calendar.js";
3
4
  import { CompResolver } from "./comps.js";
4
5
  import { type ClientOptions } from "./config.js";
5
6
  import { DancerResolver } from "./dancers.js";
@@ -47,6 +48,7 @@ export declare class Release {
47
48
  readonly teams: TeamFinder;
48
49
  readonly events: EventFinder;
49
50
  readonly upcoming: UpcomingFinder;
51
+ readonly calendar: CalendarFinder;
50
52
  readonly tabs: TabFinder;
51
53
  readonly panels: PanelFinder;
52
54
  readonly feisPanels: FeisPanelFinder;
@@ -57,6 +57,7 @@ export interface UpcomingField {
57
57
  date_hi: string | null;
58
58
  year: number;
59
59
  feis_brand_id: string | null;
60
+ feis_instance_id?: string | null;
60
61
  comp: FieldComp | null;
61
62
  state: FieldState;
62
63
  flags: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/archive-client",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Open a versioned release of an Irish dance results archive: download, verify, decompress and cache it by tag, keep the cache, and resolve feises and schools against it",
5
5
  "keywords": ["irish-dance", "results", "release", "cache", "sqlite"],
6
6
  "license": "MIT",