bowlslink-client 0.1.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 ADDED
@@ -0,0 +1,167 @@
1
+ # bowlslink-client
2
+
3
+ A TypeScript client library for the [BowlsLink](https://results.bowlslink.com.au/) Results API. Fetch pennant teams, match results, ladder standings, and team sheets for any Australian bowls club.
4
+
5
+ Built for club website maintainers who want to display live pennant data without reverse-engineering the API themselves.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install bowlslink-client
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { BowlsLinkClient } from "bowlslink-client";
17
+
18
+ const client = new BowlsLinkClient({
19
+ clubId: "2d83742c-5153-4f22-bcb3-68868f34e0d2", // Your club's BowlsLink UUID
20
+ });
21
+
22
+ // Get all pennant teams, matches, and standings
23
+ const data = await client.getPennantData();
24
+
25
+ for (const team of data.teams) {
26
+ console.log(`${team.name} — ${team.competitionName}`);
27
+ console.log(` Ladder: ${team.ladder?.position ?? "?"} (W${team.ladder?.wins} L${team.ladder?.losses})`);
28
+ console.log(` Matches: ${team.matches.length}`);
29
+ }
30
+
31
+ // Get team sheets (player/rink data) for a specific match
32
+ const detail = await client.getMatchDetail("c0acaf56-ec90-4112-ac48-9d1d8412e7d8");
33
+
34
+ for (const team of detail.teams) {
35
+ console.log(`\n${team.teamName}`);
36
+ for (const rink of team.rinks) {
37
+ console.log(` ${rink.label}: ${rink.players.map((p) => `${p.name} (${p.position})`).join(", ")}`);
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Finding Your Club ID
43
+
44
+ Your club's BowlsLink UUID is in the URL when you view your club on the results site:
45
+
46
+ ```
47
+ https://results.bowlslink.com.au/club/2d83742c-5153-4f22-bcb3-68868f34e0d2
48
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49
+ This is your club ID
50
+ ```
51
+
52
+ You can also find it by searching for your club at [results.bowlslink.com.au/search](https://results.bowlslink.com.au/search).
53
+
54
+ ## API
55
+
56
+ ### `new BowlsLinkClient(config)`
57
+
58
+ | Option | Type | Required | Description |
59
+ |--------|------|----------|-------------|
60
+ | `clubId` | `string` | ✅ | Your club's BowlsLink UUID |
61
+ | `baseUrl` | `string` | | API base URL (default: `https://api.bowlslink.com.au/results-api`) |
62
+ | `fetch` | `typeof fetch` | | Custom fetch implementation for testing or non-browser environments |
63
+
64
+ ### `client.getPennantData(): Promise<PennantData>`
65
+
66
+ Returns all active pennant entries for the club with competition details, ladder standings, and match history including finals.
67
+
68
+ ```ts
69
+ interface PennantData {
70
+ lastUpdated: string; // ISO 8601 timestamp
71
+ teams: Team[];
72
+ }
73
+
74
+ interface Team {
75
+ name: string; // e.g. "Keilor 1"
76
+ competitionId: string;
77
+ competitionName: string | null; // e.g. "2025-26 Metro Pennant Weekend Division 2"
78
+ competitionStatus: string | null;
79
+ bowlslinkUrl: string; // Link to competition on BowlsLink
80
+ ladder: LadderEntry | null;
81
+ matches: Match[];
82
+ }
83
+
84
+ interface Match {
85
+ matchId: string;
86
+ round: number;
87
+ roundLabel: string; // "Round 1", "Semi-Finals", "Grand Final", etc.
88
+ dateUtc: number; // Unix timestamp
89
+ state: string; // "PLAYED" | "SCHEDULED" | etc.
90
+ isHome: boolean;
91
+ isFinals: boolean; // true for finals-series matches
92
+ opponent: string;
93
+ opponentId: string;
94
+ myCompetitorId: string;
95
+ teamScore: number | null;
96
+ opponentScore: number | null;
97
+ outcome: "W" | "L" | "draw" | "np" | null;
98
+ teamPoints: number | null;
99
+ }
100
+ ```
101
+
102
+ ### `client.getMatchDetail(matchId): Promise<MatchDetail>`
103
+
104
+ Returns team sheets with rink and player data for a specific match.
105
+
106
+ ```ts
107
+ interface MatchDetail {
108
+ matchId: string;
109
+ teams: MatchTeam[];
110
+ }
111
+
112
+ interface MatchTeam {
113
+ competitorId: string;
114
+ teamName: string;
115
+ rinks: Rink[];
116
+ }
117
+
118
+ interface Rink {
119
+ label: string; // "Rink 1", "Rink 2", etc.
120
+ players: Player[]; // Sorted: lead, second, third, skip
121
+ }
122
+
123
+ interface Player {
124
+ name: string;
125
+ position: string; // "lead" | "second" | "third" | "skip"
126
+ }
127
+ ```
128
+
129
+ ## Known Quirks
130
+
131
+ Things we've discovered about the BowlsLink API that this library handles for you:
132
+
133
+ - **Finals are a separate endpoint.** Regular season matches come from `/competition/{id}/matches`, but finals come from `/competition/{id}/finals-series-matches`. This library fetches both and merges them.
134
+
135
+ - **Higher-division finals are separate competitions.** Divisions 1–3 have their finals as entirely separate competition entries (e.g. "Division 2 Finals (Divisional)"). The regular season competition moves to `completed` status and drops off the active club entries feed. Lower divisions (4+) keep finals within the same competition using the `isFinalsSeries` flag.
136
+
137
+ - **Ladder API only returns pool 1.** In multi-section competitions, the `/ladder` endpoint only returns standings for Section 1. For teams in other sections, this library automatically computes standings from match results as a fallback.
138
+
139
+ - **The API is public but undocumented.** These endpoints are reverse-engineered from the BowlsLink SPA. They could change without notice.
140
+
141
+ ## Server-Side Usage
142
+
143
+ The BowlsLink API has CORS headers allowing browser access, but for club websites we recommend calling it server-side to avoid exposing your requests to rate limiting and to enable caching:
144
+
145
+ ```ts
146
+ import express from "express";
147
+ import { BowlsLinkClient } from "bowlslink-client";
148
+
149
+ const app = express();
150
+ const client = new BowlsLinkClient({ clubId: "your-club-id" });
151
+
152
+ let cache = null;
153
+ const CACHE_TTL = 10 * 60 * 1000; // 10 minutes
154
+
155
+ app.get("/api/pennant", async (req, res) => {
156
+ if (cache && Date.now() - cache.timestamp < CACHE_TTL) {
157
+ return res.json(cache.data);
158
+ }
159
+ const data = await client.getPennantData();
160
+ cache = { data, timestamp: Date.now() };
161
+ res.json(data);
162
+ });
163
+ ```
164
+
165
+ ## License
166
+
167
+ MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { JsonApiInclude, JsonApiResponse } from "./types.js";
2
+ /**
3
+ * Fetch a path from the BowlsLink Results API and return the parsed JSON:API response.
4
+ *
5
+ * @throws {Error} On non-OK HTTP responses.
6
+ */
7
+ export declare function apiFetch(path: string, baseUrl?: string, fetchFn?: typeof globalThis.fetch): Promise<JsonApiResponse>;
8
+ /**
9
+ * Build a lookup map of `{ [id]: attributes }` for a specific type from a
10
+ * JSON:API `include` array.
11
+ */
12
+ export declare function buildLookup(include: JsonApiInclude[], type: string): Record<string, Record<string, unknown>>;
13
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKlE;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAyB,EAClC,OAAO,GAAE,OAAO,UAAU,CAAC,KAAwB,GAClD,OAAO,CAAC,eAAe,CAAC,CAO1B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,cAAc,EAAE,EACzB,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAQzC"}
package/dist/api.js ADDED
@@ -0,0 +1,29 @@
1
+ const DEFAULT_BASE_URL = "https://api.bowlslink.com.au/results-api";
2
+ const HEADERS = { Accept: "application/json", "Content-Type": "application/json" };
3
+ /**
4
+ * Fetch a path from the BowlsLink Results API and return the parsed JSON:API response.
5
+ *
6
+ * @throws {Error} On non-OK HTTP responses.
7
+ */
8
+ export async function apiFetch(path, baseUrl = DEFAULT_BASE_URL, fetchFn = globalThis.fetch) {
9
+ const url = `${baseUrl}${path}`;
10
+ const res = await fetchFn(url, { headers: HEADERS });
11
+ if (!res.ok) {
12
+ throw new Error(`BowlsLink API error: ${res.status} ${res.statusText} for ${url}`);
13
+ }
14
+ return (await res.json());
15
+ }
16
+ /**
17
+ * Build a lookup map of `{ [id]: attributes }` for a specific type from a
18
+ * JSON:API `include` array.
19
+ */
20
+ export function buildLookup(include, type) {
21
+ const map = {};
22
+ for (const item of include) {
23
+ if (item.type === type) {
24
+ map[item.id] = item.attributes ?? {};
25
+ }
26
+ }
27
+ return map;
28
+ }
29
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;AACpE,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE,kBAAkB,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;AAEnF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,UAAkB,gBAAgB,EAClC,UAAmC,UAAU,CAAC,KAAK;IAEnD,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,IAAI,EAAE,CAAC;IAChC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACrD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,QAAQ,GAAG,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoB,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,OAAyB,EACzB,IAAY;IAEZ,MAAM,GAAG,GAA4C,EAAE,CAAC;IACxD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACvB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiFetch = apiFetch;
4
+ exports.buildLookup = buildLookup;
5
+ const DEFAULT_BASE_URL = "https://api.bowlslink.com.au/results-api";
6
+ const HEADERS = { Accept: "application/json", "Content-Type": "application/json" };
7
+ /**
8
+ * Fetch a path from the BowlsLink Results API and return the parsed JSON:API response.
9
+ *
10
+ * @throws {Error} On non-OK HTTP responses.
11
+ */
12
+ async function apiFetch(path, baseUrl = DEFAULT_BASE_URL, fetchFn = globalThis.fetch) {
13
+ const url = `${baseUrl}${path}`;
14
+ const res = await fetchFn(url, { headers: HEADERS });
15
+ if (!res.ok) {
16
+ throw new Error(`BowlsLink API error: ${res.status} ${res.statusText} for ${url}`);
17
+ }
18
+ return (await res.json());
19
+ }
20
+ /**
21
+ * Build a lookup map of `{ [id]: attributes }` for a specific type from a
22
+ * JSON:API `include` array.
23
+ */
24
+ function buildLookup(include, type) {
25
+ const map = {};
26
+ for (const item of include) {
27
+ if (item.type === type) {
28
+ map[item.id] = item.attributes ?? {};
29
+ }
30
+ }
31
+ return map;
32
+ }
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BowlsLinkClient = void 0;
4
+ const api_js_1 = require("./api.js");
5
+ const DEFAULT_BASE_URL = "https://api.bowlslink.com.au/results-api";
6
+ const POSITION_ORDER = ["lead", "second", "third", "skip"];
7
+ /**
8
+ * BowlsLink client for fetching a single club's pennant data.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { BowlsLinkClient } from "bowlslink-client";
13
+ *
14
+ * const client = new BowlsLinkClient({ clubId: "2d83742c-..." });
15
+ * const data = await client.getPennantData();
16
+ * console.log(data.teams);
17
+ * ```
18
+ */
19
+ class BowlsLinkClient {
20
+ clubId;
21
+ baseUrl;
22
+ fetchFn;
23
+ constructor(config) {
24
+ this.clubId = config.clubId;
25
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
26
+ this.fetchFn = config.fetch ?? globalThis.fetch;
27
+ }
28
+ /**
29
+ * Fetch all active pennant entries for the club, including competition
30
+ * details, ladder standings, regular-season matches, and finals.
31
+ */
32
+ async getPennantData() {
33
+ // 1. Fetch all active entries for the club
34
+ const entriesPayload = await this.fetch(`/club/${this.clubId}/entries?filter%5Bstate%5D=active`);
35
+ const entries = entriesPayload.include
36
+ .filter((item) => item.type === "entry")
37
+ .map((item) => ({
38
+ id: item.id,
39
+ name: String(item.attributes.name ?? ""),
40
+ competitorId: item.includes?.competitor?.id,
41
+ competitionId: item.includes?.competition?.id,
42
+ }));
43
+ // 2. Fetch competition detail, ladder, and matches per unique competition
44
+ const uniqueCompIds = [...new Set(entries.map((e) => e.competitionId).filter(Boolean))];
45
+ const competitionData = Object.fromEntries(await Promise.all(uniqueCompIds.map(async (compId) => {
46
+ const [compPayload, ladderPayload, matchesPayload, finalsPayload] = await Promise.all([
47
+ this.fetch(`/competition/${compId}`),
48
+ this.fetch(`/competition/${compId}/ladder`),
49
+ this.fetch(`/competition/${compId}/matches`),
50
+ this.fetch(`/competition/${compId}/finals-series-matches`).catch(() => null),
51
+ ]);
52
+ // Merge finals-series-matches into regular matches so the rest of
53
+ // the processing (competitor lookup, match shaping) works unchanged.
54
+ if (finalsPayload?.include?.length) {
55
+ matchesPayload.include = [...matchesPayload.include, ...finalsPayload.include];
56
+ }
57
+ return [compId, { compPayload, ladderPayload, matchesPayload }];
58
+ })));
59
+ // 3. Build each team
60
+ const teams = entries
61
+ .filter((e) => e.competitionId && competitionData[e.competitionId])
62
+ .map((entry) => this.buildTeam(entry, competitionData[entry.competitionId]));
63
+ // Drop entries with no matches and sort by name
64
+ const activeTeams = teams
65
+ .filter((t) => t.matches.length > 0)
66
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
67
+ return {
68
+ lastUpdated: new Date().toISOString(),
69
+ teams: activeTeams,
70
+ };
71
+ }
72
+ /**
73
+ * Fetch match detail (team sheets with rink/player data) for a single match.
74
+ */
75
+ async getMatchDetail(matchId) {
76
+ const payload = await this.fetch(`/match/${matchId}`);
77
+ const includes = payload.include ?? [];
78
+ // Index competitors
79
+ const competitors = {};
80
+ for (const i of includes) {
81
+ if (i.type === "competitor") {
82
+ competitors[i.id] = String(i.attributes.name ?? "");
83
+ }
84
+ }
85
+ // Index players
86
+ const playerIndex = {};
87
+ for (const i of includes) {
88
+ if (i.type === "competitorPlayer") {
89
+ playerIndex[i.id] = {
90
+ name: String(i.attributes.fullName ?? ""),
91
+ position: String(i.attributes.assignedPosition ?? "player"),
92
+ competitorId: i.includes?.competitor?.id,
93
+ };
94
+ }
95
+ }
96
+ // Build rinks per competitor from rinkMatchResult objects
97
+ const rinksByComp = {};
98
+ for (const i of includes) {
99
+ if (i.type === "rinkMatchResult") {
100
+ const rinkLabel = String(i.attributes.specialisation ?? "").replace(/^Team\s+/i, "Rink ");
101
+ const processPlayers = (refs) => (refs ?? [])
102
+ .map((ref) => playerIndex[ref.id])
103
+ .filter(Boolean)
104
+ .sort((a, b) => POSITION_ORDER.indexOf(a.position) - POSITION_ORDER.indexOf(b.position));
105
+ const c1Players = processPlayers(i.includes?.competitorOnePlayers);
106
+ const c2Players = processPlayers(i.includes?.competitorTwoPlayers);
107
+ for (const players of [c1Players, c2Players]) {
108
+ if (players.length === 0)
109
+ continue;
110
+ const compId = players[0].competitorId;
111
+ if (!compId)
112
+ continue;
113
+ if (!rinksByComp[compId])
114
+ rinksByComp[compId] = [];
115
+ rinksByComp[compId].push({
116
+ label: rinkLabel,
117
+ players: players.map(({ name, position }) => ({ name, position })),
118
+ });
119
+ }
120
+ }
121
+ }
122
+ // Sort rinks within each team
123
+ for (const rinks of Object.values(rinksByComp)) {
124
+ rinks.sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
125
+ }
126
+ const teams = Object.entries(competitors).map(([compId, name]) => ({
127
+ competitorId: compId,
128
+ teamName: name,
129
+ rinks: rinksByComp[compId] ?? [],
130
+ }));
131
+ return { matchId, teams };
132
+ }
133
+ // ─── Private helpers ─────────────────────────────────────────────────────
134
+ fetch(path) {
135
+ return (0, api_js_1.apiFetch)(path, this.baseUrl, this.fetchFn);
136
+ }
137
+ buildTeam(entry, data) {
138
+ const { compPayload, ladderPayload, matchesPayload } = data;
139
+ // Competition details
140
+ const competition = compPayload.include.find((i) => i.type === "competition");
141
+ const compAttrs = competition?.attributes ?? {};
142
+ // Ladder row (may only be present for pool 1 in multi-section competitions)
143
+ const ladderRow = ladderPayload.include.find((i) => i.type === "ladderRow" &&
144
+ i.attributes?.competitorId === entry.competitorId);
145
+ let ladder = ladderRow ? this.parseLadderRow(ladderRow.attributes.fields) : null;
146
+ // Build lookups from matches payload
147
+ const competitorNames = (0, api_js_1.buildLookup)(matchesPayload.include, "competitor");
148
+ const resultMap = (0, api_js_1.buildLookup)(matchesPayload.include, "multiFormatResult");
149
+ // Compute ladder from match data if the API didn't return one
150
+ if (!ladder && entry.competitorId) {
151
+ ladder = this.computeLadder(entry.competitorId, matchesPayload.include, resultMap);
152
+ }
153
+ // Filter and shape matches for this team
154
+ const matches = this.buildMatches(entry, matchesPayload.include, competitorNames, resultMap);
155
+ return {
156
+ name: entry.name,
157
+ competitionId: entry.competitionId ?? "",
158
+ competitionName: compAttrs.name ?? null,
159
+ competitionStatus: compAttrs.competitionStatus ?? null,
160
+ bowlslinkUrl: `https://results.bowlslink.com.au/competition/${entry.competitionId}`,
161
+ ladder,
162
+ matches,
163
+ };
164
+ }
165
+ /**
166
+ * When the ladder API doesn't return data for a competitor (common in
167
+ * multi-section competitions where /ladder only returns pool 1), compute
168
+ * standings from match results as a fallback.
169
+ */
170
+ computeLadder(competitorId, includes, resultMap) {
171
+ // Determine which pool this competitor plays in
172
+ const poolMatch = includes.find((i) => i.type === "match" &&
173
+ (i.includes?.competitorOne?.id === competitorId ||
174
+ i.includes?.competitorTwo?.id === competitorId));
175
+ const competitorPool = poolMatch?.attributes?.pool ?? null;
176
+ if (competitorPool === null)
177
+ return null;
178
+ // Gather all played matches in this pool (exclude finals)
179
+ const poolMatches = includes.filter((i) => i.type === "match" &&
180
+ i.attributes?.pool === competitorPool &&
181
+ i.attributes?.matchState === "PLAYED" &&
182
+ !i.attributes?.isFinalsSeries);
183
+ // Build per-competitor stats
184
+ const standings = {};
185
+ const ensure = (id) => {
186
+ if (!standings[id]) {
187
+ standings[id] = { played: 0, wins: 0, losses: 0, draws: 0, score: 0, againstScore: 0, points: 0 };
188
+ }
189
+ };
190
+ for (const m of poolMatches) {
191
+ const c1id = m.includes?.competitorOne?.id;
192
+ const c2id = m.includes?.competitorTwo?.id;
193
+ const resultId = m.includes?.result?.id;
194
+ const result = resultId ? resultMap[resultId] : undefined;
195
+ if (!result?.isCompleted)
196
+ continue;
197
+ if (c1id)
198
+ ensure(c1id);
199
+ if (c2id)
200
+ ensure(c2id);
201
+ const s1 = result.competitorOneScore ?? 0;
202
+ const s2 = result.competitorTwoScore ?? 0;
203
+ const p1 = result.competitorOnePoints ?? 0;
204
+ const p2 = result.competitorTwoPoints ?? 0;
205
+ if (c1id) {
206
+ standings[c1id].played += 1;
207
+ standings[c1id].score += s1;
208
+ standings[c1id].againstScore += s2;
209
+ standings[c1id].points += p1;
210
+ }
211
+ if (c2id) {
212
+ standings[c2id].played += 1;
213
+ standings[c2id].score += s2;
214
+ standings[c2id].againstScore += s1;
215
+ standings[c2id].points += p2;
216
+ }
217
+ const winnerId = result.winnerId;
218
+ if (winnerId === c1id) {
219
+ if (c1id)
220
+ standings[c1id].wins += 1;
221
+ if (c2id)
222
+ standings[c2id].losses += 1;
223
+ }
224
+ else if (winnerId === c2id) {
225
+ if (c2id)
226
+ standings[c2id].wins += 1;
227
+ if (c1id)
228
+ standings[c1id].losses += 1;
229
+ }
230
+ else {
231
+ if (c1id)
232
+ standings[c1id].draws += 1;
233
+ if (c2id)
234
+ standings[c2id].draws += 1;
235
+ }
236
+ }
237
+ // Sort by points desc, then score difference
238
+ const sorted = Object.entries(standings).sort(([, a], [, b]) => {
239
+ const ptsDiff = b.points - a.points;
240
+ if (ptsDiff !== 0)
241
+ return ptsDiff;
242
+ return (b.score - b.againstScore) - (a.score - a.againstScore);
243
+ });
244
+ const myIdx = sorted.findIndex(([cid]) => cid === competitorId);
245
+ if (myIdx === -1)
246
+ return null;
247
+ const my = sorted[myIdx][1];
248
+ return {
249
+ position: myIdx + 1,
250
+ played: my.played,
251
+ wins: my.wins,
252
+ losses: my.losses,
253
+ draws: my.draws,
254
+ byes: 0,
255
+ score: my.score,
256
+ againstScore: my.againstScore,
257
+ scoreDifference: my.score - my.againstScore,
258
+ points: my.points,
259
+ };
260
+ }
261
+ buildMatches(entry, includes, competitorNames, resultMap) {
262
+ if (!entry.competitorId)
263
+ return [];
264
+ return includes
265
+ .filter((i) => i.type === "match")
266
+ .filter((m) => {
267
+ const c1 = m.includes?.competitorOne?.id;
268
+ const c2 = m.includes?.competitorTwo?.id;
269
+ return c1 === entry.competitorId || c2 === entry.competitorId;
270
+ })
271
+ .map((m) => {
272
+ const attrs = m.attributes;
273
+ const c1 = m.includes?.competitorOne?.id;
274
+ const isHome = c1 === entry.competitorId;
275
+ const opponentId = isHome
276
+ ? m.includes?.competitorTwo?.id ?? ""
277
+ : c1 ?? "";
278
+ const resultId = m.includes?.result?.id;
279
+ const result = resultId ? resultMap[resultId] : undefined;
280
+ let outcome = null;
281
+ if (result?.isCompleted) {
282
+ if (result.winnerId === null) {
283
+ outcome = result.status === "non-played" ? "np" : "draw";
284
+ }
285
+ else {
286
+ outcome = result.winnerId === entry.competitorId ? "W" : "L";
287
+ }
288
+ }
289
+ return {
290
+ matchId: m.id,
291
+ round: attrs.round,
292
+ roundLabel: String(attrs.roundLabel ?? ""),
293
+ dateUtc: attrs.matchDayUtc,
294
+ state: String(attrs.matchState ?? ""),
295
+ isHome,
296
+ isFinals: Boolean(attrs.isFinalsSeries),
297
+ opponent: String(competitorNames[opponentId]?.name ?? "TBD"),
298
+ opponentId,
299
+ myCompetitorId: entry.competitorId,
300
+ teamScore: result
301
+ ? (isHome ? result.competitorOneScore : result.competitorTwoScore)
302
+ : null,
303
+ opponentScore: result
304
+ ? (isHome ? result.competitorTwoScore : result.competitorOneScore)
305
+ : null,
306
+ outcome,
307
+ teamPoints: result
308
+ ? (isHome ? result.competitorOnePoints : result.competitorTwoPoints)
309
+ : null,
310
+ };
311
+ })
312
+ .sort((a, b) => a.round - b.round);
313
+ }
314
+ parseLadderRow(fields) {
315
+ return {
316
+ position: fields.position,
317
+ played: fields.played,
318
+ wins: fields.wins,
319
+ losses: fields.losses,
320
+ draws: fields.draws,
321
+ byes: fields.byes,
322
+ score: fields.score,
323
+ againstScore: fields.againstScore,
324
+ scoreDifference: fields.scoreDifference,
325
+ points: fields.points,
326
+ };
327
+ }
328
+ }
329
+ exports.BowlsLinkClient = BowlsLinkClient;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BowlsLinkClient = void 0;
4
+ var client_js_1 = require("./client.js");
5
+ Object.defineProperty(exports, "BowlsLinkClient", { enumerable: true, get: function () { return client_js_1.BowlsLinkClient; } });
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // ─── Public types ────────────────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,39 @@
1
+ import type { BowlsLinkConfig, MatchDetail, PennantData } from "./types.js";
2
+ /**
3
+ * BowlsLink client for fetching a single club's pennant data.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { BowlsLinkClient } from "bowlslink-client";
8
+ *
9
+ * const client = new BowlsLinkClient({ clubId: "2d83742c-..." });
10
+ * const data = await client.getPennantData();
11
+ * console.log(data.teams);
12
+ * ```
13
+ */
14
+ export declare class BowlsLinkClient {
15
+ private readonly clubId;
16
+ private readonly baseUrl;
17
+ private readonly fetchFn;
18
+ constructor(config: BowlsLinkConfig);
19
+ /**
20
+ * Fetch all active pennant entries for the club, including competition
21
+ * details, ladder standings, regular-season matches, and finals.
22
+ */
23
+ getPennantData(): Promise<PennantData>;
24
+ /**
25
+ * Fetch match detail (team sheets with rink/player data) for a single match.
26
+ */
27
+ getMatchDetail(matchId: string): Promise<MatchDetail>;
28
+ private fetch;
29
+ private buildTeam;
30
+ /**
31
+ * When the ladder API doesn't return data for a competitor (common in
32
+ * multi-section competitions where /ladder only returns pool 1), compute
33
+ * standings from match results as a fallback.
34
+ */
35
+ private computeLadder;
36
+ private buildMatches;
37
+ private parseLadderRow;
38
+ }
39
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EAIf,WAAW,EAEX,WAAW,EAGZ,MAAM,YAAY,CAAC;AAKpB;;;;;;;;;;;GAWG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,MAAM,EAAE,eAAe;IAMnC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IA2D5C;;OAEG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAuE3D,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,SAAS;IA6CjB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsGrB,OAAO,CAAC,YAAY;IA4DpB,OAAO,CAAC,cAAc;CAcvB"}
package/dist/client.js ADDED
@@ -0,0 +1,326 @@
1
+ import { apiFetch, buildLookup } from "./api.js";
2
+ const DEFAULT_BASE_URL = "https://api.bowlslink.com.au/results-api";
3
+ const POSITION_ORDER = ["lead", "second", "third", "skip"];
4
+ /**
5
+ * BowlsLink client for fetching a single club's pennant data.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { BowlsLinkClient } from "bowlslink-client";
10
+ *
11
+ * const client = new BowlsLinkClient({ clubId: "2d83742c-..." });
12
+ * const data = await client.getPennantData();
13
+ * console.log(data.teams);
14
+ * ```
15
+ */
16
+ export class BowlsLinkClient {
17
+ clubId;
18
+ baseUrl;
19
+ fetchFn;
20
+ constructor(config) {
21
+ this.clubId = config.clubId;
22
+ this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
23
+ this.fetchFn = config.fetch ?? globalThis.fetch;
24
+ }
25
+ /**
26
+ * Fetch all active pennant entries for the club, including competition
27
+ * details, ladder standings, regular-season matches, and finals.
28
+ */
29
+ async getPennantData() {
30
+ // 1. Fetch all active entries for the club
31
+ const entriesPayload = await this.fetch(`/club/${this.clubId}/entries?filter%5Bstate%5D=active`);
32
+ const entries = entriesPayload.include
33
+ .filter((item) => item.type === "entry")
34
+ .map((item) => ({
35
+ id: item.id,
36
+ name: String(item.attributes.name ?? ""),
37
+ competitorId: item.includes?.competitor?.id,
38
+ competitionId: item.includes?.competition?.id,
39
+ }));
40
+ // 2. Fetch competition detail, ladder, and matches per unique competition
41
+ const uniqueCompIds = [...new Set(entries.map((e) => e.competitionId).filter(Boolean))];
42
+ const competitionData = Object.fromEntries(await Promise.all(uniqueCompIds.map(async (compId) => {
43
+ const [compPayload, ladderPayload, matchesPayload, finalsPayload] = await Promise.all([
44
+ this.fetch(`/competition/${compId}`),
45
+ this.fetch(`/competition/${compId}/ladder`),
46
+ this.fetch(`/competition/${compId}/matches`),
47
+ this.fetch(`/competition/${compId}/finals-series-matches`).catch(() => null),
48
+ ]);
49
+ // Merge finals-series-matches into regular matches so the rest of
50
+ // the processing (competitor lookup, match shaping) works unchanged.
51
+ if (finalsPayload?.include?.length) {
52
+ matchesPayload.include = [...matchesPayload.include, ...finalsPayload.include];
53
+ }
54
+ return [compId, { compPayload, ladderPayload, matchesPayload }];
55
+ })));
56
+ // 3. Build each team
57
+ const teams = entries
58
+ .filter((e) => e.competitionId && competitionData[e.competitionId])
59
+ .map((entry) => this.buildTeam(entry, competitionData[entry.competitionId]));
60
+ // Drop entries with no matches and sort by name
61
+ const activeTeams = teams
62
+ .filter((t) => t.matches.length > 0)
63
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
64
+ return {
65
+ lastUpdated: new Date().toISOString(),
66
+ teams: activeTeams,
67
+ };
68
+ }
69
+ /**
70
+ * Fetch match detail (team sheets with rink/player data) for a single match.
71
+ */
72
+ async getMatchDetail(matchId) {
73
+ const payload = await this.fetch(`/match/${matchId}`);
74
+ const includes = payload.include ?? [];
75
+ // Index competitors
76
+ const competitors = {};
77
+ for (const i of includes) {
78
+ if (i.type === "competitor") {
79
+ competitors[i.id] = String(i.attributes.name ?? "");
80
+ }
81
+ }
82
+ // Index players
83
+ const playerIndex = {};
84
+ for (const i of includes) {
85
+ if (i.type === "competitorPlayer") {
86
+ playerIndex[i.id] = {
87
+ name: String(i.attributes.fullName ?? ""),
88
+ position: String(i.attributes.assignedPosition ?? "player"),
89
+ competitorId: i.includes?.competitor?.id,
90
+ };
91
+ }
92
+ }
93
+ // Build rinks per competitor from rinkMatchResult objects
94
+ const rinksByComp = {};
95
+ for (const i of includes) {
96
+ if (i.type === "rinkMatchResult") {
97
+ const rinkLabel = String(i.attributes.specialisation ?? "").replace(/^Team\s+/i, "Rink ");
98
+ const processPlayers = (refs) => (refs ?? [])
99
+ .map((ref) => playerIndex[ref.id])
100
+ .filter(Boolean)
101
+ .sort((a, b) => POSITION_ORDER.indexOf(a.position) - POSITION_ORDER.indexOf(b.position));
102
+ const c1Players = processPlayers(i.includes?.competitorOnePlayers);
103
+ const c2Players = processPlayers(i.includes?.competitorTwoPlayers);
104
+ for (const players of [c1Players, c2Players]) {
105
+ if (players.length === 0)
106
+ continue;
107
+ const compId = players[0].competitorId;
108
+ if (!compId)
109
+ continue;
110
+ if (!rinksByComp[compId])
111
+ rinksByComp[compId] = [];
112
+ rinksByComp[compId].push({
113
+ label: rinkLabel,
114
+ players: players.map(({ name, position }) => ({ name, position })),
115
+ });
116
+ }
117
+ }
118
+ }
119
+ // Sort rinks within each team
120
+ for (const rinks of Object.values(rinksByComp)) {
121
+ rinks.sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
122
+ }
123
+ const teams = Object.entries(competitors).map(([compId, name]) => ({
124
+ competitorId: compId,
125
+ teamName: name,
126
+ rinks: rinksByComp[compId] ?? [],
127
+ }));
128
+ return { matchId, teams };
129
+ }
130
+ // ─── Private helpers ─────────────────────────────────────────────────────
131
+ fetch(path) {
132
+ return apiFetch(path, this.baseUrl, this.fetchFn);
133
+ }
134
+ buildTeam(entry, data) {
135
+ const { compPayload, ladderPayload, matchesPayload } = data;
136
+ // Competition details
137
+ const competition = compPayload.include.find((i) => i.type === "competition");
138
+ const compAttrs = competition?.attributes ?? {};
139
+ // Ladder row (may only be present for pool 1 in multi-section competitions)
140
+ const ladderRow = ladderPayload.include.find((i) => i.type === "ladderRow" &&
141
+ i.attributes?.competitorId === entry.competitorId);
142
+ let ladder = ladderRow ? this.parseLadderRow(ladderRow.attributes.fields) : null;
143
+ // Build lookups from matches payload
144
+ const competitorNames = buildLookup(matchesPayload.include, "competitor");
145
+ const resultMap = buildLookup(matchesPayload.include, "multiFormatResult");
146
+ // Compute ladder from match data if the API didn't return one
147
+ if (!ladder && entry.competitorId) {
148
+ ladder = this.computeLadder(entry.competitorId, matchesPayload.include, resultMap);
149
+ }
150
+ // Filter and shape matches for this team
151
+ const matches = this.buildMatches(entry, matchesPayload.include, competitorNames, resultMap);
152
+ return {
153
+ name: entry.name,
154
+ competitionId: entry.competitionId ?? "",
155
+ competitionName: compAttrs.name ?? null,
156
+ competitionStatus: compAttrs.competitionStatus ?? null,
157
+ bowlslinkUrl: `https://results.bowlslink.com.au/competition/${entry.competitionId}`,
158
+ ladder,
159
+ matches,
160
+ };
161
+ }
162
+ /**
163
+ * When the ladder API doesn't return data for a competitor (common in
164
+ * multi-section competitions where /ladder only returns pool 1), compute
165
+ * standings from match results as a fallback.
166
+ */
167
+ computeLadder(competitorId, includes, resultMap) {
168
+ // Determine which pool this competitor plays in
169
+ const poolMatch = includes.find((i) => i.type === "match" &&
170
+ (i.includes?.competitorOne?.id === competitorId ||
171
+ i.includes?.competitorTwo?.id === competitorId));
172
+ const competitorPool = poolMatch?.attributes?.pool ?? null;
173
+ if (competitorPool === null)
174
+ return null;
175
+ // Gather all played matches in this pool (exclude finals)
176
+ const poolMatches = includes.filter((i) => i.type === "match" &&
177
+ i.attributes?.pool === competitorPool &&
178
+ i.attributes?.matchState === "PLAYED" &&
179
+ !i.attributes?.isFinalsSeries);
180
+ // Build per-competitor stats
181
+ const standings = {};
182
+ const ensure = (id) => {
183
+ if (!standings[id]) {
184
+ standings[id] = { played: 0, wins: 0, losses: 0, draws: 0, score: 0, againstScore: 0, points: 0 };
185
+ }
186
+ };
187
+ for (const m of poolMatches) {
188
+ const c1id = m.includes?.competitorOne?.id;
189
+ const c2id = m.includes?.competitorTwo?.id;
190
+ const resultId = m.includes?.result?.id;
191
+ const result = resultId ? resultMap[resultId] : undefined;
192
+ if (!result?.isCompleted)
193
+ continue;
194
+ if (c1id)
195
+ ensure(c1id);
196
+ if (c2id)
197
+ ensure(c2id);
198
+ const s1 = result.competitorOneScore ?? 0;
199
+ const s2 = result.competitorTwoScore ?? 0;
200
+ const p1 = result.competitorOnePoints ?? 0;
201
+ const p2 = result.competitorTwoPoints ?? 0;
202
+ if (c1id) {
203
+ standings[c1id].played += 1;
204
+ standings[c1id].score += s1;
205
+ standings[c1id].againstScore += s2;
206
+ standings[c1id].points += p1;
207
+ }
208
+ if (c2id) {
209
+ standings[c2id].played += 1;
210
+ standings[c2id].score += s2;
211
+ standings[c2id].againstScore += s1;
212
+ standings[c2id].points += p2;
213
+ }
214
+ const winnerId = result.winnerId;
215
+ if (winnerId === c1id) {
216
+ if (c1id)
217
+ standings[c1id].wins += 1;
218
+ if (c2id)
219
+ standings[c2id].losses += 1;
220
+ }
221
+ else if (winnerId === c2id) {
222
+ if (c2id)
223
+ standings[c2id].wins += 1;
224
+ if (c1id)
225
+ standings[c1id].losses += 1;
226
+ }
227
+ else {
228
+ if (c1id)
229
+ standings[c1id].draws += 1;
230
+ if (c2id)
231
+ standings[c2id].draws += 1;
232
+ }
233
+ }
234
+ // Sort by points desc, then score difference
235
+ const sorted = Object.entries(standings).sort(([, a], [, b]) => {
236
+ const ptsDiff = b.points - a.points;
237
+ if (ptsDiff !== 0)
238
+ return ptsDiff;
239
+ return (b.score - b.againstScore) - (a.score - a.againstScore);
240
+ });
241
+ const myIdx = sorted.findIndex(([cid]) => cid === competitorId);
242
+ if (myIdx === -1)
243
+ return null;
244
+ const my = sorted[myIdx][1];
245
+ return {
246
+ position: myIdx + 1,
247
+ played: my.played,
248
+ wins: my.wins,
249
+ losses: my.losses,
250
+ draws: my.draws,
251
+ byes: 0,
252
+ score: my.score,
253
+ againstScore: my.againstScore,
254
+ scoreDifference: my.score - my.againstScore,
255
+ points: my.points,
256
+ };
257
+ }
258
+ buildMatches(entry, includes, competitorNames, resultMap) {
259
+ if (!entry.competitorId)
260
+ return [];
261
+ return includes
262
+ .filter((i) => i.type === "match")
263
+ .filter((m) => {
264
+ const c1 = m.includes?.competitorOne?.id;
265
+ const c2 = m.includes?.competitorTwo?.id;
266
+ return c1 === entry.competitorId || c2 === entry.competitorId;
267
+ })
268
+ .map((m) => {
269
+ const attrs = m.attributes;
270
+ const c1 = m.includes?.competitorOne?.id;
271
+ const isHome = c1 === entry.competitorId;
272
+ const opponentId = isHome
273
+ ? m.includes?.competitorTwo?.id ?? ""
274
+ : c1 ?? "";
275
+ const resultId = m.includes?.result?.id;
276
+ const result = resultId ? resultMap[resultId] : undefined;
277
+ let outcome = null;
278
+ if (result?.isCompleted) {
279
+ if (result.winnerId === null) {
280
+ outcome = result.status === "non-played" ? "np" : "draw";
281
+ }
282
+ else {
283
+ outcome = result.winnerId === entry.competitorId ? "W" : "L";
284
+ }
285
+ }
286
+ return {
287
+ matchId: m.id,
288
+ round: attrs.round,
289
+ roundLabel: String(attrs.roundLabel ?? ""),
290
+ dateUtc: attrs.matchDayUtc,
291
+ state: String(attrs.matchState ?? ""),
292
+ isHome,
293
+ isFinals: Boolean(attrs.isFinalsSeries),
294
+ opponent: String(competitorNames[opponentId]?.name ?? "TBD"),
295
+ opponentId,
296
+ myCompetitorId: entry.competitorId,
297
+ teamScore: result
298
+ ? (isHome ? result.competitorOneScore : result.competitorTwoScore)
299
+ : null,
300
+ opponentScore: result
301
+ ? (isHome ? result.competitorTwoScore : result.competitorOneScore)
302
+ : null,
303
+ outcome,
304
+ teamPoints: result
305
+ ? (isHome ? result.competitorOnePoints : result.competitorTwoPoints)
306
+ : null,
307
+ };
308
+ })
309
+ .sort((a, b) => a.round - b.round);
310
+ }
311
+ parseLadderRow(fields) {
312
+ return {
313
+ position: fields.position,
314
+ played: fields.played,
315
+ wins: fields.wins,
316
+ losses: fields.losses,
317
+ draws: fields.draws,
318
+ byes: fields.byes,
319
+ score: fields.score,
320
+ againstScore: fields.againstScore,
321
+ scoreDifference: fields.scoreDifference,
322
+ points: fields.points,
323
+ };
324
+ }
325
+ }
326
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { BowlsLinkClient } from "./client.js";
2
+ export type { BowlsLinkConfig, LadderEntry, Match, MatchDetail, MatchTeam, PennantData, Player, Rink, Team, } from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACV,eAAe,EACf,WAAW,EACX,KAAK,EACL,WAAW,EACX,SAAS,EACT,WAAW,EACX,MAAM,EACN,IAAI,EACJ,IAAI,GACL,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { BowlsLinkClient } from "./client.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,101 @@
1
+ /** Configuration for a BowlsLink client instance. */
2
+ export interface BowlsLinkConfig {
3
+ /** The BowlsLink club UUID (found on the club's BowlsLink results page). */
4
+ clubId: string;
5
+ /**
6
+ * Optional base URL for the results API.
7
+ * @default "https://api.bowlslink.com.au/results-api"
8
+ */
9
+ baseUrl?: string;
10
+ /**
11
+ * Optional custom fetch implementation (for testing or environments without
12
+ * a global fetch). Defaults to the global `fetch`.
13
+ */
14
+ fetch?: typeof globalThis.fetch;
15
+ }
16
+ /** A club's pennant team within a competition. */
17
+ export interface Team {
18
+ name: string;
19
+ competitionId: string;
20
+ competitionName: string | null;
21
+ competitionStatus: string | null;
22
+ bowlslinkUrl: string;
23
+ ladder: LadderEntry | null;
24
+ matches: Match[];
25
+ }
26
+ /** Ladder (standings) row for a team. */
27
+ export interface LadderEntry {
28
+ position: number;
29
+ played: number;
30
+ wins: number;
31
+ losses: number;
32
+ draws: number;
33
+ byes: number;
34
+ score: number;
35
+ againstScore: number;
36
+ scoreDifference: number;
37
+ points: number;
38
+ }
39
+ /** A single match for a team. */
40
+ export interface Match {
41
+ matchId: string;
42
+ round: number;
43
+ roundLabel: string;
44
+ dateUtc: number;
45
+ state: string;
46
+ isHome: boolean;
47
+ isFinals: boolean;
48
+ opponent: string;
49
+ opponentId: string;
50
+ myCompetitorId: string;
51
+ teamScore: number | null;
52
+ opponentScore: number | null;
53
+ outcome: "W" | "L" | "draw" | "np" | null;
54
+ teamPoints: number | null;
55
+ }
56
+ /** Full pennant data response for a club. */
57
+ export interface PennantData {
58
+ lastUpdated: string;
59
+ teams: Team[];
60
+ }
61
+ /** A player within a rink. */
62
+ export interface Player {
63
+ name: string;
64
+ position: string;
65
+ }
66
+ /** A rink (sub-team) within a match. */
67
+ export interface Rink {
68
+ label: string;
69
+ players: Player[];
70
+ }
71
+ /** A team's rink/player data for a specific match. */
72
+ export interface MatchTeam {
73
+ competitorId: string;
74
+ teamName: string;
75
+ rinks: Rink[];
76
+ }
77
+ /** Match detail response with team sheets. */
78
+ export interface MatchDetail {
79
+ matchId: string;
80
+ teams: MatchTeam[];
81
+ }
82
+ /** A single item in a JSON:API `include` array. */
83
+ export interface JsonApiInclude {
84
+ type: string;
85
+ id: string;
86
+ attributes: Record<string, unknown>;
87
+ includes?: Record<string, {
88
+ type: string;
89
+ id: string;
90
+ } | {
91
+ type: string;
92
+ id: string;
93
+ }[]>;
94
+ }
95
+ /** Shape of a JSON:API response from BowlsLink. */
96
+ export interface JsonApiResponse {
97
+ data: unknown;
98
+ include: JsonApiInclude[];
99
+ metadata?: unknown;
100
+ }
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ─── Public types ────────────────────────────────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "bowlslink-client",
3
+ "version": "0.1.0",
4
+ "description": "Client library for the BowlsLink Results API — fetch club entries, matches, ladders, and team sheets.",
5
+ "type": "module",
6
+ "main": "dist/cjs/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/cjs/index.d.ts",
17
+ "default": "./dist/cjs/index.js"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "prepare": "npm run build",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "bowlslink",
33
+ "bowls",
34
+ "lawn-bowls",
35
+ "australia",
36
+ "pennant",
37
+ "results"
38
+ ],
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/sje397/bowlslink-api"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.7.0",
46
+ "vitest": "^3.0.0"
47
+ }
48
+ }