refactor: make match-collector export its types, and consume them in frontend
All checks were successful
pipeline / lint-and-format (push) Successful in 4m22s
pipeline / build-and-push-images (push) Successful in 2m11s

This commit is contained in:
2026-04-30 00:06:53 +02:00
parent db2ca353c5
commit e1ab81854a
36 changed files with 513 additions and 351 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { debounce, isEmpty } from '~/utils/helpers' import { debounce, isEmpty } from '~/utils/helpers'
import type { ChampionSummary, LaneData, ChampionData } from 'match_collector'
// Constants // Constants
const CHAMPIONS_API_URL = '/api/champions' const CHAMPIONS_API_URL = '/api/champions'

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { CDRAGON_BASE, mapPath } from '~/utils/cdragon' import { CDRAGON_BASE, mapPath } from '~/utils/cdragon'
import type { Perk, Item } from '~/types/cdragon'
const props = defineProps<{ const props = defineProps<{
keystoneId: number keystoneId: number
itemId: number itemId: number

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { CDRAGON_BASE, mapPath } from '~/utils/cdragon' import { CDRAGON_BASE, mapPath } from '~/utils/cdragon'
import type { PerkStyle, Perk } from '~/types/cdragon'
interface RuneBuild { interface RuneBuild {
count: number count: number
primaryStyle: number primaryStyle: number

View File

@@ -1,4 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FirstBackGroup } from 'match_collector'
import type { Item } from '~/types/cdragon'
defineProps<{ defineProps<{
firstBacks: FirstBackGroup[] firstBacks: FirstBackGroup[]
itemMap: Map<number, Item> itemMap: Map<number, Item>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Item } from '~/types/cdragon'
interface ItemData { interface ItemData {
data: number data: number
count: number count: number

View File

@@ -5,8 +5,10 @@ import CompactRuneSelector from '~/components/build/CompactRuneSelector.vue'
import ItemRow from '~/components/build/ItemRow.vue' import ItemRow from '~/components/build/ItemRow.vue'
import FirstBack from '~/components/build/FirstBack.vue' import FirstBack from '~/components/build/FirstBack.vue'
import type { Build } from 'match_collector'
const props = defineProps<{ const props = defineProps<{
builds: Builds builds: Array<Build>
}>() }>()
// State // State

View File

@@ -1,6 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { CDRAGON_BASE, mapPath } from '~/utils/cdragon' import { CDRAGON_BASE, mapPath } from '~/utils/cdragon'
import type { Item } from '~/types/cdragon'
import type { ItemTag } from 'match_collector'
interface Props { interface Props {
item: Item item: Item
size?: number size?: number

View File

@@ -7,6 +7,9 @@ import {
type ParsedDescription type ParsedDescription
} from 'dragon-item-parser' } from 'dragon-item-parser'
import type { Item } from '~/types/cdragon'
import type { ItemTag } from 'match_collector'
interface Props { interface Props {
item: Item | null item: Item | null
show: boolean show: boolean

View File

@@ -1,6 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { LinePath as svgdomarrowsLinePath } from 'svg-dom-arrows' import { LinePath as svgdomarrowsLinePath } from 'svg-dom-arrows'
import type { ItemTree } from 'match_collector'
import type { Item } from '~/types/cdragon'
defineProps<{ defineProps<{
tree: ItemTree tree: ItemTree
parentCount?: number parentCount?: number

View File

@@ -2,6 +2,8 @@
import { CDRAGON_BASE } from '~/utils/cdragon' import { CDRAGON_BASE } from '~/utils/cdragon'
import MatchupSpectrum from './Spectrum.vue' import MatchupSpectrum from './Spectrum.vue'
import type { MatchupData } from 'match_collector'
defineProps<{ defineProps<{
matchups?: Array<MatchupData> matchups?: Array<MatchupData>
championId: number championId: number

View File

@@ -2,6 +2,8 @@
import { ref } from 'vue' import { ref } from 'vue'
import { CDRAGON_BASE } from '~/utils/cdragon' import { CDRAGON_BASE } from '~/utils/cdragon'
import type { MatchupData } from 'match_collector'
defineProps<{ defineProps<{
matchups?: Array<MatchupData> matchups?: Array<MatchupData>
championId: number championId: number

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { LaneData } from 'match_collector'
defineProps<{ defineProps<{
championName?: string championName?: string
championLanes?: Array<LaneData> championLanes?: Array<LaneData>

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon' import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon'
import type { LaneData } from 'match_collector'
defineProps<{ defineProps<{
championName?: string championName?: string
championLanes?: Array<LaneData> championLanes?: Array<LaneData>

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon' import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon'
import type { LaneData } from 'match_collector'
defineProps<{ defineProps<{
championName?: string championName?: string
championLanes?: Array<LaneData> championLanes?: Array<LaneData>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PerkStyle, Perk } from '~/types/cdragon'
const props = defineProps<{ const props = defineProps<{
primaryStyleId: number primaryStyleId: number
secondaryStyleId: number secondaryStyleId: number
@@ -8,13 +10,14 @@ const props = defineProps<{
const primaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] }) const primaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] })
const secondaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] }) const secondaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] })
const { data: perks_data }: PerksResponse = await useFetch('/api/cdragon/perks') const { data: perks_data }: { data: Ref<Array<Perk>> } = await useFetch('/api/cdragon/perks')
const perks = reactive(new Map()) const perks = reactive(new Map())
for (const perk of perks_data.value) { for (const perk of perks_data.value) {
perks.set(perk.id, perk) perks.set(perk.id, perk)
} }
const { data: stylesData }: PerkStylesResponse = await useFetch('/api/cdragon/perkstyles') const { data: stylesData }: { data: Ref<{ styles: Array<PerkStyle> }> } =
await useFetch('/api/cdragon/perkstyles')
watch( watch(
() => props.primaryStyleId, () => props.primaryStyleId,
async (_newP, _oldP) => { async (_newP, _oldP) => {

View File

@@ -10,6 +10,9 @@ import {
} from 'chart.js' } from 'chart.js'
import { Bar } from 'vue-chartjs' import { Bar } from 'vue-chartjs'
import type { Champion } from '~/types/cdragon'
import type { LaneData } from 'match_collector'
// Register // Register
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale) ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)

View File

@@ -1,4 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { LaneData } from 'match_collector'
import type { Champion } from '~/types/cdragon'
defineProps<{ defineProps<{
title: string title: string
tier: Array<{ champion: Champion; lane: LaneData }> tier: Array<{ champion: Champion; lane: LaneData }>

View File

@@ -1,3 +1,5 @@
import type { Builds } from 'match_collector'
/** /**
* Composable for managing build data * Composable for managing build data
*/ */

View File

@@ -17,26 +17,7 @@ export default withNuxt([
languageOptions: { languageOptions: {
globals: { globals: {
...globals.browser, ...globals.browser,
...globals.node, ...globals.node
// Add global types from our API definitions
ChampionSummary: 'readonly',
LaneData: 'readonly',
ChampionData: 'readonly',
ItemTree: 'readonly',
ItemTag: 'readonly',
Builds: 'readonly',
PerkStyle: 'readonly',
PerksResponse: 'readonly',
PerkStylesResponse: 'readonly',
Champion: 'readonly',
ChampionsResponse: 'readonly',
ChampionResponse: 'readonly',
ItemResponse: 'readonly',
MatchupData: 'readonly',
Item: 'readonly',
SummonerSpell: 'readonly',
Perk: 'readonly',
FirstBackGroup: 'readonly'
} }
}, },
rules: { rules: {

View File

@@ -14,6 +14,7 @@
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"dragon-item-parser": "file:../dragon-item-parser", "dragon-item-parser": "file:../dragon-item-parser",
"match_collector": "file:../match_collector",
"mongodb": "^6.10.0", "mongodb": "^6.10.0",
"nuxt": "^3.17.5", "nuxt": "^3.17.5",
"nuxt-umami": "^3.2.1", "nuxt-umami": "^3.2.1",
@@ -9909,6 +9910,88 @@
"resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==" "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="
}, },
"node_modules/match_collector": {
"version": "1.0.0",
"resolved": "file:../match_collector",
"license": "ISC",
"dependencies": {
"dragon-item-parser": "file:../dragon-item-parser",
"mongodb": "^7.2.0"
}
},
"node_modules/match_collector/node_modules/@types/whatwg-url": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
"integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
"dependencies": {
"@types/webidl-conversions": "*"
}
},
"node_modules/match_collector/node_modules/bson": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz",
"integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==",
"engines": {
"node": ">=20.19.0"
}
},
"node_modules/match_collector/node_modules/mongodb": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz",
"integrity": "sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==",
"dependencies": {
"@mongodb-js/saslprep": "^1.3.0",
"bson": "^7.2.0",
"mongodb-connection-string-url": "^7.0.0"
},
"engines": {
"node": ">=20.19.0"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.806.0",
"@mongodb-js/zstd": "^7.0.0",
"gcp-metadata": "^7.0.1",
"kerberos": "^7.0.0",
"mongodb-client-encryption": ">=7.0.0 <7.1.0",
"snappy": "^7.3.2",
"socks": "^2.8.6"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/match_collector/node_modules/mongodb-connection-string-url": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz",
"integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==",
"dependencies": {
"@types/whatwg-url": "^13.0.0",
"whatwg-url": "^14.1.0"
},
"engines": {
"node": ">=20.19.0"
}
},
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.27.1", "version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",

View File

@@ -14,6 +14,7 @@
"format:check": "prettier --check ." "format:check": "prettier --check ."
}, },
"dependencies": { "dependencies": {
"match_collector": "file:../match_collector",
"dragon-item-parser": "file:../dragon-item-parser", "dragon-item-parser": "file:../dragon-item-parser",
"@nuxt/eslint": "^1.12.1", "@nuxt/eslint": "^1.12.1",
"@nuxt/fonts": "^0.11.3", "@nuxt/fonts": "^0.11.3",

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import type { ChampionData } from 'match_collector'
const route = useRoute() const route = useRoute()
const championAlias = route.params.alias as string const championAlias = route.params.alias as string

View File

@@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon' import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon'
import type { LaneData, ChampionData, Champion } from 'match_collector'
const route = useRoute() const route = useRoute()
const lane = route.params.lane as string const lane = route.params.lane as string
const { data: championsData }: ChampionsResponse = await useFetch('/api/cdragon/champion-summary') const { data: championsData }: { data: Ref<Array<Champion>> } = await useFetch(
'/api/cdragon/champion-summary'
)
const { data: championsLanes }: { data: Ref<Array<ChampionData>> } = const { data: championsLanes }: { data: Ref<Array<ChampionData>> } =
await useFetch('/api/champions') await useFetch('/api/champions')

View File

@@ -1,4 +1,5 @@
import type { MongoClient } from 'mongodb' import type { MongoClient } from 'mongodb'
import type { ChampionData } from 'match_collector'
import { connectToDatabase, fetchLatestPatch } from '../../utils/mongo' import { connectToDatabase, fetchLatestPatch } from '../../utils/mongo'
async function championInfos(client: MongoClient, patch: string, championAlias: string) { async function championInfos(client: MongoClient, patch: string, championAlias: string) {

View File

@@ -1,4 +1,5 @@
import type { MongoClient } from 'mongodb' import type { MongoClient } from 'mongodb'
import type { ChampionData } from 'match_collector'
import { connectToDatabase, fetchLatestPatch } from '../utils/mongo' import { connectToDatabase, fetchLatestPatch } from '../utils/mongo'
async function champions(client: MongoClient, patch: string) { async function champions(client: MongoClient, patch: string) {
@@ -11,7 +12,6 @@ async function champions(client: MongoClient, patch: string) {
if (x.lanes != undefined && x.lanes != null) { if (x.lanes != undefined && x.lanes != null) {
for (const lane of x.lanes) { for (const lane of x.lanes) {
delete lane.builds delete lane.builds
delete lane.runes
} }
} }
}) })

View File

@@ -1,126 +0,0 @@
declare global {
/**
* Item tags derived from purchase patterns
*/
type ItemTag = 'ahead' | 'behind' | 'region_euw' | 'region_eun' | 'region_na' | 'region_kr'
/**
* Represents an item in the build tree
*/
interface ItemTree {
count: number
data: number
children: ItemTree[]
tags: ItemTag[]
}
/**
* Represents a complete build with runes and items
*/
interface Build {
runeKeystone: number
runes: Rune[]
items: ItemTree
bootsFirst: number
count: number
boots: Array<{ count: number; data: number }>
suppItems: Array<{ count: number; data: number }>
startItems: Array<{ count: number; data: number }>
pickrate: number
firstBacks?: FirstBackGroup[]
}
/**
* Represents champion build information (array of builds)
*/
type Builds = Array<Build>
/**
* Represents a rune configuration
*/
interface Rune {
count: number
primaryStyle: number
secondaryStyle: number
selections: number[]
pickrate: number
}
/**
* Represents counter data for a champion
*/
interface MatchupData {
championId: number
winrate: number
games: number
championName: string
championAlias: string
}
/**
* Represents an item in a first back item set
*/
interface FirstBackItemSetEntry {
itemId: number
count: number
}
/**
* Represents an item set (combination of items)
*/
interface ItemSet {
items: FirstBackItemSetEntry[]
totalGold: number
}
/**
* Represents a grouped first back by item set
*/
interface FirstBackGroup {
itemSet: ItemSet
count: number
pickrate: number
avgTimestamp: number
}
/**
* Represents lane-specific champion data
*/
interface LaneData {
data: string
count: number
winningMatches: number
losingMatches: number
winrate: number
pickrate: number
builds?: Builds
summonerSpells: Array<{ id: number; count: number; pickrate: number }>
matchups?: MatchupData[]
}
/**
* Represents complete champion data
*/
interface ChampionData {
id: number
name: string
alias: string
gameCount: number
winrate: number
pickrate: number
lanes: LaneData[]
}
/**
* Champion summary from CDragon
*/
interface ChampionSummary {
id: number
name: string
alias: string
squarePortraitPath: string
// Add other relevant fields as needed
}
}
export {}

View File

@@ -1,27 +1,17 @@
import type { ItemStats } from 'dragon-item-parser' import type { ItemStats } from 'dragon-item-parser'
declare global { type Champion = {
type ChampionsResponse = {
data: Ref<Array<Champion>>
}
type ChampionResponse = {
data: Ref<ChampionFull>
}
type Champion = {
name: string name: string
alias: string alias: string
squarePortraitPath: string squarePortraitPath: string
} }
type ChampionFull = { type ChampionFull = {
name: string name: string
alias: string alias: string
squarePortraitPath: string squarePortraitPath: string
title: string title: string
} }
type ItemResponse = { type Item = {
data: Ref<Array<Item>>
}
type Item = {
id: number id: number
iconPath: string iconPath: string
name?: string name?: string
@@ -32,29 +22,22 @@ declare global {
price?: number price?: number
priceTotal?: number priceTotal?: number
stats?: ItemStats stats?: ItemStats
} }
type SummonerSpell = { type SummonerSpell = {
id: number id: number
iconPath: string iconPath: string
name: string name: string
} }
type PerksResponse = { type Perk = {
data: Ref<Array<Perk>>
}
type Perk = {
id: number id: number
name: string name: string
iconPath: string iconPath: string
} }
type PerkStylesResponse = { type PerkStyle = {
data: Ref<{ styles: Array<PerkStyle> }>
}
type PerkStyle = {
id: number id: number
name: string name: string
iconPath: string iconPath: string
slots: Array<{ perks: Array<number> }> slots: Array<{ perks: Array<number> }>
}
} }
export {} export type { Champion, ChampionFull, Item, SummonerSpell, Perk, PerkStyle }

View File

@@ -1,3 +1,5 @@
import type { Build, ItemTree } from 'match_collector'
/** /**
* Gets all late game items from the item tree (items beyond first level) * Gets all late game items from the item tree (items beyond first level)
* Returns a flat array of unique items with their counts * Returns a flat array of unique items with their counts
@@ -55,7 +57,9 @@ function trimTreeDepth(tree: ItemTree, maxDepth: number, currentDepth: number =
count: tree.count, count: tree.count,
data: tree.data, data: tree.data,
children: [], children: [],
tags: tree.tags tags: tree.tags,
boughtWhen: tree.boughtWhen,
platformCount: tree.platformCount
} }
// If we haven't reached maxDepth, include children // If we haven't reached maxDepth, include children

View File

@@ -1 +1,2 @@
node_modules node_modules
dist

View File

@@ -1,14 +1,22 @@
{ {
"name": "match_collector", "name": "match_collector",
"version": "1.0.0", "version": "1.0.0",
"main": "index.ts", "main": "dist/lib.js",
"types": "dist/lib.d.ts",
"type": "module", "type": "module",
"exports": {
".": {
"types": "./dist/lib.d.ts",
"import": "./dist/lib.js"
}
},
"scripts": { "scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint .", "lint": "eslint ./src",
"lint:fix": "eslint --fix .", "lint:fix": "eslint --fix ./src",
"format": "prettier --write .", "format": "prettier --write ./src",
"format:check": "prettier --check .", "format:check": "prettier --check ./src",
"dev": "node --import=tsx src/index.ts" "dev": "node --import=tsx src/index.ts"
}, },
"author": "", "author": "",

View File

@@ -1,8 +1,6 @@
import { MongoClient } from 'mongodb' import { MongoClient } from 'mongodb'
import { import {
ItemTree,
GoldAdvantageTag, GoldAdvantageTag,
PlatformCounts,
treeInit, treeInit,
treeMerge, treeMerge,
treeCutBranches, treeCutBranches,
@@ -15,14 +13,27 @@ import { PLATFORM_KEYS } from './platform'
import { import {
initItemDict as initFirstBackItemDict, initItemDict as initFirstBackItemDict,
extractFirstBackFromMatch, extractFirstBackFromMatch,
groupFirstBacksByItemSet, groupFirstBacksByItemSet
FirstBackData,
FirstBackGroup
} from './first_back' } from './first_back'
import { Match, Timeline, Participant, Frame } from './api' import { Match, Timeline, Participant, Frame } from './api'
import type {
Rune,
InternalBuild,
InternalBuildWithStartItems,
InternalLaneData,
InternalChampionData,
FirstBackData
} from './types'
function sameArrays(array1: Array<number>, array2: Array<number>) { // Type aliases for internal use
type Builds = InternalBuild[]
type Build = InternalBuild
type BuildWithStartItems = InternalBuildWithStartItems
type LaneData = InternalLaneData
type ChampionData = InternalChampionData
function sameArrays(array1: number[], array2: number[]) {
if (array1.length != array2.length) return false if (array1.length != array2.length) return false
for (const e of array1) { for (const e of array1) {
if (!array2.includes(e)) return false if (!array2.includes(e)) return false
@@ -55,77 +66,6 @@ function arrayRemovePercentage(
} }
} }
type Rune = {
count: number
primaryStyle: number
secondaryStyle: number
selections: Array<number>
pickrate?: number
}
type Build = {
runeKeystone: number
runes: Array<Rune>
items: ItemTree
bootsFirstCount: number
bootsFirst?: number
count: number
suppItems: Array<{ data: number; count: number }>
boots: Array<{ data: number; count: number }>
pickrate?: number
// First back data (collected during processing, grouped in finalize)
firstBacksRaw?: FirstBackData[]
firstBacks?: FirstBackGroup[]
}
type BuildWithStartItems = {
runeKeystone: number
runes: Array<Rune>
items: ItemTree
bootsFirst?: number
bootsFirstCount: number
count: number
startItems: Array<{ data: number; count: number }>
suppItems: Array<{ data: number; count: number }>
boots: Array<{ data: number; count: number }>
pickrate?: number
firstBacksRaw?: FirstBackData[]
firstBacks?: FirstBackGroup[]
}
type Builds = Build[]
type Champion = {
id: number
name: string
alias: string
}
type MatchupData = {
championId: number
winrate: number
games: number
championName: string
championAlias: string
}
type LaneData = {
data: string
count: number
winningMatches: number
losingMatches: number
winrate: number
pickrate: number
builds: Builds
matchups?: Array<MatchupData>
summonerSpells: Array<{ id: number; count: number; pickrate?: number }>
// Region distribution for this lane (used for tag derivation)
regionDistribution?: PlatformCounts
}
type ChampionData = {
champion: Champion
winningMatches: number
losingMatches: number
lanes: Array<LaneData>
}
// Helper function to create rune configuration from participant // Helper function to create rune configuration from participant
function createRuneConfiguration(participant: Participant): Rune { function createRuneConfiguration(participant: Participant): Rune {
const primaryStyle = participant.perks.styles[0].style const primaryStyle = participant.perks.styles[0].style

View File

@@ -1,4 +1,8 @@
import { Match, Timeline } from './api' import { Match, Timeline } from './api'
import type { BackEvent, ItemSet, FirstBackData, FirstBackGroup } from './types'
// Re-export types for backward compatibility
export type { BackEvent, ItemSet, FirstBackData, FirstBackGroup }
// Item dictionary for gold information // Item dictionary for gold information
const itemDict = new Map< const itemDict = new Map<
@@ -49,38 +53,6 @@ function shouldTrackItem(itemId: number): boolean {
return true return true
} }
// A single back event with all items purchased
export type BackEvent = {
timestamp: number
items: Array<{
itemId: number
gold: number
}>
totalGold: number // Total gold value of items bought
}
// Item set - a unique combination of items bought together
export type ItemSet = {
items: Array<{ itemId: number; count: number }> // Items with quantities
totalGold: number // Total gold value of items
}
// First back data with item set
export type FirstBackData = {
timestamp: number
itemSet: ItemSet
}
// Grouped first back by item set
export type FirstBackGroup = {
// The item set combination
itemSet: ItemSet
// Stats for this item set
count: number
pickrate: number // Overall pickrate for this item set
avgTimestamp: number
}
// Create a unique key for an item set (sorted by itemId for consistency) // Create a unique key for an item set (sorted by itemId for consistency)
function itemSetKey(items: Array<{ itemId: number; count: number }>): string { function itemSetKey(items: Array<{ itemId: number; count: number }>): string {
const sorted = [...items].sort((a, b) => a.itemId - b.itemId) const sorted = [...items].sort((a, b) => a.itemId - b.itemId)

View File

@@ -6,31 +6,7 @@ import {
} from './platform' } from './platform'
import type { PlatformCounts } from './platform' import type { PlatformCounts } from './platform'
import type { GoldAdvantageTag, ItemTag, ItemTree } from './types'
type GoldAdvantageTag = 'ahead' | 'behind' | 'even'
// Item tags that can be derived from purchase patterns
type ItemTag = 'ahead' | 'behind' | 'region_euw' | 'region_eun' | 'region_na' | 'region_kr'
type ItemTree = {
data: number | undefined
count: number
children: Array<ItemTree>
// Gold advantage tracking
boughtWhen: {
aheadCount: number
behindCount: number
evenCount: number
meanGold: number
}
// Platform tracking
platformCount: PlatformCounts
// Derived tags for display
tags: Array<ItemTag>
}
function treeInit(): ItemTree { function treeInit(): ItemTree {
return { return {
@@ -330,7 +306,6 @@ function treeDeriveTags(itemtree: ItemTree, expectedRegionDistribution?: Platfor
} }
export { export {
ItemTree,
PlatformCounts, PlatformCounts,
GoldAdvantageTag, GoldAdvantageTag,
ItemTag, ItemTag,

View File

@@ -0,0 +1,27 @@
/**
* Match Collector Library
* Exports all shared types for use by other projects (e.g., frontend)
*/
// Export all types
export type {
ItemTag,
GoldAdvantageTag,
PlatformCounts,
ItemTree,
Rune,
ItemCountEntry,
FirstBackItemSetEntry,
ItemSet,
FirstBackGroup,
Build,
Builds,
MatchupData,
SummonerSpellData,
LaneData,
ChampionData,
ChampionSummary,
Champion,
BackEvent,
FirstBackData
} from './types'

View File

@@ -0,0 +1,256 @@
/**
* Shared types between match_collector and frontend
*/
/**
* Item tags derived from purchase patterns
*/
export type ItemTag = 'ahead' | 'behind' | 'region_euw' | 'region_eun' | 'region_na' | 'region_kr'
/**
* Gold advantage tag for item purchases
*/
export type GoldAdvantageTag = 'ahead' | 'behind' | 'even'
/**
* Platform counts for region tracking
*/
export interface PlatformCounts {
euw: number
eun: number
na: number
kr: number
}
/**
* Represents an item in the build tree
*/
export interface ItemTree {
data: number | undefined
count: number
children: ItemTree[]
tags: ItemTag[]
// Gold advantage tracking (used during processing)
boughtWhen: {
aheadCount: number
behindCount: number
evenCount: number
meanGold: number
}
// Platform tracking (used during processing)
platformCount: PlatformCounts
}
/**
* Represents a rune configuration
*/
export interface Rune {
count: number
primaryStyle: number
secondaryStyle: number
selections: number[]
pickrate?: number
}
/**
* Represents an item entry with count
*/
export interface ItemCountEntry {
count: number
data: number
}
/**
* Represents an item in a first back item set
*/
export interface FirstBackItemSetEntry {
itemId: number
count: number
}
/**
* Represents an item set (combination of items)
*/
export interface ItemSet {
items: FirstBackItemSetEntry[]
totalGold: number
}
/**
* Internal type for first back data during processing
*/
export interface FirstBackData {
timestamp: number
itemSet: ItemSet
}
/**
* Represents a grouped first back by item set
*/
export interface FirstBackGroup {
itemSet: ItemSet
count: number
pickrate: number
avgTimestamp: number
}
/**
* Internal build type with processing fields
*/
export interface InternalBuild {
runeKeystone: number
runes: Rune[]
items: ItemTree
bootsFirstCount: number
bootsFirst?: number
count: number
suppItems: ItemCountEntry[]
boots: ItemCountEntry[]
pickrate?: number
firstBacksRaw?: FirstBackData[]
firstBacks?: FirstBackGroup[]
}
/**
* Internal build type with start items
*/
export interface InternalBuildWithStartItems {
runeKeystone: number
runes: Rune[]
items: ItemTree
bootsFirst?: number
bootsFirstCount: number
count: number
startItems: ItemCountEntry[]
suppItems: ItemCountEntry[]
boots: ItemCountEntry[]
pickrate?: number
firstBacksRaw?: FirstBackData[]
firstBacks?: FirstBackGroup[]
}
/**
* Represents a complete build with runes and items (final output format)
*/
export interface Build {
runeKeystone: number
runes: Rune[]
items: ItemTree
bootsFirst: number
count: number
boots: ItemCountEntry[]
suppItems: ItemCountEntry[]
startItems: ItemCountEntry[]
pickrate: number
firstBacks?: FirstBackGroup[]
}
/**
* Represents champion build information (array of builds)
*/
export type Builds = Build[]
/**
* Represents counter data for a champion
*/
export interface MatchupData {
championId: number
winrate: number
games: number
championName: string
championAlias: string
}
/**
* Represents summoner spell data
*/
export interface SummonerSpellData {
id: number
count: number
pickrate?: number
}
/**
* Internal lane data with processing fields
*/
export interface InternalLaneData {
data: string
count: number
winningMatches: number
losingMatches: number
winrate: number
pickrate: number
builds: InternalBuild[]
matchups?: MatchupData[]
summonerSpells: SummonerSpellData[]
regionDistribution?: PlatformCounts
}
/**
* Represents lane-specific champion data (final output format)
*/
export interface LaneData {
data: string
count: number
winningMatches: number
losingMatches: number
winrate: number
pickrate: number
builds?: Builds
summonerSpells: SummonerSpellData[]
matchups?: MatchupData[]
}
/**
* Internal champion data with processing fields
*/
export interface InternalChampionData {
champion: Champion
winningMatches: number
losingMatches: number
lanes: InternalLaneData[]
}
/**
* Represents complete champion data (final output format)
*/
export interface ChampionData {
id: number
name: string
alias: string
gameCount: number
winrate: number
pickrate: number
lanes: LaneData[]
}
/**
* Champion summary from CDragon
*/
export interface ChampionSummary {
id: number
name: string
alias: string
squarePortraitPath: string
}
/**
* Internal type for champion info
*/
export interface Champion {
id: number
name: string
alias: string
}
/**
* Internal type for back event
*/
export interface BackEvent {
timestamp: number
items: Array<{
itemId: number
gold: number
}>
totalGold: number
}

View File

@@ -1,6 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"types": ["node"], "types": ["node"],
"declaration": true "declaration": true,
} "outDir": "./dist",
"rootDir": "./src",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
} }