@ekmanss/5eplay 20260716.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/http.js ADDED
@@ -0,0 +1,75 @@
1
+ import { FiveEPlayError } from './errors.js';
2
+ export async function fetchJson(context, url, options) {
3
+ const startedAt = performance.now();
4
+ let response;
5
+ try {
6
+ response = await context.fetch(url, {
7
+ method: options.method ?? 'GET',
8
+ signal: context.signal,
9
+ headers: options.body === undefined ? undefined : { 'content-type': 'application/json' },
10
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
11
+ });
12
+ }
13
+ catch (error) {
14
+ if (context.signal.aborted)
15
+ throw context.signal.reason;
16
+ throw new FiveEPlayError('5EPlay request failed', {
17
+ code: 'HTTP_ERROR',
18
+ operation: context.operation,
19
+ stage: options.stage,
20
+ retryable: true,
21
+ matchId: context.matchId,
22
+ cause: error,
23
+ });
24
+ }
25
+ const body = await response.text();
26
+ context.diagnostics.push({
27
+ kind: options.kind,
28
+ status: response.status,
29
+ durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
30
+ bytes: new TextEncoder().encode(body).byteLength,
31
+ ...(options.mapNumber === undefined ? {} : { mapNumber: options.mapNumber }),
32
+ ...(options.tab === undefined ? {} : { tab: options.tab }),
33
+ ...(options.page === undefined ? {} : { page: options.page }),
34
+ });
35
+ if (response.status === 404) {
36
+ throw new FiveEPlayError('5EPlay match was not found', {
37
+ code: 'MATCH_NOT_FOUND', operation: context.operation, stage: options.stage,
38
+ retryable: false, matchId: context.matchId, details: { status: response.status },
39
+ });
40
+ }
41
+ if (!response.ok) {
42
+ throw new FiveEPlayError(`5EPlay request returned HTTP ${response.status}`, {
43
+ code: 'HTTP_ERROR', operation: context.operation, stage: options.stage,
44
+ retryable: response.status === 429 || response.status >= 500,
45
+ matchId: context.matchId, details: { status: response.status },
46
+ });
47
+ }
48
+ try {
49
+ return JSON.parse(body);
50
+ }
51
+ catch (error) {
52
+ throw new FiveEPlayError('5EPlay returned invalid JSON', {
53
+ code: 'INVALID_RESPONSE', operation: context.operation, stage: options.stage,
54
+ retryable: true, matchId: context.matchId, cause: error,
55
+ });
56
+ }
57
+ }
58
+ export function responseData(value, context) {
59
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
60
+ throw new FiveEPlayError('5EPlay response envelope is invalid', {
61
+ code: 'INVALID_RESPONSE', retryable: true, ...context,
62
+ });
63
+ }
64
+ const envelope = value;
65
+ if (envelope.success !== true || !('data' in envelope)) {
66
+ throw new FiveEPlayError('5EPlay response reported failure', {
67
+ code: 'INVALID_RESPONSE', retryable: true, ...context,
68
+ details: {
69
+ errcode: typeof envelope.errcode === 'number' ? envelope.errcode : null,
70
+ message: typeof envelope.message === 'string' ? envelope.message : null,
71
+ },
72
+ });
73
+ }
74
+ return envelope.data;
75
+ }
@@ -0,0 +1,10 @@
1
+ export { getFiveEPlayMatch } from './client.js';
2
+ export { FiveEPlayError } from './errors.js';
3
+ export { matchIdentityFromInput } from './input.js';
4
+ export { getFiveEPlayLiveMatches } from './live_matches.js';
5
+ export { renderFiveEPlayMatchMarkdown } from './markdown.js';
6
+ export { createFiveEPlayMatchSession } from './session.js';
7
+ export { fiveEPlayMarkdownOutputPath, writeFiveEPlayMatchMarkdown, } from './write_markdown.js';
8
+ export type { FiveEPlayMarkdownOptions } from './markdown.js';
9
+ export type { WriteFiveEPlayMatchMarkdownOptions, WriteFiveEPlayMatchMarkdownResult, } from './write_markdown.js';
10
+ export type { CreateFiveEPlayMatchSessionOptions, FiveEPlayAnalysisMap, FiveEPlayAnalysisPlayer, FiveEPlayBombEvent, FiveEPlayClientOptions, FiveEPlayCommunityCard, FiveEPlayCommunityRatings, FiveEPlayCommunityScore, FiveEPlayDiagnosticWarning, FiveEPlayErrorCode, FiveEPlayHalfScore, FiveEPlayJson, FiveEPlayJsonObject, FiveEPlayLiveMatch, FiveEPlayLiveMatchMap, FiveEPlayLiveMatchesData, FiveEPlayLiveMatchesDiagnostics, FiveEPlayLiveMatchTeam, FiveEPlayKillEvent, FiveEPlayLogEvent, FiveEPlayLogPlayer, FiveEPlayMap, FiveEPlayMapStatus, FiveEPlayMatch, FiveEPlayMatchDiagnostics, FiveEPlayMatchIdentity, FiveEPlayMatchSession, FiveEPlayMatchStatus, FiveEPlayOperation, FiveEPlayPlayerDuel, FiveEPlayPlayerEquipment, FiveEPlayPlayerMetrics, FiveEPlayPlayerStats, FiveEPlayPrematchAnalysis, FiveEPlayProgressEvent, FiveEPlayRealtimeUpdate, FiveEPlayRequestDiagnostic, FiveEPlayRequestOptions, FiveEPlayRoundEnd, FiveEPlayRoundStart, FiveEPlaySide, FiveEPlayStage, FiveEPlayTeam, FiveEPlayTeamMapState, FiveEPlayTeamPlayerStats, FiveEPlayTournament, FiveEPlayVetoEntry, FiveEPlayWebSocketFactory, FiveEPlayWebSocketLike, GetFiveEPlayMatchOptions, GetFiveEPlayMatchResult, GetFiveEPlayLiveMatchesOptions, GetFiveEPlayLiveMatchesResult, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { getFiveEPlayMatch } from './client.js';
2
+ export { FiveEPlayError } from './errors.js';
3
+ export { matchIdentityFromInput } from './input.js';
4
+ export { getFiveEPlayLiveMatches } from './live_matches.js';
5
+ export { renderFiveEPlayMatchMarkdown } from './markdown.js';
6
+ export { createFiveEPlayMatchSession } from './session.js';
7
+ export { fiveEPlayMarkdownOutputPath, writeFiveEPlayMatchMarkdown, } from './write_markdown.js';
@@ -0,0 +1,3 @@
1
+ import type { FiveEPlayMatchIdentity } from './types.js';
2
+ export declare function matchIdentityFromInput(input: string): FiveEPlayMatchIdentity | null;
3
+ export declare function requireMatchIdentity(input: string): FiveEPlayMatchIdentity;
package/dist/input.js ADDED
@@ -0,0 +1,45 @@
1
+ import { FiveEPlayError } from './errors.js';
2
+ const ID_PATTERN = /^csgo_mc_(\d+)$/;
3
+ export function matchIdentityFromInput(input) {
4
+ if (typeof input !== 'string' || !input.trim())
5
+ return null;
6
+ const value = input.trim();
7
+ const direct = value.match(ID_PATTERN);
8
+ if (direct) {
9
+ const numericId = Number(direct[1]);
10
+ if (!Number.isSafeInteger(numericId) || numericId <= 0)
11
+ return null;
12
+ return {
13
+ id: value,
14
+ numericId,
15
+ url: `https://event.5eplay.com/csgo/matches/${value}`,
16
+ };
17
+ }
18
+ try {
19
+ const url = new URL(value);
20
+ if (url.protocol !== 'https:' || url.hostname !== 'event.5eplay.com')
21
+ return null;
22
+ const pathMatch = url.pathname.match(/^\/csgo\/matches\/(csgo_mc_(\d+))\/?$/);
23
+ if (!pathMatch)
24
+ return null;
25
+ const numericId = Number(pathMatch[2]);
26
+ if (!Number.isSafeInteger(numericId) || numericId <= 0)
27
+ return null;
28
+ const id = pathMatch[1];
29
+ return { id, numericId, url: `https://event.5eplay.com/csgo/matches/${id}` };
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ export function requireMatchIdentity(input) {
36
+ const identity = matchIdentityFromInput(input);
37
+ if (identity)
38
+ return identity;
39
+ throw new FiveEPlayError('match must be a csgo_mc_<id> identifier or canonical https://event.5eplay.com/csgo/matches/csgo_mc_<id> URL', {
40
+ code: 'INVALID_INPUT',
41
+ operation: 'match-detail',
42
+ stage: 'validating-input',
43
+ retryable: false,
44
+ });
45
+ }
@@ -0,0 +1,2 @@
1
+ import type { GetFiveEPlayLiveMatchesOptions, GetFiveEPlayLiveMatchesResult } from './types.js';
2
+ export declare function getFiveEPlayLiveMatches(options?: GetFiveEPlayLiveMatchesOptions): Promise<GetFiveEPlayLiveMatchesResult>;
@@ -0,0 +1,209 @@
1
+ import { asFiveEPlayError, FiveEPlayError } from './errors.js';
2
+ import { fetchJson, responseData } from './http.js';
3
+ import { matchIdentityFromInput } from './input.js';
4
+ import { integer, record, records, text } from './value.js';
5
+ const LIST_PAGE = 'https://event.5eplay.com/csgo/matches';
6
+ const LIST_API = 'https://app.5eplay.com/api/tournament/session_list';
7
+ const PAGE_SIZE = 20;
8
+ function emit(options, stage, message) {
9
+ options.onProgress?.({
10
+ operation: 'live-matches',
11
+ stage,
12
+ message,
13
+ timestamp: new Date().toISOString(),
14
+ });
15
+ }
16
+ function requestLifetime(options) {
17
+ const controller = new AbortController();
18
+ const timeoutMs = options.timeoutMs ?? 5_000;
19
+ const timeout = setTimeout(() => {
20
+ controller.abort(new FiveEPlayError(`5EPlay live-list request timed out after ${timeoutMs}ms`, {
21
+ code: 'TIMEOUT', operation: 'live-matches', stage: 'fetching-live-matches', retryable: true,
22
+ }));
23
+ }, timeoutMs);
24
+ const abort = () => controller.abort(options.signal?.reason);
25
+ if (options.signal?.aborted)
26
+ abort();
27
+ else
28
+ options.signal?.addEventListener('abort', abort, { once: true });
29
+ return {
30
+ signal: controller.signal,
31
+ dispose: () => {
32
+ clearTimeout(timeout);
33
+ options.signal?.removeEventListener('abort', abort);
34
+ },
35
+ };
36
+ }
37
+ function isLive(value) {
38
+ const state = record(value.state);
39
+ return state.status === '1' || state.status === 1
40
+ || records(state.bout_states).some((map) => map.status === '1' || map.status === 1);
41
+ }
42
+ function mapStatus(value) {
43
+ if (value === '1' || value === 1)
44
+ return 'live';
45
+ if (value === '2' || value === 2)
46
+ return 'completed';
47
+ if (value === '0' || value === 0 || value === '-1' || value === -1)
48
+ return 'upcoming';
49
+ return 'unknown';
50
+ }
51
+ function liveTeam(value, seriesScore, fallbackId) {
52
+ const source = record(value);
53
+ return {
54
+ id: text(source.id) ?? fallbackId,
55
+ name: text(source.disp_name)?.trim() ?? '',
56
+ country: text(source.country),
57
+ rank: integer(source.rank),
58
+ valveRank: integer(record(source.v_rank).rank),
59
+ seriesScore: integer(seriesScore),
60
+ };
61
+ }
62
+ function liveMap(value, matchId, teams) {
63
+ const source = record(value);
64
+ const number = integer(source.bout_num);
65
+ const name = text(source.map_name)?.trim();
66
+ if (number === null || !name)
67
+ return null;
68
+ return {
69
+ id: `${matchId}_${number}`,
70
+ number,
71
+ name,
72
+ status: mapStatus(source.status),
73
+ winnerTeamId: source.result === 't1' ? teams[0]?.id ?? null
74
+ : source.result === 't2' ? teams[1]?.id ?? null : null,
75
+ teams: [
76
+ { teamId: teams[0]?.id ?? 't1', score: integer(source.t1_score) },
77
+ { teamId: teams[1]?.id ?? 't2', score: integer(source.t2_score) },
78
+ ],
79
+ };
80
+ }
81
+ function liveMatch(value) {
82
+ const source = record(value);
83
+ if (!isLive(source))
84
+ return null;
85
+ const matchInfo = record(source.mc_info);
86
+ const state = record(source.state);
87
+ const identity = matchIdentityFromInput(text(matchInfo.id) ?? '');
88
+ if (!identity)
89
+ return null;
90
+ const teams = [
91
+ liveTeam(matchInfo.t1_info, state.t1_score, 't1'),
92
+ liveTeam(matchInfo.t2_info, state.t2_score, 't2'),
93
+ ];
94
+ const maps = records(state.bout_states).flatMap((map) => {
95
+ const transformed = liveMap(map, identity.id, teams);
96
+ return transformed ? [transformed] : [];
97
+ }).sort((left, right) => left.number - right.number);
98
+ const tournament = record(source.tt_info);
99
+ return {
100
+ id: identity.id,
101
+ numericId: identity.numericId,
102
+ url: identity.url,
103
+ status: 'live',
104
+ bestOf: integer(matchInfo.format),
105
+ scheduledAtUnixSeconds: integer(matchInfo.plan_ts),
106
+ stage: text(matchInfo.tt_stage),
107
+ stageDescription: text(matchInfo.tt_stage_desc),
108
+ tournament: {
109
+ id: text(tournament.id),
110
+ name: text(tournament.disp_name)?.trim() ?? '',
111
+ grade: text(tournament.grade),
112
+ gradeLabel: text(tournament.grade_label),
113
+ },
114
+ teams,
115
+ maps,
116
+ currentMap: maps.find((map) => map.status === 'live') ?? null,
117
+ };
118
+ }
119
+ export async function getFiveEPlayLiveMatches(options = {}) {
120
+ const startedAt = new Date().toISOString();
121
+ const started = performance.now();
122
+ const lifetime = requestLifetime(options);
123
+ const requests = [];
124
+ const context = {
125
+ operation: 'live-matches',
126
+ signal: lifetime.signal,
127
+ fetch: options.fetch ?? globalThis.fetch,
128
+ diagnostics: requests,
129
+ };
130
+ if (typeof context.fetch !== 'function') {
131
+ lifetime.dispose();
132
+ throw new FiveEPlayError('global fetch is unavailable', {
133
+ code: 'INTERNAL_ERROR', operation: 'live-matches', stage: 'fetching-live-matches',
134
+ retryable: false,
135
+ });
136
+ }
137
+ emit(options, 'fetching-live-matches', 'Fetching currently live 5EPlay CS2 matches');
138
+ try {
139
+ const matches = [];
140
+ const seen = new Set();
141
+ for (let page = 1;; page += 1) {
142
+ const query = new URLSearchParams({
143
+ game_status: '1',
144
+ game_type: '1',
145
+ grades: '',
146
+ page: String(page),
147
+ limit: String(PAGE_SIZE),
148
+ });
149
+ const response = await fetchJson(context, `${LIST_API}?${query}`, {
150
+ kind: 'live-list', stage: 'fetching-live-matches', page,
151
+ });
152
+ const data = record(responseData(response, {
153
+ operation: 'live-matches', stage: 'fetching-live-matches',
154
+ }));
155
+ if (!Array.isArray(data.matches)) {
156
+ throw new FiveEPlayError('5EPlay live-list response did not contain a match array', {
157
+ code: 'INVALID_RESPONSE', operation: 'live-matches', stage: 'fetching-live-matches',
158
+ retryable: true,
159
+ });
160
+ }
161
+ const pageMatches = records(data.matches);
162
+ let newLiveMatches = 0;
163
+ for (const source of pageMatches) {
164
+ const transformed = liveMatch(source);
165
+ if (!transformed || seen.has(transformed.id))
166
+ continue;
167
+ seen.add(transformed.id);
168
+ matches.push(transformed);
169
+ newLiveMatches += 1;
170
+ }
171
+ const pageIsEntirelyLive = pageMatches.length === PAGE_SIZE
172
+ && pageMatches.every(isLive);
173
+ if (!pageIsEntirelyLive || newLiveMatches === 0)
174
+ break;
175
+ }
176
+ const completedAt = new Date().toISOString();
177
+ const result = {
178
+ data: {
179
+ schemaVersion: '1.0.0',
180
+ capturedAt: completedAt,
181
+ source: { provider: '5eplay', url: LIST_PAGE },
182
+ hasLiveMatches: matches.length > 0,
183
+ matches,
184
+ },
185
+ diagnostics: {
186
+ schemaVersion: '1.0.0',
187
+ operation: 'live-matches',
188
+ startedAt,
189
+ completedAt,
190
+ durationMs: Math.round((performance.now() - started) * 100) / 100,
191
+ requests,
192
+ },
193
+ };
194
+ emit(options, 'completed', `Found ${matches.length} live 5EPlay CS2 matches`);
195
+ return result;
196
+ }
197
+ catch (error) {
198
+ if (lifetime.signal.aborted && lifetime.signal.reason instanceof Error) {
199
+ throw lifetime.signal.reason;
200
+ }
201
+ throw asFiveEPlayError(error, {
202
+ code: 'INTERNAL_ERROR', operation: 'live-matches', stage: 'fetching-live-matches',
203
+ retryable: false,
204
+ });
205
+ }
206
+ finally {
207
+ lifetime.dispose();
208
+ }
209
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { FiveEPlayLogEvent } from './types.js';
2
+ export declare function transformLogRecord(value: unknown): FiveEPlayLogEvent;
3
+ export declare function compareUpdateVersions(left: string, right: string): number;
4
+ export declare function mergeLogEvents(existing: FiveEPlayLogEvent[], additions: FiveEPlayLogEvent[]): FiveEPlayLogEvent[];
package/dist/log.js ADDED
@@ -0,0 +1,153 @@
1
+ import { flag, integer, json, record, side, text } from './value.js';
2
+ function player(value, nameKeys, idKey, sideKey) {
3
+ const name = nameKeys.map((key) => text(value[key])).find(Boolean) ?? '';
4
+ return {
5
+ id: idKey ? text(value[idKey]) : null,
6
+ name,
7
+ side: sideKey ? side(value[sideKey]) : null,
8
+ };
9
+ }
10
+ function populated(value) {
11
+ return Object.values(value).some((item) => item !== '' && item !== null && item !== undefined && item !== false && item !== 0);
12
+ }
13
+ function kindFor(parsed) {
14
+ if (populated(record(parsed.round_start)))
15
+ return 'round-start';
16
+ if (populated(record(parsed.round_end)))
17
+ return 'round-end';
18
+ if (populated(record(parsed.player_join)))
19
+ return 'player-joined';
20
+ if (populated(record(parsed.player_quit)))
21
+ return 'player-left';
22
+ if (populated(record(parsed.kill)))
23
+ return 'kill';
24
+ if (populated(record(parsed.bomb_planted)))
25
+ return 'bomb-planted';
26
+ if (populated(record(parsed.bomb_defused)))
27
+ return 'bomb-defused';
28
+ if (populated(record(parsed.suicide)))
29
+ return 'suicide';
30
+ if (populated(record(parsed.match_started)))
31
+ return 'match-started';
32
+ if (parsed.restart !== undefined)
33
+ return 'restart';
34
+ return 'unknown';
35
+ }
36
+ function killEvent(parsed) {
37
+ const kill = record(parsed.kill);
38
+ if (!populated(kill))
39
+ return null;
40
+ const assist = record(parsed.assist);
41
+ const assister = populated(assist)
42
+ ? player(assist, ['assister_nick', 'assister_name'], undefined, 'assister_side')
43
+ : null;
44
+ const flasherName = text(kill.flasher_nick);
45
+ return {
46
+ eventId: text(kill.event_id),
47
+ killer: player(kill, ['killer_nick', 'killer_name'], 'killer_id', 'killer_side'),
48
+ victim: player(kill, ['victim_nick', 'victim_name'], 'victim_id', 'victim_side'),
49
+ assister,
50
+ flasher: flasherName
51
+ ? { id: null, name: flasherName, side: side(kill.flasher_side) }
52
+ : null,
53
+ weapon: text(kill.weapon),
54
+ weaponLogoUrl: text(kill.weapon_logo),
55
+ headshot: flag(kill.head_shot) === true,
56
+ wallbang: flag(kill.penetrated) === true,
57
+ throughSmoke: flag(kill.through_smoke) === true,
58
+ noScope: flag(kill.no_scope) === true,
59
+ killerBlind: flag(kill.killer_blind) === true,
60
+ killerPosition: { x: integer(kill.killer_x), y: integer(kill.killer_y) },
61
+ victimPosition: { x: integer(kill.victim_x), y: integer(kill.victim_y) },
62
+ };
63
+ }
64
+ function bombEvent(parsed) {
65
+ const bomb = record(parsed.bomb_planted);
66
+ if (!populated(bomb))
67
+ return null;
68
+ return {
69
+ player: player(bomb, ['player_nick', 'player_name']),
70
+ site: text(bomb.bomb_site),
71
+ ctPlayers: integer(bomb.ct_players),
72
+ tPlayers: integer(bomb.t_players),
73
+ };
74
+ }
75
+ function parseLogInfo(value) {
76
+ if (value !== null && typeof value === 'object' && !Array.isArray(value))
77
+ return record(value);
78
+ if (typeof value !== 'string' || !value)
79
+ return {};
80
+ try {
81
+ return record(JSON.parse(value));
82
+ }
83
+ catch {
84
+ return {};
85
+ }
86
+ }
87
+ export function transformLogRecord(value) {
88
+ const row = record(value);
89
+ const parsed = parseLogInfo(row.log_info);
90
+ const roundStart = record(parsed.round_start);
91
+ const roundEnd = record(parsed.round_end);
92
+ const joined = record(parsed.player_join);
93
+ const left = record(parsed.player_quit);
94
+ const suicide = record(parsed.suicide);
95
+ const defused = record(parsed.bomb_defused);
96
+ const winner = side(roundEnd.winner);
97
+ const restart = parsed.restart === undefined ? null : json(parsed.restart);
98
+ return {
99
+ updateVersion: text(row.update_version) ?? text(parsed.update_version) ?? '',
100
+ matchId: text(row.match_id) ?? '',
101
+ tournamentId: text(row.tt_id),
102
+ mapId: text(row.bout_id),
103
+ mapNumber: integer(row.bout_num),
104
+ map: text(row.map_name),
105
+ type: integer(parsed.type),
106
+ kind: kindFor(parsed),
107
+ roundStart: populated(roundStart) ? {
108
+ round: integer(roundStart.round_num),
109
+ map: text(roundStart.map),
110
+ mapNumber: integer(roundStart.bout_num),
111
+ } : null,
112
+ roundEnd: populated(roundEnd) ? {
113
+ ctScore: integer(roundEnd.ct_score),
114
+ tScore: integer(roundEnd.t_score),
115
+ winnerSide: winner,
116
+ reason: text(roundEnd.win_type),
117
+ reasonCode: integer(roundEnd.win_type_app),
118
+ } : null,
119
+ playerJoined: populated(joined)
120
+ ? player(joined, ['player_nick', 'player_name'])
121
+ : null,
122
+ playerLeft: populated(left)
123
+ ? player(left, ['player_nick', 'player_name'], undefined, 'player_side')
124
+ : null,
125
+ kill: killEvent(parsed),
126
+ suicide: populated(suicide) ? {
127
+ player: player(suicide, ['player_nick', 'player_name'], undefined, 'side'),
128
+ weapon: text(suicide.weapon),
129
+ weaponLogoUrl: text(suicide.weapon_logo),
130
+ } : null,
131
+ bombPlanted: bombEvent(parsed),
132
+ bombDefused: populated(defused)
133
+ ? player(defused, ['player_nick', 'player_name'])
134
+ : null,
135
+ restart: restart,
136
+ };
137
+ }
138
+ export function compareUpdateVersions(left, right) {
139
+ if (/^\d+$/.test(left) && /^\d+$/.test(right)) {
140
+ const a = BigInt(left);
141
+ const b = BigInt(right);
142
+ return a < b ? -1 : a > b ? 1 : 0;
143
+ }
144
+ return left.localeCompare(right);
145
+ }
146
+ export function mergeLogEvents(existing, additions) {
147
+ const byVersion = new Map();
148
+ for (const event of [...existing, ...additions]) {
149
+ const key = event.updateVersion || JSON.stringify(event);
150
+ byVersion.set(key, event);
151
+ }
152
+ return [...byVersion.values()].sort((leftEvent, rightEvent) => compareUpdateVersions(leftEvent.updateVersion, rightEvent.updateVersion));
153
+ }
@@ -0,0 +1,5 @@
1
+ import type { GetFiveEPlayMatchResult } from './types.js';
2
+ export interface FiveEPlayMarkdownOptions {
3
+ includeDiagnostics?: boolean;
4
+ }
5
+ export declare function renderFiveEPlayMatchMarkdown(result: GetFiveEPlayMatchResult, options?: FiveEPlayMarkdownOptions): string;