@ufcalendar/sdk 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +170 -2
- package/dist/index.d.ts +170 -2
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -28,7 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
29
|
|
|
30
30
|
// src/version.ts
|
|
31
|
-
var VERSION = "0.
|
|
31
|
+
var VERSION = "0.2.0";
|
|
32
32
|
|
|
33
33
|
// src/client.ts
|
|
34
34
|
var DEFAULT_BASE_URL = "https://api.ufcalendar.com/v1";
|
|
@@ -199,6 +199,12 @@ var FightAPI = class {
|
|
|
199
199
|
eventChanges(idOrSlug) {
|
|
200
200
|
return this.get(`events/${encodeURIComponent(String(idOrSlug))}/changes`);
|
|
201
201
|
}
|
|
202
|
+
/** Latest real-time LiveState snapshot on fight night (Business+), or null
|
|
203
|
+
* when nothing is being streamed. The WebSocket at wss://live.ufcalendar.com/v1
|
|
204
|
+
* pushes the same document as it changes. */
|
|
205
|
+
eventLive(idOrSlug) {
|
|
206
|
+
return this.get(`events/${encodeURIComponent(String(idOrSlug))}/live`);
|
|
207
|
+
}
|
|
202
208
|
/* -------------------------------------------------------------- fights */
|
|
203
209
|
fight(fightId) {
|
|
204
210
|
return this.get(`fights/${encodeURIComponent(String(fightId))}`);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/client.ts"],"sourcesContent":["export { FightAPI, FightAPIError, DEFAULT_BASE_URL } from './client';\nexport type { FightAPIOptions, Query, QueryValue } from './client';\nexport { VERSION } from './version';\nexport type * from './types';\n/** The generated OpenAPI path map, for anything the hand-written types above\n * do not cover. Regenerated by `pnpm gen:types` from apps/api/openapi.json. */\nexport type { paths, operations } from './schema';\n","/** Kept in lockstep with package.json and the User-Agent by\n * `apps/api/src/surfaces.test.ts`. Bump all three together. */\nexport const VERSION = '0.1.1';\n","/**\n * Thin, dependency-free client for https://api.ufcalendar.com/v1.\n *\n * Every method returns the parsed `data` value of the JSON envelope\n * (`{\"data\": ..., \"meta\": ...}`); list endpoints are async generators that\n * follow cursor pagination for you. Errors throw `FightAPIError` carrying\n * the API's `code`, `message` and `requestId` — quote the request id when\n * you write to api@ufcalendar.com.\n *\n * Mirrors sdk/python/ufcalendar/client.py one method per endpoint, in\n * camelCase. The two clients are kept in lockstep by\n * `apps/api/src/surfaces.test.ts`.\n *\n * The API serves no betting odds, by design. Fighter `images` are Wikimedia\n * Commons / Creative Commons files — the `license` and `artist` fields you\n * receive must be displayed as a credit.\n */\nimport { VERSION } from './version';\nimport type {\n BroadcastRight,\n CareerBout,\n CareerStats,\n Envelope,\n EventChange,\n EventDetail,\n EventSummary,\n Fight,\n Fighter,\n FighterSummary,\n Judge,\n Meta,\n Org,\n Plans,\n PowerIndex,\n RankingsBoard,\n RateLimit,\n Scorecards,\n Usage,\n Venue,\n WebhookEndpoint,\n} from './types';\n\nexport const DEFAULT_BASE_URL = 'https://api.ufcalendar.com/v1';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport interface FightAPIOptions {\n /** `ufcalendar_…` key from https://www.ufcalendar.com/account/api. Falls back\n * to `UFCALENDAR_API_KEY` / `UFCAL_API_KEY` in the environment. Only\n * `plans()` works without one. */\n apiKey?: string;\n /** Override for testing; defaults to the production `/v1`. */\n baseUrl?: string;\n /** Inject a fetch (a test double, an instrumented fetch, undici). */\n fetch?: typeof fetch;\n /** Per-request timeout. Default 30s. */\n timeoutMs?: number;\n /** Extra headers on every request. */\n headers?: Record<string, string>;\n}\n\n/** An error response from the Fight API. */\nexport class FightAPIError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId: string | null;\n\n constructor(status: number, code: string, message: string, requestId: string | null = null) {\n super(`${status} ${code}: ${message}${requestId ? ` (request_id=${requestId})` : ''}`);\n this.name = 'FightAPIError';\n this.status = status;\n this.code = code;\n this.requestId = requestId;\n }\n}\n\nexport type QueryValue = string | number | boolean | null | undefined;\nexport type Query = Record<string, QueryValue>;\n\ninterface PageOptions {\n /** Stop after this many rows (across pages). */\n limit?: number;\n}\n\n/**\n * A browser forbids setting User-Agent on fetch, so we only set ours\n * off-browser. The test is `document`, NOT `navigator`: Node 21+ ships a\n * global `navigator`, so a navigator check would silently drop the header\n * on every modern Node runtime — which is the one place it works.\n */\nfunction canSetUserAgent(): boolean {\n return typeof document === 'undefined' && typeof window === 'undefined';\n}\n\nfunction envKey(): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;\n return env?.UFCALENDAR_API_KEY || env?.UFCAL_API_KEY;\n}\n\nexport class FightAPI {\n readonly baseUrl: string;\n readonly apiKey: string | undefined;\n\n /** `meta` of the most recent response (pagination cursor, generated_at …). */\n lastMeta: Meta | null = null;\n /** `X-RateLimit-*` of the most recent response. */\n lastRateLimit: RateLimit = { limit: null, remaining: null, reset: null };\n\n readonly #fetch: typeof fetch;\n readonly #timeoutMs: number;\n readonly #headers: Record<string, string>;\n\n constructor(apiKeyOrOptions?: string | FightAPIOptions, options: FightAPIOptions = {}) {\n const opts: FightAPIOptions =\n typeof apiKeyOrOptions === 'string'\n ? { ...options, apiKey: apiKeyOrOptions }\n : { ...options, ...(apiKeyOrOptions ?? {}) };\n this.apiKey = opts.apiKey ?? envKey();\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#fetch = opts.fetch ?? globalThis.fetch;\n this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#headers = opts.headers ?? {};\n if (!this.#fetch) {\n throw new Error('No global fetch available. Use Node 18+ or pass { fetch }.');\n }\n }\n\n /* ---------------------------------------------------------------- core */\n\n #url(path: string, params?: Query): string {\n const url = new URL(`${this.baseUrl}/${path.replace(/^\\/+/, '')}`);\n for (const [k, v] of Object.entries(params ?? {})) {\n if (v === undefined || v === null) continue;\n url.searchParams.set(k, String(v));\n }\n return url.toString();\n }\n\n async #request<T>(\n method: string,\n path: string,\n { params, body, open = false }: { params?: Query; body?: unknown; open?: boolean } = {},\n ): Promise<Envelope<T>> {\n if (!this.apiKey && !open) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n const headers: Record<string, string> = { Accept: 'application/json', ...this.#headers };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n if (canSetUserAgent()) headers['User-Agent'] = `ufcalendar-typescript/${VERSION}`;\n if (body !== undefined) headers['Content-Type'] = 'application/json';\n\n let res: Response;\n try {\n res = await this.#fetch(this.#url(path, params), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.#timeoutMs),\n });\n } catch (e) {\n const err = e as { name?: string; message?: string };\n if (err?.name === 'TimeoutError') {\n throw new FightAPIError(0, 'timeout', `Request to ${path} timed out after ${this.#timeoutMs}ms`);\n }\n throw new FightAPIError(0, 'network_error', err?.message ?? String(e));\n }\n\n this.lastRateLimit = {\n limit: res.headers.get('X-RateLimit-Limit'),\n remaining: res.headers.get('X-RateLimit-Remaining'),\n reset: res.headers.get('X-RateLimit-Reset'),\n };\n\n if (res.status === 204) {\n this.lastMeta = null;\n return { data: undefined as T };\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = undefined;\n }\n\n if (!res.ok) {\n const err = (parsed as { error?: { code?: string; message?: string; request_id?: string } } | undefined)?.error;\n if (err) {\n throw new FightAPIError(\n res.status,\n String(err.code ?? 'error'),\n String(err.message ?? text.slice(0, 200)),\n err.request_id ?? res.headers.get('x-request-id'),\n );\n }\n throw new FightAPIError(res.status, 'http_error', text.slice(0, 200), res.headers.get('x-request-id'));\n }\n\n const env = (parsed ?? {}) as Envelope<T>;\n this.lastMeta = env.meta ?? null;\n return env;\n }\n\n /** Raw GET returning the `data` value. Escape hatch for new endpoints. */\n async get<T = unknown>(path: string, params?: Query): Promise<T> {\n return (await this.#request<T>('GET', path, { params })).data;\n }\n\n /** Raw GET returning the whole `{data, meta}` envelope. */\n async getWithMeta<T = unknown>(path: string, params?: Query): Promise<Envelope<T>> {\n return this.#request<T>('GET', path, { params });\n }\n\n async *#paginate<T>(path: string, params: Query, opts: PageOptions = {}): AsyncGenerator<T, void, void> {\n // Never fetch a bigger page than the caller wants rows: `limit: 3` used\n // to pull 100 rows and throw 97 away, burning the caller's quota on a\n // request they did not ask for (and, on a trial key, a noticeable slice\n // of the 100 they get).\n const pageSize = Math.min(100, opts.limit ?? 100);\n const query: Query = { limit: pageSize, ...params };\n let seen = 0;\n for (;;) {\n const env = await this.#request<T[]>('GET', path, { params: query });\n for (const row of env.data ?? []) {\n yield row;\n seen += 1;\n if (opts.limit !== undefined && seen >= opts.limit) return;\n }\n const cursor = env.meta?.pagination?.next_cursor;\n if (!cursor) return;\n query.cursor = cursor;\n }\n }\n\n /* --------------------------------------------------------------- plans */\n\n /**\n * Plans, quotas, the free 1-day trial rule, the MCP endpoint and the doc\n * links. The only endpoint that answers without a credential.\n */\n async plans(): Promise<Plans> {\n return (await this.#request<Plans>('GET', 'plans', { open: true })).data;\n }\n\n /* ---------------------------------------------------------------- orgs */\n\n /** Launch orgs with capability flags (stats / rounds / rankings / broadcasts / predictions). */\n orgs(): Promise<Org[]> {\n return this.get<Org[]>('orgs');\n }\n\n org(slug: string): Promise<Org> {\n return this.get<Org>(`orgs/${encodeURIComponent(slug)}`);\n }\n\n /* -------------------------------------------------------------- events */\n\n /**\n * Schedule + results. Bare call = the upcoming calendar, soonest first.\n * `status: 'completed'` (or `order: 'desc'`) browses the archive\n * newest-first. `from` / `to` are `YYYY-MM-DD`.\n */\n events(\n opts: {\n org?: string;\n status?: string;\n from?: string;\n to?: string;\n order?: 'asc' | 'desc';\n limit?: number;\n } = {},\n ): AsyncGenerator<EventSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<EventSummary>('events', params as Query, { limit });\n }\n\n /** One event with its full fight card, venue and broadcasts. */\n event(idOrSlug: string | number): Promise<EventDetail> {\n return this.get<EventDetail>(`events/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Card-change diff log (fight added/removed, opponent swapped, date moved, profile merged). */\n eventChanges(idOrSlug: string | number): Promise<EventChange[]> {\n return this.get<EventChange[]>(`events/${encodeURIComponent(String(idOrSlug))}/changes`);\n }\n\n /* -------------------------------------------------------------- fights */\n\n fight(fightId: number | string): Promise<Fight> {\n return this.get<Fight>(`fights/${encodeURIComponent(String(fightId))}`);\n }\n\n /** Per-fight totals for both corners. */\n fightStats(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/stats`);\n }\n\n /** Round-by-round stat lines for both corners. */\n fightRounds(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/rounds`);\n }\n\n /**\n * The judges' scorecards for a bout — the official commission record:\n * decision type, point deductions, and one card per judge with every\n * round, the totals and `winner_fighter_id`. Scores are oriented to\n * `fighter_a_id` / `fighter_b_id`, both repeated on the payload.\n *\n * Check `scores_known` on a card before charting totals: when it is false\n * the commission published only the outcome. Throws 404 for a bout that\n * did not go to the judges.\n */\n fightScorecards(fightId: number | string): Promise<Scorecards> {\n return this.get<Scorecards>(`fights/${encodeURIComponent(String(fightId))}/scorecards`);\n }\n\n /* -------------------------------------------------------------- judges */\n\n /**\n * Every official who has scored a launch-org bout, busiest first. Rates\n * mean little below ~10 fights; pass `minFights: 10`.\n */\n judges(\n opts: { q?: string; org?: string; minFights?: number; limit?: number } = {},\n ): AsyncGenerator<Judge, void, void> {\n const { limit, minFights, ...rest } = opts;\n return this.#paginate<Judge>('judges', { ...rest, min_fights: minFights } as Query, { limit });\n }\n\n judge(judgeId: number | string): Promise<Judge> {\n return this.get<Judge>(`judges/${encodeURIComponent(String(judgeId))}`);\n }\n\n /** Every card this judge has turned in, newest first. */\n judgeScorecards(\n judgeId: number | string,\n opts: { limit?: number } = {},\n ): AsyncGenerator<Record<string, unknown>, void, void> {\n return this.#paginate<Record<string, unknown>>(\n `judges/${encodeURIComponent(String(judgeId))}/scorecards`,\n {},\n opts,\n );\n }\n\n /* ------------------------------------------------------------ fighters */\n\n fighters(\n opts: { q?: string; org?: string; country?: string; limit?: number } = {},\n ): AsyncGenerator<FighterSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<FighterSummary>('fighters', params as Query, { limit });\n }\n\n /** Bio, records, career stats, Power Index and CC-licensed images. */\n fighter(idOrSlug: string | number): Promise<Fighter> {\n return this.get<Fighter>(`fighters/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Complete multi-promotion career timeline. */\n fighterHistory(idOrSlug: string | number): Promise<CareerBout[]> {\n return this.get<CareerBout[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/history`);\n }\n\n /** Career statistics, one source-stamped row per scope (`pro-mma`, `ufc-only` …). */\n fighterStats(idOrSlug: string | number): Promise<CareerStats[]> {\n return this.get<CareerStats[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/stats`);\n }\n\n /** Every official ranking row the fighter ever held, newest first. */\n fighterRankings(idOrSlug: string | number): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/rankings`);\n }\n\n fighterPowerIndex(idOrSlug: string | number): Promise<PowerIndex & Record<string, unknown>> {\n return this.get<PowerIndex & Record<string, unknown>>(\n `fighters/${encodeURIComponent(String(idOrSlug))}/power-index`,\n );\n }\n\n /* ------------------------------------------------------------ rankings */\n\n /**\n * Official board, point-in-time. `date: 'YYYY-MM-DD'` returns the board\n * that was valid on that day (UFC history back to 2013; rank 0 = champion).\n */\n rankings(org = 'ufc', opts: { date?: string; board?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(`rankings/${encodeURIComponent(org)}`, opts as Query);\n }\n\n divisionRankings(org: string, division: string, opts: { date?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(\n `rankings/${encodeURIComponent(org)}/${encodeURIComponent(division)}`,\n opts as Query,\n );\n }\n\n /** Current champions across every launch org. */\n champions(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('champions');\n }\n\n /** UFCalendar Power Index board. */\n powerIndex(org = 'ufc'): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>(`power-index/${encodeURIComponent(org)}`);\n }\n\n /* ---------------------------------------------------------------- misc */\n\n /** Model win probabilities for upcoming UFC bouts. */\n predictionsUpcoming(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('predictions/upcoming');\n }\n\n /** Who airs the promotion, per ISO-2 country. */\n broadcastRights(org = 'ufc', opts: { country?: string } = {}): Promise<BroadcastRight[]> {\n return this.get<BroadcastRight[]>(`broadcast-rights/${encodeURIComponent(org)}`, opts as Query);\n }\n\n venue(venueId: number | string): Promise<Venue> {\n return this.get<Venue>(`venues/${encodeURIComponent(String(venueId))}`);\n }\n\n /** Typeahead across fighters and events. */\n search(q: string): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>('search', { q });\n }\n\n /** Your key's month-to-date quota usage. */\n usage(): Promise<Usage> {\n return this.get<Usage>('usage');\n }\n\n /**\n * Subscribable ICS feed URL for calendar apps (authenticates via `?key=`).\n *\n * Throws without a key rather than returning `?key=`: that empty URL is\n * pasted into a calendar app, fails there hours later, and the failure\n * surfaces nowhere near this call.\n */\n calendarIcsUrl(org = 'ufc'): string {\n if (!this.apiKey) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n return `${this.baseUrl}/calendar/${encodeURIComponent(org)}.ics?key=${encodeURIComponent(this.apiKey)}`;\n }\n\n /* ------------------------------------------------------------ webhooks */\n\n webhookEndpoints(): Promise<WebhookEndpoint[]> {\n return this.get<WebhookEndpoint[]>('webhook-endpoints');\n }\n\n /**\n * Register a signed webhook (Pro and up). `events` ⊆ `event.announced`,\n * `fight.result`, `card.changed`, `event.completed`. The signing secret is\n * returned ONCE, in this response.\n */\n async createWebhookEndpoint(url: string, events?: string[]): Promise<WebhookEndpoint> {\n const body: Record<string, unknown> = { url };\n if (events?.length) body.events = events;\n return (await this.#request<WebhookEndpoint>('POST', 'webhook-endpoints', { body })).data;\n }\n\n async deleteWebhookEndpoint(endpointId: number | string): Promise<void> {\n await this.#request<unknown>('DELETE', `webhook-endpoints/${encodeURIComponent(String(endpointId))}`);\n }\n\n async rotateWebhookSecret(endpointId: number | string): Promise<WebhookEndpoint> {\n return (\n await this.#request<WebhookEndpoint>(\n 'POST',\n `webhook-endpoints/${encodeURIComponent(String(endpointId))}/rotate-secret`,\n )\n ).data;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/client.ts"],"sourcesContent":["export { FightAPI, FightAPIError, DEFAULT_BASE_URL } from './client';\nexport type { FightAPIOptions, Query, QueryValue } from './client';\nexport { VERSION } from './version';\nexport type * from './types';\n/** The generated OpenAPI path map, for anything the hand-written types above\n * do not cover. Regenerated by `pnpm gen:types` from apps/api/openapi.json. */\nexport type { paths, operations } from './schema';\n","/** Kept in lockstep with package.json and the User-Agent by\n * `apps/api/src/surfaces.test.ts`. Bump all three together. */\nexport const VERSION = '0.2.0';\n","/**\n * Thin, dependency-free client for https://api.ufcalendar.com/v1.\n *\n * Every method returns the parsed `data` value of the JSON envelope\n * (`{\"data\": ..., \"meta\": ...}`); list endpoints are async generators that\n * follow cursor pagination for you. Errors throw `FightAPIError` carrying\n * the API's `code`, `message` and `requestId` — quote the request id when\n * you write to api@ufcalendar.com.\n *\n * Mirrors sdk/python/ufcalendar/client.py one method per endpoint, in\n * camelCase. The two clients are kept in lockstep by\n * `apps/api/src/surfaces.test.ts`.\n *\n * The API serves no betting odds, by design. Fighter `images` are Wikimedia\n * Commons / Creative Commons files — the `license` and `artist` fields you\n * receive must be displayed as a credit.\n */\nimport { VERSION } from './version';\nimport type {\n BroadcastRight,\n CareerBout,\n CareerStats,\n Envelope,\n EventChange,\n EventDetail,\n EventSummary,\n Fight,\n Fighter,\n FighterSummary,\n Judge,\n Meta,\n Org,\n Plans,\n PowerIndex,\n RankingsBoard,\n RateLimit,\n Scorecards,\n Usage,\n Venue,\n WebhookEndpoint,\n LiveState,\n} from './types';\n\nexport const DEFAULT_BASE_URL = 'https://api.ufcalendar.com/v1';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport interface FightAPIOptions {\n /** `ufcalendar_…` key from https://www.ufcalendar.com/account/api. Falls back\n * to `UFCALENDAR_API_KEY` / `UFCAL_API_KEY` in the environment. Only\n * `plans()` works without one. */\n apiKey?: string;\n /** Override for testing; defaults to the production `/v1`. */\n baseUrl?: string;\n /** Inject a fetch (a test double, an instrumented fetch, undici). */\n fetch?: typeof fetch;\n /** Per-request timeout. Default 30s. */\n timeoutMs?: number;\n /** Extra headers on every request. */\n headers?: Record<string, string>;\n}\n\n/** An error response from the Fight API. */\nexport class FightAPIError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId: string | null;\n\n constructor(status: number, code: string, message: string, requestId: string | null = null) {\n super(`${status} ${code}: ${message}${requestId ? ` (request_id=${requestId})` : ''}`);\n this.name = 'FightAPIError';\n this.status = status;\n this.code = code;\n this.requestId = requestId;\n }\n}\n\nexport type QueryValue = string | number | boolean | null | undefined;\nexport type Query = Record<string, QueryValue>;\n\ninterface PageOptions {\n /** Stop after this many rows (across pages). */\n limit?: number;\n}\n\n/**\n * A browser forbids setting User-Agent on fetch, so we only set ours\n * off-browser. The test is `document`, NOT `navigator`: Node 21+ ships a\n * global `navigator`, so a navigator check would silently drop the header\n * on every modern Node runtime — which is the one place it works.\n */\nfunction canSetUserAgent(): boolean {\n return typeof document === 'undefined' && typeof window === 'undefined';\n}\n\nfunction envKey(): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;\n return env?.UFCALENDAR_API_KEY || env?.UFCAL_API_KEY;\n}\n\nexport class FightAPI {\n readonly baseUrl: string;\n readonly apiKey: string | undefined;\n\n /** `meta` of the most recent response (pagination cursor, generated_at …). */\n lastMeta: Meta | null = null;\n /** `X-RateLimit-*` of the most recent response. */\n lastRateLimit: RateLimit = { limit: null, remaining: null, reset: null };\n\n readonly #fetch: typeof fetch;\n readonly #timeoutMs: number;\n readonly #headers: Record<string, string>;\n\n constructor(apiKeyOrOptions?: string | FightAPIOptions, options: FightAPIOptions = {}) {\n const opts: FightAPIOptions =\n typeof apiKeyOrOptions === 'string'\n ? { ...options, apiKey: apiKeyOrOptions }\n : { ...options, ...(apiKeyOrOptions ?? {}) };\n this.apiKey = opts.apiKey ?? envKey();\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#fetch = opts.fetch ?? globalThis.fetch;\n this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#headers = opts.headers ?? {};\n if (!this.#fetch) {\n throw new Error('No global fetch available. Use Node 18+ or pass { fetch }.');\n }\n }\n\n /* ---------------------------------------------------------------- core */\n\n #url(path: string, params?: Query): string {\n const url = new URL(`${this.baseUrl}/${path.replace(/^\\/+/, '')}`);\n for (const [k, v] of Object.entries(params ?? {})) {\n if (v === undefined || v === null) continue;\n url.searchParams.set(k, String(v));\n }\n return url.toString();\n }\n\n async #request<T>(\n method: string,\n path: string,\n { params, body, open = false }: { params?: Query; body?: unknown; open?: boolean } = {},\n ): Promise<Envelope<T>> {\n if (!this.apiKey && !open) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n const headers: Record<string, string> = { Accept: 'application/json', ...this.#headers };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n if (canSetUserAgent()) headers['User-Agent'] = `ufcalendar-typescript/${VERSION}`;\n if (body !== undefined) headers['Content-Type'] = 'application/json';\n\n let res: Response;\n try {\n res = await this.#fetch(this.#url(path, params), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.#timeoutMs),\n });\n } catch (e) {\n const err = e as { name?: string; message?: string };\n if (err?.name === 'TimeoutError') {\n throw new FightAPIError(0, 'timeout', `Request to ${path} timed out after ${this.#timeoutMs}ms`);\n }\n throw new FightAPIError(0, 'network_error', err?.message ?? String(e));\n }\n\n this.lastRateLimit = {\n limit: res.headers.get('X-RateLimit-Limit'),\n remaining: res.headers.get('X-RateLimit-Remaining'),\n reset: res.headers.get('X-RateLimit-Reset'),\n };\n\n if (res.status === 204) {\n this.lastMeta = null;\n return { data: undefined as T };\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = undefined;\n }\n\n if (!res.ok) {\n const err = (parsed as { error?: { code?: string; message?: string; request_id?: string } } | undefined)?.error;\n if (err) {\n throw new FightAPIError(\n res.status,\n String(err.code ?? 'error'),\n String(err.message ?? text.slice(0, 200)),\n err.request_id ?? res.headers.get('x-request-id'),\n );\n }\n throw new FightAPIError(res.status, 'http_error', text.slice(0, 200), res.headers.get('x-request-id'));\n }\n\n const env = (parsed ?? {}) as Envelope<T>;\n this.lastMeta = env.meta ?? null;\n return env;\n }\n\n /** Raw GET returning the `data` value. Escape hatch for new endpoints. */\n async get<T = unknown>(path: string, params?: Query): Promise<T> {\n return (await this.#request<T>('GET', path, { params })).data;\n }\n\n /** Raw GET returning the whole `{data, meta}` envelope. */\n async getWithMeta<T = unknown>(path: string, params?: Query): Promise<Envelope<T>> {\n return this.#request<T>('GET', path, { params });\n }\n\n async *#paginate<T>(path: string, params: Query, opts: PageOptions = {}): AsyncGenerator<T, void, void> {\n // Never fetch a bigger page than the caller wants rows: `limit: 3` used\n // to pull 100 rows and throw 97 away, burning the caller's quota on a\n // request they did not ask for (and, on a trial key, a noticeable slice\n // of the 100 they get).\n const pageSize = Math.min(100, opts.limit ?? 100);\n const query: Query = { limit: pageSize, ...params };\n let seen = 0;\n for (;;) {\n const env = await this.#request<T[]>('GET', path, { params: query });\n for (const row of env.data ?? []) {\n yield row;\n seen += 1;\n if (opts.limit !== undefined && seen >= opts.limit) return;\n }\n const cursor = env.meta?.pagination?.next_cursor;\n if (!cursor) return;\n query.cursor = cursor;\n }\n }\n\n /* --------------------------------------------------------------- plans */\n\n /**\n * Plans, quotas, the free 1-day trial rule, the MCP endpoint and the doc\n * links. The only endpoint that answers without a credential.\n */\n async plans(): Promise<Plans> {\n return (await this.#request<Plans>('GET', 'plans', { open: true })).data;\n }\n\n /* ---------------------------------------------------------------- orgs */\n\n /** Launch orgs with capability flags (stats / rounds / rankings / broadcasts / predictions). */\n orgs(): Promise<Org[]> {\n return this.get<Org[]>('orgs');\n }\n\n org(slug: string): Promise<Org> {\n return this.get<Org>(`orgs/${encodeURIComponent(slug)}`);\n }\n\n /* -------------------------------------------------------------- events */\n\n /**\n * Schedule + results. Bare call = the upcoming calendar, soonest first.\n * `status: 'completed'` (or `order: 'desc'`) browses the archive\n * newest-first. `from` / `to` are `YYYY-MM-DD`.\n */\n events(\n opts: {\n org?: string;\n status?: string;\n from?: string;\n to?: string;\n order?: 'asc' | 'desc';\n limit?: number;\n } = {},\n ): AsyncGenerator<EventSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<EventSummary>('events', params as Query, { limit });\n }\n\n /** One event with its full fight card, venue and broadcasts. */\n event(idOrSlug: string | number): Promise<EventDetail> {\n return this.get<EventDetail>(`events/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Card-change diff log (fight added/removed, opponent swapped, date moved, profile merged). */\n eventChanges(idOrSlug: string | number): Promise<EventChange[]> {\n return this.get<EventChange[]>(`events/${encodeURIComponent(String(idOrSlug))}/changes`);\n }\n\n /** Latest real-time LiveState snapshot on fight night (Business+), or null\n * when nothing is being streamed. The WebSocket at wss://live.ufcalendar.com/v1\n * pushes the same document as it changes. */\n eventLive(idOrSlug: string | number): Promise<LiveState | null> {\n return this.get<LiveState | null>(`events/${encodeURIComponent(String(idOrSlug))}/live`);\n }\n\n /* -------------------------------------------------------------- fights */\n\n fight(fightId: number | string): Promise<Fight> {\n return this.get<Fight>(`fights/${encodeURIComponent(String(fightId))}`);\n }\n\n /** Per-fight totals for both corners. */\n fightStats(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/stats`);\n }\n\n /** Round-by-round stat lines for both corners. */\n fightRounds(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/rounds`);\n }\n\n /**\n * The judges' scorecards for a bout — the official commission record:\n * decision type, point deductions, and one card per judge with every\n * round, the totals and `winner_fighter_id`. Scores are oriented to\n * `fighter_a_id` / `fighter_b_id`, both repeated on the payload.\n *\n * Check `scores_known` on a card before charting totals: when it is false\n * the commission published only the outcome. Throws 404 for a bout that\n * did not go to the judges.\n */\n fightScorecards(fightId: number | string): Promise<Scorecards> {\n return this.get<Scorecards>(`fights/${encodeURIComponent(String(fightId))}/scorecards`);\n }\n\n /* -------------------------------------------------------------- judges */\n\n /**\n * Every official who has scored a launch-org bout, busiest first. Rates\n * mean little below ~10 fights; pass `minFights: 10`.\n */\n judges(\n opts: { q?: string; org?: string; minFights?: number; limit?: number } = {},\n ): AsyncGenerator<Judge, void, void> {\n const { limit, minFights, ...rest } = opts;\n return this.#paginate<Judge>('judges', { ...rest, min_fights: minFights } as Query, { limit });\n }\n\n judge(judgeId: number | string): Promise<Judge> {\n return this.get<Judge>(`judges/${encodeURIComponent(String(judgeId))}`);\n }\n\n /** Every card this judge has turned in, newest first. */\n judgeScorecards(\n judgeId: number | string,\n opts: { limit?: number } = {},\n ): AsyncGenerator<Record<string, unknown>, void, void> {\n return this.#paginate<Record<string, unknown>>(\n `judges/${encodeURIComponent(String(judgeId))}/scorecards`,\n {},\n opts,\n );\n }\n\n /* ------------------------------------------------------------ fighters */\n\n fighters(\n opts: { q?: string; org?: string; country?: string; limit?: number } = {},\n ): AsyncGenerator<FighterSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<FighterSummary>('fighters', params as Query, { limit });\n }\n\n /** Bio, records, career stats, Power Index and CC-licensed images. */\n fighter(idOrSlug: string | number): Promise<Fighter> {\n return this.get<Fighter>(`fighters/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Complete multi-promotion career timeline. */\n fighterHistory(idOrSlug: string | number): Promise<CareerBout[]> {\n return this.get<CareerBout[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/history`);\n }\n\n /** Career statistics, one source-stamped row per scope (`pro-mma`, `ufc-only` …). */\n fighterStats(idOrSlug: string | number): Promise<CareerStats[]> {\n return this.get<CareerStats[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/stats`);\n }\n\n /** Every official ranking row the fighter ever held, newest first. */\n fighterRankings(idOrSlug: string | number): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/rankings`);\n }\n\n fighterPowerIndex(idOrSlug: string | number): Promise<PowerIndex & Record<string, unknown>> {\n return this.get<PowerIndex & Record<string, unknown>>(\n `fighters/${encodeURIComponent(String(idOrSlug))}/power-index`,\n );\n }\n\n /* ------------------------------------------------------------ rankings */\n\n /**\n * Official board, point-in-time. `date: 'YYYY-MM-DD'` returns the board\n * that was valid on that day (UFC history back to 2013; rank 0 = champion).\n */\n rankings(org = 'ufc', opts: { date?: string; board?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(`rankings/${encodeURIComponent(org)}`, opts as Query);\n }\n\n divisionRankings(org: string, division: string, opts: { date?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(\n `rankings/${encodeURIComponent(org)}/${encodeURIComponent(division)}`,\n opts as Query,\n );\n }\n\n /** Current champions across every launch org. */\n champions(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('champions');\n }\n\n /** UFCalendar Power Index board. */\n powerIndex(org = 'ufc'): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>(`power-index/${encodeURIComponent(org)}`);\n }\n\n /* ---------------------------------------------------------------- misc */\n\n /** Model win probabilities for upcoming UFC bouts. */\n predictionsUpcoming(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('predictions/upcoming');\n }\n\n /** Who airs the promotion, per ISO-2 country. */\n broadcastRights(org = 'ufc', opts: { country?: string } = {}): Promise<BroadcastRight[]> {\n return this.get<BroadcastRight[]>(`broadcast-rights/${encodeURIComponent(org)}`, opts as Query);\n }\n\n venue(venueId: number | string): Promise<Venue> {\n return this.get<Venue>(`venues/${encodeURIComponent(String(venueId))}`);\n }\n\n /** Typeahead across fighters and events. */\n search(q: string): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>('search', { q });\n }\n\n /** Your key's month-to-date quota usage. */\n usage(): Promise<Usage> {\n return this.get<Usage>('usage');\n }\n\n /**\n * Subscribable ICS feed URL for calendar apps (authenticates via `?key=`).\n *\n * Throws without a key rather than returning `?key=`: that empty URL is\n * pasted into a calendar app, fails there hours later, and the failure\n * surfaces nowhere near this call.\n */\n calendarIcsUrl(org = 'ufc'): string {\n if (!this.apiKey) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n return `${this.baseUrl}/calendar/${encodeURIComponent(org)}.ics?key=${encodeURIComponent(this.apiKey)}`;\n }\n\n /* ------------------------------------------------------------ webhooks */\n\n webhookEndpoints(): Promise<WebhookEndpoint[]> {\n return this.get<WebhookEndpoint[]>('webhook-endpoints');\n }\n\n /**\n * Register a signed webhook (Pro and up). `events` ⊆ `event.announced`,\n * `fight.result`, `card.changed`, `event.completed`. The signing secret is\n * returned ONCE, in this response.\n */\n async createWebhookEndpoint(url: string, events?: string[]): Promise<WebhookEndpoint> {\n const body: Record<string, unknown> = { url };\n if (events?.length) body.events = events;\n return (await this.#request<WebhookEndpoint>('POST', 'webhook-endpoints', { body })).data;\n }\n\n async deleteWebhookEndpoint(endpointId: number | string): Promise<void> {\n await this.#request<unknown>('DELETE', `webhook-endpoints/${encodeURIComponent(String(endpointId))}`);\n }\n\n async rotateWebhookSecret(endpointId: number | string): Promise<WebhookEndpoint> {\n return (\n await this.#request<WebhookEndpoint>(\n 'POST',\n `webhook-endpoints/${encodeURIComponent(String(endpointId))}/rotate-secret`,\n )\n ).data;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -89,6 +89,100 @@ interface EventChange {
|
|
|
89
89
|
after: unknown;
|
|
90
90
|
observed_at: string | null;
|
|
91
91
|
}
|
|
92
|
+
interface LiveStatLine {
|
|
93
|
+
knockdowns: number | null;
|
|
94
|
+
sig_strikes_landed: number | null;
|
|
95
|
+
sig_strikes_attempted: number | null;
|
|
96
|
+
total_strikes_landed: number | null;
|
|
97
|
+
total_strikes_attempted: number | null;
|
|
98
|
+
takedowns_landed: number | null;
|
|
99
|
+
takedowns_attempted: number | null;
|
|
100
|
+
submission_attempts: number | null;
|
|
101
|
+
reversals: number | null;
|
|
102
|
+
control_time_sec: number;
|
|
103
|
+
[k: string]: number | null;
|
|
104
|
+
}
|
|
105
|
+
interface LiveCorner {
|
|
106
|
+
src_id: number | null;
|
|
107
|
+
fighter_id: number | null;
|
|
108
|
+
name: string;
|
|
109
|
+
short: string;
|
|
110
|
+
}
|
|
111
|
+
interface LiveCardRow {
|
|
112
|
+
fight_id: number | null;
|
|
113
|
+
src_id: number | null;
|
|
114
|
+
order: number | null;
|
|
115
|
+
section: 'main' | 'prelims' | 'early_prelims';
|
|
116
|
+
status: 'upcoming' | 'live' | 'final' | 'cancelled';
|
|
117
|
+
weight_class: string | null;
|
|
118
|
+
possible_rounds: number | null;
|
|
119
|
+
referee: string | null;
|
|
120
|
+
a: LiveCorner | null;
|
|
121
|
+
b: LiveCorner | null;
|
|
122
|
+
result: {
|
|
123
|
+
winner: 'a' | 'b' | null;
|
|
124
|
+
method: string | null;
|
|
125
|
+
round: number | null;
|
|
126
|
+
time: string | null;
|
|
127
|
+
nc: boolean;
|
|
128
|
+
} | null;
|
|
129
|
+
}
|
|
130
|
+
interface LiveTimelineEntry {
|
|
131
|
+
t: string;
|
|
132
|
+
type: string;
|
|
133
|
+
round: number | null;
|
|
134
|
+
clock: string | null;
|
|
135
|
+
side: 'a' | 'b' | null;
|
|
136
|
+
}
|
|
137
|
+
interface LiveCurrent {
|
|
138
|
+
src_id: number | null;
|
|
139
|
+
fight_id: number | null;
|
|
140
|
+
phase: 'prefight' | 'round' | 'break' | 'over';
|
|
141
|
+
round: number | null;
|
|
142
|
+
possible_rounds: number | null;
|
|
143
|
+
round_length_sec: number;
|
|
144
|
+
round_started_at: string | null;
|
|
145
|
+
clock_sec: number | null;
|
|
146
|
+
clock_observed_at: number;
|
|
147
|
+
official: boolean;
|
|
148
|
+
stats: {
|
|
149
|
+
a: LiveStatLine | null;
|
|
150
|
+
b: LiveStatLine | null;
|
|
151
|
+
} | null;
|
|
152
|
+
rounds: {
|
|
153
|
+
n: number;
|
|
154
|
+
a: LiveStatLine | null;
|
|
155
|
+
b: LiveStatLine | null;
|
|
156
|
+
}[];
|
|
157
|
+
timeline: LiveTimelineEntry[];
|
|
158
|
+
}
|
|
159
|
+
/** The real-time document pushed over wss://live.ufcalendar.com/v1 and
|
|
160
|
+
* returned by GET /v1/events/{id}/live. */
|
|
161
|
+
interface LiveState {
|
|
162
|
+
v: 1;
|
|
163
|
+
seq: number;
|
|
164
|
+
ts: number;
|
|
165
|
+
hash: string;
|
|
166
|
+
event: {
|
|
167
|
+
id: number | null;
|
|
168
|
+
slug: string | null;
|
|
169
|
+
fmid: number | null;
|
|
170
|
+
name: string | null;
|
|
171
|
+
org: string;
|
|
172
|
+
status: 'upcoming' | 'live' | 'completed';
|
|
173
|
+
source: string;
|
|
174
|
+
};
|
|
175
|
+
card: LiveCardRow[];
|
|
176
|
+
current: LiveCurrent | null;
|
|
177
|
+
last: {
|
|
178
|
+
src_id: number | null;
|
|
179
|
+
fight_id: number | null;
|
|
180
|
+
winner: 'a' | 'b' | null;
|
|
181
|
+
method: string | null;
|
|
182
|
+
round: number | null;
|
|
183
|
+
time: string | null;
|
|
184
|
+
} | null;
|
|
185
|
+
}
|
|
92
186
|
/** Identity fields only — the full record is on `/v1/fighters/{id}`. */
|
|
93
187
|
interface FighterRef {
|
|
94
188
|
id: number;
|
|
@@ -367,6 +461,10 @@ declare class FightAPI {
|
|
|
367
461
|
event(idOrSlug: string | number): Promise<EventDetail>;
|
|
368
462
|
/** Card-change diff log (fight added/removed, opponent swapped, date moved, profile merged). */
|
|
369
463
|
eventChanges(idOrSlug: string | number): Promise<EventChange[]>;
|
|
464
|
+
/** Latest real-time LiveState snapshot on fight night (Business+), or null
|
|
465
|
+
* when nothing is being streamed. The WebSocket at wss://live.ufcalendar.com/v1
|
|
466
|
+
* pushes the same document as it changes. */
|
|
467
|
+
eventLive(idOrSlug: string | number): Promise<LiveState | null>;
|
|
370
468
|
fight(fightId: number | string): Promise<Fight>;
|
|
371
469
|
/** Per-fight totals for both corners. */
|
|
372
470
|
fightStats(fightId: number | string): Promise<Record<string, unknown>[]>;
|
|
@@ -460,7 +558,7 @@ declare class FightAPI {
|
|
|
460
558
|
|
|
461
559
|
/** Kept in lockstep with package.json and the User-Agent by
|
|
462
560
|
* `apps/api/src/surfaces.test.ts`. Bump all three together. */
|
|
463
|
-
declare const VERSION = "0.
|
|
561
|
+
declare const VERSION = "0.2.0";
|
|
464
562
|
|
|
465
563
|
/**
|
|
466
564
|
* This file was auto-generated by openapi-typescript.
|
|
@@ -886,6 +984,76 @@ interface paths {
|
|
|
886
984
|
patch?: never;
|
|
887
985
|
trace?: never;
|
|
888
986
|
};
|
|
987
|
+
"/v1/events/{idOrSlug}/live": {
|
|
988
|
+
parameters: {
|
|
989
|
+
query?: never;
|
|
990
|
+
header?: never;
|
|
991
|
+
path?: never;
|
|
992
|
+
cookie?: never;
|
|
993
|
+
};
|
|
994
|
+
/**
|
|
995
|
+
* Live snapshot (Business+)
|
|
996
|
+
* @description The latest real-time document for an event on fight night — the same `LiveState` the WebSocket pushes: card order and statuses, the bout in progress (`current`: phase, round, running clock, unofficial totals and per-round stats, referee, a timestamped action timeline), and the last result. `data` is `null` with `meta.live: false` when nothing is being streamed. Poll it at most every few seconds, or connect to the WebSocket in `meta.websocket` (`?key=` on the URL, then send `{"action":"subscribe","event":"<slug>"}`) and receive every change as it lands. UFC only in v1. Requires Business or Enterprise (403 `tier_required` otherwise).
|
|
997
|
+
*/
|
|
998
|
+
get: {
|
|
999
|
+
parameters: {
|
|
1000
|
+
query?: never;
|
|
1001
|
+
header?: never;
|
|
1002
|
+
path: {
|
|
1003
|
+
/** @description Numeric id or slug of the event. Stale slugs 308-redirect to the canonical URL. */
|
|
1004
|
+
idOrSlug: string;
|
|
1005
|
+
};
|
|
1006
|
+
cookie?: never;
|
|
1007
|
+
};
|
|
1008
|
+
requestBody?: never;
|
|
1009
|
+
responses: {
|
|
1010
|
+
/** @description LiveState or null */
|
|
1011
|
+
200: {
|
|
1012
|
+
headers: {
|
|
1013
|
+
[name: string]: unknown;
|
|
1014
|
+
};
|
|
1015
|
+
content: {
|
|
1016
|
+
"application/json": unknown;
|
|
1017
|
+
};
|
|
1018
|
+
};
|
|
1019
|
+
/** @description Missing/invalid API key. `{"error":{"code":"unauthorized",...}}` */
|
|
1020
|
+
401: {
|
|
1021
|
+
headers: {
|
|
1022
|
+
[name: string]: unknown;
|
|
1023
|
+
};
|
|
1024
|
+
content?: never;
|
|
1025
|
+
};
|
|
1026
|
+
/** @description No active plan on the key's account (`subscription_required`) — choose a plan or start the free 1-day trial at https://www.ufcalendar.com/account/api. */
|
|
1027
|
+
403: {
|
|
1028
|
+
headers: {
|
|
1029
|
+
[name: string]: unknown;
|
|
1030
|
+
};
|
|
1031
|
+
content?: never;
|
|
1032
|
+
};
|
|
1033
|
+
/** @description Not found (staged-org data 404s until the org is promoted). */
|
|
1034
|
+
404: {
|
|
1035
|
+
headers: {
|
|
1036
|
+
[name: string]: unknown;
|
|
1037
|
+
};
|
|
1038
|
+
content?: never;
|
|
1039
|
+
};
|
|
1040
|
+
/** @description Rate limited (`rate_limited`) or monthly quota reached (`quota_exceeded`). Honor `Retry-After`. */
|
|
1041
|
+
429: {
|
|
1042
|
+
headers: {
|
|
1043
|
+
[name: string]: unknown;
|
|
1044
|
+
};
|
|
1045
|
+
content?: never;
|
|
1046
|
+
};
|
|
1047
|
+
};
|
|
1048
|
+
};
|
|
1049
|
+
put?: never;
|
|
1050
|
+
post?: never;
|
|
1051
|
+
delete?: never;
|
|
1052
|
+
options?: never;
|
|
1053
|
+
head?: never;
|
|
1054
|
+
patch?: never;
|
|
1055
|
+
trace?: never;
|
|
1056
|
+
};
|
|
889
1057
|
"/v1/events/{idOrSlug}/changes": {
|
|
890
1058
|
parameters: {
|
|
891
1059
|
query?: never;
|
|
@@ -3109,4 +3277,4 @@ interface paths {
|
|
|
3109
3277
|
}
|
|
3110
3278
|
type operations = Record<string, never>;
|
|
3111
3279
|
|
|
3112
|
-
export { type Broadcast, type BroadcastRight, type CareerBout, type CareerStats, DEFAULT_BASE_URL, type Envelope, type EventChange, type EventDetail, type EventStatus, type EventSummary, type Fight, FightAPI, FightAPIError, type FightAPIOptions, type FightResult, type Fighter, type FighterImage, type FighterRef, type FighterSummary, type Judge, type Meta, type Org, type Plan, type Plans, type PowerIndex, type Query, type QueryValue, type RankingDivision, type RankingEntry, type RankingsBoard, type RateLimit, type Scorecard, type ScorecardRound, type Scorecards, type Usage, VERSION, type Venue, type WebhookEndpoint, type operations, type paths };
|
|
3280
|
+
export { type Broadcast, type BroadcastRight, type CareerBout, type CareerStats, DEFAULT_BASE_URL, type Envelope, type EventChange, type EventDetail, type EventStatus, type EventSummary, type Fight, FightAPI, FightAPIError, type FightAPIOptions, type FightResult, type Fighter, type FighterImage, type FighterRef, type FighterSummary, type Judge, type LiveCardRow, type LiveCorner, type LiveCurrent, type LiveStatLine, type LiveState, type LiveTimelineEntry, type Meta, type Org, type Plan, type Plans, type PowerIndex, type Query, type QueryValue, type RankingDivision, type RankingEntry, type RankingsBoard, type RateLimit, type Scorecard, type ScorecardRound, type Scorecards, type Usage, VERSION, type Venue, type WebhookEndpoint, type operations, type paths };
|
package/dist/index.d.ts
CHANGED
|
@@ -89,6 +89,100 @@ interface EventChange {
|
|
|
89
89
|
after: unknown;
|
|
90
90
|
observed_at: string | null;
|
|
91
91
|
}
|
|
92
|
+
interface LiveStatLine {
|
|
93
|
+
knockdowns: number | null;
|
|
94
|
+
sig_strikes_landed: number | null;
|
|
95
|
+
sig_strikes_attempted: number | null;
|
|
96
|
+
total_strikes_landed: number | null;
|
|
97
|
+
total_strikes_attempted: number | null;
|
|
98
|
+
takedowns_landed: number | null;
|
|
99
|
+
takedowns_attempted: number | null;
|
|
100
|
+
submission_attempts: number | null;
|
|
101
|
+
reversals: number | null;
|
|
102
|
+
control_time_sec: number;
|
|
103
|
+
[k: string]: number | null;
|
|
104
|
+
}
|
|
105
|
+
interface LiveCorner {
|
|
106
|
+
src_id: number | null;
|
|
107
|
+
fighter_id: number | null;
|
|
108
|
+
name: string;
|
|
109
|
+
short: string;
|
|
110
|
+
}
|
|
111
|
+
interface LiveCardRow {
|
|
112
|
+
fight_id: number | null;
|
|
113
|
+
src_id: number | null;
|
|
114
|
+
order: number | null;
|
|
115
|
+
section: 'main' | 'prelims' | 'early_prelims';
|
|
116
|
+
status: 'upcoming' | 'live' | 'final' | 'cancelled';
|
|
117
|
+
weight_class: string | null;
|
|
118
|
+
possible_rounds: number | null;
|
|
119
|
+
referee: string | null;
|
|
120
|
+
a: LiveCorner | null;
|
|
121
|
+
b: LiveCorner | null;
|
|
122
|
+
result: {
|
|
123
|
+
winner: 'a' | 'b' | null;
|
|
124
|
+
method: string | null;
|
|
125
|
+
round: number | null;
|
|
126
|
+
time: string | null;
|
|
127
|
+
nc: boolean;
|
|
128
|
+
} | null;
|
|
129
|
+
}
|
|
130
|
+
interface LiveTimelineEntry {
|
|
131
|
+
t: string;
|
|
132
|
+
type: string;
|
|
133
|
+
round: number | null;
|
|
134
|
+
clock: string | null;
|
|
135
|
+
side: 'a' | 'b' | null;
|
|
136
|
+
}
|
|
137
|
+
interface LiveCurrent {
|
|
138
|
+
src_id: number | null;
|
|
139
|
+
fight_id: number | null;
|
|
140
|
+
phase: 'prefight' | 'round' | 'break' | 'over';
|
|
141
|
+
round: number | null;
|
|
142
|
+
possible_rounds: number | null;
|
|
143
|
+
round_length_sec: number;
|
|
144
|
+
round_started_at: string | null;
|
|
145
|
+
clock_sec: number | null;
|
|
146
|
+
clock_observed_at: number;
|
|
147
|
+
official: boolean;
|
|
148
|
+
stats: {
|
|
149
|
+
a: LiveStatLine | null;
|
|
150
|
+
b: LiveStatLine | null;
|
|
151
|
+
} | null;
|
|
152
|
+
rounds: {
|
|
153
|
+
n: number;
|
|
154
|
+
a: LiveStatLine | null;
|
|
155
|
+
b: LiveStatLine | null;
|
|
156
|
+
}[];
|
|
157
|
+
timeline: LiveTimelineEntry[];
|
|
158
|
+
}
|
|
159
|
+
/** The real-time document pushed over wss://live.ufcalendar.com/v1 and
|
|
160
|
+
* returned by GET /v1/events/{id}/live. */
|
|
161
|
+
interface LiveState {
|
|
162
|
+
v: 1;
|
|
163
|
+
seq: number;
|
|
164
|
+
ts: number;
|
|
165
|
+
hash: string;
|
|
166
|
+
event: {
|
|
167
|
+
id: number | null;
|
|
168
|
+
slug: string | null;
|
|
169
|
+
fmid: number | null;
|
|
170
|
+
name: string | null;
|
|
171
|
+
org: string;
|
|
172
|
+
status: 'upcoming' | 'live' | 'completed';
|
|
173
|
+
source: string;
|
|
174
|
+
};
|
|
175
|
+
card: LiveCardRow[];
|
|
176
|
+
current: LiveCurrent | null;
|
|
177
|
+
last: {
|
|
178
|
+
src_id: number | null;
|
|
179
|
+
fight_id: number | null;
|
|
180
|
+
winner: 'a' | 'b' | null;
|
|
181
|
+
method: string | null;
|
|
182
|
+
round: number | null;
|
|
183
|
+
time: string | null;
|
|
184
|
+
} | null;
|
|
185
|
+
}
|
|
92
186
|
/** Identity fields only — the full record is on `/v1/fighters/{id}`. */
|
|
93
187
|
interface FighterRef {
|
|
94
188
|
id: number;
|
|
@@ -367,6 +461,10 @@ declare class FightAPI {
|
|
|
367
461
|
event(idOrSlug: string | number): Promise<EventDetail>;
|
|
368
462
|
/** Card-change diff log (fight added/removed, opponent swapped, date moved, profile merged). */
|
|
369
463
|
eventChanges(idOrSlug: string | number): Promise<EventChange[]>;
|
|
464
|
+
/** Latest real-time LiveState snapshot on fight night (Business+), or null
|
|
465
|
+
* when nothing is being streamed. The WebSocket at wss://live.ufcalendar.com/v1
|
|
466
|
+
* pushes the same document as it changes. */
|
|
467
|
+
eventLive(idOrSlug: string | number): Promise<LiveState | null>;
|
|
370
468
|
fight(fightId: number | string): Promise<Fight>;
|
|
371
469
|
/** Per-fight totals for both corners. */
|
|
372
470
|
fightStats(fightId: number | string): Promise<Record<string, unknown>[]>;
|
|
@@ -460,7 +558,7 @@ declare class FightAPI {
|
|
|
460
558
|
|
|
461
559
|
/** Kept in lockstep with package.json and the User-Agent by
|
|
462
560
|
* `apps/api/src/surfaces.test.ts`. Bump all three together. */
|
|
463
|
-
declare const VERSION = "0.
|
|
561
|
+
declare const VERSION = "0.2.0";
|
|
464
562
|
|
|
465
563
|
/**
|
|
466
564
|
* This file was auto-generated by openapi-typescript.
|
|
@@ -886,6 +984,76 @@ interface paths {
|
|
|
886
984
|
patch?: never;
|
|
887
985
|
trace?: never;
|
|
888
986
|
};
|
|
987
|
+
"/v1/events/{idOrSlug}/live": {
|
|
988
|
+
parameters: {
|
|
989
|
+
query?: never;
|
|
990
|
+
header?: never;
|
|
991
|
+
path?: never;
|
|
992
|
+
cookie?: never;
|
|
993
|
+
};
|
|
994
|
+
/**
|
|
995
|
+
* Live snapshot (Business+)
|
|
996
|
+
* @description The latest real-time document for an event on fight night — the same `LiveState` the WebSocket pushes: card order and statuses, the bout in progress (`current`: phase, round, running clock, unofficial totals and per-round stats, referee, a timestamped action timeline), and the last result. `data` is `null` with `meta.live: false` when nothing is being streamed. Poll it at most every few seconds, or connect to the WebSocket in `meta.websocket` (`?key=` on the URL, then send `{"action":"subscribe","event":"<slug>"}`) and receive every change as it lands. UFC only in v1. Requires Business or Enterprise (403 `tier_required` otherwise).
|
|
997
|
+
*/
|
|
998
|
+
get: {
|
|
999
|
+
parameters: {
|
|
1000
|
+
query?: never;
|
|
1001
|
+
header?: never;
|
|
1002
|
+
path: {
|
|
1003
|
+
/** @description Numeric id or slug of the event. Stale slugs 308-redirect to the canonical URL. */
|
|
1004
|
+
idOrSlug: string;
|
|
1005
|
+
};
|
|
1006
|
+
cookie?: never;
|
|
1007
|
+
};
|
|
1008
|
+
requestBody?: never;
|
|
1009
|
+
responses: {
|
|
1010
|
+
/** @description LiveState or null */
|
|
1011
|
+
200: {
|
|
1012
|
+
headers: {
|
|
1013
|
+
[name: string]: unknown;
|
|
1014
|
+
};
|
|
1015
|
+
content: {
|
|
1016
|
+
"application/json": unknown;
|
|
1017
|
+
};
|
|
1018
|
+
};
|
|
1019
|
+
/** @description Missing/invalid API key. `{"error":{"code":"unauthorized",...}}` */
|
|
1020
|
+
401: {
|
|
1021
|
+
headers: {
|
|
1022
|
+
[name: string]: unknown;
|
|
1023
|
+
};
|
|
1024
|
+
content?: never;
|
|
1025
|
+
};
|
|
1026
|
+
/** @description No active plan on the key's account (`subscription_required`) — choose a plan or start the free 1-day trial at https://www.ufcalendar.com/account/api. */
|
|
1027
|
+
403: {
|
|
1028
|
+
headers: {
|
|
1029
|
+
[name: string]: unknown;
|
|
1030
|
+
};
|
|
1031
|
+
content?: never;
|
|
1032
|
+
};
|
|
1033
|
+
/** @description Not found (staged-org data 404s until the org is promoted). */
|
|
1034
|
+
404: {
|
|
1035
|
+
headers: {
|
|
1036
|
+
[name: string]: unknown;
|
|
1037
|
+
};
|
|
1038
|
+
content?: never;
|
|
1039
|
+
};
|
|
1040
|
+
/** @description Rate limited (`rate_limited`) or monthly quota reached (`quota_exceeded`). Honor `Retry-After`. */
|
|
1041
|
+
429: {
|
|
1042
|
+
headers: {
|
|
1043
|
+
[name: string]: unknown;
|
|
1044
|
+
};
|
|
1045
|
+
content?: never;
|
|
1046
|
+
};
|
|
1047
|
+
};
|
|
1048
|
+
};
|
|
1049
|
+
put?: never;
|
|
1050
|
+
post?: never;
|
|
1051
|
+
delete?: never;
|
|
1052
|
+
options?: never;
|
|
1053
|
+
head?: never;
|
|
1054
|
+
patch?: never;
|
|
1055
|
+
trace?: never;
|
|
1056
|
+
};
|
|
889
1057
|
"/v1/events/{idOrSlug}/changes": {
|
|
890
1058
|
parameters: {
|
|
891
1059
|
query?: never;
|
|
@@ -3109,4 +3277,4 @@ interface paths {
|
|
|
3109
3277
|
}
|
|
3110
3278
|
type operations = Record<string, never>;
|
|
3111
3279
|
|
|
3112
|
-
export { type Broadcast, type BroadcastRight, type CareerBout, type CareerStats, DEFAULT_BASE_URL, type Envelope, type EventChange, type EventDetail, type EventStatus, type EventSummary, type Fight, FightAPI, FightAPIError, type FightAPIOptions, type FightResult, type Fighter, type FighterImage, type FighterRef, type FighterSummary, type Judge, type Meta, type Org, type Plan, type Plans, type PowerIndex, type Query, type QueryValue, type RankingDivision, type RankingEntry, type RankingsBoard, type RateLimit, type Scorecard, type ScorecardRound, type Scorecards, type Usage, VERSION, type Venue, type WebhookEndpoint, type operations, type paths };
|
|
3280
|
+
export { type Broadcast, type BroadcastRight, type CareerBout, type CareerStats, DEFAULT_BASE_URL, type Envelope, type EventChange, type EventDetail, type EventStatus, type EventSummary, type Fight, FightAPI, FightAPIError, type FightAPIOptions, type FightResult, type Fighter, type FighterImage, type FighterRef, type FighterSummary, type Judge, type LiveCardRow, type LiveCorner, type LiveCurrent, type LiveStatLine, type LiveState, type LiveTimelineEntry, type Meta, type Org, type Plan, type Plans, type PowerIndex, type Query, type QueryValue, type RankingDivision, type RankingEntry, type RankingsBoard, type RateLimit, type Scorecard, type ScorecardRound, type Scorecards, type Usage, VERSION, type Venue, type WebhookEndpoint, type operations, type paths };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var VERSION = "0.
|
|
2
|
+
var VERSION = "0.2.0";
|
|
3
3
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
var DEFAULT_BASE_URL = "https://api.ufcalendar.com/v1";
|
|
@@ -170,6 +170,12 @@ var FightAPI = class {
|
|
|
170
170
|
eventChanges(idOrSlug) {
|
|
171
171
|
return this.get(`events/${encodeURIComponent(String(idOrSlug))}/changes`);
|
|
172
172
|
}
|
|
173
|
+
/** Latest real-time LiveState snapshot on fight night (Business+), or null
|
|
174
|
+
* when nothing is being streamed. The WebSocket at wss://live.ufcalendar.com/v1
|
|
175
|
+
* pushes the same document as it changes. */
|
|
176
|
+
eventLive(idOrSlug) {
|
|
177
|
+
return this.get(`events/${encodeURIComponent(String(idOrSlug))}/live`);
|
|
178
|
+
}
|
|
173
179
|
/* -------------------------------------------------------------- fights */
|
|
174
180
|
fight(fightId) {
|
|
175
181
|
return this.get(`fights/${encodeURIComponent(String(fightId))}`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts","../src/client.ts"],"sourcesContent":["/** Kept in lockstep with package.json and the User-Agent by\n * `apps/api/src/surfaces.test.ts`. Bump all three together. */\nexport const VERSION = '0.1.1';\n","/**\n * Thin, dependency-free client for https://api.ufcalendar.com/v1.\n *\n * Every method returns the parsed `data` value of the JSON envelope\n * (`{\"data\": ..., \"meta\": ...}`); list endpoints are async generators that\n * follow cursor pagination for you. Errors throw `FightAPIError` carrying\n * the API's `code`, `message` and `requestId` — quote the request id when\n * you write to api@ufcalendar.com.\n *\n * Mirrors sdk/python/ufcalendar/client.py one method per endpoint, in\n * camelCase. The two clients are kept in lockstep by\n * `apps/api/src/surfaces.test.ts`.\n *\n * The API serves no betting odds, by design. Fighter `images` are Wikimedia\n * Commons / Creative Commons files — the `license` and `artist` fields you\n * receive must be displayed as a credit.\n */\nimport { VERSION } from './version';\nimport type {\n BroadcastRight,\n CareerBout,\n CareerStats,\n Envelope,\n EventChange,\n EventDetail,\n EventSummary,\n Fight,\n Fighter,\n FighterSummary,\n Judge,\n Meta,\n Org,\n Plans,\n PowerIndex,\n RankingsBoard,\n RateLimit,\n Scorecards,\n Usage,\n Venue,\n WebhookEndpoint,\n} from './types';\n\nexport const DEFAULT_BASE_URL = 'https://api.ufcalendar.com/v1';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport interface FightAPIOptions {\n /** `ufcalendar_…` key from https://www.ufcalendar.com/account/api. Falls back\n * to `UFCALENDAR_API_KEY` / `UFCAL_API_KEY` in the environment. Only\n * `plans()` works without one. */\n apiKey?: string;\n /** Override for testing; defaults to the production `/v1`. */\n baseUrl?: string;\n /** Inject a fetch (a test double, an instrumented fetch, undici). */\n fetch?: typeof fetch;\n /** Per-request timeout. Default 30s. */\n timeoutMs?: number;\n /** Extra headers on every request. */\n headers?: Record<string, string>;\n}\n\n/** An error response from the Fight API. */\nexport class FightAPIError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId: string | null;\n\n constructor(status: number, code: string, message: string, requestId: string | null = null) {\n super(`${status} ${code}: ${message}${requestId ? ` (request_id=${requestId})` : ''}`);\n this.name = 'FightAPIError';\n this.status = status;\n this.code = code;\n this.requestId = requestId;\n }\n}\n\nexport type QueryValue = string | number | boolean | null | undefined;\nexport type Query = Record<string, QueryValue>;\n\ninterface PageOptions {\n /** Stop after this many rows (across pages). */\n limit?: number;\n}\n\n/**\n * A browser forbids setting User-Agent on fetch, so we only set ours\n * off-browser. The test is `document`, NOT `navigator`: Node 21+ ships a\n * global `navigator`, so a navigator check would silently drop the header\n * on every modern Node runtime — which is the one place it works.\n */\nfunction canSetUserAgent(): boolean {\n return typeof document === 'undefined' && typeof window === 'undefined';\n}\n\nfunction envKey(): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;\n return env?.UFCALENDAR_API_KEY || env?.UFCAL_API_KEY;\n}\n\nexport class FightAPI {\n readonly baseUrl: string;\n readonly apiKey: string | undefined;\n\n /** `meta` of the most recent response (pagination cursor, generated_at …). */\n lastMeta: Meta | null = null;\n /** `X-RateLimit-*` of the most recent response. */\n lastRateLimit: RateLimit = { limit: null, remaining: null, reset: null };\n\n readonly #fetch: typeof fetch;\n readonly #timeoutMs: number;\n readonly #headers: Record<string, string>;\n\n constructor(apiKeyOrOptions?: string | FightAPIOptions, options: FightAPIOptions = {}) {\n const opts: FightAPIOptions =\n typeof apiKeyOrOptions === 'string'\n ? { ...options, apiKey: apiKeyOrOptions }\n : { ...options, ...(apiKeyOrOptions ?? {}) };\n this.apiKey = opts.apiKey ?? envKey();\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#fetch = opts.fetch ?? globalThis.fetch;\n this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#headers = opts.headers ?? {};\n if (!this.#fetch) {\n throw new Error('No global fetch available. Use Node 18+ or pass { fetch }.');\n }\n }\n\n /* ---------------------------------------------------------------- core */\n\n #url(path: string, params?: Query): string {\n const url = new URL(`${this.baseUrl}/${path.replace(/^\\/+/, '')}`);\n for (const [k, v] of Object.entries(params ?? {})) {\n if (v === undefined || v === null) continue;\n url.searchParams.set(k, String(v));\n }\n return url.toString();\n }\n\n async #request<T>(\n method: string,\n path: string,\n { params, body, open = false }: { params?: Query; body?: unknown; open?: boolean } = {},\n ): Promise<Envelope<T>> {\n if (!this.apiKey && !open) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n const headers: Record<string, string> = { Accept: 'application/json', ...this.#headers };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n if (canSetUserAgent()) headers['User-Agent'] = `ufcalendar-typescript/${VERSION}`;\n if (body !== undefined) headers['Content-Type'] = 'application/json';\n\n let res: Response;\n try {\n res = await this.#fetch(this.#url(path, params), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.#timeoutMs),\n });\n } catch (e) {\n const err = e as { name?: string; message?: string };\n if (err?.name === 'TimeoutError') {\n throw new FightAPIError(0, 'timeout', `Request to ${path} timed out after ${this.#timeoutMs}ms`);\n }\n throw new FightAPIError(0, 'network_error', err?.message ?? String(e));\n }\n\n this.lastRateLimit = {\n limit: res.headers.get('X-RateLimit-Limit'),\n remaining: res.headers.get('X-RateLimit-Remaining'),\n reset: res.headers.get('X-RateLimit-Reset'),\n };\n\n if (res.status === 204) {\n this.lastMeta = null;\n return { data: undefined as T };\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = undefined;\n }\n\n if (!res.ok) {\n const err = (parsed as { error?: { code?: string; message?: string; request_id?: string } } | undefined)?.error;\n if (err) {\n throw new FightAPIError(\n res.status,\n String(err.code ?? 'error'),\n String(err.message ?? text.slice(0, 200)),\n err.request_id ?? res.headers.get('x-request-id'),\n );\n }\n throw new FightAPIError(res.status, 'http_error', text.slice(0, 200), res.headers.get('x-request-id'));\n }\n\n const env = (parsed ?? {}) as Envelope<T>;\n this.lastMeta = env.meta ?? null;\n return env;\n }\n\n /** Raw GET returning the `data` value. Escape hatch for new endpoints. */\n async get<T = unknown>(path: string, params?: Query): Promise<T> {\n return (await this.#request<T>('GET', path, { params })).data;\n }\n\n /** Raw GET returning the whole `{data, meta}` envelope. */\n async getWithMeta<T = unknown>(path: string, params?: Query): Promise<Envelope<T>> {\n return this.#request<T>('GET', path, { params });\n }\n\n async *#paginate<T>(path: string, params: Query, opts: PageOptions = {}): AsyncGenerator<T, void, void> {\n // Never fetch a bigger page than the caller wants rows: `limit: 3` used\n // to pull 100 rows and throw 97 away, burning the caller's quota on a\n // request they did not ask for (and, on a trial key, a noticeable slice\n // of the 100 they get).\n const pageSize = Math.min(100, opts.limit ?? 100);\n const query: Query = { limit: pageSize, ...params };\n let seen = 0;\n for (;;) {\n const env = await this.#request<T[]>('GET', path, { params: query });\n for (const row of env.data ?? []) {\n yield row;\n seen += 1;\n if (opts.limit !== undefined && seen >= opts.limit) return;\n }\n const cursor = env.meta?.pagination?.next_cursor;\n if (!cursor) return;\n query.cursor = cursor;\n }\n }\n\n /* --------------------------------------------------------------- plans */\n\n /**\n * Plans, quotas, the free 1-day trial rule, the MCP endpoint and the doc\n * links. The only endpoint that answers without a credential.\n */\n async plans(): Promise<Plans> {\n return (await this.#request<Plans>('GET', 'plans', { open: true })).data;\n }\n\n /* ---------------------------------------------------------------- orgs */\n\n /** Launch orgs with capability flags (stats / rounds / rankings / broadcasts / predictions). */\n orgs(): Promise<Org[]> {\n return this.get<Org[]>('orgs');\n }\n\n org(slug: string): Promise<Org> {\n return this.get<Org>(`orgs/${encodeURIComponent(slug)}`);\n }\n\n /* -------------------------------------------------------------- events */\n\n /**\n * Schedule + results. Bare call = the upcoming calendar, soonest first.\n * `status: 'completed'` (or `order: 'desc'`) browses the archive\n * newest-first. `from` / `to` are `YYYY-MM-DD`.\n */\n events(\n opts: {\n org?: string;\n status?: string;\n from?: string;\n to?: string;\n order?: 'asc' | 'desc';\n limit?: number;\n } = {},\n ): AsyncGenerator<EventSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<EventSummary>('events', params as Query, { limit });\n }\n\n /** One event with its full fight card, venue and broadcasts. */\n event(idOrSlug: string | number): Promise<EventDetail> {\n return this.get<EventDetail>(`events/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Card-change diff log (fight added/removed, opponent swapped, date moved, profile merged). */\n eventChanges(idOrSlug: string | number): Promise<EventChange[]> {\n return this.get<EventChange[]>(`events/${encodeURIComponent(String(idOrSlug))}/changes`);\n }\n\n /* -------------------------------------------------------------- fights */\n\n fight(fightId: number | string): Promise<Fight> {\n return this.get<Fight>(`fights/${encodeURIComponent(String(fightId))}`);\n }\n\n /** Per-fight totals for both corners. */\n fightStats(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/stats`);\n }\n\n /** Round-by-round stat lines for both corners. */\n fightRounds(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/rounds`);\n }\n\n /**\n * The judges' scorecards for a bout — the official commission record:\n * decision type, point deductions, and one card per judge with every\n * round, the totals and `winner_fighter_id`. Scores are oriented to\n * `fighter_a_id` / `fighter_b_id`, both repeated on the payload.\n *\n * Check `scores_known` on a card before charting totals: when it is false\n * the commission published only the outcome. Throws 404 for a bout that\n * did not go to the judges.\n */\n fightScorecards(fightId: number | string): Promise<Scorecards> {\n return this.get<Scorecards>(`fights/${encodeURIComponent(String(fightId))}/scorecards`);\n }\n\n /* -------------------------------------------------------------- judges */\n\n /**\n * Every official who has scored a launch-org bout, busiest first. Rates\n * mean little below ~10 fights; pass `minFights: 10`.\n */\n judges(\n opts: { q?: string; org?: string; minFights?: number; limit?: number } = {},\n ): AsyncGenerator<Judge, void, void> {\n const { limit, minFights, ...rest } = opts;\n return this.#paginate<Judge>('judges', { ...rest, min_fights: minFights } as Query, { limit });\n }\n\n judge(judgeId: number | string): Promise<Judge> {\n return this.get<Judge>(`judges/${encodeURIComponent(String(judgeId))}`);\n }\n\n /** Every card this judge has turned in, newest first. */\n judgeScorecards(\n judgeId: number | string,\n opts: { limit?: number } = {},\n ): AsyncGenerator<Record<string, unknown>, void, void> {\n return this.#paginate<Record<string, unknown>>(\n `judges/${encodeURIComponent(String(judgeId))}/scorecards`,\n {},\n opts,\n );\n }\n\n /* ------------------------------------------------------------ fighters */\n\n fighters(\n opts: { q?: string; org?: string; country?: string; limit?: number } = {},\n ): AsyncGenerator<FighterSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<FighterSummary>('fighters', params as Query, { limit });\n }\n\n /** Bio, records, career stats, Power Index and CC-licensed images. */\n fighter(idOrSlug: string | number): Promise<Fighter> {\n return this.get<Fighter>(`fighters/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Complete multi-promotion career timeline. */\n fighterHistory(idOrSlug: string | number): Promise<CareerBout[]> {\n return this.get<CareerBout[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/history`);\n }\n\n /** Career statistics, one source-stamped row per scope (`pro-mma`, `ufc-only` …). */\n fighterStats(idOrSlug: string | number): Promise<CareerStats[]> {\n return this.get<CareerStats[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/stats`);\n }\n\n /** Every official ranking row the fighter ever held, newest first. */\n fighterRankings(idOrSlug: string | number): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/rankings`);\n }\n\n fighterPowerIndex(idOrSlug: string | number): Promise<PowerIndex & Record<string, unknown>> {\n return this.get<PowerIndex & Record<string, unknown>>(\n `fighters/${encodeURIComponent(String(idOrSlug))}/power-index`,\n );\n }\n\n /* ------------------------------------------------------------ rankings */\n\n /**\n * Official board, point-in-time. `date: 'YYYY-MM-DD'` returns the board\n * that was valid on that day (UFC history back to 2013; rank 0 = champion).\n */\n rankings(org = 'ufc', opts: { date?: string; board?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(`rankings/${encodeURIComponent(org)}`, opts as Query);\n }\n\n divisionRankings(org: string, division: string, opts: { date?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(\n `rankings/${encodeURIComponent(org)}/${encodeURIComponent(division)}`,\n opts as Query,\n );\n }\n\n /** Current champions across every launch org. */\n champions(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('champions');\n }\n\n /** UFCalendar Power Index board. */\n powerIndex(org = 'ufc'): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>(`power-index/${encodeURIComponent(org)}`);\n }\n\n /* ---------------------------------------------------------------- misc */\n\n /** Model win probabilities for upcoming UFC bouts. */\n predictionsUpcoming(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('predictions/upcoming');\n }\n\n /** Who airs the promotion, per ISO-2 country. */\n broadcastRights(org = 'ufc', opts: { country?: string } = {}): Promise<BroadcastRight[]> {\n return this.get<BroadcastRight[]>(`broadcast-rights/${encodeURIComponent(org)}`, opts as Query);\n }\n\n venue(venueId: number | string): Promise<Venue> {\n return this.get<Venue>(`venues/${encodeURIComponent(String(venueId))}`);\n }\n\n /** Typeahead across fighters and events. */\n search(q: string): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>('search', { q });\n }\n\n /** Your key's month-to-date quota usage. */\n usage(): Promise<Usage> {\n return this.get<Usage>('usage');\n }\n\n /**\n * Subscribable ICS feed URL for calendar apps (authenticates via `?key=`).\n *\n * Throws without a key rather than returning `?key=`: that empty URL is\n * pasted into a calendar app, fails there hours later, and the failure\n * surfaces nowhere near this call.\n */\n calendarIcsUrl(org = 'ufc'): string {\n if (!this.apiKey) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n return `${this.baseUrl}/calendar/${encodeURIComponent(org)}.ics?key=${encodeURIComponent(this.apiKey)}`;\n }\n\n /* ------------------------------------------------------------ webhooks */\n\n webhookEndpoints(): Promise<WebhookEndpoint[]> {\n return this.get<WebhookEndpoint[]>('webhook-endpoints');\n }\n\n /**\n * Register a signed webhook (Pro and up). `events` ⊆ `event.announced`,\n * `fight.result`, `card.changed`, `event.completed`. The signing secret is\n * returned ONCE, in this response.\n */\n async createWebhookEndpoint(url: string, events?: string[]): Promise<WebhookEndpoint> {\n const body: Record<string, unknown> = { url };\n if (events?.length) body.events = events;\n return (await this.#request<WebhookEndpoint>('POST', 'webhook-endpoints', { body })).data;\n }\n\n async deleteWebhookEndpoint(endpointId: number | string): Promise<void> {\n await this.#request<unknown>('DELETE', `webhook-endpoints/${encodeURIComponent(String(endpointId))}`);\n }\n\n async rotateWebhookSecret(endpointId: number | string): Promise<WebhookEndpoint> {\n return (\n await this.#request<WebhookEndpoint>(\n 'POST',\n `webhook-endpoints/${encodeURIComponent(String(endpointId))}/rotate-secret`,\n )\n ).data;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/client.ts"],"sourcesContent":["/** Kept in lockstep with package.json and the User-Agent by\n * `apps/api/src/surfaces.test.ts`. Bump all three together. */\nexport const VERSION = '0.2.0';\n","/**\n * Thin, dependency-free client for https://api.ufcalendar.com/v1.\n *\n * Every method returns the parsed `data` value of the JSON envelope\n * (`{\"data\": ..., \"meta\": ...}`); list endpoints are async generators that\n * follow cursor pagination for you. Errors throw `FightAPIError` carrying\n * the API's `code`, `message` and `requestId` — quote the request id when\n * you write to api@ufcalendar.com.\n *\n * Mirrors sdk/python/ufcalendar/client.py one method per endpoint, in\n * camelCase. The two clients are kept in lockstep by\n * `apps/api/src/surfaces.test.ts`.\n *\n * The API serves no betting odds, by design. Fighter `images` are Wikimedia\n * Commons / Creative Commons files — the `license` and `artist` fields you\n * receive must be displayed as a credit.\n */\nimport { VERSION } from './version';\nimport type {\n BroadcastRight,\n CareerBout,\n CareerStats,\n Envelope,\n EventChange,\n EventDetail,\n EventSummary,\n Fight,\n Fighter,\n FighterSummary,\n Judge,\n Meta,\n Org,\n Plans,\n PowerIndex,\n RankingsBoard,\n RateLimit,\n Scorecards,\n Usage,\n Venue,\n WebhookEndpoint,\n LiveState,\n} from './types';\n\nexport const DEFAULT_BASE_URL = 'https://api.ufcalendar.com/v1';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nexport interface FightAPIOptions {\n /** `ufcalendar_…` key from https://www.ufcalendar.com/account/api. Falls back\n * to `UFCALENDAR_API_KEY` / `UFCAL_API_KEY` in the environment. Only\n * `plans()` works without one. */\n apiKey?: string;\n /** Override for testing; defaults to the production `/v1`. */\n baseUrl?: string;\n /** Inject a fetch (a test double, an instrumented fetch, undici). */\n fetch?: typeof fetch;\n /** Per-request timeout. Default 30s. */\n timeoutMs?: number;\n /** Extra headers on every request. */\n headers?: Record<string, string>;\n}\n\n/** An error response from the Fight API. */\nexport class FightAPIError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId: string | null;\n\n constructor(status: number, code: string, message: string, requestId: string | null = null) {\n super(`${status} ${code}: ${message}${requestId ? ` (request_id=${requestId})` : ''}`);\n this.name = 'FightAPIError';\n this.status = status;\n this.code = code;\n this.requestId = requestId;\n }\n}\n\nexport type QueryValue = string | number | boolean | null | undefined;\nexport type Query = Record<string, QueryValue>;\n\ninterface PageOptions {\n /** Stop after this many rows (across pages). */\n limit?: number;\n}\n\n/**\n * A browser forbids setting User-Agent on fetch, so we only set ours\n * off-browser. The test is `document`, NOT `navigator`: Node 21+ ships a\n * global `navigator`, so a navigator check would silently drop the header\n * on every modern Node runtime — which is the one place it works.\n */\nfunction canSetUserAgent(): boolean {\n return typeof document === 'undefined' && typeof window === 'undefined';\n}\n\nfunction envKey(): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;\n return env?.UFCALENDAR_API_KEY || env?.UFCAL_API_KEY;\n}\n\nexport class FightAPI {\n readonly baseUrl: string;\n readonly apiKey: string | undefined;\n\n /** `meta` of the most recent response (pagination cursor, generated_at …). */\n lastMeta: Meta | null = null;\n /** `X-RateLimit-*` of the most recent response. */\n lastRateLimit: RateLimit = { limit: null, remaining: null, reset: null };\n\n readonly #fetch: typeof fetch;\n readonly #timeoutMs: number;\n readonly #headers: Record<string, string>;\n\n constructor(apiKeyOrOptions?: string | FightAPIOptions, options: FightAPIOptions = {}) {\n const opts: FightAPIOptions =\n typeof apiKeyOrOptions === 'string'\n ? { ...options, apiKey: apiKeyOrOptions }\n : { ...options, ...(apiKeyOrOptions ?? {}) };\n this.apiKey = opts.apiKey ?? envKey();\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#fetch = opts.fetch ?? globalThis.fetch;\n this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#headers = opts.headers ?? {};\n if (!this.#fetch) {\n throw new Error('No global fetch available. Use Node 18+ or pass { fetch }.');\n }\n }\n\n /* ---------------------------------------------------------------- core */\n\n #url(path: string, params?: Query): string {\n const url = new URL(`${this.baseUrl}/${path.replace(/^\\/+/, '')}`);\n for (const [k, v] of Object.entries(params ?? {})) {\n if (v === undefined || v === null) continue;\n url.searchParams.set(k, String(v));\n }\n return url.toString();\n }\n\n async #request<T>(\n method: string,\n path: string,\n { params, body, open = false }: { params?: Query; body?: unknown; open?: boolean } = {},\n ): Promise<Envelope<T>> {\n if (!this.apiKey && !open) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n const headers: Record<string, string> = { Accept: 'application/json', ...this.#headers };\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;\n if (canSetUserAgent()) headers['User-Agent'] = `ufcalendar-typescript/${VERSION}`;\n if (body !== undefined) headers['Content-Type'] = 'application/json';\n\n let res: Response;\n try {\n res = await this.#fetch(this.#url(path, params), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.#timeoutMs),\n });\n } catch (e) {\n const err = e as { name?: string; message?: string };\n if (err?.name === 'TimeoutError') {\n throw new FightAPIError(0, 'timeout', `Request to ${path} timed out after ${this.#timeoutMs}ms`);\n }\n throw new FightAPIError(0, 'network_error', err?.message ?? String(e));\n }\n\n this.lastRateLimit = {\n limit: res.headers.get('X-RateLimit-Limit'),\n remaining: res.headers.get('X-RateLimit-Remaining'),\n reset: res.headers.get('X-RateLimit-Reset'),\n };\n\n if (res.status === 204) {\n this.lastMeta = null;\n return { data: undefined as T };\n }\n\n const text = await res.text();\n let parsed: unknown = undefined;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = undefined;\n }\n\n if (!res.ok) {\n const err = (parsed as { error?: { code?: string; message?: string; request_id?: string } } | undefined)?.error;\n if (err) {\n throw new FightAPIError(\n res.status,\n String(err.code ?? 'error'),\n String(err.message ?? text.slice(0, 200)),\n err.request_id ?? res.headers.get('x-request-id'),\n );\n }\n throw new FightAPIError(res.status, 'http_error', text.slice(0, 200), res.headers.get('x-request-id'));\n }\n\n const env = (parsed ?? {}) as Envelope<T>;\n this.lastMeta = env.meta ?? null;\n return env;\n }\n\n /** Raw GET returning the `data` value. Escape hatch for new endpoints. */\n async get<T = unknown>(path: string, params?: Query): Promise<T> {\n return (await this.#request<T>('GET', path, { params })).data;\n }\n\n /** Raw GET returning the whole `{data, meta}` envelope. */\n async getWithMeta<T = unknown>(path: string, params?: Query): Promise<Envelope<T>> {\n return this.#request<T>('GET', path, { params });\n }\n\n async *#paginate<T>(path: string, params: Query, opts: PageOptions = {}): AsyncGenerator<T, void, void> {\n // Never fetch a bigger page than the caller wants rows: `limit: 3` used\n // to pull 100 rows and throw 97 away, burning the caller's quota on a\n // request they did not ask for (and, on a trial key, a noticeable slice\n // of the 100 they get).\n const pageSize = Math.min(100, opts.limit ?? 100);\n const query: Query = { limit: pageSize, ...params };\n let seen = 0;\n for (;;) {\n const env = await this.#request<T[]>('GET', path, { params: query });\n for (const row of env.data ?? []) {\n yield row;\n seen += 1;\n if (opts.limit !== undefined && seen >= opts.limit) return;\n }\n const cursor = env.meta?.pagination?.next_cursor;\n if (!cursor) return;\n query.cursor = cursor;\n }\n }\n\n /* --------------------------------------------------------------- plans */\n\n /**\n * Plans, quotas, the free 1-day trial rule, the MCP endpoint and the doc\n * links. The only endpoint that answers without a credential.\n */\n async plans(): Promise<Plans> {\n return (await this.#request<Plans>('GET', 'plans', { open: true })).data;\n }\n\n /* ---------------------------------------------------------------- orgs */\n\n /** Launch orgs with capability flags (stats / rounds / rankings / broadcasts / predictions). */\n orgs(): Promise<Org[]> {\n return this.get<Org[]>('orgs');\n }\n\n org(slug: string): Promise<Org> {\n return this.get<Org>(`orgs/${encodeURIComponent(slug)}`);\n }\n\n /* -------------------------------------------------------------- events */\n\n /**\n * Schedule + results. Bare call = the upcoming calendar, soonest first.\n * `status: 'completed'` (or `order: 'desc'`) browses the archive\n * newest-first. `from` / `to` are `YYYY-MM-DD`.\n */\n events(\n opts: {\n org?: string;\n status?: string;\n from?: string;\n to?: string;\n order?: 'asc' | 'desc';\n limit?: number;\n } = {},\n ): AsyncGenerator<EventSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<EventSummary>('events', params as Query, { limit });\n }\n\n /** One event with its full fight card, venue and broadcasts. */\n event(idOrSlug: string | number): Promise<EventDetail> {\n return this.get<EventDetail>(`events/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Card-change diff log (fight added/removed, opponent swapped, date moved, profile merged). */\n eventChanges(idOrSlug: string | number): Promise<EventChange[]> {\n return this.get<EventChange[]>(`events/${encodeURIComponent(String(idOrSlug))}/changes`);\n }\n\n /** Latest real-time LiveState snapshot on fight night (Business+), or null\n * when nothing is being streamed. The WebSocket at wss://live.ufcalendar.com/v1\n * pushes the same document as it changes. */\n eventLive(idOrSlug: string | number): Promise<LiveState | null> {\n return this.get<LiveState | null>(`events/${encodeURIComponent(String(idOrSlug))}/live`);\n }\n\n /* -------------------------------------------------------------- fights */\n\n fight(fightId: number | string): Promise<Fight> {\n return this.get<Fight>(`fights/${encodeURIComponent(String(fightId))}`);\n }\n\n /** Per-fight totals for both corners. */\n fightStats(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/stats`);\n }\n\n /** Round-by-round stat lines for both corners. */\n fightRounds(fightId: number | string): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fights/${encodeURIComponent(String(fightId))}/rounds`);\n }\n\n /**\n * The judges' scorecards for a bout — the official commission record:\n * decision type, point deductions, and one card per judge with every\n * round, the totals and `winner_fighter_id`. Scores are oriented to\n * `fighter_a_id` / `fighter_b_id`, both repeated on the payload.\n *\n * Check `scores_known` on a card before charting totals: when it is false\n * the commission published only the outcome. Throws 404 for a bout that\n * did not go to the judges.\n */\n fightScorecards(fightId: number | string): Promise<Scorecards> {\n return this.get<Scorecards>(`fights/${encodeURIComponent(String(fightId))}/scorecards`);\n }\n\n /* -------------------------------------------------------------- judges */\n\n /**\n * Every official who has scored a launch-org bout, busiest first. Rates\n * mean little below ~10 fights; pass `minFights: 10`.\n */\n judges(\n opts: { q?: string; org?: string; minFights?: number; limit?: number } = {},\n ): AsyncGenerator<Judge, void, void> {\n const { limit, minFights, ...rest } = opts;\n return this.#paginate<Judge>('judges', { ...rest, min_fights: minFights } as Query, { limit });\n }\n\n judge(judgeId: number | string): Promise<Judge> {\n return this.get<Judge>(`judges/${encodeURIComponent(String(judgeId))}`);\n }\n\n /** Every card this judge has turned in, newest first. */\n judgeScorecards(\n judgeId: number | string,\n opts: { limit?: number } = {},\n ): AsyncGenerator<Record<string, unknown>, void, void> {\n return this.#paginate<Record<string, unknown>>(\n `judges/${encodeURIComponent(String(judgeId))}/scorecards`,\n {},\n opts,\n );\n }\n\n /* ------------------------------------------------------------ fighters */\n\n fighters(\n opts: { q?: string; org?: string; country?: string; limit?: number } = {},\n ): AsyncGenerator<FighterSummary, void, void> {\n const { limit, ...params } = opts;\n return this.#paginate<FighterSummary>('fighters', params as Query, { limit });\n }\n\n /** Bio, records, career stats, Power Index and CC-licensed images. */\n fighter(idOrSlug: string | number): Promise<Fighter> {\n return this.get<Fighter>(`fighters/${encodeURIComponent(String(idOrSlug))}`);\n }\n\n /** Complete multi-promotion career timeline. */\n fighterHistory(idOrSlug: string | number): Promise<CareerBout[]> {\n return this.get<CareerBout[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/history`);\n }\n\n /** Career statistics, one source-stamped row per scope (`pro-mma`, `ufc-only` …). */\n fighterStats(idOrSlug: string | number): Promise<CareerStats[]> {\n return this.get<CareerStats[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/stats`);\n }\n\n /** Every official ranking row the fighter ever held, newest first. */\n fighterRankings(idOrSlug: string | number): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>(`fighters/${encodeURIComponent(String(idOrSlug))}/rankings`);\n }\n\n fighterPowerIndex(idOrSlug: string | number): Promise<PowerIndex & Record<string, unknown>> {\n return this.get<PowerIndex & Record<string, unknown>>(\n `fighters/${encodeURIComponent(String(idOrSlug))}/power-index`,\n );\n }\n\n /* ------------------------------------------------------------ rankings */\n\n /**\n * Official board, point-in-time. `date: 'YYYY-MM-DD'` returns the board\n * that was valid on that day (UFC history back to 2013; rank 0 = champion).\n */\n rankings(org = 'ufc', opts: { date?: string; board?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(`rankings/${encodeURIComponent(org)}`, opts as Query);\n }\n\n divisionRankings(org: string, division: string, opts: { date?: string } = {}): Promise<RankingsBoard> {\n return this.get<RankingsBoard>(\n `rankings/${encodeURIComponent(org)}/${encodeURIComponent(division)}`,\n opts as Query,\n );\n }\n\n /** Current champions across every launch org. */\n champions(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('champions');\n }\n\n /** UFCalendar Power Index board. */\n powerIndex(org = 'ufc'): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>(`power-index/${encodeURIComponent(org)}`);\n }\n\n /* ---------------------------------------------------------------- misc */\n\n /** Model win probabilities for upcoming UFC bouts. */\n predictionsUpcoming(): Promise<Record<string, unknown>[]> {\n return this.get<Record<string, unknown>[]>('predictions/upcoming');\n }\n\n /** Who airs the promotion, per ISO-2 country. */\n broadcastRights(org = 'ufc', opts: { country?: string } = {}): Promise<BroadcastRight[]> {\n return this.get<BroadcastRight[]>(`broadcast-rights/${encodeURIComponent(org)}`, opts as Query);\n }\n\n venue(venueId: number | string): Promise<Venue> {\n return this.get<Venue>(`venues/${encodeURIComponent(String(venueId))}`);\n }\n\n /** Typeahead across fighters and events. */\n search(q: string): Promise<Record<string, unknown>> {\n return this.get<Record<string, unknown>>('search', { q });\n }\n\n /** Your key's month-to-date quota usage. */\n usage(): Promise<Usage> {\n return this.get<Usage>('usage');\n }\n\n /**\n * Subscribable ICS feed URL for calendar apps (authenticates via `?key=`).\n *\n * Throws without a key rather than returning `?key=`: that empty URL is\n * pasted into a calendar app, fails there hours later, and the failure\n * surfaces nowhere near this call.\n */\n calendarIcsUrl(org = 'ufc'): string {\n if (!this.apiKey) {\n throw new FightAPIError(\n 401,\n 'no_api_key',\n 'No API key. Pass new FightAPI(\"ufcalendar_...\") or set UFCALENDAR_API_KEY. ' +\n 'Keys (and the free 1-day trial) live at https://www.ufcalendar.com/account/api',\n );\n }\n return `${this.baseUrl}/calendar/${encodeURIComponent(org)}.ics?key=${encodeURIComponent(this.apiKey)}`;\n }\n\n /* ------------------------------------------------------------ webhooks */\n\n webhookEndpoints(): Promise<WebhookEndpoint[]> {\n return this.get<WebhookEndpoint[]>('webhook-endpoints');\n }\n\n /**\n * Register a signed webhook (Pro and up). `events` ⊆ `event.announced`,\n * `fight.result`, `card.changed`, `event.completed`. The signing secret is\n * returned ONCE, in this response.\n */\n async createWebhookEndpoint(url: string, events?: string[]): Promise<WebhookEndpoint> {\n const body: Record<string, unknown> = { url };\n if (events?.length) body.events = events;\n return (await this.#request<WebhookEndpoint>('POST', 'webhook-endpoints', { body })).data;\n }\n\n async deleteWebhookEndpoint(endpointId: number | string): Promise<void> {\n await this.#request<unknown>('DELETE', `webhook-endpoints/${encodeURIComponent(String(endpointId))}`);\n }\n\n async rotateWebhookSecret(endpointId: number | string): Promise<WebhookEndpoint> {\n return (\n await this.#request<WebhookEndpoint>(\n 'POST',\n `webhook-endpoints/${encodeURIComponent(String(endpointId))}/rotate-secret`,\n )\n ).data;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ufcalendar/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TypeScript client for the UFCalendar Fight API: UFC, PFL, OKTAGON, BKFC and RIZIN events, fight cards, results, per-round stats, fighter careers, judges' scorecards and UFC rankings history since 2013.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|