fansunited-data-layer 0.18.0 → 0.18.3
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 +5 -0
- package/api/fansunited/sports/competition/hydrated.d.ts +29 -8
- package/api/fansunited/sports/competition/hydrated.d.ts.map +1 -1
- package/api/fansunited/sports/competition/hydrated.js +10 -1
- package/api/fansunited/sports/competition/hydrated.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,11 @@ const board = await getFansUnitedSportsCompetitionHydrated("fb:c:1", {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
for (const stage of board.season.stages) {
|
|
46
|
+
// Stage-name labels are hydrated alongside competitors —
|
|
47
|
+
// entities.get(stageNameId) returns a localized FUSportsStageName.
|
|
48
|
+
const label = board.entities.get(stage.stageNameId);
|
|
49
|
+
console.log(`\n=== ${label?.name ?? stage.stageNameId} ===`);
|
|
50
|
+
|
|
46
51
|
for (const group of stage.groups ?? []) {
|
|
47
52
|
for (const entry of group.standings) {
|
|
48
53
|
const team = board.entities.get(entry.competitorId);
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* Hydrated wrapper for the Fans United Sports API competition endpoint.
|
|
3
3
|
*
|
|
4
4
|
* Composes the (cached) raw competition response with a (cached) batch
|
|
5
|
-
* resolution of every entity
|
|
6
|
-
*
|
|
5
|
+
* resolution of every referenced entity via the Search API, and returns one
|
|
6
|
+
* object the consumer can render directly. Hydration covers:
|
|
7
|
+
* - everything listed in `meta.entityIds` (competitors, venues, …)
|
|
8
|
+
* - every `stage.stageNameId` referenced by `season.stages[]`
|
|
9
|
+
* ({@link FUSportsStageName}) — these are pulled from the response
|
|
10
|
+
* structure because the upstream `meta.entity_ids` field has been
|
|
11
|
+
* observed not to include them.
|
|
7
12
|
*
|
|
8
13
|
* Cache topology:
|
|
9
14
|
* - sports/competition response → cached under "sportsCompetition"
|
|
@@ -12,12 +17,23 @@
|
|
|
12
17
|
* by id only; the raw multi-locale payload serves every locale via
|
|
13
18
|
* transformEntity(raw, lang).
|
|
14
19
|
*
|
|
15
|
-
* @example
|
|
20
|
+
* @example Standings + localized stage labels
|
|
21
|
+
* ```ts
|
|
16
22
|
* const data = await getFansUnitedSportsCompetitionHydrated("fb:c:1", { locale: "BG" });
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
23
|
+
*
|
|
24
|
+
* for (const stage of data.season.stages) {
|
|
25
|
+
* // Stage name lookup — same shape as any other entity in the map.
|
|
26
|
+
* const label = data.entities.get(stage.stageNameId);
|
|
27
|
+
* console.log(`\n=== ${label?.name ?? stage.stageNameId} ===`);
|
|
28
|
+
*
|
|
29
|
+
* for (const group of stage.groups ?? []) {
|
|
30
|
+
* for (const entry of group.standings) {
|
|
31
|
+
* const team = data.entities.get(entry.competitorId);
|
|
32
|
+
* console.log(`${entry.rank} ${team?.name ?? entry.competitorId}`);
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
20
35
|
* }
|
|
36
|
+
* ```
|
|
21
37
|
*/
|
|
22
38
|
import type { FUSportsCompetitionDetails } from "../../../../types/canonical/sports-competition.types";
|
|
23
39
|
import type { DataLayerConfig } from "../../../../config";
|
|
@@ -34,8 +50,13 @@ export interface GetSportsCompetitionHydratedOptions {
|
|
|
34
50
|
/**
|
|
35
51
|
* Sports API competition response plus a lookup map for every referenced entity.
|
|
36
52
|
*
|
|
37
|
-
* The map is keyed by entity ID
|
|
38
|
-
*
|
|
53
|
+
* The map is keyed by entity ID and holds the localized {@link SearchEntityResult}
|
|
54
|
+
* union — competitors (teams/athletes), venues, countries, coaches, and
|
|
55
|
+
* stage-name labels (`FUSportsStageName & { entityType: "stageName" }`) for
|
|
56
|
+
* every `stage.stageNameId` referenced by `season.stages[]`.
|
|
57
|
+
*
|
|
58
|
+
* Missing IDs (404s from the Search API) are silently omitted — render code
|
|
59
|
+
* should fall back gracefully on a missing entry:
|
|
39
60
|
* `entities.get(id) ?? { name: id }`.
|
|
40
61
|
*/
|
|
41
62
|
export interface FUSportsCompetitionHydrated extends FUSportsCompetitionDetails {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrated.d.ts","sourceRoot":"","sources":["../../../../../src/lib/api/fansunited/sports/competition/hydrated.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hydrated.d.ts","sourceRoot":"","sources":["../../../../../src/lib/api/fansunited/sports/competition/hydrated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sDAAsD,CAAC;AACvG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAkB7D,MAAM,WAAW,mCAAmC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,2BAA4B,SAAQ,0BAA0B;IAC3E,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC7C;AAED,wBAAsB,sCAAsC,CACxD,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,mCAAmC,EAC7C,MAAM,CAAC,EAAE,eAAe,GACzB,OAAO,CAAC,2BAA2B,CAAC,CA0BtC"}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { getFansUnitedSportsCompetition } from "./index.js";
|
|
2
2
|
import { getFansUnitedEntitiesByIds } from "../../search/index.js";
|
|
3
|
+
function collectStageNameIds(details) {
|
|
4
|
+
const ids = [];
|
|
5
|
+
for (const stage of details.season.stages) {
|
|
6
|
+
if (stage.stageNameId) ids.push(stage.stageNameId);
|
|
7
|
+
}
|
|
8
|
+
return ids;
|
|
9
|
+
}
|
|
3
10
|
async function getFansUnitedSportsCompetitionHydrated(competitionId, options, config) {
|
|
4
11
|
const raw = await getFansUnitedSportsCompetition(
|
|
5
12
|
competitionId,
|
|
6
13
|
{ seasonId: options?.seasonId, next: options?.next },
|
|
7
14
|
config
|
|
8
15
|
);
|
|
9
|
-
const entityIds =
|
|
16
|
+
const entityIds = Array.from(
|
|
17
|
+
/* @__PURE__ */ new Set([...raw.meta.entityIds, ...collectStageNameIds(raw)])
|
|
18
|
+
);
|
|
10
19
|
const entities = /* @__PURE__ */ new Map();
|
|
11
20
|
if (entityIds.length > 0) {
|
|
12
21
|
const resolved = await getFansUnitedEntitiesByIds(entityIds, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrated.js","sources":["../../../../../src/lib/api/fansunited/sports/competition/hydrated.ts"],"sourcesContent":["/**\n * Hydrated wrapper for the Fans United Sports API competition endpoint.\n *\n * Composes the (cached) raw competition response with a (cached) batch\n * resolution of every entity
|
|
1
|
+
{"version":3,"file":"hydrated.js","sources":["../../../../../src/lib/api/fansunited/sports/competition/hydrated.ts"],"sourcesContent":["/**\n * Hydrated wrapper for the Fans United Sports API competition endpoint.\n *\n * Composes the (cached) raw competition response with a (cached) batch\n * resolution of every referenced entity via the Search API, and returns one\n * object the consumer can render directly. Hydration covers:\n * - everything listed in `meta.entityIds` (competitors, venues, …)\n * - every `stage.stageNameId` referenced by `season.stages[]`\n * ({@link FUSportsStageName}) — these are pulled from the response\n * structure because the upstream `meta.entity_ids` field has been\n * observed not to include them.\n *\n * Cache topology:\n * - sports/competition response → cached under \"sportsCompetition\"\n * (5min stale / 1h max) by id+season.\n * - search/entity responses → cached under \"search\" (24h stale / 7d max)\n * by id only; the raw multi-locale payload serves every locale via\n * transformEntity(raw, lang).\n *\n * @example Standings + localized stage labels\n * ```ts\n * const data = await getFansUnitedSportsCompetitionHydrated(\"fb:c:1\", { locale: \"BG\" });\n *\n * for (const stage of data.season.stages) {\n * // Stage name lookup — same shape as any other entity in the map.\n * const label = data.entities.get(stage.stageNameId);\n * console.log(`\\n=== ${label?.name ?? stage.stageNameId} ===`);\n *\n * for (const group of stage.groups ?? []) {\n * for (const entry of group.standings) {\n * const team = data.entities.get(entry.competitorId);\n * console.log(`${entry.rank} ${team?.name ?? entry.competitorId}`);\n * }\n * }\n * }\n * ```\n */\n\nimport type { FUSportsCompetitionDetails } from \"../../../../types/canonical/sports-competition.types\";\nimport type { DataLayerConfig } from \"../../../../config\";\nimport type { NextCacheOptions } from \"../../http\";\nimport type { SearchEntityResult } from \"../../search/types\";\nimport { getFansUnitedSportsCompetition } from \"./index\";\nimport { getFansUnitedEntitiesByIds } from \"../../search\";\n\n/**\n * Stage-name IDs are referenced via {@link FUSportsCompetitionStage.stageNameId}\n * but the upstream `meta.entity_ids` field has been observed not to include\n * them. Collect explicitly so they're guaranteed to hydrate into the entities\n * map alongside teams, players, etc.\n */\nfunction collectStageNameIds(details: FUSportsCompetitionDetails): string[] {\n const ids: string[] = [];\n for (const stage of details.season.stages) {\n if (stage.stageNameId) ids.push(stage.stageNameId);\n }\n return ids;\n}\n\nexport interface GetSportsCompetitionHydratedOptions {\n /** Season identifier (e.g. \"fb:c:1:2025/26\"). Defaults to the active season. */\n seasonId?: string;\n /** Locale code (e.g. \"EN\", \"BG\") used to resolve entity translations. Falls back to EN. */\n locale?: string;\n /** Next.js cache options for ISR/on-demand revalidation. */\n next?: NextCacheOptions;\n}\n\n/**\n * Sports API competition response plus a lookup map for every referenced entity.\n *\n * The map is keyed by entity ID and holds the localized {@link SearchEntityResult}\n * union — competitors (teams/athletes), venues, countries, coaches, and\n * stage-name labels (`FUSportsStageName & { entityType: \"stageName\" }`) for\n * every `stage.stageNameId` referenced by `season.stages[]`.\n *\n * Missing IDs (404s from the Search API) are silently omitted — render code\n * should fall back gracefully on a missing entry:\n * `entities.get(id) ?? { name: id }`.\n */\nexport interface FUSportsCompetitionHydrated extends FUSportsCompetitionDetails {\n entities: Map<string, SearchEntityResult>;\n}\n\nexport async function getFansUnitedSportsCompetitionHydrated(\n competitionId: string,\n options?: GetSportsCompetitionHydratedOptions,\n config?: DataLayerConfig\n): Promise<FUSportsCompetitionHydrated> {\n const raw = await getFansUnitedSportsCompetition(\n competitionId,\n { seasonId: options?.seasonId, next: options?.next },\n config\n );\n\n // Union meta.entity_ids with stage_name_ids walked from season.stages —\n // dedupe so duplicates only cost one Search API row.\n const entityIds = Array.from(\n new Set<string>([...raw.meta.entityIds, ...collectStageNameIds(raw)])\n );\n const entities = new Map<string, SearchEntityResult>();\n\n if (entityIds.length > 0) {\n const resolved = await getFansUnitedEntitiesByIds(entityIds, {\n lang: options?.locale,\n next: options?.next,\n config,\n });\n for (const e of resolved) {\n entities.set(e.id, e);\n }\n }\n\n return { ...raw, entities };\n}\n"],"names":[],"mappings":";;AAmDA,SAAS,oBAAoB,SAA+C;AACxE,QAAM,MAAgB,CAAA;AACtB,aAAW,SAAS,QAAQ,OAAO,QAAQ;AACvC,QAAI,MAAM,YAAa,KAAI,KAAK,MAAM,WAAW;AAAA,EACrD;AACA,SAAO;AACX;AA2BA,eAAsB,uCAClB,eACA,SACA,QACoC;AACpC,QAAM,MAAM,MAAM;AAAA,IACd;AAAA,IACA,EAAE,UAAU,SAAS,UAAU,MAAM,SAAS,KAAA;AAAA,IAC9C;AAAA,EAAA;AAKJ,QAAM,YAAY,MAAM;AAAA,IACpB,oBAAI,IAAY,CAAC,GAAG,IAAI,KAAK,WAAW,GAAG,oBAAoB,GAAG,CAAC,CAAC;AAAA,EAAA;AAExE,QAAM,+BAAe,IAAA;AAErB,MAAI,UAAU,SAAS,GAAG;AACtB,UAAM,WAAW,MAAM,2BAA2B,WAAW;AAAA,MACzD,MAAM,SAAS;AAAA,MACf,MAAM,SAAS;AAAA,MACf;AAAA,IAAA,CACH;AACD,eAAW,KAAK,UAAU;AACtB,eAAS,IAAI,EAAE,IAAI,CAAC;AAAA,IACxB;AAAA,EACJ;AAEA,SAAO,EAAE,GAAG,KAAK,SAAA;AACrB;"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "A TypeScript library for fetching and transforming sports data from multiple API providers. Returns clean, canonical types that are provider-agnostic.",
|
|
5
5
|
"homepage": "https://fansunited.com/",
|
|
6
6
|
"private": false,
|
|
7
|
-
"version": "0.18.
|
|
7
|
+
"version": "0.18.3",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": false,
|
|
10
10
|
"module": "./fansunited-data-layer.js",
|