Skip to content

Database Design

WingStats Manager の SQLite スキーマと、electron/database/ 配下に分割された repository パターン の構成。

技術スタック

項目選定
DBSQLite (better-sqlite3)
ORMなし(生 SQL + prepared statements)
配置場所Electron メインプロセス (electron/database/)
アクセスIPC 経由で renderer から呼び出し

Repository 構成

code-split-and-coverage spec で electron/database.ts (旧 868 行) を 6 つの repository に分割。electron/database.ts は薄い facade として残り、内部で各 repository を組み立てる。

electron/database/
├── DatabaseConnection.ts       # 接続管理 + マイグレーション
├── MatchRepository.ts          # matches / players / bans / picks
├── ProfileRepository.ts        # profiles / settings
├── StatsService.ts             # 集計クエリ (Match から派生)
├── BattleListRepository.ts     # browser 自動化由来の battle_list キャッシュ
├── SeasonRepository.ts         # シーズン情報
├── ApiResponseRepository.ts    # API レスポンスの生キャッシュ
├── match/
│   └── matchDetailQueries.ts   # 試合詳細の純関数クエリ群
├── index.ts
└── types.ts

Repository とテーブルの対応

Repository主な責務扱うテーブル
DatabaseConnection接続 + マイグレーション + トランザクション(全)
MatchRepository試合の保存 / 取得 / 削除matches, players, bans, picks, log_files
ProfileRepositoryプロファイル / ユーザー設定profiles, settings
StatsService統計集計(read-only)matches, players, bans, picks
BattleListRepositorybrowser 自動化のリストキャッシュbattle_list_cache
SeasonRepositoryシーズン管理seasons
ApiResponseRepositoryAPI レスポンスの生キャッシュapi_responses

純関数クエリ層

electron/database/match/matchDetailQueries.tsDatabase インスタンスを引数に取る純関数群fetchMatchPlayers / fetchMatchBans / fetchMatchPicks / fetchMatchDetail)。MatchRepository から利用する。テストでは in-memory better-sqlite3 を渡せる。

テーブル設計

matches - 試合テーブル

カラム名説明
idTEXT PRIMARY KEY試合 ID(log 由来は timestamp ベース、browser 由来は BattleId と同値)
battle_idTEXT, NULL 許可, UNIQUE戦績サイト由来の BattleId。log 由来は saveBattleDetails の照合成功時に書き戻し
timestampTEXT試合開始日時(ISO8601)
dateTEXT日付(YYYY-MM-DD
timeTEXT時刻(HH:MM:SS、後付けマイグレーション)
typeTEXT試合タイプ(log 由来: Rank_2v2 等 / browser 由来: mapBattleTypeId() 経由)
durationINTEGER試合時間(秒)
log_fileTEXT元ログファイルパス
created_atTEXTDB 登録日時

照合フォールバック階段BrowserAutomationServicebattle_detail を保存する経路):

  1. findMatchByBattleId(BattleId)matches.battle_id 直引き(決定的)
  2. findMatchByOpponentUids(opponents, StartTime) — 対戦相手 UID 集合一致 + 最近接時刻
  3. miss なら saveBattleDetails(matchId 未指定) — 時刻 ±5 分窓 + UID 一致による UPDATE

players - プレイヤー結果テーブル

基本カラム

