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">
import { debounce, isEmpty } from '~/utils/helpers'
import type { ChampionSummary, LaneData, ChampionData } from 'match_collector'
// Constants
const CHAMPIONS_API_URL = '/api/champions'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { PerkStyle, Perk } from '~/types/cdragon'
const props = defineProps<{
primaryStyleId: number
secondaryStyleId: number
@@ -8,13 +10,14 @@ const props = defineProps<{
const primaryStyle: 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())
for (const perk of perks_data.value) {
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(
() => props.primaryStyleId,
async (_newP, _oldP) => {

View File

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

View File

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

View File

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

View File

@@ -17,26 +17,7 @@ export default withNuxt([
languageOptions: {
globals: {
...globals.browser,
...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'
...globals.node
}
},
rules: {

View File

@@ -14,6 +14,7 @@
"chart.js": "^4.5.0",
"dotenv": "^17.2.3",
"dragon-item-parser": "file:../dragon-item-parser",
"match_collector": "file:../match_collector",
"mongodb": "^6.10.0",
"nuxt": "^3.17.5",
"nuxt-umami": "^3.2.1",
@@ -9909,6 +9910,88 @@
"resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
"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": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",

View File

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

View File

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

View File

@@ -1,10 +1,14 @@
<script setup lang="ts">
import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon'
import type { LaneData, ChampionData, Champion } from 'match_collector'
const route = useRoute()
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>> } =
await useFetch('/api/champions')

View File

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

View File

@@ -1,4 +1,5 @@
import type { MongoClient } from 'mongodb'
import type { ChampionData } from 'match_collector'
import { connectToDatabase, fetchLatestPatch } from '../utils/mongo'
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) {
for (const lane of x.lanes) {
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,60 +1,43 @@
import type { ItemStats } from 'dragon-item-parser'
declare global {
type ChampionsResponse = {
data: Ref<Array<Champion>>
}
type ChampionResponse = {
data: Ref<ChampionFull>
}
type Champion = {
name: string
alias: string
squarePortraitPath: string
}
type ChampionFull = {
name: string
alias: string
squarePortraitPath: string
title: string
}
type ItemResponse = {
data: Ref<Array<Item>>
}
type Item = {
id: number
iconPath: string
name?: string
description?: string
plaintext?: string
into?: number[]
from?: number[]
price?: number
priceTotal?: number
stats?: ItemStats
}
type SummonerSpell = {
id: number
iconPath: string
name: string
}
type PerksResponse = {
data: Ref<Array<Perk>>
}
type Perk = {
id: number
name: string
iconPath: string
}
type PerkStylesResponse = {
data: Ref<{ styles: Array<PerkStyle> }>
}
type PerkStyle = {
id: number
name: string
iconPath: string
slots: Array<{ perks: Array<number> }>
}
type Champion = {
name: string
alias: string
squarePortraitPath: string
}
type ChampionFull = {
name: string
alias: string
squarePortraitPath: string
title: string
}
type Item = {
id: number
iconPath: string
name?: string
description?: string
plaintext?: string
into?: number[]
from?: number[]
price?: number
priceTotal?: number
stats?: ItemStats
}
type SummonerSpell = {
id: number
iconPath: string
name: string
}
type Perk = {
id: number
name: string
iconPath: string
}
type PerkStyle = {
id: number
name: string
iconPath: string
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)
* 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,
data: tree.data,
children: [],
tags: tree.tags
tags: tree.tags,
boughtWhen: tree.boughtWhen,
platformCount: tree.platformCount
}
// If we haven't reached maxDepth, include children