@scorio/client-sdk 1.0.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.
@@ -0,0 +1,564 @@
1
+ interface ListResponse<T> {
2
+ readonly data: readonly T[];
3
+ readonly meta: {
4
+ readonly count: number;
5
+ };
6
+ }
7
+ interface SingleResponse<T> {
8
+ readonly data: T;
9
+ }
10
+ interface Sport {
11
+ readonly id: string;
12
+ readonly name: string;
13
+ readonly alias: string;
14
+ readonly category: 'sport' | 'esport' | 'virtual' | 'racing' | 'fighting';
15
+ }
16
+ interface SportCount extends Sport {
17
+ readonly live_count: number;
18
+ readonly prematch_count: number;
19
+ }
20
+ interface Region {
21
+ readonly id: string;
22
+ readonly name: string;
23
+ readonly alias: string;
24
+ readonly sport_id: string;
25
+ }
26
+ interface Competition {
27
+ readonly id: string;
28
+ readonly name: string;
29
+ readonly region_id: string;
30
+ readonly sport_id: string;
31
+ }
32
+ interface GameInfo {
33
+ readonly game_state: string;
34
+ readonly score: readonly [number, number];
35
+ readonly game_time?: number;
36
+ readonly set_count?: number;
37
+ readonly add_info?: string;
38
+ readonly stoppage_time?: {
39
+ readonly first_half?: string;
40
+ readonly second_half?: string;
41
+ };
42
+ }
43
+ interface GameStats {
44
+ readonly [key: string]: readonly number[] | readonly (readonly number[])[];
45
+ }
46
+ interface Outcome {
47
+ readonly id: string;
48
+ readonly name: string;
49
+ readonly price: number;
50
+ readonly is_suspended: boolean;
51
+ }
52
+ interface Market {
53
+ readonly id: string;
54
+ readonly name: string;
55
+ readonly type: string;
56
+ readonly line: number | null;
57
+ readonly outcomes: readonly Outcome[];
58
+ }
59
+ interface Game {
60
+ readonly id: string;
61
+ readonly home_team: string;
62
+ readonly away_team: string;
63
+ readonly status: 'prematch' | 'live' | 'ended';
64
+ readonly start_time: number;
65
+ readonly is_live: boolean;
66
+ readonly markets_count: number;
67
+ readonly competition_id: string;
68
+ readonly sport_id: string;
69
+ readonly score_text: string;
70
+ readonly info: GameInfo | null;
71
+ readonly stats: GameStats | null;
72
+ readonly updated_at: string;
73
+ readonly markets: readonly Market[];
74
+ }
75
+ interface LiveSnapshot {
76
+ readonly sports: readonly SportCount[];
77
+ readonly games: readonly Game[];
78
+ }
79
+ interface OddsSummary {
80
+ readonly event_id: string;
81
+ readonly event_name: string;
82
+ readonly current_price: number;
83
+ readonly min_price: number;
84
+ readonly max_price: number;
85
+ readonly avg_price: number;
86
+ readonly change_count: number;
87
+ }
88
+ interface OddsMover {
89
+ readonly event_id: string;
90
+ readonly game_id: string;
91
+ readonly current_price: number;
92
+ readonly opening_price: number;
93
+ readonly price_change: number;
94
+ readonly change_percent: number;
95
+ readonly game_name: string;
96
+ readonly market_name: string;
97
+ readonly event_name: string;
98
+ }
99
+ interface PricePoint {
100
+ readonly price: number;
101
+ readonly previous_price: number;
102
+ readonly recorded_at: string;
103
+ }
104
+ type Plan = 'free' | 'starter' | 'pro' | 'business';
105
+ interface DebugConfig {
106
+ readonly host: string;
107
+ readonly secret: string;
108
+ readonly plan?: Plan;
109
+ }
110
+ interface ScorioSDKConfig {
111
+ readonly apiKey: string;
112
+ readonly host?: string;
113
+ readonly logger?: boolean;
114
+ readonly timeout?: number;
115
+ readonly debug?: DebugConfig;
116
+ }
117
+ interface PaginationOptions {
118
+ readonly limit?: number;
119
+ readonly offset?: number;
120
+ }
121
+ interface StartingSoonOptions {
122
+ readonly minutes?: number;
123
+ }
124
+ interface SearchOptions {
125
+ readonly limit?: number;
126
+ }
127
+ interface OddsMoversOptions {
128
+ readonly minutes?: number;
129
+ readonly limit?: number;
130
+ }
131
+ interface PriceHistoryOptions {
132
+ readonly from?: number;
133
+ readonly to?: number;
134
+ readonly limit?: number;
135
+ }
136
+ interface ScheduleOptions {
137
+ readonly date?: string;
138
+ readonly dateFrom?: string;
139
+ readonly dateTo?: string;
140
+ readonly sportId?: string;
141
+ readonly competitionId?: string;
142
+ }
143
+ interface HealthResponse {
144
+ readonly status: 'ok';
145
+ }
146
+ interface PingResponse {
147
+ readonly pong: true;
148
+ }
149
+
150
+ interface HttpRequest {
151
+ readonly method: 'GET';
152
+ readonly url: string;
153
+ readonly headers: Record<string, string>;
154
+ readonly timeout: number;
155
+ }
156
+ interface HttpResponse {
157
+ readonly status: number;
158
+ readonly headers: {
159
+ get(name: string): string | null;
160
+ };
161
+ readonly json: () => Promise<unknown>;
162
+ }
163
+ interface HttpClientPort {
164
+ request(req: HttpRequest): Promise<HttpResponse>;
165
+ }
166
+
167
+ declare class ScorioSDK {
168
+ private readonly apiKey;
169
+ private readonly host;
170
+ private readonly timeout;
171
+ private readonly logger;
172
+ private readonly config;
173
+ private readonly http;
174
+ constructor(config: ScorioSDKConfig, httpClient?: HttpClientPort);
175
+ private get;
176
+ /**
177
+ * List all available sports.
178
+ *
179
+ * Returns every available sport sorted by display order.
180
+ * Use this to build your top-level navigation or sport selector.
181
+ *
182
+ * @returns List of Sport objects with id, name, alias, and category.
183
+ *
184
+ * **Plans:** free, starter, pro, business | **Group:** catalog
185
+ *
186
+ * **Polling:** Cache locally for 60 seconds. The sport catalog changes infrequently.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const { data, meta } = await client.getSports();
191
+ * // data: [{ id, name, alias, category }, ...]
192
+ * // meta: { count: number }
193
+ * ```
194
+ */
195
+ getSports(): Promise<ListResponse<Sport>>;
196
+ /**
197
+ * Sports with live and prematch game counts.
198
+ *
199
+ * Returns all sports along with the number of currently live and upcoming prematch games for each.
200
+ * Perfect for sidebar badges, sport selector counts, or deciding which sport feeds to subscribe to.
201
+ *
202
+ * @returns List of SportCount objects (Sport + live_count + prematch_count).
203
+ *
204
+ * **Plans:** free, starter, pro, business | **Group:** catalog
205
+ *
206
+ * **Polling:** Every 10-30 seconds. Counts change as games go live or finish.
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const { data } = await client.getSportsCounts();
211
+ * // data: [{ id, name, alias, category, live_count, prematch_count }, ...]
212
+ * ```
213
+ */
214
+ getSportsCounts(): Promise<ListResponse<SportCount>>;
215
+ /**
216
+ * List regions for a sport.
217
+ *
218
+ * Returns all regions (countries or geographic areas) that currently have active games for the specified sport.
219
+ * Build the second level of your navigation tree (e.g., England, Germany, Spain for Football).
220
+ *
221
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
222
+ * @returns List of Region objects with id, name, alias, and sport_id.
223
+ *
224
+ * **Plans:** free, starter, pro, business | **Group:** catalog
225
+ *
226
+ * **Polling:** Cache locally for 60 seconds.
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * const { data } = await client.getRegions('01KNSZTCFAHSBS092VMZS7PMCW');
231
+ * // data: [{ id, name, alias, sport_id }, ...]
232
+ * ```
233
+ */
234
+ getRegions(sportId: string): Promise<ListResponse<Region>>;
235
+ /**
236
+ * List competitions for a region.
237
+ *
238
+ * Returns all competitions (leagues, tournaments, cups) within the specified region.
239
+ * Build the third level of your navigation tree (e.g., Premier League, Championship, FA Cup for England).
240
+ *
241
+ * @param regionId - Unique region identifier (ULID). Get IDs from {@link getRegions}.
242
+ * @returns List of Competition objects with id, name, region_id, and sport_id.
243
+ *
244
+ * **Plans:** free, starter, pro, business | **Group:** catalog
245
+ *
246
+ * **Polling:** Cache locally for 60 seconds.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * const { data } = await client.getCompetitions('01JRQ00000ENGLAND0000001');
251
+ * // data: [{ id, name, region_id, sport_id }, ...]
252
+ * ```
253
+ */
254
+ getCompetitions(regionId: string): Promise<ListResponse<Competition>>;
255
+ /**
256
+ * List all competitions for a sport (flat list).
257
+ *
258
+ * Returns all competitions for the specified sport across ALL regions in one call.
259
+ * Useful for building flat competition selectors, search/filter dropdowns, or schedule filters.
260
+ *
261
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
262
+ * @returns Flat list of Competition objects.
263
+ *
264
+ * **Plans:** free, starter, pro, business | **Group:** catalog
265
+ *
266
+ * **Polling:** Cache locally for 60 seconds.
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * const { data } = await client.getCompetitionsBySport('01KNSZTCFAHSBS092VMZS7PMCW');
271
+ * // data: [{ id: "...", name: "Premier League", region_id, sport_id }, ...]
272
+ * ```
273
+ */
274
+ getCompetitionsBySport(sportId: string): Promise<ListResponse<Competition>>;
275
+ /**
276
+ * All live games across all sports.
277
+ *
278
+ * Returns every currently live (in-play) game in a single call.
279
+ * Ideal for multi-sport live tickers, aggregate live feeds, or monitoring total live game volume.
280
+ *
281
+ * @returns List of Game objects with is_live: true, including scores, stats, and market counts.
282
+ *
283
+ * **Plans:** starter, pro, business | **Group:** live
284
+ *
285
+ * **Polling:** Every 1-2 seconds for real-time updates.
286
+ * At peak hours this can return 200+ games. If you only need one sport, use {@link getLiveGamesBySport}.
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * const { data } = await client.getAllLiveGames();
291
+ * // data: [{ id, home_team, away_team, status: "live", score_text, info, stats, ... }, ...]
292
+ * ```
293
+ */
294
+ getAllLiveGames(): Promise<ListResponse<Game>>;
295
+ /**
296
+ * Live games for a specific sport.
297
+ *
298
+ * Returns all currently live (in-play) games for the specified sport.
299
+ * More efficient than {@link getAllLiveGames} when you only need one sport.
300
+ *
301
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
302
+ * @returns List of live Game objects filtered to the requested sport.
303
+ *
304
+ * **Plans:** starter, pro, business | **Group:** live
305
+ *
306
+ * **Polling:** Every 1-2 seconds for real-time updates.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * const { data } = await client.getLiveGamesBySport('01KNSZTCFAHSBS092VMZS7PMCW');
311
+ * // data: [{ id, home_team, away_team, score_text: "2:1 (67')", ... }, ...]
312
+ * ```
313
+ */
314
+ getLiveGamesBySport(sportId: string): Promise<ListResponse<Game>>;
315
+ /**
316
+ * Full live dashboard snapshot.
317
+ *
318
+ * Returns a complete live dashboard in a single API call: sport counts and all live games combined.
319
+ * Combines `/api/sports/counts` and `/api/live/games` to reduce round trips.
320
+ *
321
+ * @returns LiveSnapshot containing sports (with counts) and all live games.
322
+ *
323
+ * **Plans:** starter, pro, business | **Group:** live
324
+ *
325
+ * **Polling:** Every 2-5 seconds. This is a heavier endpoint.
326
+ * For granular updates after initial load, prefer individual endpoints.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * const { data } = await client.getLiveSnapshot();
331
+ * // data: { sports: [{ id, name, live_count, prematch_count, ... }], games: [...] }
332
+ * ```
333
+ */
334
+ getLiveSnapshot(): Promise<SingleResponse<LiveSnapshot>>;
335
+ /**
336
+ * Prematch games for a sport (paginated).
337
+ *
338
+ * Returns upcoming (prematch) games for the specified sport with pagination support.
339
+ * Sports like Football can have 1500+ prematch games.
340
+ *
341
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
342
+ * @param options - Pagination options: limit (1-100, default 50) and offset (default 0).
343
+ * @returns Paginated list of prematch Game objects.
344
+ *
345
+ * **Plans:** starter, pro, business | **Group:** prematch
346
+ *
347
+ * **Polling:** Every 10-30 seconds. Prematch odds update less frequently than live.
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * const { data, meta } = await client.getPrematchGames('01KNSZTCFAHSBS092VMZS7PMCW', { limit: 20 });
352
+ * // data: [{ id, home_team, away_team, status: "prematch", start_time, ... }, ...]
353
+ * ```
354
+ */
355
+ getPrematchGames(sportId: string, options?: PaginationOptions): Promise<ListResponse<Game>>;
356
+ /**
357
+ * Games for a competition (paginated).
358
+ *
359
+ * Returns games for a specific competition with pagination.
360
+ * Includes games that have recently started (up to 2 hours ago) and all upcoming games.
361
+ *
362
+ * @param competitionId - Unique competition identifier (ULID). Get IDs from {@link getCompetitions} or {@link getCompetitionsBySport}.
363
+ * @param options - Pagination options: limit (1-100, default 50) and offset (default 0).
364
+ * @returns Paginated list of Game objects sorted by start time.
365
+ *
366
+ * **Plans:** starter, pro, business | **Group:** games
367
+ *
368
+ * **Polling:** Every 10-30 seconds for prematch, 1-2 seconds if showing live games.
369
+ *
370
+ * @example
371
+ * ```ts
372
+ * const { data } = await client.getGamesByCompetition('01KNSZTCFGMM6JE2B9YQFGQCDZ', { limit: 10 });
373
+ * ```
374
+ */
375
+ getGamesByCompetition(competitionId: string, options?: PaginationOptions): Promise<ListResponse<Game>>;
376
+ /**
377
+ * Games starting within N minutes.
378
+ *
379
+ * Returns games that are about to start within the specified time window.
380
+ * Great for "Starting Soon" widgets or last-minute bet notifications.
381
+ *
382
+ * @param options - minutes: time window (1-180, default 30).
383
+ * @returns List of prematch Game objects sorted by start time (soonest first).
384
+ *
385
+ * **Plans:** starter, pro, business | **Group:** games
386
+ *
387
+ * **Polling:** Every 30-60 seconds.
388
+ *
389
+ * @example
390
+ * ```ts
391
+ * const { data } = await client.getGamesStartingSoon({ minutes: 60 });
392
+ * ```
393
+ */
394
+ getGamesStartingSoon(options?: StartingSoonOptions): Promise<ListResponse<Game>>;
395
+ /**
396
+ * Search games by team name.
397
+ *
398
+ * Case-insensitive partial match against both home_team and away_team across all sports.
399
+ * Sorted by relevance, then by start time.
400
+ *
401
+ * @param query - Search query string (minimum 2 characters).
402
+ * @param options - limit: max results (1-50, default 20).
403
+ * @returns List of matching Game objects.
404
+ *
405
+ * **Plans:** starter, pro, business | **Group:** games
406
+ *
407
+ * **Polling:** On demand (triggered by user input).
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * const { data } = await client.searchGames('Arsenal', { limit: 10 });
412
+ * ```
413
+ */
414
+ searchGames(query: string, options?: SearchOptions): Promise<ListResponse<Game>>;
415
+ /**
416
+ * Full game detail with all markets and odds.
417
+ *
418
+ * Returns complete data for a single game: metadata, all markets, and all outcomes.
419
+ * Everything needed to render a full betting interface.
420
+ *
421
+ * @param gameId - Unique game identifier (ULID). Get IDs from any endpoint that returns Game objects.
422
+ * @returns Single Game object with nested markets and outcomes.
423
+ *
424
+ * **Plans:** starter, pro, business | **Group:** games
425
+ *
426
+ * Markets are populated only on **pro** and **business** plans.
427
+ * Lower plans receive `markets: []` and `markets_count: 0`.
428
+ *
429
+ * **Polling:** Every 1-2 seconds for live games, 10-30 seconds for prematch.
430
+ *
431
+ * @example
432
+ * ```ts
433
+ * const { data } = await client.getGameDetail('01TESTGAME00000LIVESOC01');
434
+ * // data.markets: [{ id, name, type, line, outcomes: [{ id, name, price, is_suspended }] }]
435
+ * ```
436
+ */
437
+ getGameDetail(gameId: string): Promise<SingleResponse<Game>>;
438
+ /**
439
+ * Games by date or date range.
440
+ *
441
+ * Returns all games scheduled for a specific date or range, optionally filtered by sport or competition.
442
+ * Uses UTC dates. Defaults to today if no date params provided.
443
+ *
444
+ * @param options - date (YYYY-MM-DD), dateFrom/dateTo (max 90 days), sportId, competitionId.
445
+ * @returns List of Game objects sorted by start time.
446
+ *
447
+ * **Plans:** starter, pro, business | **Group:** schedule
448
+ *
449
+ * **Polling:** Every 60 seconds.
450
+ *
451
+ * @example
452
+ * ```ts
453
+ * const { data } = await client.getSchedule({ date: '2026-04-10', sportId: '01KNSZTCFAHSBS092VMZS7PMCW' });
454
+ * ```
455
+ */
456
+ getSchedule(options?: ScheduleOptions): Promise<ListResponse<Game>>;
457
+ /**
458
+ * Min/max/avg price summary per outcome for a game.
459
+ *
460
+ * Returns statistical summary of odds for every outcome: min, max, average price, and number of changes.
461
+ * Useful for identifying value bets, overreactions, and market inefficiencies.
462
+ *
463
+ * @param gameId - Unique game identifier (ULID).
464
+ * @returns List of OddsSummary objects, one per outcome.
465
+ *
466
+ * **Plans:** pro, business | **Group:** odds
467
+ *
468
+ * **Polling:** Every 10-30 seconds.
469
+ *
470
+ * @example
471
+ * ```ts
472
+ * const { data } = await client.getGameOddsSummary('01TESTGAME00000LIVESOC01');
473
+ * // data: [{ event_id, event_name, current_price, min_price, max_price, avg_price, change_count }]
474
+ * ```
475
+ */
476
+ getGameOddsSummary(gameId: string): Promise<ListResponse<OddsSummary>>;
477
+ /**
478
+ * Top odds movements (biggest price changes).
479
+ *
480
+ * Returns outcomes with the largest odds movements within a given time window.
481
+ * Key differentiator for professional bettors and odds aggregation platforms.
482
+ *
483
+ * @param options - minutes: look-back window (1-1440, default 60), limit: max results (1-100, default 20).
484
+ * @returns List of OddsMover objects sorted by absolute change percentage (largest first).
485
+ *
486
+ * **Plans:** pro, business | **Group:** odds
487
+ *
488
+ * **Polling:** Every 5-10 seconds for near-real-time steam move alerts.
489
+ *
490
+ * @example
491
+ * ```ts
492
+ * const { data } = await client.getOddsMovers({ minutes: 30, limit: 10 });
493
+ * // data: [{ event_id, game_id, current_price, opening_price, price_change, change_percent, ... }]
494
+ * ```
495
+ */
496
+ getOddsMovers(options?: OddsMoversOptions): Promise<ListResponse<OddsMover>>;
497
+ /**
498
+ * Historical price changes for an outcome.
499
+ *
500
+ * Returns the full history of price changes for a specific outcome (selection).
501
+ * Build odds movement charts, sparklines, or trend analysis visualizations.
502
+ * 60-day retention, tick-level granularity.
503
+ *
504
+ * @param eventId - Unique outcome identifier (ULID). Get IDs from Game objects with markets.
505
+ * @param options - from/to (unix timestamps), limit (1-5000, default 500).
506
+ * @returns Chronological list of PricePoint objects (oldest first).
507
+ *
508
+ * **Plans:** pro, business | **Group:** odds
509
+ *
510
+ * **Polling:** On demand.
511
+ *
512
+ * @example
513
+ * ```ts
514
+ * const { data } = await client.getEventPriceHistory('01TESTEVT00000ARS0NAL01', { limit: 100 });
515
+ * // data: [{ price, previous_price, recorded_at }, ...]
516
+ * ```
517
+ */
518
+ getEventPriceHistory(eventId: string, options?: PriceHistoryOptions): Promise<ListResponse<PricePoint>>;
519
+ /**
520
+ * Health check.
521
+ *
522
+ * Returns the current health status of the API. No authentication required.
523
+ * Use in load balancer health checks, uptime monitoring, or status pages.
524
+ *
525
+ * @returns `{ status: "ok" }` when the API is healthy.
526
+ *
527
+ * **Plans:** all (no authentication required) | **Group:** system
528
+ *
529
+ * **Polling:** Every 30-60 seconds from your monitoring system.
530
+ */
531
+ health(): Promise<HealthResponse>;
532
+ /**
533
+ * Ping.
534
+ *
535
+ * Simple connectivity check. No authentication required.
536
+ * Quick liveness probe for monitoring or client-side connectivity checks.
537
+ *
538
+ * @returns `{ pong: true }`
539
+ *
540
+ * **Plans:** all (no authentication required) | **Group:** system
541
+ */
542
+ ping(): Promise<PingResponse>;
543
+ }
544
+
545
+ declare class ScorioError extends Error {
546
+ readonly status: number;
547
+ readonly code: string;
548
+ constructor(message: string, status: number, code: string);
549
+ }
550
+ declare class PlanLimitError extends ScorioError {
551
+ readonly currentPlan: string;
552
+ constructor(message: string, currentPlan: string);
553
+ }
554
+ declare class RateLimitError extends ScorioError {
555
+ readonly retryAfter: number;
556
+ constructor(message: string, retryAfter: number);
557
+ }
558
+ declare class NetworkError extends ScorioError {
559
+ constructor(message: string, options?: {
560
+ cause?: unknown;
561
+ });
562
+ }
563
+
564
+ export { type Competition, type DebugConfig, type Game, type GameInfo, type GameStats, type HealthResponse, type ListResponse, type LiveSnapshot, type Market, NetworkError, type OddsMover, type OddsMoversOptions, type OddsSummary, type Outcome, type PaginationOptions, type PingResponse, type Plan, PlanLimitError, type PriceHistoryOptions, type PricePoint, RateLimitError, type Region, type ScheduleOptions, ScorioError, type ScorioSDKConfig, type SearchOptions, type SingleResponse, type Sport, type SportCount, type StartingSoonOptions, ScorioSDK as default };
package/dist/index.js ADDED
@@ -0,0 +1,623 @@
1
+ // src/domain/errors.ts
2
+ var ScorioError = class extends Error {
3
+ status;
4
+ code;
5
+ constructor(message, status, code) {
6
+ super(message);
7
+ this.name = "ScorioError";
8
+ this.status = status;
9
+ this.code = code;
10
+ }
11
+ };
12
+ var PlanLimitError = class extends ScorioError {
13
+ currentPlan;
14
+ constructor(message, currentPlan) {
15
+ super(message, 403, "plan_limit");
16
+ this.name = "PlanLimitError";
17
+ this.currentPlan = currentPlan;
18
+ }
19
+ };
20
+ var RateLimitError = class extends ScorioError {
21
+ retryAfter;
22
+ constructor(message, retryAfter) {
23
+ super(message, 429, "rate_limit");
24
+ this.name = "RateLimitError";
25
+ this.retryAfter = retryAfter;
26
+ }
27
+ };
28
+ var NetworkError = class extends ScorioError {
29
+ constructor(message, options) {
30
+ super(message, 0, "network_error");
31
+ this.name = "NetworkError";
32
+ if (options?.cause) {
33
+ this.cause = options.cause;
34
+ }
35
+ }
36
+ };
37
+
38
+ // src/infrastructure/http/error-mapper.ts
39
+ function isErrorBody(body) {
40
+ return typeof body === "object" && body !== null && "error" in body;
41
+ }
42
+ function extractMessage(body) {
43
+ if (isErrorBody(body)) {
44
+ return body.message ?? body.error ?? "Unknown error";
45
+ }
46
+ return "Unknown error";
47
+ }
48
+ function extractPlan(body) {
49
+ if (isErrorBody(body) && typeof body.message === "string") {
50
+ const match = body.message.match(/Current plan: (\w+)/);
51
+ if (match?.[1]) return match[1];
52
+ }
53
+ return "unknown";
54
+ }
55
+ async function mapResponseToError(status, headers, jsonFn) {
56
+ let body;
57
+ try {
58
+ body = await jsonFn();
59
+ } catch {
60
+ body = null;
61
+ }
62
+ const message = extractMessage(body);
63
+ if (status === 403 && isErrorBody(body) && body.error === "plan_limit") {
64
+ return new PlanLimitError(message, extractPlan(body));
65
+ }
66
+ if (status === 429) {
67
+ const retryAfter = Number(headers.get("retry-after") ?? "1");
68
+ return new RateLimitError(message, retryAfter);
69
+ }
70
+ return new ScorioError(message, status, "api_error");
71
+ }
72
+
73
+ // src/infrastructure/http/fetch-client.ts
74
+ var FetchHttpClient = class {
75
+ async request(req) {
76
+ try {
77
+ const response = await fetch(req.url, {
78
+ method: req.method,
79
+ headers: req.headers,
80
+ signal: AbortSignal.timeout(req.timeout)
81
+ });
82
+ return {
83
+ status: response.status,
84
+ headers: response.headers,
85
+ json: () => response.json()
86
+ };
87
+ } catch (error) {
88
+ if (error instanceof DOMException && error.name === "TimeoutError") {
89
+ throw new NetworkError(`Request timed out after ${req.timeout}ms`);
90
+ }
91
+ if (error instanceof TypeError) {
92
+ throw new NetworkError("Network request failed", { cause: error });
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+ };
98
+
99
+ // src/infrastructure/http/headers.ts
100
+ var PLAN_TO_HEADER = {
101
+ free: "BASIC",
102
+ starter: "STARTER",
103
+ pro: "ULTRA",
104
+ business: "MEGA"
105
+ };
106
+ function buildHeaders(apiKey, host, debug, isSystem) {
107
+ const headers = {
108
+ Accept: "application/json"
109
+ };
110
+ if (isSystem) return headers;
111
+ if (debug) {
112
+ headers["x-rapidapi-proxy-secret"] = debug.secret;
113
+ if (debug.plan) {
114
+ headers["x-rapidapi-subscription"] = PLAN_TO_HEADER[debug.plan];
115
+ }
116
+ } else {
117
+ headers["X-RapidAPI-Key"] = apiKey;
118
+ headers["X-RapidAPI-Host"] = host;
119
+ }
120
+ return headers;
121
+ }
122
+
123
+ // src/infrastructure/http/url-builder.ts
124
+ function serializeParams(params) {
125
+ if (!params) return "";
126
+ const entries = [];
127
+ for (const [k, v] of Object.entries(params)) {
128
+ if (v !== void 0 && v !== null && v !== "") {
129
+ entries.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
130
+ }
131
+ }
132
+ return entries.join("&");
133
+ }
134
+ function buildUrl(host, path, params) {
135
+ const base = `https://${host}${path}`;
136
+ const search = serializeParams(params);
137
+ return search ? `${base}?${search}` : base;
138
+ }
139
+
140
+ // src/application/sdk.ts
141
+ var ScorioSDK = class {
142
+ apiKey;
143
+ host;
144
+ timeout;
145
+ logger;
146
+ config;
147
+ http;
148
+ constructor(config, httpClient) {
149
+ if (!config.apiKey) {
150
+ throw new Error("apiKey is required");
151
+ }
152
+ this.apiKey = config.apiKey;
153
+ this.host = config.debug?.host ?? config.host ?? "";
154
+ this.timeout = config.timeout ?? 3e4;
155
+ this.logger = config.logger ?? false;
156
+ this.config = config;
157
+ this.http = httpClient ?? new FetchHttpClient();
158
+ if (!this.host) {
159
+ throw new Error("host is required (or provide debug.host)");
160
+ }
161
+ }
162
+ async get(path, params, isSystem = false) {
163
+ const url = buildUrl(this.host, path, params);
164
+ const headers = buildHeaders(this.apiKey, this.host, this.config.debug, isSystem);
165
+ if (this.logger) {
166
+ console.log(`[scorio-sdk] GET ${path}`);
167
+ }
168
+ const response = await this.http.request({
169
+ method: "GET",
170
+ url,
171
+ headers,
172
+ timeout: this.timeout
173
+ });
174
+ if (response.status < 200 || response.status >= 300) {
175
+ throw await mapResponseToError(response.status, response.headers, response.json);
176
+ }
177
+ return response.json();
178
+ }
179
+ // ─── Catalog ───────────────────────────────────────────────────
180
+ /**
181
+ * List all available sports.
182
+ *
183
+ * Returns every available sport sorted by display order.
184
+ * Use this to build your top-level navigation or sport selector.
185
+ *
186
+ * @returns List of Sport objects with id, name, alias, and category.
187
+ *
188
+ * **Plans:** free, starter, pro, business | **Group:** catalog
189
+ *
190
+ * **Polling:** Cache locally for 60 seconds. The sport catalog changes infrequently.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const { data, meta } = await client.getSports();
195
+ * // data: [{ id, name, alias, category }, ...]
196
+ * // meta: { count: number }
197
+ * ```
198
+ */
199
+ async getSports() {
200
+ return this.get("/api/sports");
201
+ }
202
+ /**
203
+ * Sports with live and prematch game counts.
204
+ *
205
+ * Returns all sports along with the number of currently live and upcoming prematch games for each.
206
+ * Perfect for sidebar badges, sport selector counts, or deciding which sport feeds to subscribe to.
207
+ *
208
+ * @returns List of SportCount objects (Sport + live_count + prematch_count).
209
+ *
210
+ * **Plans:** free, starter, pro, business | **Group:** catalog
211
+ *
212
+ * **Polling:** Every 10-30 seconds. Counts change as games go live or finish.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const { data } = await client.getSportsCounts();
217
+ * // data: [{ id, name, alias, category, live_count, prematch_count }, ...]
218
+ * ```
219
+ */
220
+ async getSportsCounts() {
221
+ return this.get("/api/sports/counts");
222
+ }
223
+ /**
224
+ * List regions for a sport.
225
+ *
226
+ * Returns all regions (countries or geographic areas) that currently have active games for the specified sport.
227
+ * Build the second level of your navigation tree (e.g., England, Germany, Spain for Football).
228
+ *
229
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
230
+ * @returns List of Region objects with id, name, alias, and sport_id.
231
+ *
232
+ * **Plans:** free, starter, pro, business | **Group:** catalog
233
+ *
234
+ * **Polling:** Cache locally for 60 seconds.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * const { data } = await client.getRegions('01KNSZTCFAHSBS092VMZS7PMCW');
239
+ * // data: [{ id, name, alias, sport_id }, ...]
240
+ * ```
241
+ */
242
+ async getRegions(sportId) {
243
+ return this.get(`/api/sports/${sportId}/regions`);
244
+ }
245
+ /**
246
+ * List competitions for a region.
247
+ *
248
+ * Returns all competitions (leagues, tournaments, cups) within the specified region.
249
+ * Build the third level of your navigation tree (e.g., Premier League, Championship, FA Cup for England).
250
+ *
251
+ * @param regionId - Unique region identifier (ULID). Get IDs from {@link getRegions}.
252
+ * @returns List of Competition objects with id, name, region_id, and sport_id.
253
+ *
254
+ * **Plans:** free, starter, pro, business | **Group:** catalog
255
+ *
256
+ * **Polling:** Cache locally for 60 seconds.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * const { data } = await client.getCompetitions('01JRQ00000ENGLAND0000001');
261
+ * // data: [{ id, name, region_id, sport_id }, ...]
262
+ * ```
263
+ */
264
+ async getCompetitions(regionId) {
265
+ return this.get(`/api/regions/${regionId}/competitions`);
266
+ }
267
+ /**
268
+ * List all competitions for a sport (flat list).
269
+ *
270
+ * Returns all competitions for the specified sport across ALL regions in one call.
271
+ * Useful for building flat competition selectors, search/filter dropdowns, or schedule filters.
272
+ *
273
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
274
+ * @returns Flat list of Competition objects.
275
+ *
276
+ * **Plans:** free, starter, pro, business | **Group:** catalog
277
+ *
278
+ * **Polling:** Cache locally for 60 seconds.
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * const { data } = await client.getCompetitionsBySport('01KNSZTCFAHSBS092VMZS7PMCW');
283
+ * // data: [{ id: "...", name: "Premier League", region_id, sport_id }, ...]
284
+ * ```
285
+ */
286
+ async getCompetitionsBySport(sportId) {
287
+ return this.get(`/api/sports/${sportId}/competitions`);
288
+ }
289
+ // ─── Live ──────────────────────────────────────────────────────
290
+ /**
291
+ * All live games across all sports.
292
+ *
293
+ * Returns every currently live (in-play) game in a single call.
294
+ * Ideal for multi-sport live tickers, aggregate live feeds, or monitoring total live game volume.
295
+ *
296
+ * @returns List of Game objects with is_live: true, including scores, stats, and market counts.
297
+ *
298
+ * **Plans:** starter, pro, business | **Group:** live
299
+ *
300
+ * **Polling:** Every 1-2 seconds for real-time updates.
301
+ * At peak hours this can return 200+ games. If you only need one sport, use {@link getLiveGamesBySport}.
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * const { data } = await client.getAllLiveGames();
306
+ * // data: [{ id, home_team, away_team, status: "live", score_text, info, stats, ... }, ...]
307
+ * ```
308
+ */
309
+ async getAllLiveGames() {
310
+ return this.get("/api/live/games");
311
+ }
312
+ /**
313
+ * Live games for a specific sport.
314
+ *
315
+ * Returns all currently live (in-play) games for the specified sport.
316
+ * More efficient than {@link getAllLiveGames} when you only need one sport.
317
+ *
318
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
319
+ * @returns List of live Game objects filtered to the requested sport.
320
+ *
321
+ * **Plans:** starter, pro, business | **Group:** live
322
+ *
323
+ * **Polling:** Every 1-2 seconds for real-time updates.
324
+ *
325
+ * @example
326
+ * ```ts
327
+ * const { data } = await client.getLiveGamesBySport('01KNSZTCFAHSBS092VMZS7PMCW');
328
+ * // data: [{ id, home_team, away_team, score_text: "2:1 (67')", ... }, ...]
329
+ * ```
330
+ */
331
+ async getLiveGamesBySport(sportId) {
332
+ return this.get(`/api/live/games/${sportId}`);
333
+ }
334
+ /**
335
+ * Full live dashboard snapshot.
336
+ *
337
+ * Returns a complete live dashboard in a single API call: sport counts and all live games combined.
338
+ * Combines `/api/sports/counts` and `/api/live/games` to reduce round trips.
339
+ *
340
+ * @returns LiveSnapshot containing sports (with counts) and all live games.
341
+ *
342
+ * **Plans:** starter, pro, business | **Group:** live
343
+ *
344
+ * **Polling:** Every 2-5 seconds. This is a heavier endpoint.
345
+ * For granular updates after initial load, prefer individual endpoints.
346
+ *
347
+ * @example
348
+ * ```ts
349
+ * const { data } = await client.getLiveSnapshot();
350
+ * // data: { sports: [{ id, name, live_count, prematch_count, ... }], games: [...] }
351
+ * ```
352
+ */
353
+ async getLiveSnapshot() {
354
+ return this.get("/api/live/snapshot");
355
+ }
356
+ // ─── Prematch ──────────────────────────────────────────────────
357
+ /**
358
+ * Prematch games for a sport (paginated).
359
+ *
360
+ * Returns upcoming (prematch) games for the specified sport with pagination support.
361
+ * Sports like Football can have 1500+ prematch games.
362
+ *
363
+ * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.
364
+ * @param options - Pagination options: limit (1-100, default 50) and offset (default 0).
365
+ * @returns Paginated list of prematch Game objects.
366
+ *
367
+ * **Plans:** starter, pro, business | **Group:** prematch
368
+ *
369
+ * **Polling:** Every 10-30 seconds. Prematch odds update less frequently than live.
370
+ *
371
+ * @example
372
+ * ```ts
373
+ * const { data, meta } = await client.getPrematchGames('01KNSZTCFAHSBS092VMZS7PMCW', { limit: 20 });
374
+ * // data: [{ id, home_team, away_team, status: "prematch", start_time, ... }, ...]
375
+ * ```
376
+ */
377
+ async getPrematchGames(sportId, options) {
378
+ return this.get(`/api/prematch/games/${sportId}`, {
379
+ limit: options?.limit,
380
+ offset: options?.offset
381
+ });
382
+ }
383
+ // ─── Games ─────────────────────────────────────────────────────
384
+ /**
385
+ * Games for a competition (paginated).
386
+ *
387
+ * Returns games for a specific competition with pagination.
388
+ * Includes games that have recently started (up to 2 hours ago) and all upcoming games.
389
+ *
390
+ * @param competitionId - Unique competition identifier (ULID). Get IDs from {@link getCompetitions} or {@link getCompetitionsBySport}.
391
+ * @param options - Pagination options: limit (1-100, default 50) and offset (default 0).
392
+ * @returns Paginated list of Game objects sorted by start time.
393
+ *
394
+ * **Plans:** starter, pro, business | **Group:** games
395
+ *
396
+ * **Polling:** Every 10-30 seconds for prematch, 1-2 seconds if showing live games.
397
+ *
398
+ * @example
399
+ * ```ts
400
+ * const { data } = await client.getGamesByCompetition('01KNSZTCFGMM6JE2B9YQFGQCDZ', { limit: 10 });
401
+ * ```
402
+ */
403
+ async getGamesByCompetition(competitionId, options) {
404
+ return this.get(`/api/competitions/${competitionId}/games`, {
405
+ limit: options?.limit,
406
+ offset: options?.offset
407
+ });
408
+ }
409
+ /**
410
+ * Games starting within N minutes.
411
+ *
412
+ * Returns games that are about to start within the specified time window.
413
+ * Great for "Starting Soon" widgets or last-minute bet notifications.
414
+ *
415
+ * @param options - minutes: time window (1-180, default 30).
416
+ * @returns List of prematch Game objects sorted by start time (soonest first).
417
+ *
418
+ * **Plans:** starter, pro, business | **Group:** games
419
+ *
420
+ * **Polling:** Every 30-60 seconds.
421
+ *
422
+ * @example
423
+ * ```ts
424
+ * const { data } = await client.getGamesStartingSoon({ minutes: 60 });
425
+ * ```
426
+ */
427
+ async getGamesStartingSoon(options) {
428
+ return this.get("/api/games/starting-soon", {
429
+ minutes: options?.minutes
430
+ });
431
+ }
432
+ /**
433
+ * Search games by team name.
434
+ *
435
+ * Case-insensitive partial match against both home_team and away_team across all sports.
436
+ * Sorted by relevance, then by start time.
437
+ *
438
+ * @param query - Search query string (minimum 2 characters).
439
+ * @param options - limit: max results (1-50, default 20).
440
+ * @returns List of matching Game objects.
441
+ *
442
+ * **Plans:** starter, pro, business | **Group:** games
443
+ *
444
+ * **Polling:** On demand (triggered by user input).
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * const { data } = await client.searchGames('Arsenal', { limit: 10 });
449
+ * ```
450
+ */
451
+ async searchGames(query, options) {
452
+ return this.get("/api/games/search", {
453
+ q: query,
454
+ limit: options?.limit
455
+ });
456
+ }
457
+ /**
458
+ * Full game detail with all markets and odds.
459
+ *
460
+ * Returns complete data for a single game: metadata, all markets, and all outcomes.
461
+ * Everything needed to render a full betting interface.
462
+ *
463
+ * @param gameId - Unique game identifier (ULID). Get IDs from any endpoint that returns Game objects.
464
+ * @returns Single Game object with nested markets and outcomes.
465
+ *
466
+ * **Plans:** starter, pro, business | **Group:** games
467
+ *
468
+ * Markets are populated only on **pro** and **business** plans.
469
+ * Lower plans receive `markets: []` and `markets_count: 0`.
470
+ *
471
+ * **Polling:** Every 1-2 seconds for live games, 10-30 seconds for prematch.
472
+ *
473
+ * @example
474
+ * ```ts
475
+ * const { data } = await client.getGameDetail('01TESTGAME00000LIVESOC01');
476
+ * // data.markets: [{ id, name, type, line, outcomes: [{ id, name, price, is_suspended }] }]
477
+ * ```
478
+ */
479
+ async getGameDetail(gameId) {
480
+ return this.get(`/api/games/${gameId}`);
481
+ }
482
+ // ─── Schedule ──────────────────────────────────────────────────
483
+ /**
484
+ * Games by date or date range.
485
+ *
486
+ * Returns all games scheduled for a specific date or range, optionally filtered by sport or competition.
487
+ * Uses UTC dates. Defaults to today if no date params provided.
488
+ *
489
+ * @param options - date (YYYY-MM-DD), dateFrom/dateTo (max 90 days), sportId, competitionId.
490
+ * @returns List of Game objects sorted by start time.
491
+ *
492
+ * **Plans:** starter, pro, business | **Group:** schedule
493
+ *
494
+ * **Polling:** Every 60 seconds.
495
+ *
496
+ * @example
497
+ * ```ts
498
+ * const { data } = await client.getSchedule({ date: '2026-04-10', sportId: '01KNSZTCFAHSBS092VMZS7PMCW' });
499
+ * ```
500
+ */
501
+ async getSchedule(options) {
502
+ return this.get("/api/schedule", {
503
+ date: options?.date,
504
+ date_from: options?.dateFrom,
505
+ date_to: options?.dateTo,
506
+ sport_id: options?.sportId,
507
+ competition_id: options?.competitionId
508
+ });
509
+ }
510
+ // ─── Odds Intelligence ─────────────────────────────────────────
511
+ /**
512
+ * Min/max/avg price summary per outcome for a game.
513
+ *
514
+ * Returns statistical summary of odds for every outcome: min, max, average price, and number of changes.
515
+ * Useful for identifying value bets, overreactions, and market inefficiencies.
516
+ *
517
+ * @param gameId - Unique game identifier (ULID).
518
+ * @returns List of OddsSummary objects, one per outcome.
519
+ *
520
+ * **Plans:** pro, business | **Group:** odds
521
+ *
522
+ * **Polling:** Every 10-30 seconds.
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * const { data } = await client.getGameOddsSummary('01TESTGAME00000LIVESOC01');
527
+ * // data: [{ event_id, event_name, current_price, min_price, max_price, avg_price, change_count }]
528
+ * ```
529
+ */
530
+ async getGameOddsSummary(gameId) {
531
+ return this.get(`/api/games/${gameId}/odds-summary`);
532
+ }
533
+ /**
534
+ * Top odds movements (biggest price changes).
535
+ *
536
+ * Returns outcomes with the largest odds movements within a given time window.
537
+ * Key differentiator for professional bettors and odds aggregation platforms.
538
+ *
539
+ * @param options - minutes: look-back window (1-1440, default 60), limit: max results (1-100, default 20).
540
+ * @returns List of OddsMover objects sorted by absolute change percentage (largest first).
541
+ *
542
+ * **Plans:** pro, business | **Group:** odds
543
+ *
544
+ * **Polling:** Every 5-10 seconds for near-real-time steam move alerts.
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * const { data } = await client.getOddsMovers({ minutes: 30, limit: 10 });
549
+ * // data: [{ event_id, game_id, current_price, opening_price, price_change, change_percent, ... }]
550
+ * ```
551
+ */
552
+ async getOddsMovers(options) {
553
+ return this.get("/api/odds/movers", {
554
+ minutes: options?.minutes,
555
+ limit: options?.limit
556
+ });
557
+ }
558
+ /**
559
+ * Historical price changes for an outcome.
560
+ *
561
+ * Returns the full history of price changes for a specific outcome (selection).
562
+ * Build odds movement charts, sparklines, or trend analysis visualizations.
563
+ * 60-day retention, tick-level granularity.
564
+ *
565
+ * @param eventId - Unique outcome identifier (ULID). Get IDs from Game objects with markets.
566
+ * @param options - from/to (unix timestamps), limit (1-5000, default 500).
567
+ * @returns Chronological list of PricePoint objects (oldest first).
568
+ *
569
+ * **Plans:** pro, business | **Group:** odds
570
+ *
571
+ * **Polling:** On demand.
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * const { data } = await client.getEventPriceHistory('01TESTEVT00000ARS0NAL01', { limit: 100 });
576
+ * // data: [{ price, previous_price, recorded_at }, ...]
577
+ * ```
578
+ */
579
+ async getEventPriceHistory(eventId, options) {
580
+ return this.get(`/api/events/${eventId}/price-history`, {
581
+ from: options?.from,
582
+ to: options?.to,
583
+ limit: options?.limit
584
+ });
585
+ }
586
+ // ─── System ────────────────────────────────────────────────────
587
+ /**
588
+ * Health check.
589
+ *
590
+ * Returns the current health status of the API. No authentication required.
591
+ * Use in load balancer health checks, uptime monitoring, or status pages.
592
+ *
593
+ * @returns `{ status: "ok" }` when the API is healthy.
594
+ *
595
+ * **Plans:** all (no authentication required) | **Group:** system
596
+ *
597
+ * **Polling:** Every 30-60 seconds from your monitoring system.
598
+ */
599
+ async health() {
600
+ return this.get("/api/health", void 0, true);
601
+ }
602
+ /**
603
+ * Ping.
604
+ *
605
+ * Simple connectivity check. No authentication required.
606
+ * Quick liveness probe for monitoring or client-side connectivity checks.
607
+ *
608
+ * @returns `{ pong: true }`
609
+ *
610
+ * **Plans:** all (no authentication required) | **Group:** system
611
+ */
612
+ async ping() {
613
+ return this.get("/api/ping", void 0, true);
614
+ }
615
+ };
616
+ export {
617
+ NetworkError,
618
+ PlanLimitError,
619
+ RateLimitError,
620
+ ScorioError,
621
+ ScorioSDK as default
622
+ };
623
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/domain/errors.ts","../src/infrastructure/http/error-mapper.ts","../src/infrastructure/http/fetch-client.ts","../src/infrastructure/http/headers.ts","../src/infrastructure/http/url-builder.ts","../src/application/sdk.ts"],"sourcesContent":["export class ScorioError extends Error {\n\treadonly status: number;\n\treadonly code: string;\n\n\tconstructor(message: string, status: number, code: string) {\n\t\tsuper(message);\n\t\tthis.name = 'ScorioError';\n\t\tthis.status = status;\n\t\tthis.code = code;\n\t}\n}\n\nexport class PlanLimitError extends ScorioError {\n\treadonly currentPlan: string;\n\n\tconstructor(message: string, currentPlan: string) {\n\t\tsuper(message, 403, 'plan_limit');\n\t\tthis.name = 'PlanLimitError';\n\t\tthis.currentPlan = currentPlan;\n\t}\n}\n\nexport class RateLimitError extends ScorioError {\n\treadonly retryAfter: number;\n\n\tconstructor(message: string, retryAfter: number) {\n\t\tsuper(message, 429, 'rate_limit');\n\t\tthis.name = 'RateLimitError';\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n\nexport class NetworkError extends ScorioError {\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(message, 0, 'network_error');\n\t\tthis.name = 'NetworkError';\n\t\tif (options?.cause) {\n\t\t\tthis.cause = options.cause;\n\t\t}\n\t}\n}\n","import { NetworkError, PlanLimitError, RateLimitError, ScorioError } from '../../domain/errors.js';\n\ninterface ErrorBody {\n\terror?: string;\n\tmessage?: string;\n}\n\nfunction isErrorBody(body: unknown): body is ErrorBody {\n\treturn typeof body === 'object' && body !== null && 'error' in body;\n}\n\nfunction extractMessage(body: unknown): string {\n\tif (isErrorBody(body)) {\n\t\treturn body.message ?? body.error ?? 'Unknown error';\n\t}\n\treturn 'Unknown error';\n}\n\nfunction extractPlan(body: unknown): string {\n\tif (isErrorBody(body) && typeof body.message === 'string') {\n\t\tconst match = body.message.match(/Current plan: (\\w+)/);\n\t\tif (match?.[1]) return match[1];\n\t}\n\treturn 'unknown';\n}\n\nexport async function mapResponseToError(\n\tstatus: number,\n\theaders: { get(name: string): string | null },\n\tjsonFn: () => Promise<unknown>,\n): Promise<ScorioError> {\n\tlet body: unknown;\n\ttry {\n\t\tbody = await jsonFn();\n\t} catch {\n\t\tbody = null;\n\t}\n\n\tconst message = extractMessage(body);\n\n\tif (status === 403 && isErrorBody(body) && body.error === 'plan_limit') {\n\t\treturn new PlanLimitError(message, extractPlan(body));\n\t}\n\n\tif (status === 429) {\n\t\tconst retryAfter = Number(headers.get('retry-after') ?? '1');\n\t\treturn new RateLimitError(message, retryAfter);\n\t}\n\n\treturn new ScorioError(message, status, 'api_error');\n}\n\nexport { NetworkError };\n","import type {\n\tHttpClientPort,\n\tHttpRequest,\n\tHttpResponse,\n} from '../../application/ports/http-client.port.js';\nimport { NetworkError } from '../../domain/errors.js';\n\nexport class FetchHttpClient implements HttpClientPort {\n\tasync request(req: HttpRequest): Promise<HttpResponse> {\n\t\ttry {\n\t\t\tconst response = await fetch(req.url, {\n\t\t\t\tmethod: req.method,\n\t\t\t\theaders: req.headers,\n\t\t\t\tsignal: AbortSignal.timeout(req.timeout),\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t\tjson: () => response.json() as Promise<unknown>,\n\t\t\t};\n\t\t} catch (error: unknown) {\n\t\t\tif (error instanceof DOMException && error.name === 'TimeoutError') {\n\t\t\t\tthrow new NetworkError(`Request timed out after ${req.timeout}ms`);\n\t\t\t}\n\t\t\tif (error instanceof TypeError) {\n\t\t\t\tthrow new NetworkError('Network request failed', { cause: error });\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n","import type { DebugConfig, Plan } from '../../domain/types.js';\n\n/** Maps SDK plan names to the x-rapidapi-subscription header values\n * that the server's plan-guard.ts resolves to the correct internal tier. */\nconst PLAN_TO_HEADER: Record<Plan, string> = {\n\tfree: 'BASIC',\n\tstarter: 'STARTER',\n\tpro: 'ULTRA',\n\tbusiness: 'MEGA',\n};\n\nexport function buildHeaders(\n\tapiKey: string,\n\thost: string,\n\tdebug: DebugConfig | undefined,\n\tisSystem: boolean,\n): Record<string, string> {\n\tconst headers: Record<string, string> = {\n\t\tAccept: 'application/json',\n\t};\n\n\tif (isSystem) return headers;\n\n\tif (debug) {\n\t\theaders['x-rapidapi-proxy-secret'] = debug.secret;\n\t\tif (debug.plan) {\n\t\t\theaders['x-rapidapi-subscription'] = PLAN_TO_HEADER[debug.plan];\n\t\t}\n\t} else {\n\t\theaders['X-RapidAPI-Key'] = apiKey;\n\t\theaders['X-RapidAPI-Host'] = host;\n\t}\n\n\treturn headers;\n}\n","export function serializeParams(params?: Record<string, string | number | undefined>): string {\n\tif (!params) return '';\n\tconst entries: string[] = [];\n\tfor (const [k, v] of Object.entries(params)) {\n\t\tif (v !== undefined && v !== null && v !== '') {\n\t\t\tentries.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n\t\t}\n\t}\n\treturn entries.join('&');\n}\n\nexport function buildUrl(\n\thost: string,\n\tpath: string,\n\tparams?: Record<string, string | number | undefined>,\n): string {\n\tconst base = `https://${host}${path}`;\n\tconst search = serializeParams(params);\n\treturn search ? `${base}?${search}` : base;\n}\n","import type {\n\tCompetition,\n\tGame,\n\tHealthResponse,\n\tListResponse,\n\tLiveSnapshot,\n\tOddsMover,\n\tOddsMoversOptions,\n\tOddsSummary,\n\tPaginationOptions,\n\tPingResponse,\n\tPriceHistoryOptions,\n\tPricePoint,\n\tRegion,\n\tScheduleOptions,\n\tScorioSDKConfig,\n\tSearchOptions,\n\tSingleResponse,\n\tSport,\n\tSportCount,\n\tStartingSoonOptions,\n} from '../domain/types.js';\nimport { mapResponseToError } from '../infrastructure/http/error-mapper.js';\nimport { FetchHttpClient } from '../infrastructure/http/fetch-client.js';\nimport { buildHeaders } from '../infrastructure/http/headers.js';\nimport { buildUrl } from '../infrastructure/http/url-builder.js';\nimport type { HttpClientPort } from './ports/http-client.port.js';\n\nexport default class ScorioSDK {\n\tprivate readonly apiKey: string;\n\tprivate readonly host: string;\n\tprivate readonly timeout: number;\n\tprivate readonly logger: boolean;\n\tprivate readonly config: ScorioSDKConfig;\n\tprivate readonly http: HttpClientPort;\n\n\tconstructor(config: ScorioSDKConfig, httpClient?: HttpClientPort) {\n\t\tif (!config.apiKey) {\n\t\t\tthrow new Error('apiKey is required');\n\t\t}\n\t\tthis.apiKey = config.apiKey;\n\t\tthis.host = config.debug?.host ?? config.host ?? '';\n\t\tthis.timeout = config.timeout ?? 30_000;\n\t\tthis.logger = config.logger ?? false;\n\t\tthis.config = config;\n\t\tthis.http = httpClient ?? new FetchHttpClient();\n\n\t\tif (!this.host) {\n\t\t\tthrow new Error('host is required (or provide debug.host)');\n\t\t}\n\t}\n\n\tprivate async get<T>(\n\t\tpath: string,\n\t\tparams?: Record<string, string | number | undefined>,\n\t\tisSystem = false,\n\t): Promise<T> {\n\t\tconst url = buildUrl(this.host, path, params);\n\t\tconst headers = buildHeaders(this.apiKey, this.host, this.config.debug, isSystem);\n\n\t\tif (this.logger) {\n\t\t\tconsole.log(`[scorio-sdk] GET ${path}`);\n\t\t}\n\n\t\tconst response = await this.http.request({\n\t\t\tmethod: 'GET',\n\t\t\turl,\n\t\t\theaders,\n\t\t\ttimeout: this.timeout,\n\t\t});\n\n\t\tif (response.status < 200 || response.status >= 300) {\n\t\t\tthrow await mapResponseToError(response.status, response.headers, response.json);\n\t\t}\n\n\t\treturn response.json() as Promise<T>;\n\t}\n\n\t// ─── Catalog ───────────────────────────────────────────────────\n\n\t/**\n\t * List all available sports.\n\t *\n\t * Returns every available sport sorted by display order.\n\t * Use this to build your top-level navigation or sport selector.\n\t *\n\t * @returns List of Sport objects with id, name, alias, and category.\n\t *\n\t * **Plans:** free, starter, pro, business | **Group:** catalog\n\t *\n\t * **Polling:** Cache locally for 60 seconds. The sport catalog changes infrequently.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data, meta } = await client.getSports();\n\t * // data: [{ id, name, alias, category }, ...]\n\t * // meta: { count: number }\n\t * ```\n\t */\n\tasync getSports(): Promise<ListResponse<Sport>> {\n\t\treturn this.get('/api/sports');\n\t}\n\n\t/**\n\t * Sports with live and prematch game counts.\n\t *\n\t * Returns all sports along with the number of currently live and upcoming prematch games for each.\n\t * Perfect for sidebar badges, sport selector counts, or deciding which sport feeds to subscribe to.\n\t *\n\t * @returns List of SportCount objects (Sport + live_count + prematch_count).\n\t *\n\t * **Plans:** free, starter, pro, business | **Group:** catalog\n\t *\n\t * **Polling:** Every 10-30 seconds. Counts change as games go live or finish.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getSportsCounts();\n\t * // data: [{ id, name, alias, category, live_count, prematch_count }, ...]\n\t * ```\n\t */\n\tasync getSportsCounts(): Promise<ListResponse<SportCount>> {\n\t\treturn this.get('/api/sports/counts');\n\t}\n\n\t/**\n\t * List regions for a sport.\n\t *\n\t * Returns all regions (countries or geographic areas) that currently have active games for the specified sport.\n\t * Build the second level of your navigation tree (e.g., England, Germany, Spain for Football).\n\t *\n\t * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.\n\t * @returns List of Region objects with id, name, alias, and sport_id.\n\t *\n\t * **Plans:** free, starter, pro, business | **Group:** catalog\n\t *\n\t * **Polling:** Cache locally for 60 seconds.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getRegions('01KNSZTCFAHSBS092VMZS7PMCW');\n\t * // data: [{ id, name, alias, sport_id }, ...]\n\t * ```\n\t */\n\tasync getRegions(sportId: string): Promise<ListResponse<Region>> {\n\t\treturn this.get(`/api/sports/${sportId}/regions`);\n\t}\n\n\t/**\n\t * List competitions for a region.\n\t *\n\t * Returns all competitions (leagues, tournaments, cups) within the specified region.\n\t * Build the third level of your navigation tree (e.g., Premier League, Championship, FA Cup for England).\n\t *\n\t * @param regionId - Unique region identifier (ULID). Get IDs from {@link getRegions}.\n\t * @returns List of Competition objects with id, name, region_id, and sport_id.\n\t *\n\t * **Plans:** free, starter, pro, business | **Group:** catalog\n\t *\n\t * **Polling:** Cache locally for 60 seconds.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getCompetitions('01JRQ00000ENGLAND0000001');\n\t * // data: [{ id, name, region_id, sport_id }, ...]\n\t * ```\n\t */\n\tasync getCompetitions(regionId: string): Promise<ListResponse<Competition>> {\n\t\treturn this.get(`/api/regions/${regionId}/competitions`);\n\t}\n\n\t/**\n\t * List all competitions for a sport (flat list).\n\t *\n\t * Returns all competitions for the specified sport across ALL regions in one call.\n\t * Useful for building flat competition selectors, search/filter dropdowns, or schedule filters.\n\t *\n\t * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.\n\t * @returns Flat list of Competition objects.\n\t *\n\t * **Plans:** free, starter, pro, business | **Group:** catalog\n\t *\n\t * **Polling:** Cache locally for 60 seconds.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getCompetitionsBySport('01KNSZTCFAHSBS092VMZS7PMCW');\n\t * // data: [{ id: \"...\", name: \"Premier League\", region_id, sport_id }, ...]\n\t * ```\n\t */\n\tasync getCompetitionsBySport(sportId: string): Promise<ListResponse<Competition>> {\n\t\treturn this.get(`/api/sports/${sportId}/competitions`);\n\t}\n\n\t// ─── Live ──────────────────────────────────────────────────────\n\n\t/**\n\t * All live games across all sports.\n\t *\n\t * Returns every currently live (in-play) game in a single call.\n\t * Ideal for multi-sport live tickers, aggregate live feeds, or monitoring total live game volume.\n\t *\n\t * @returns List of Game objects with is_live: true, including scores, stats, and market counts.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** live\n\t *\n\t * **Polling:** Every 1-2 seconds for real-time updates.\n\t * At peak hours this can return 200+ games. If you only need one sport, use {@link getLiveGamesBySport}.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getAllLiveGames();\n\t * // data: [{ id, home_team, away_team, status: \"live\", score_text, info, stats, ... }, ...]\n\t * ```\n\t */\n\tasync getAllLiveGames(): Promise<ListResponse<Game>> {\n\t\treturn this.get('/api/live/games');\n\t}\n\n\t/**\n\t * Live games for a specific sport.\n\t *\n\t * Returns all currently live (in-play) games for the specified sport.\n\t * More efficient than {@link getAllLiveGames} when you only need one sport.\n\t *\n\t * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.\n\t * @returns List of live Game objects filtered to the requested sport.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** live\n\t *\n\t * **Polling:** Every 1-2 seconds for real-time updates.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getLiveGamesBySport('01KNSZTCFAHSBS092VMZS7PMCW');\n\t * // data: [{ id, home_team, away_team, score_text: \"2:1 (67')\", ... }, ...]\n\t * ```\n\t */\n\tasync getLiveGamesBySport(sportId: string): Promise<ListResponse<Game>> {\n\t\treturn this.get(`/api/live/games/${sportId}`);\n\t}\n\n\t/**\n\t * Full live dashboard snapshot.\n\t *\n\t * Returns a complete live dashboard in a single API call: sport counts and all live games combined.\n\t * Combines `/api/sports/counts` and `/api/live/games` to reduce round trips.\n\t *\n\t * @returns LiveSnapshot containing sports (with counts) and all live games.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** live\n\t *\n\t * **Polling:** Every 2-5 seconds. This is a heavier endpoint.\n\t * For granular updates after initial load, prefer individual endpoints.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getLiveSnapshot();\n\t * // data: { sports: [{ id, name, live_count, prematch_count, ... }], games: [...] }\n\t * ```\n\t */\n\tasync getLiveSnapshot(): Promise<SingleResponse<LiveSnapshot>> {\n\t\treturn this.get('/api/live/snapshot');\n\t}\n\n\t// ─── Prematch ──────────────────────────────────────────────────\n\n\t/**\n\t * Prematch games for a sport (paginated).\n\t *\n\t * Returns upcoming (prematch) games for the specified sport with pagination support.\n\t * Sports like Football can have 1500+ prematch games.\n\t *\n\t * @param sportId - Unique sport identifier (ULID). Get IDs from {@link getSports}.\n\t * @param options - Pagination options: limit (1-100, default 50) and offset (default 0).\n\t * @returns Paginated list of prematch Game objects.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** prematch\n\t *\n\t * **Polling:** Every 10-30 seconds. Prematch odds update less frequently than live.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data, meta } = await client.getPrematchGames('01KNSZTCFAHSBS092VMZS7PMCW', { limit: 20 });\n\t * // data: [{ id, home_team, away_team, status: \"prematch\", start_time, ... }, ...]\n\t * ```\n\t */\n\tasync getPrematchGames(\n\t\tsportId: string,\n\t\toptions?: PaginationOptions,\n\t): Promise<ListResponse<Game>> {\n\t\treturn this.get(`/api/prematch/games/${sportId}`, {\n\t\t\tlimit: options?.limit,\n\t\t\toffset: options?.offset,\n\t\t});\n\t}\n\n\t// ─── Games ─────────────────────────────────────────────────────\n\n\t/**\n\t * Games for a competition (paginated).\n\t *\n\t * Returns games for a specific competition with pagination.\n\t * Includes games that have recently started (up to 2 hours ago) and all upcoming games.\n\t *\n\t * @param competitionId - Unique competition identifier (ULID). Get IDs from {@link getCompetitions} or {@link getCompetitionsBySport}.\n\t * @param options - Pagination options: limit (1-100, default 50) and offset (default 0).\n\t * @returns Paginated list of Game objects sorted by start time.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** games\n\t *\n\t * **Polling:** Every 10-30 seconds for prematch, 1-2 seconds if showing live games.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getGamesByCompetition('01KNSZTCFGMM6JE2B9YQFGQCDZ', { limit: 10 });\n\t * ```\n\t */\n\tasync getGamesByCompetition(\n\t\tcompetitionId: string,\n\t\toptions?: PaginationOptions,\n\t): Promise<ListResponse<Game>> {\n\t\treturn this.get(`/api/competitions/${competitionId}/games`, {\n\t\t\tlimit: options?.limit,\n\t\t\toffset: options?.offset,\n\t\t});\n\t}\n\n\t/**\n\t * Games starting within N minutes.\n\t *\n\t * Returns games that are about to start within the specified time window.\n\t * Great for \"Starting Soon\" widgets or last-minute bet notifications.\n\t *\n\t * @param options - minutes: time window (1-180, default 30).\n\t * @returns List of prematch Game objects sorted by start time (soonest first).\n\t *\n\t * **Plans:** starter, pro, business | **Group:** games\n\t *\n\t * **Polling:** Every 30-60 seconds.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getGamesStartingSoon({ minutes: 60 });\n\t * ```\n\t */\n\tasync getGamesStartingSoon(options?: StartingSoonOptions): Promise<ListResponse<Game>> {\n\t\treturn this.get('/api/games/starting-soon', {\n\t\t\tminutes: options?.minutes,\n\t\t});\n\t}\n\n\t/**\n\t * Search games by team name.\n\t *\n\t * Case-insensitive partial match against both home_team and away_team across all sports.\n\t * Sorted by relevance, then by start time.\n\t *\n\t * @param query - Search query string (minimum 2 characters).\n\t * @param options - limit: max results (1-50, default 20).\n\t * @returns List of matching Game objects.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** games\n\t *\n\t * **Polling:** On demand (triggered by user input).\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.searchGames('Arsenal', { limit: 10 });\n\t * ```\n\t */\n\tasync searchGames(query: string, options?: SearchOptions): Promise<ListResponse<Game>> {\n\t\treturn this.get('/api/games/search', {\n\t\t\tq: query,\n\t\t\tlimit: options?.limit,\n\t\t});\n\t}\n\n\t/**\n\t * Full game detail with all markets and odds.\n\t *\n\t * Returns complete data for a single game: metadata, all markets, and all outcomes.\n\t * Everything needed to render a full betting interface.\n\t *\n\t * @param gameId - Unique game identifier (ULID). Get IDs from any endpoint that returns Game objects.\n\t * @returns Single Game object with nested markets and outcomes.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** games\n\t *\n\t * Markets are populated only on **pro** and **business** plans.\n\t * Lower plans receive `markets: []` and `markets_count: 0`.\n\t *\n\t * **Polling:** Every 1-2 seconds for live games, 10-30 seconds for prematch.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getGameDetail('01TESTGAME00000LIVESOC01');\n\t * // data.markets: [{ id, name, type, line, outcomes: [{ id, name, price, is_suspended }] }]\n\t * ```\n\t */\n\tasync getGameDetail(gameId: string): Promise<SingleResponse<Game>> {\n\t\treturn this.get(`/api/games/${gameId}`);\n\t}\n\n\t// ─── Schedule ──────────────────────────────────────────────────\n\n\t/**\n\t * Games by date or date range.\n\t *\n\t * Returns all games scheduled for a specific date or range, optionally filtered by sport or competition.\n\t * Uses UTC dates. Defaults to today if no date params provided.\n\t *\n\t * @param options - date (YYYY-MM-DD), dateFrom/dateTo (max 90 days), sportId, competitionId.\n\t * @returns List of Game objects sorted by start time.\n\t *\n\t * **Plans:** starter, pro, business | **Group:** schedule\n\t *\n\t * **Polling:** Every 60 seconds.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getSchedule({ date: '2026-04-10', sportId: '01KNSZTCFAHSBS092VMZS7PMCW' });\n\t * ```\n\t */\n\tasync getSchedule(options?: ScheduleOptions): Promise<ListResponse<Game>> {\n\t\treturn this.get('/api/schedule', {\n\t\t\tdate: options?.date,\n\t\t\tdate_from: options?.dateFrom,\n\t\t\tdate_to: options?.dateTo,\n\t\t\tsport_id: options?.sportId,\n\t\t\tcompetition_id: options?.competitionId,\n\t\t});\n\t}\n\n\t// ─── Odds Intelligence ─────────────────────────────────────────\n\n\t/**\n\t * Min/max/avg price summary per outcome for a game.\n\t *\n\t * Returns statistical summary of odds for every outcome: min, max, average price, and number of changes.\n\t * Useful for identifying value bets, overreactions, and market inefficiencies.\n\t *\n\t * @param gameId - Unique game identifier (ULID).\n\t * @returns List of OddsSummary objects, one per outcome.\n\t *\n\t * **Plans:** pro, business | **Group:** odds\n\t *\n\t * **Polling:** Every 10-30 seconds.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getGameOddsSummary('01TESTGAME00000LIVESOC01');\n\t * // data: [{ event_id, event_name, current_price, min_price, max_price, avg_price, change_count }]\n\t * ```\n\t */\n\tasync getGameOddsSummary(gameId: string): Promise<ListResponse<OddsSummary>> {\n\t\treturn this.get(`/api/games/${gameId}/odds-summary`);\n\t}\n\n\t/**\n\t * Top odds movements (biggest price changes).\n\t *\n\t * Returns outcomes with the largest odds movements within a given time window.\n\t * Key differentiator for professional bettors and odds aggregation platforms.\n\t *\n\t * @param options - minutes: look-back window (1-1440, default 60), limit: max results (1-100, default 20).\n\t * @returns List of OddsMover objects sorted by absolute change percentage (largest first).\n\t *\n\t * **Plans:** pro, business | **Group:** odds\n\t *\n\t * **Polling:** Every 5-10 seconds for near-real-time steam move alerts.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getOddsMovers({ minutes: 30, limit: 10 });\n\t * // data: [{ event_id, game_id, current_price, opening_price, price_change, change_percent, ... }]\n\t * ```\n\t */\n\tasync getOddsMovers(options?: OddsMoversOptions): Promise<ListResponse<OddsMover>> {\n\t\treturn this.get('/api/odds/movers', {\n\t\t\tminutes: options?.minutes,\n\t\t\tlimit: options?.limit,\n\t\t});\n\t}\n\n\t/**\n\t * Historical price changes for an outcome.\n\t *\n\t * Returns the full history of price changes for a specific outcome (selection).\n\t * Build odds movement charts, sparklines, or trend analysis visualizations.\n\t * 60-day retention, tick-level granularity.\n\t *\n\t * @param eventId - Unique outcome identifier (ULID). Get IDs from Game objects with markets.\n\t * @param options - from/to (unix timestamps), limit (1-5000, default 500).\n\t * @returns Chronological list of PricePoint objects (oldest first).\n\t *\n\t * **Plans:** pro, business | **Group:** odds\n\t *\n\t * **Polling:** On demand.\n\t *\n\t * @example\n\t * ```ts\n\t * const { data } = await client.getEventPriceHistory('01TESTEVT00000ARS0NAL01', { limit: 100 });\n\t * // data: [{ price, previous_price, recorded_at }, ...]\n\t * ```\n\t */\n\tasync getEventPriceHistory(\n\t\teventId: string,\n\t\toptions?: PriceHistoryOptions,\n\t): Promise<ListResponse<PricePoint>> {\n\t\treturn this.get(`/api/events/${eventId}/price-history`, {\n\t\t\tfrom: options?.from,\n\t\t\tto: options?.to,\n\t\t\tlimit: options?.limit,\n\t\t});\n\t}\n\n\t// ─── System ────────────────────────────────────────────────────\n\n\t/**\n\t * Health check.\n\t *\n\t * Returns the current health status of the API. No authentication required.\n\t * Use in load balancer health checks, uptime monitoring, or status pages.\n\t *\n\t * @returns `{ status: \"ok\" }` when the API is healthy.\n\t *\n\t * **Plans:** all (no authentication required) | **Group:** system\n\t *\n\t * **Polling:** Every 30-60 seconds from your monitoring system.\n\t */\n\tasync health(): Promise<HealthResponse> {\n\t\treturn this.get('/api/health', undefined, true);\n\t}\n\n\t/**\n\t * Ping.\n\t *\n\t * Simple connectivity check. No authentication required.\n\t * Quick liveness probe for monitoring or client-side connectivity checks.\n\t *\n\t * @returns `{ pong: true }`\n\t *\n\t * **Plans:** all (no authentication required) | **Group:** system\n\t */\n\tasync ping(): Promise<PingResponse> {\n\t\treturn this.get('/api/ping', undefined, true);\n\t}\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@scorio/client-sdk",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript SDK for the Scorio Sports API — real-time odds, live scores, and historical price analytics across 80+ sports",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "sports",
21
+ "betting",
22
+ "odds",
23
+ "live-scores",
24
+ "sports-data",
25
+ "sportsbook",
26
+ "sports-api",
27
+ "odds-api",
28
+ "betting-api",
29
+ "live-betting",
30
+ "prematch",
31
+ "price-history",
32
+ "football",
33
+ "basketball",
34
+ "tennis",
35
+ "esports",
36
+ "rapidapi",
37
+ "sdk",
38
+ "typescript"
39
+ ],
40
+ "author": "Scorio <support@sportsdataapi.com>",
41
+ "license": "MIT",
42
+ "homepage": "https://github.com/ts-ign0re/scorio#readme",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/ts-ign0re/scorio.git",
46
+ "directory": "packages/scorio-sdk"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "check": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "test:integration": "vitest run src/__tests__/integration/",
54
+ "test:build": "tsup && vitest run -c vitest.build.config.ts src/__tests__/integration/",
55
+ "clean": "rm -rf dist *.tsbuildinfo"
56
+ },
57
+ "devDependencies": {
58
+ "tsup": "^8.4.0",
59
+ "vitest": "^4.1.4"
60
+ }
61
+ }