カラム名説明
idINTEGER PRIMARY KEY自動採番 ID
match_idTEXT試合 ID(FK: matches.id
uidTEXTプレイヤー UID
nameTEXTプレイヤー名
teamTEXTチーム('A' or 'B'
resultTEXT結果('Win' or 'Lose'
character_idINTEGER使用キャラ ID
available_character_idsTEXT (JSON 配列)所持キャラ 4 枠(spec: available-chars

戦績詳細カラム(API 連携時に取得)

カラム名説明対応する JSON
kill_countINTEGER撃破数BattleInfo.BeatCnt
death_countINTEGER被撃破数BattleInfo.BeatedCnt
damageINTEGER与ダメージBattleInfo.ExportDamage
score_beforeINTEGER試合前スコアUserInfo.ScoreBefore
score_afterINTEGER試合後スコアUserInfo.ScoreAfter
is_mvpINTEGERMVP 判定(0/1)UserInfo.IsMvp
rank_idINTEGER段位 IDUserInfo.DaDuanId
awake_kill_countINTEGER覚醒撃破数BattleInfo.BeatAwakeCnt
friendly_kill_countINTEGER味方撃破数BattleInfo.BeatFreidnCnt
debuff_clear_countINTEGERデバフ解除数BattleInfo.Debarrass
avatar_urlTEXTアバター URLUserInfo.Avatar

ログ解析データには戦績詳細が含まれないため、これらは NULL 許容。BrowserAutomationService の API 連携時に UPDATE される。

bans - BAN テーブル

カラム名説明
idINTEGER PRIMARY KEY自動採番 ID
match_idTEXT試合 ID(FK: matches.id
character_idINTEGERBAN されたキャラ ID
banner_uidTEXTBAN したプレイヤー UID
banner_nameTEXTBAN したプレイヤー名
teamTEXTチーム('A' or 'B'

picks - PICK テーブル

カラム名説明
idINTEGER PRIMARY KEY自動採番 ID
match_idTEXT試合 ID(FK: matches.id
character_idINTEGERピックされたキャラ ID
player_uidTEXTプレイヤー UID
player_nameTEXTプレイヤー名
teamTEXTチーム('A' or 'B'
is_candidate_fallbackINTEGER候補絞り込み fallback で決まった Pick かどうか(0/1)
candidate_character_idsTEXT (JSON 配列)fallback 時の候補キャラ ID 配列

log_files - ログファイル管理テーブル

カラム名説明
idINTEGER PRIMARY KEY自動採番 ID
file_pathTEXT UNIQUEログファイルパス
file_nameTEXTファイル名
file_sizeINTEGERファイルサイズ(bytes)
match_countINTEGER検出した試合数
imported_atTEXTインポート日時
statusTEXTステータス('pending' / 'imported' / 'error'

命名規約: snake_case ↔ camelCase

DB 列名は snake_case、TypeScript 側は camelCase。SELECT 時に AS 句で変換する。

sql
SELECT character_id AS characterId, player_uid AS playerUid FROM picks

これを怠ると、UI 側で「データなし」表示になる典型バグが発生する。

IPC API

renderer (window.electronAPI.db.*) から呼ぶ主要メソッド。詳細は Electron Mainelectron/preload/api/db.ts を参照。

ts
db.analyzeAndSaveLog(filePath, myUid?)
db.getLogFiles()
db.deleteLogFile(fileId)
db.getMatches(options?)
db.getMatchDetail(matchId)
db.getPlayerStats(uid?)
db.getCharacterStats(type?)
db.getDashboardSummary(uid?)
db.getWinRateTrend(uid, days?)

処理フロー

ログインポートフロー

戦績詳細取得フロー(ブラウザ自動化)

データソースの違い:

  • ログ解析: 試合基本情報、チーム構成、BAN/PICK、勝敗のみ
  • API 連携: 上記 + K/D/A、ダメージ、スコア変動、MVP 等の詳細戦績

ファイル構造

electron/
├── database.ts                 # 薄い facade(旧 868 行 → 335 行)
├── database/
│   ├── DatabaseConnection.ts
│   ├── MatchRepository.ts
│   ├── ProfileRepository.ts
│   ├── StatsService.ts
│   ├── BattleListRepository.ts
│   ├── SeasonRepository.ts
│   ├── ApiResponseRepository.ts
│   ├── match/matchDetailQueries.ts
│   ├── index.ts
│   ├── types.ts
│   └── __tests__/
└── preload/api/db.ts           # 公開 API

テスト戦略

  • In-memory better-sqlite3: repository テストは :memory: で接続を作り、各テストで fresh schema を立てる
  • 純関数クエリ層 (matchDetailQueries.ts) は DB インスタンスを DI で受けるため、in-memory DB を直接渡せる
  • 既存テスト: electron/database/__tests__/, electron/database/match/__tests__/

今後の拡張候補

  • [ ] K/D/A 統計の計算・表示
  • [ ] スコア推移グラフ
  • [ ] 平均ダメージ・MVP 率の表示
  • [ ] データエクスポート機能(CSV / JSON)
  • [ ] log の自動監視・自動インポート(spec: realtime-log-monitoring

Last verified: 2026-05-06 / against commit e351b23a