@claudinho/core 0.2.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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/index.d.ts +406 -0
- package/dist/index.js +2909 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arturo Garrido
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @claudinho/core ⚽
|
|
2
|
+
|
|
3
|
+
Shared domain model, data-provider adapters, and helpers for **Claudinho** —
|
|
4
|
+
the 2026 men's football tournament in your dev environment. This is the engine
|
|
5
|
+
behind [`@claudinho/cli`](https://www.npmjs.com/package/@claudinho/cli) and
|
|
6
|
+
[`@claudinho/mcp`](https://www.npmjs.com/package/@claudinho/mcp).
|
|
7
|
+
|
|
8
|
+
> ⚠️ **Not affiliated with, endorsed by, or connected to FIFA or Anthropic.**
|
|
9
|
+
> An independent, open-source fan project. Facts + emoji flags only.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i @claudinho/core
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What's inside
|
|
18
|
+
|
|
19
|
+
- **Domain model** — `Match` (incl. `venue`/`city`/`country`), `Team`, `Stage`, `Status`, `PunditPick`, `LedgerRow`
|
|
20
|
+
- **`ProviderAdapter`** — the swappable data-vendor interface; `EspnAdapter` included
|
|
21
|
+
- **Static schedule** — all 104 fixtures (groups, venues, host cities, kickoffs) bundled; query with `allFixtures`, `fixturesByDate` (groups by your timezone), `fixturesByTeam`, `fixturesByGroup`, `nextFixtureForTeam`, `groups`
|
|
22
|
+
- **Live overlay** — `makeAdapter`, `getMatchesForDate`, `getLiveMatches`, `mergeLive` (static base + live state, with graceful degradation)
|
|
23
|
+
- **Standings** — `computeStandings` (points / GD / GF tiebreak)
|
|
24
|
+
- **Helpers** — emoji flags (`nationToFlag`), TZ-aware time (`formatKickoff`, `countdown`, `localDate`), location strings (`matchLocation`), localized commentary flair (`matchFlavor` / `FlavorLevel`), validators (`isValidDate`, `isValidTimeZone`)
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { allFixtures, nextFixtureForTeam, formatKickoff } from '@claudinho/core';
|
|
30
|
+
|
|
31
|
+
const next = nextFixtureForTeam('MEX');
|
|
32
|
+
console.log(next?.home.flag, 'vs', next?.away.flag,
|
|
33
|
+
formatKickoff(next!.kickoff, { tz: 'America/Mexico_City', locale: 'es' }));
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
MIT © 2026 Arturo Garrido · [source & issues](https://github.com/arturogarrido/claudinho)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
_Built while watching the games._ **#VibingLaVidaLoca** ⚽
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claudinho domain model — the canonical, provider-agnostic shapes.
|
|
3
|
+
* Every data vendor maps INTO these types via a ProviderAdapter.
|
|
4
|
+
*/
|
|
5
|
+
/** A national team. `flag` is an emoji (no image assets, no copyright). */
|
|
6
|
+
interface Team {
|
|
7
|
+
/** Short code, typically the FIFA/IOC 3-letter abbreviation (e.g. "MEX"). */
|
|
8
|
+
code: string;
|
|
9
|
+
/** Human-readable name (e.g. "Mexico"). */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Emoji flag (e.g. "🇲🇽"). */
|
|
12
|
+
flag: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Tournament stage. "3P" is the third-place play-off. "FRIENDLY" is used for
|
|
16
|
+
* non-tournament fixtures (e.g. international friendlies) surfaced when the
|
|
17
|
+
* adapter points at a non-World-Cup competition.
|
|
18
|
+
*/
|
|
19
|
+
type Stage = 'GROUP' | 'R32' | 'R16' | 'QF' | 'SF' | '3P' | 'F' | 'FRIENDLY';
|
|
20
|
+
/** Normalized match status across all providers. */
|
|
21
|
+
type Status = 'SCHEDULED' | 'LIVE' | 'HT' | 'FT' | 'POSTPONED' | 'CANCELLED';
|
|
22
|
+
/** Match outcome from the home team's perspective. */
|
|
23
|
+
type Outcome = 'H' | 'D' | 'A';
|
|
24
|
+
/** A discrete in-match event (provider-dependent; often absent on free tiers). */
|
|
25
|
+
interface MatchEvent {
|
|
26
|
+
type: 'GOAL' | 'OWN_GOAL' | 'PEN' | 'YELLOW' | 'RED' | 'SUB';
|
|
27
|
+
minute: number;
|
|
28
|
+
teamCode: string;
|
|
29
|
+
player?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A single fixture/result — the central entity. */
|
|
32
|
+
interface Match {
|
|
33
|
+
/** Stable id (provider id for now; cross-provider keying comes later). */
|
|
34
|
+
id: string;
|
|
35
|
+
stage: Stage;
|
|
36
|
+
/** Group letter "A".."L" for the group stage; undefined for knockouts. */
|
|
37
|
+
group?: string;
|
|
38
|
+
/** Kickoff time, ISO 8601 in UTC (always ends in "Z"). */
|
|
39
|
+
kickoff: string;
|
|
40
|
+
venue: string;
|
|
41
|
+
/** Host city (e.g. "Mexico City"). Present when the provider supplies it. */
|
|
42
|
+
city?: string;
|
|
43
|
+
/** Host country (e.g. "Mexico"). Present when the provider supplies it. */
|
|
44
|
+
country?: string;
|
|
45
|
+
home: Team;
|
|
46
|
+
away: Team;
|
|
47
|
+
/** Present once the match is no longer purely SCHEDULED. */
|
|
48
|
+
score?: {
|
|
49
|
+
home: number;
|
|
50
|
+
away: number;
|
|
51
|
+
};
|
|
52
|
+
/** Live match minute when in progress. */
|
|
53
|
+
minute?: number;
|
|
54
|
+
status: Status;
|
|
55
|
+
/** Goals/cards when the provider supplies them. */
|
|
56
|
+
events?: MatchEvent[];
|
|
57
|
+
/** When this snapshot was produced (ISO 8601). */
|
|
58
|
+
updatedAt: string;
|
|
59
|
+
}
|
|
60
|
+
/** The AI pundit's prediction for a fixture, localized. */
|
|
61
|
+
interface PunditPick {
|
|
62
|
+
matchId: string;
|
|
63
|
+
lang: string;
|
|
64
|
+
scoreline: {
|
|
65
|
+
home: number;
|
|
66
|
+
away: number;
|
|
67
|
+
};
|
|
68
|
+
outcome: Outcome;
|
|
69
|
+
/** One-line rationale, localized. */
|
|
70
|
+
reason: string;
|
|
71
|
+
/** 0..1 self-reported confidence. */
|
|
72
|
+
confidence: number;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
}
|
|
75
|
+
/** One scored row in the pundit accuracy ledger. */
|
|
76
|
+
interface LedgerRow {
|
|
77
|
+
matchId: string;
|
|
78
|
+
predicted: {
|
|
79
|
+
home: number;
|
|
80
|
+
away: number;
|
|
81
|
+
outcome: Outcome;
|
|
82
|
+
};
|
|
83
|
+
actual: {
|
|
84
|
+
home: number;
|
|
85
|
+
away: number;
|
|
86
|
+
outcome: Outcome;
|
|
87
|
+
};
|
|
88
|
+
exactHit: boolean;
|
|
89
|
+
outcomeHit: boolean;
|
|
90
|
+
/** Brier score component for the outcome prediction. */
|
|
91
|
+
brier: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Emoji flags from country identifiers — zero image assets, no copyright,
|
|
96
|
+
* render natively almost everywhere.
|
|
97
|
+
*
|
|
98
|
+
* Region codes are ISO 3166-1 alpha-2 (e.g. "MX"), with three special
|
|
99
|
+
* subdivision codes for the home nations ("GB-SCT", "GB-ENG", "GB-WLS")
|
|
100
|
+
* which use emoji tag sequences rather than regional-indicator pairs.
|
|
101
|
+
*/
|
|
102
|
+
/**
|
|
103
|
+
* Convert a region code to an emoji flag.
|
|
104
|
+
* - "MX" -> 🇲🇽
|
|
105
|
+
* - "GB-SCT" -> 🏴 (Scotland subdivision flag)
|
|
106
|
+
* Returns the neutral flag for anything unrecognized.
|
|
107
|
+
*/
|
|
108
|
+
declare function flagEmoji(region: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a flag emoji from a nation name or code.
|
|
111
|
+
* Tries the name table first (ESPN reliably provides display names),
|
|
112
|
+
* then the 3-letter code table, then a programmatic Intl reverse-lookup.
|
|
113
|
+
* Falls back to the neutral flag.
|
|
114
|
+
*/
|
|
115
|
+
declare function nationToFlag(nameOrCode: string | undefined | null): string;
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a region code (alpha-2 / subdivision) from a nation name or code.
|
|
118
|
+
* Order: curated name table → 3-letter code table → Intl reverse-lookup.
|
|
119
|
+
*/
|
|
120
|
+
declare function nationToRegion(nameOrCode: string | undefined | null): string | undefined;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resolve the effective timezone: explicit arg > CLAUDINHO_TZ env > system.
|
|
124
|
+
*
|
|
125
|
+
* Crucially, an *invalid* candidate is skipped rather than returned, so a bad
|
|
126
|
+
* `--tz`/CLAUDINHO_TZ can never reach an Intl call and throw. The worst case is
|
|
127
|
+
* a silent fall back to the system zone (or undefined → runtime default).
|
|
128
|
+
* Callers that want to *tell* the user it was invalid should check
|
|
129
|
+
* `isValidTimeZone` themselves (the CLI does).
|
|
130
|
+
*/
|
|
131
|
+
declare function resolveTz(explicit?: string): string | undefined;
|
|
132
|
+
interface FormatOpts {
|
|
133
|
+
tz?: string;
|
|
134
|
+
locale?: string;
|
|
135
|
+
}
|
|
136
|
+
/** Format a kickoff like "Thu 19:00" in the target timezone/locale. */
|
|
137
|
+
declare function formatKickoff(iso: string, opts?: FormatOpts): string;
|
|
138
|
+
/** Compact human countdown until kickoff: "3d4h", "2h10m", "45m", or "now". */
|
|
139
|
+
declare function countdown(iso: string, from?: Date): string;
|
|
140
|
+
/** The calendar date (YYYY-MM-DD) of a kickoff in the target timezone. */
|
|
141
|
+
declare function localDate(iso: string, tz?: string): string;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Small input validators shared across clients. Pure, dependency-free, and
|
|
145
|
+
* safe in Node and Workers.
|
|
146
|
+
*/
|
|
147
|
+
/** True if `tz` is an IANA timezone the runtime accepts (e.g. "America/Mexico_City"). */
|
|
148
|
+
declare function isValidTimeZone(tz: string | undefined | null): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* True if `s` is a real calendar date in strict `YYYY-MM-DD` form. Rejects
|
|
151
|
+
* malformed strings AND impossible dates that JS would otherwise roll over
|
|
152
|
+
* (e.g. "2026-02-30").
|
|
153
|
+
*/
|
|
154
|
+
declare function isValidDate(s: string | undefined | null): boolean;
|
|
155
|
+
|
|
156
|
+
/** Outcome (home perspective) from a final/looking scoreline. */
|
|
157
|
+
declare function outcomeFromScore(home: number, away: number): Outcome;
|
|
158
|
+
/** Is the match currently in play (including halftime)? */
|
|
159
|
+
declare function isLive(status: Status): boolean;
|
|
160
|
+
/** Has the match finished in regulation/normal completion? */
|
|
161
|
+
declare function isFinished(status: Status): boolean;
|
|
162
|
+
/** Compact scoreline string, e.g. "1–0" (en dash) or "vs" when unscored. */
|
|
163
|
+
declare function scoreline(match: Match): string;
|
|
164
|
+
/**
|
|
165
|
+
* Human-readable location: venue plus city/country when the provider supplies
|
|
166
|
+
* them, e.g. "Estadio Banorte, Mexico City, Mexico". Keeping this in one place
|
|
167
|
+
* means the CLI and MCP surfaces stay consistent — and gives the model an
|
|
168
|
+
* unambiguous city so it never has to guess one.
|
|
169
|
+
*/
|
|
170
|
+
declare function matchLocation(match: Match): string;
|
|
171
|
+
/** Sort comparator by kickoff time, ascending. */
|
|
172
|
+
declare function byKickoff(a: Match, b: Match): number;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Commentary "flavor" — a small, localized layer of football-broadcast energy
|
|
176
|
+
* that lets surfaces (and the model) narrate scores with some spice.
|
|
177
|
+
*
|
|
178
|
+
* IMPORTANT (legal): these are GENERIC, unattributed exclamations that evoke the
|
|
179
|
+
* *genre* of football commentary (with a Latin-American TV spirit for `es`).
|
|
180
|
+
* They must NEVER quote, name, or impersonate a real commentator or reproduce a
|
|
181
|
+
* person's signature catchphrase. Keep it that way when extending the banks.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
/** Commentary-flair intensity. */
|
|
185
|
+
type FlavorLevel = 'off' | 'subtle' | 'full';
|
|
186
|
+
declare const FLAVOR_LEVELS: readonly ["off", "subtle", "full"];
|
|
187
|
+
/** Spice is on by default — the project ships `full`. */
|
|
188
|
+
declare const DEFAULT_FLAVOR: FlavorLevel;
|
|
189
|
+
declare function isFlavorLevel(s: string): s is FlavorLevel;
|
|
190
|
+
/** Coerce arbitrary input (flag/env) to a level, defaulting to `full`. */
|
|
191
|
+
declare function asFlavorLevel(s: string | undefined | null): FlavorLevel;
|
|
192
|
+
/**
|
|
193
|
+
* A short, localized exclamation for a match's moment — or '' when the level or
|
|
194
|
+
* moment calls for restraint. Deterministic per match id. Never quotes a person.
|
|
195
|
+
*/
|
|
196
|
+
declare function matchFlavor(m: Match, opts?: {
|
|
197
|
+
level?: FlavorLevel;
|
|
198
|
+
locale?: string;
|
|
199
|
+
}): string;
|
|
200
|
+
|
|
201
|
+
/** The full bundled fixture list. */
|
|
202
|
+
declare function allFixtures(): Match[];
|
|
203
|
+
/**
|
|
204
|
+
* Fixtures on a given calendar date ("YYYY-MM-DD"), grouped in the caller's
|
|
205
|
+
* timezone (`tz`; defaults to env/system via `resolveTz`).
|
|
206
|
+
*
|
|
207
|
+
* Grouping uses the *local* date — the same zone the UI shows the weekday in —
|
|
208
|
+
* so a late-UTC kickoff (e.g. `01:00Z`, which is the previous evening in the
|
|
209
|
+
* Americas) lands on the day the user actually experiences it. Filtering on the
|
|
210
|
+
* raw UTC date instead would put it under a header whose weekday contradicts the
|
|
211
|
+
* one rendered for the match (e.g. a Friday match shown under Saturday's date).
|
|
212
|
+
*/
|
|
213
|
+
declare function fixturesByDate(dateISO: string, fixtures?: Match[], tz?: string): Match[];
|
|
214
|
+
/** All fixtures involving a team code (case-insensitive). */
|
|
215
|
+
declare function fixturesByTeam(code: string, fixtures?: Match[]): Match[];
|
|
216
|
+
/** All fixtures in a group letter ("A".."L"). */
|
|
217
|
+
declare function fixturesByGroup(group: string, fixtures?: Match[]): Match[];
|
|
218
|
+
/** The next upcoming fixture for a team at/after `from` (default now). */
|
|
219
|
+
declare function nextFixtureForTeam(code: string, opts?: {
|
|
220
|
+
from?: Date;
|
|
221
|
+
fixtures?: Match[];
|
|
222
|
+
}): Match | undefined;
|
|
223
|
+
/** Sorted list of distinct group letters present in the schedule. */
|
|
224
|
+
declare function groups(fixtures?: Match[]): string[];
|
|
225
|
+
|
|
226
|
+
/** One row of a group table. */
|
|
227
|
+
interface StandingRow {
|
|
228
|
+
team: Team;
|
|
229
|
+
played: number;
|
|
230
|
+
won: number;
|
|
231
|
+
drawn: number;
|
|
232
|
+
lost: number;
|
|
233
|
+
goalsFor: number;
|
|
234
|
+
goalsAgainst: number;
|
|
235
|
+
goalDiff: number;
|
|
236
|
+
points: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Compute a group table from a set of matches. Teams are seeded from every
|
|
240
|
+
* match (so a pre-tournament table still lists all four teams at 0), and only
|
|
241
|
+
* finished matches with a score contribute to the tally.
|
|
242
|
+
*
|
|
243
|
+
* Sort: points, then goal difference, then goals for, then name. (Real FIFA
|
|
244
|
+
* tiebreakers add head-to-head and fair-play; this is the standard simplified
|
|
245
|
+
* ordering used for display.)
|
|
246
|
+
*/
|
|
247
|
+
declare function computeStandings(matches: Match[]): StandingRow[];
|
|
248
|
+
|
|
249
|
+
/** Capabilities a provider advertises so callers can pick a strategy. */
|
|
250
|
+
interface ProviderCapabilities {
|
|
251
|
+
/** True if the provider can push events (websocket/SSE) vs poll-only. */
|
|
252
|
+
push: boolean;
|
|
253
|
+
/** Rough event->feed latency hint, in seconds (for poll-cadence tuning). */
|
|
254
|
+
latencyHintSec: number;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* The single swap-point for data vendors. Every provider (ESPN, API-Football,
|
|
258
|
+
* Goalserve, …) implements this and maps INTO the canonical Match model, so the
|
|
259
|
+
* vendor choice stays a one-module decision.
|
|
260
|
+
*/
|
|
261
|
+
interface ProviderAdapter {
|
|
262
|
+
readonly name: string;
|
|
263
|
+
readonly capabilities: ProviderCapabilities;
|
|
264
|
+
/** All fixtures/results for a single calendar date (provider's timezone semantics). */
|
|
265
|
+
fetchByDate(dateISO: string): Promise<Match[]>;
|
|
266
|
+
/** Currently in-progress matches (poll path). */
|
|
267
|
+
fetchLive(): Promise<Match[]>;
|
|
268
|
+
/** Optional inclusive date-range fetch (used for schedule generation). */
|
|
269
|
+
fetchWindow?(startDate: string, endDate: string): Promise<Match[]>;
|
|
270
|
+
/** Optional push subscription (websocket/SSE providers). Returns an unsubscribe fn. */
|
|
271
|
+
subscribe?(onBatch: (matches: Match[]) => void): () => void;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* ESPN adapter — the free, keyless default/fallback source.
|
|
276
|
+
*
|
|
277
|
+
* Uses ESPN's unofficial public scoreboard endpoint:
|
|
278
|
+
* https://site.api.espn.com/apis/site/v2/sports/soccer/fifa.world/scoreboard
|
|
279
|
+
*
|
|
280
|
+
* Undocumented and unguaranteed — but confirmed to return real 2026 fixtures
|
|
281
|
+
* (teams, ISO-UTC kickoffs, venues, status). The SCHEDULED/FT mapping is
|
|
282
|
+
* verified against live data; the in-play minute/score path is best-effort and
|
|
283
|
+
* should be re-verified during an actual live match.
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
/** Default competition slug (the 2026 World Cup). */
|
|
287
|
+
declare const DEFAULT_COMPETITION = "fifa.world";
|
|
288
|
+
/** Build an ESPN soccer base URL for a competition slug (e.g. "fifa.friendly"). */
|
|
289
|
+
declare function competitionBase(slug: string): string;
|
|
290
|
+
interface EspnStatusType {
|
|
291
|
+
name?: string;
|
|
292
|
+
state?: string;
|
|
293
|
+
completed?: boolean;
|
|
294
|
+
shortDetail?: string;
|
|
295
|
+
}
|
|
296
|
+
interface EspnStatus {
|
|
297
|
+
type?: EspnStatusType;
|
|
298
|
+
clock?: number;
|
|
299
|
+
displayClock?: string;
|
|
300
|
+
period?: number;
|
|
301
|
+
}
|
|
302
|
+
interface EspnTeam {
|
|
303
|
+
abbreviation?: string;
|
|
304
|
+
displayName?: string;
|
|
305
|
+
shortDisplayName?: string;
|
|
306
|
+
name?: string;
|
|
307
|
+
location?: string;
|
|
308
|
+
}
|
|
309
|
+
interface EspnCompetitor {
|
|
310
|
+
homeAway?: 'home' | 'away';
|
|
311
|
+
score?: string;
|
|
312
|
+
winner?: boolean;
|
|
313
|
+
team?: EspnTeam;
|
|
314
|
+
}
|
|
315
|
+
interface EspnCompetition {
|
|
316
|
+
id?: string;
|
|
317
|
+
date?: string;
|
|
318
|
+
competitors?: EspnCompetitor[];
|
|
319
|
+
venue?: {
|
|
320
|
+
fullName?: string;
|
|
321
|
+
address?: {
|
|
322
|
+
city?: string;
|
|
323
|
+
country?: string;
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
status?: EspnStatus;
|
|
327
|
+
}
|
|
328
|
+
interface EspnSeason {
|
|
329
|
+
year?: number;
|
|
330
|
+
slug?: string;
|
|
331
|
+
}
|
|
332
|
+
interface EspnEvent {
|
|
333
|
+
id: string;
|
|
334
|
+
date: string;
|
|
335
|
+
name?: string;
|
|
336
|
+
shortName?: string;
|
|
337
|
+
season?: EspnSeason;
|
|
338
|
+
status?: EspnStatus;
|
|
339
|
+
competitions?: EspnCompetition[];
|
|
340
|
+
}
|
|
341
|
+
/** Optional context to enrich mapping (authoritative team->group letter). */
|
|
342
|
+
interface MapContext {
|
|
343
|
+
/** Map of UPPERCASE team code -> group letter ("A".."L"), from standings. */
|
|
344
|
+
groupByTeam?: Record<string, string>;
|
|
345
|
+
}
|
|
346
|
+
/** Map a single ESPN event into the canonical Match model. Exported for tests. */
|
|
347
|
+
declare function mapEspnEvent(ev: EspnEvent, ctx?: MapContext): Match;
|
|
348
|
+
interface EspnAdapterOptions {
|
|
349
|
+
baseUrl?: string;
|
|
350
|
+
fetchImpl?: typeof fetch;
|
|
351
|
+
timeoutMs?: number;
|
|
352
|
+
/**
|
|
353
|
+
* Enrich group-stage matches with their group letter via the standings
|
|
354
|
+
* endpoint (one extra request). Default true. Set false on the hot live-poll
|
|
355
|
+
* path, where group letters aren't needed and the extra call is wasteful.
|
|
356
|
+
*/
|
|
357
|
+
enrichGroups?: boolean;
|
|
358
|
+
}
|
|
359
|
+
declare class EspnAdapter implements ProviderAdapter {
|
|
360
|
+
private readonly opts;
|
|
361
|
+
readonly name = "espn";
|
|
362
|
+
readonly capabilities: ProviderCapabilities;
|
|
363
|
+
/** Cached team-code -> group-letter map (built lazily from standings). */
|
|
364
|
+
private groupMap?;
|
|
365
|
+
constructor(opts?: EspnAdapterOptions);
|
|
366
|
+
fetchByDate(dateISO: string): Promise<Match[]>;
|
|
367
|
+
fetchWindow(startDate: string, endDate: string): Promise<Match[]>;
|
|
368
|
+
fetchLive(): Promise<Match[]>;
|
|
369
|
+
/**
|
|
370
|
+
* Build (and cache) a team-code -> group-letter map from the standings
|
|
371
|
+
* endpoint. Best-effort: returns {} if standings are unavailable.
|
|
372
|
+
*/
|
|
373
|
+
fetchGroupMap(force?: boolean): Promise<Record<string, string>>;
|
|
374
|
+
private fetchScoreboard;
|
|
375
|
+
private get;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The ESPN competition slug to fetch live state from. Defaults to the 2026
|
|
380
|
+
* World Cup (`fifa.world`); override with CLAUDINHO_COMPETITION (e.g.
|
|
381
|
+
* `fifa.friendly` to follow international friendlies during pre-tournament
|
|
382
|
+
* testing). Only affects the *live* fetch — the bundled static schedule is
|
|
383
|
+
* always the World Cup.
|
|
384
|
+
*/
|
|
385
|
+
declare function resolveCompetition(explicit?: string): string;
|
|
386
|
+
/** Construct a provider adapter for a `--source` name (default: espn). */
|
|
387
|
+
declare function makeAdapter(source?: string): ProviderAdapter;
|
|
388
|
+
/**
|
|
389
|
+
* Merge live matches over a base set by id. Live entries replace base entries
|
|
390
|
+
* with the same id; unknown ids are appended.
|
|
391
|
+
*/
|
|
392
|
+
declare function mergeLive(base: Match[], live: Match[]): Match[];
|
|
393
|
+
interface LiveResult {
|
|
394
|
+
matches: Match[];
|
|
395
|
+
/** True when the provider call failed and we fell back to static data. */
|
|
396
|
+
degraded: boolean;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Matches for a date, preferring live provider data, falling back to the static
|
|
400
|
+
* schedule on any provider/network error (graceful degradation).
|
|
401
|
+
*/
|
|
402
|
+
declare function getMatchesForDate(adapter: ProviderAdapter, dateISO: string): Promise<LiveResult>;
|
|
403
|
+
/** Currently-live matches; empty + degraded on error. */
|
|
404
|
+
declare function getLiveMatches(adapter: ProviderAdapter): Promise<LiveResult>;
|
|
405
|
+
|
|
406
|
+
export { DEFAULT_COMPETITION, DEFAULT_FLAVOR, EspnAdapter, type EspnAdapterOptions, FLAVOR_LEVELS, type FlavorLevel, type FormatOpts, type LedgerRow, type LiveResult, type MapContext, type Match, type MatchEvent, type Outcome, type ProviderAdapter, type ProviderCapabilities, type PunditPick, type Stage, type StandingRow, type Status, type Team, allFixtures, asFlavorLevel, byKickoff, competitionBase, computeStandings, countdown, fixturesByDate, fixturesByGroup, fixturesByTeam, flagEmoji, formatKickoff, getLiveMatches, getMatchesForDate, groups, isFinished, isFlavorLevel, isLive, isValidDate, isValidTimeZone, localDate, makeAdapter, mapEspnEvent, matchFlavor, matchLocation, mergeLive, nationToFlag, nationToRegion, nextFixtureForTeam, outcomeFromScore, resolveCompetition, resolveTz, scoreline };
|