@ufcalendar/sdk 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,6 +49,7 @@ List endpoints are **async generators** that follow `meta.pagination.next_cursor
49
49
  | `orgs()` / `org(slug)` | `GET /v1/orgs` / `…/{slug}` |
50
50
  | `events({ org, status, from, to, order, limit })` | `GET /v1/events` (paginated) |
51
51
  | `event(slug)` / `eventChanges(slug)` | `GET /v1/events/{slug}` / `…/changes` |
52
+ | `eventLive(slug)` | `GET /v1/events/{slug}/live` — real-time `LiveState` on fight night (Pro+); the same document streams over `wss://live.ufcalendar.com/v1?key=…` |
52
53
  | `fight(id)` / `fightStats(id)` / `fightRounds(id)` | `GET /v1/fights/{id}` / `…/stats` / `…/rounds` |
53
54
  | `fightScorecards(id)` | `GET /v1/fights/{id}/scorecards` — judges, rounds, totals, deductions |
54
55
  | `judges({ q, org, minFights })` / `judge(id)` / `judgeScorecards(id)` | `GET /v1/judges` / `…/{id}` / `…/{id}/scorecards` |
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.1.1";
31
+ var VERSION = "0.2.1";
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))}`);
@@ -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.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 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.1.1";
561
+ declare const VERSION = "0.2.1";
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 (Pro+)
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 Pro or higher (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.1.1";
561
+ declare const VERSION = "0.2.1";
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 (Pro+)
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 Pro or higher (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.1.1";
2
+ var VERSION = "0.2.1";
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.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 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.1.1",
3
+ "version": "0.2.1",
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",