@rine-kit/pubg-domain 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +38 -2
- package/dist/config/default-team.json +27 -0
- package/dist/src/config/chicken-index.d.ts +9 -0
- package/dist/src/config/chicken-index.js +37 -0
- package/dist/src/config/chicken-index.js.map +1 -0
- package/dist/src/config/team.d.ts +22 -0
- package/dist/src/config/team.js +69 -0
- package/dist/src/config/team.js.map +1 -0
- package/dist/src/context/context-store.d.ts +40 -0
- package/dist/src/context/context-store.js +119 -0
- package/dist/src/context/context-store.js.map +1 -0
- package/dist/src/context/team-context.d.ts +13 -0
- package/dist/src/context/team-context.js +16 -0
- package/dist/src/context/team-context.js.map +1 -0
- package/dist/src/core.d.ts +23 -0
- package/dist/src/core.js +24 -0
- package/dist/src/core.js.map +1 -0
- package/dist/src/data/model.d.ts +123 -0
- package/dist/src/data/model.js +100 -0
- package/dist/src/data/model.js.map +1 -0
- package/dist/src/data/pubg-api-client.d.ts +59 -0
- package/dist/src/data/pubg-api-client.js +441 -0
- package/dist/src/data/pubg-api-client.js.map +1 -0
- package/dist/src/engine/query-engine.d.ts +20 -0
- package/dist/src/engine/query-engine.js +765 -0
- package/dist/src/engine/query-engine.js.map +1 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.js +28 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/migration/importer.d.ts +35 -0
- package/dist/src/migration/importer.js +229 -0
- package/dist/src/migration/importer.js.map +1 -0
- package/dist/src/review/combat-scope.d.ts +11 -0
- package/dist/src/review/combat-scope.js +101 -0
- package/dist/src/review/combat-scope.js.map +1 -0
- package/dist/src/review/event-combination-engine.d.ts +17 -0
- package/dist/src/review/event-combination-engine.js +534 -0
- package/dist/src/review/event-combination-engine.js.map +1 -0
- package/dist/src/review/fight-detector.d.ts +4 -0
- package/dist/src/review/fight-detector.js +138 -0
- package/dist/src/review/fight-detector.js.map +1 -0
- package/dist/src/review/fight-integrity-validator.d.ts +13 -0
- package/dist/src/review/fight-integrity-validator.js +134 -0
- package/dist/src/review/fight-integrity-validator.js.map +1 -0
- package/dist/src/review/fun-candidate-generator.d.ts +9 -0
- package/dist/src/review/fun-candidate-generator.js +267 -0
- package/dist/src/review/fun-candidate-generator.js.map +1 -0
- package/dist/src/review/fun-event-generator.d.ts +8 -0
- package/dist/src/review/fun-event-generator.js +740 -0
- package/dist/src/review/fun-event-generator.js.map +1 -0
- package/dist/src/review/fun-intelligence.d.ts +13 -0
- package/dist/src/review/fun-intelligence.js +20 -0
- package/dist/src/review/fun-intelligence.js.map +1 -0
- package/dist/src/review/fun-ranker.d.ts +13 -0
- package/dist/src/review/fun-ranker.js +68 -0
- package/dist/src/review/fun-ranker.js.map +1 -0
- package/dist/src/review/heavy-weapon-intelligence.d.ts +5 -0
- package/dist/src/review/heavy-weapon-intelligence.js +215 -0
- package/dist/src/review/heavy-weapon-intelligence.js.map +1 -0
- package/dist/src/review/key-operation-detector.d.ts +3 -0
- package/dist/src/review/key-operation-detector.js +126 -0
- package/dist/src/review/key-operation-detector.js.map +1 -0
- package/dist/src/review/match-selector.d.ts +18 -0
- package/dist/src/review/match-selector.js +202 -0
- package/dist/src/review/match-selector.js.map +1 -0
- package/dist/src/review/review-analyzer.d.ts +2 -0
- package/dist/src/review/review-analyzer.js +652 -0
- package/dist/src/review/review-analyzer.js.map +1 -0
- package/dist/src/review/review-facts.d.ts +17 -0
- package/dist/src/review/review-facts.js +560 -0
- package/dist/src/review/review-facts.js.map +1 -0
- package/dist/src/review/review-share.d.ts +48 -0
- package/dist/src/review/review-share.js +37 -0
- package/dist/src/review/review-share.js.map +1 -0
- package/dist/src/review/selection-store.d.ts +25 -0
- package/dist/src/review/selection-store.js +80 -0
- package/dist/src/review/selection-store.js.map +1 -0
- package/dist/src/review/session-review.d.ts +72 -0
- package/dist/src/review/session-review.js +492 -0
- package/dist/src/review/session-review.js.map +1 -0
- package/dist/src/review/special-events.d.ts +3 -0
- package/dist/src/review/special-events.js +258 -0
- package/dist/src/review/special-events.js.map +1 -0
- package/dist/src/review/subgraph.d.ts +26 -0
- package/dist/src/review/subgraph.js +146 -0
- package/dist/src/review/subgraph.js.map +1 -0
- package/dist/src/review/supplemental-intelligence.d.ts +15 -0
- package/dist/src/review/supplemental-intelligence.js +431 -0
- package/dist/src/review/supplemental-intelligence.js.map +1 -0
- package/dist/src/review/team-damage.d.ts +8 -0
- package/dist/src/review/team-damage.js +180 -0
- package/dist/src/review/team-damage.js.map +1 -0
- package/dist/src/review/telemetry-events.d.ts +72 -0
- package/dist/src/review/telemetry-events.js +573 -0
- package/dist/src/review/telemetry-events.js.map +1 -0
- package/dist/src/review/telemetry.d.ts +86 -0
- package/dist/src/review/telemetry.js +266 -0
- package/dist/src/review/telemetry.js.map +1 -0
- package/dist/src/review/types.d.ts +501 -0
- package/dist/src/review/types.js +2 -0
- package/dist/src/review/types.js.map +1 -0
- package/dist/src/review/vehicle-intelligence.d.ts +3 -0
- package/dist/src/review/vehicle-intelligence.js +148 -0
- package/dist/src/review/vehicle-intelligence.js.map +1 -0
- package/dist/src/schema/query.d.ts +876 -0
- package/dist/src/schema/query.js +185 -0
- package/dist/src/schema/query.js.map +1 -0
- package/dist/src/schema/status.d.ts +66 -0
- package/dist/src/schema/status.js +49 -0
- package/dist/src/schema/status.js.map +1 -0
- package/dist/src/service/pubg-domain-service.d.ts +192 -0
- package/dist/src/service/pubg-domain-service.js +1598 -0
- package/dist/src/service/pubg-domain-service.js.map +1 -0
- package/dist/src/storage/repository-port.d.ts +84 -0
- package/dist/src/storage/repository-port.js +2 -0
- package/dist/src/storage/repository-port.js.map +1 -0
- package/dist/src/storage/sqlite-repository-adapter.d.ts +33 -0
- package/dist/src/storage/sqlite-repository-adapter.js +66 -0
- package/dist/src/storage/sqlite-repository-adapter.js.map +1 -0
- package/dist/src/storage/sqlite-repository.d.ts +73 -0
- package/dist/src/storage/sqlite-repository.js +652 -0
- package/dist/src/storage/sqlite-repository.js.map +1 -0
- package/dist/src/time/selector-resolver.d.ts +34 -0
- package/dist/src/time/selector-resolver.js +364 -0
- package/dist/src/time/selector-resolver.js.map +1 -0
- package/package.json +42 -3
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { normalizeRecords } from '../data/model.js';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
7
|
+
function numeric(value, fallback = 0) {
|
|
8
|
+
const result = Number(value);
|
|
9
|
+
return Number.isFinite(result) ? result : fallback;
|
|
10
|
+
}
|
|
11
|
+
function nullableNumber(value) {
|
|
12
|
+
if (value === null || value === undefined || value === '')
|
|
13
|
+
return null;
|
|
14
|
+
const result = Number(value);
|
|
15
|
+
return Number.isFinite(result) ? result : null;
|
|
16
|
+
}
|
|
17
|
+
function bool(value) {
|
|
18
|
+
return value === true || value === 1 || value === '1';
|
|
19
|
+
}
|
|
20
|
+
function jsonValue(value, fallback) {
|
|
21
|
+
if (typeof value !== 'string')
|
|
22
|
+
return fallback;
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function rowToPlayer(row) {
|
|
31
|
+
return {
|
|
32
|
+
accountId: String(row.account_id),
|
|
33
|
+
playerName: String(row.player_name ?? row.account_id),
|
|
34
|
+
displayName: String(row.display_name ?? row.player_name ?? row.account_id),
|
|
35
|
+
rank: nullableNumber(row.rank),
|
|
36
|
+
kills: numeric(row.kills),
|
|
37
|
+
assists: numeric(row.assists),
|
|
38
|
+
damage: numeric(row.damage),
|
|
39
|
+
dbnos: numeric(row.dbnos),
|
|
40
|
+
revives: numeric(row.revives),
|
|
41
|
+
headshotKills: numeric(row.headshot_kills),
|
|
42
|
+
survivalTime: numeric(row.survival_time),
|
|
43
|
+
longestKill: numeric(row.longest_kill),
|
|
44
|
+
deaths: nullableNumber(row.deaths),
|
|
45
|
+
deathSemantics: String(row.death_semantics ?? 'unknown'),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function rowToMatch(row, players) {
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: numeric(row.schema_version, 3),
|
|
51
|
+
matchId: String(row.match_id),
|
|
52
|
+
shard: String(row.shard ?? 'steam'),
|
|
53
|
+
createdAt: row.created_at === null || row.created_at === undefined ? null : String(row.created_at),
|
|
54
|
+
timestamp: numeric(row.timestamp_ms),
|
|
55
|
+
matchType: String(row.match_type ?? ''),
|
|
56
|
+
gameMode: String(row.game_mode ?? ''),
|
|
57
|
+
isCompetitive: bool(row.is_competitive),
|
|
58
|
+
mapName: String(row.map_name ?? ''),
|
|
59
|
+
duration: numeric(row.duration),
|
|
60
|
+
patchVersion: String(row.patch_version ?? ''),
|
|
61
|
+
players,
|
|
62
|
+
telemetryUrl: row.telemetry_url ? String(row.telemetry_url) : null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function keyForFeature(key) {
|
|
66
|
+
return key.matchId + ':' + key.parserVersion + ':' + key.featureVersion;
|
|
67
|
+
}
|
|
68
|
+
export class SqlitePubgRepository {
|
|
69
|
+
filename;
|
|
70
|
+
db;
|
|
71
|
+
constructor(filename = ':memory:', options = {}) {
|
|
72
|
+
this.filename = filename;
|
|
73
|
+
if (!options.readOnly && filename !== ':memory:')
|
|
74
|
+
mkdirSync(dirname(filename), { recursive: true });
|
|
75
|
+
this.db = options.readOnly
|
|
76
|
+
? new DatabaseSync(filename, { readOnly: true })
|
|
77
|
+
: new DatabaseSync(filename);
|
|
78
|
+
if (!options.readOnly) {
|
|
79
|
+
this.db.exec('PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL;');
|
|
80
|
+
this.migrate();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
migrate() {
|
|
84
|
+
this.db.exec(`
|
|
85
|
+
CREATE TABLE IF NOT EXISTS matches (
|
|
86
|
+
match_id TEXT PRIMARY KEY,
|
|
87
|
+
shard TEXT NOT NULL,
|
|
88
|
+
created_at TEXT,
|
|
89
|
+
timestamp_ms INTEGER NOT NULL,
|
|
90
|
+
match_type TEXT NOT NULL,
|
|
91
|
+
game_mode TEXT NOT NULL,
|
|
92
|
+
is_competitive INTEGER NOT NULL,
|
|
93
|
+
map_name TEXT NOT NULL,
|
|
94
|
+
duration REAL NOT NULL,
|
|
95
|
+
patch_version TEXT NOT NULL,
|
|
96
|
+
telemetry_url TEXT,
|
|
97
|
+
schema_version INTEGER NOT NULL,
|
|
98
|
+
parser_version TEXT,
|
|
99
|
+
fetched_at TEXT NOT NULL,
|
|
100
|
+
source TEXT NOT NULL,
|
|
101
|
+
checked_player_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
102
|
+
raw_json TEXT
|
|
103
|
+
);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_pubg_matches_timestamp ON matches(timestamp_ms DESC);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_pubg_matches_mode_map ON matches(game_mode, map_name);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS match_players (
|
|
108
|
+
match_id TEXT NOT NULL REFERENCES matches(match_id) ON DELETE CASCADE,
|
|
109
|
+
account_id TEXT NOT NULL,
|
|
110
|
+
player_name TEXT NOT NULL,
|
|
111
|
+
display_name TEXT NOT NULL,
|
|
112
|
+
rank REAL,
|
|
113
|
+
kills REAL NOT NULL,
|
|
114
|
+
assists REAL NOT NULL,
|
|
115
|
+
damage REAL NOT NULL,
|
|
116
|
+
dbnos REAL NOT NULL,
|
|
117
|
+
revives REAL NOT NULL,
|
|
118
|
+
headshot_kills REAL NOT NULL,
|
|
119
|
+
survival_time REAL NOT NULL,
|
|
120
|
+
longest_kill REAL NOT NULL,
|
|
121
|
+
deaths REAL,
|
|
122
|
+
death_semantics TEXT NOT NULL,
|
|
123
|
+
PRIMARY KEY(match_id, account_id)
|
|
124
|
+
);
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_pubg_match_players_account ON match_players(account_id, match_id);
|
|
126
|
+
|
|
127
|
+
CREATE TABLE IF NOT EXISTS sync_state (
|
|
128
|
+
state_key TEXT PRIMARY KEY,
|
|
129
|
+
checked_at TEXT NOT NULL,
|
|
130
|
+
state_json TEXT NOT NULL
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE TABLE IF NOT EXISTS telemetry_features (
|
|
134
|
+
match_id TEXT NOT NULL,
|
|
135
|
+
parser_version TEXT NOT NULL,
|
|
136
|
+
feature_version TEXT NOT NULL,
|
|
137
|
+
facts_json TEXT NOT NULL,
|
|
138
|
+
created_at TEXT NOT NULL,
|
|
139
|
+
PRIMARY KEY(match_id, parser_version, feature_version)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
CREATE TABLE IF NOT EXISTS telemetry_prefetch_attempts (
|
|
143
|
+
match_id TEXT NOT NULL,
|
|
144
|
+
parser_version TEXT NOT NULL,
|
|
145
|
+
feature_version TEXT NOT NULL,
|
|
146
|
+
status TEXT NOT NULL,
|
|
147
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
148
|
+
last_attempted_at TEXT,
|
|
149
|
+
next_retry_at TEXT,
|
|
150
|
+
error TEXT,
|
|
151
|
+
updated_at TEXT NOT NULL,
|
|
152
|
+
PRIMARY KEY(match_id, parser_version, feature_version)
|
|
153
|
+
);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_pubg_telemetry_prefetch_due
|
|
155
|
+
ON telemetry_prefetch_attempts(parser_version, feature_version, status, next_retry_at);
|
|
156
|
+
|
|
157
|
+
CREATE TABLE IF NOT EXISTS telemetry_prefetch_runs (
|
|
158
|
+
run_id TEXT PRIMARY KEY,
|
|
159
|
+
report_date TEXT NOT NULL,
|
|
160
|
+
started_at TEXT NOT NULL,
|
|
161
|
+
finished_at TEXT NOT NULL,
|
|
162
|
+
trigger TEXT NOT NULL,
|
|
163
|
+
discovered_match_count INTEGER NOT NULL,
|
|
164
|
+
new_match_count INTEGER NOT NULL,
|
|
165
|
+
candidate_match_count INTEGER NOT NULL,
|
|
166
|
+
fetched_count INTEGER NOT NULL,
|
|
167
|
+
cache_hit_count INTEGER NOT NULL,
|
|
168
|
+
unavailable_count INTEGER NOT NULL,
|
|
169
|
+
pending_count INTEGER NOT NULL,
|
|
170
|
+
failed_match_ids_json TEXT NOT NULL DEFAULT '[]'
|
|
171
|
+
);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_pubg_telemetry_prefetch_runs_date
|
|
173
|
+
ON telemetry_prefetch_runs(report_date, started_at);
|
|
174
|
+
|
|
175
|
+
CREATE TABLE IF NOT EXISTS result_sets (
|
|
176
|
+
session_id TEXT NOT NULL,
|
|
177
|
+
result_set_id TEXT NOT NULL,
|
|
178
|
+
expires_at TEXT NOT NULL,
|
|
179
|
+
result_json TEXT NOT NULL,
|
|
180
|
+
PRIMARY KEY(session_id, result_set_id)
|
|
181
|
+
);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_pubg_result_sets_expiry ON result_sets(expires_at);
|
|
183
|
+
|
|
184
|
+
CREATE TABLE IF NOT EXISTS session_contexts (
|
|
185
|
+
session_id TEXT PRIMARY KEY,
|
|
186
|
+
expires_at TEXT NOT NULL,
|
|
187
|
+
context_json TEXT NOT NULL
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE TABLE IF NOT EXISTS squad_session_reviews (
|
|
191
|
+
squad_id TEXT NOT NULL,
|
|
192
|
+
session_key TEXT NOT NULL,
|
|
193
|
+
match_ids_json TEXT NOT NULL,
|
|
194
|
+
last_match_end_ms INTEGER NOT NULL,
|
|
195
|
+
status TEXT NOT NULL,
|
|
196
|
+
detail TEXT,
|
|
197
|
+
message TEXT,
|
|
198
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
199
|
+
updated_at TEXT NOT NULL,
|
|
200
|
+
PRIMARY KEY(squad_id, session_key)
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
CREATE TABLE IF NOT EXISTS squad_session_review_meta (
|
|
204
|
+
meta_key TEXT PRIMARY KEY,
|
|
205
|
+
meta_value TEXT NOT NULL
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
CREATE TABLE IF NOT EXISTS review_shares (
|
|
209
|
+
token_hash TEXT PRIMARY KEY,
|
|
210
|
+
kind TEXT NOT NULL,
|
|
211
|
+
squad_id TEXT NOT NULL,
|
|
212
|
+
session_key TEXT NOT NULL,
|
|
213
|
+
match_ids_json TEXT NOT NULL,
|
|
214
|
+
presentation_json TEXT NOT NULL,
|
|
215
|
+
created_at TEXT NOT NULL,
|
|
216
|
+
expires_at TEXT NOT NULL,
|
|
217
|
+
revoked_at TEXT
|
|
218
|
+
);
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_review_shares_session ON review_shares(squad_id, session_key, created_at);
|
|
220
|
+
|
|
221
|
+
CREATE TABLE IF NOT EXISTS migration_runs (
|
|
222
|
+
id TEXT PRIMARY KEY,
|
|
223
|
+
source TEXT NOT NULL,
|
|
224
|
+
started_at TEXT NOT NULL,
|
|
225
|
+
finished_at TEXT NOT NULL,
|
|
226
|
+
report_json TEXT NOT NULL
|
|
227
|
+
);
|
|
228
|
+
`);
|
|
229
|
+
}
|
|
230
|
+
close() {
|
|
231
|
+
this.db.close();
|
|
232
|
+
}
|
|
233
|
+
countMatches() {
|
|
234
|
+
const row = this.db.prepare('SELECT COUNT(*) AS count FROM matches').get();
|
|
235
|
+
return numeric(row?.count);
|
|
236
|
+
}
|
|
237
|
+
getMatch(matchId) {
|
|
238
|
+
const row = this.db.prepare('SELECT * FROM matches WHERE match_id = ?').get(matchId);
|
|
239
|
+
if (!row)
|
|
240
|
+
return null;
|
|
241
|
+
const players = this.db
|
|
242
|
+
.prepare('SELECT * FROM match_players WHERE match_id = ? ORDER BY account_id')
|
|
243
|
+
.all(matchId)
|
|
244
|
+
.map(rowToPlayer);
|
|
245
|
+
return rowToMatch(row, players);
|
|
246
|
+
}
|
|
247
|
+
listMatches(options = {}) {
|
|
248
|
+
const clauses = [];
|
|
249
|
+
const params = [];
|
|
250
|
+
if (options.fromMs !== undefined) {
|
|
251
|
+
clauses.push('timestamp_ms >= ?');
|
|
252
|
+
params.push(options.fromMs);
|
|
253
|
+
}
|
|
254
|
+
if (options.toMs !== undefined) {
|
|
255
|
+
clauses.push('timestamp_ms < ?');
|
|
256
|
+
params.push(options.toMs);
|
|
257
|
+
}
|
|
258
|
+
const limit = Math.min(Math.max(Math.trunc(options.limit ?? 10000), 1), 10000);
|
|
259
|
+
const direction = options.order === 'desc' ? 'DESC' : 'ASC';
|
|
260
|
+
const where = clauses.length ? ' WHERE ' + clauses.join(' AND ') : '';
|
|
261
|
+
const selectedMatchesSql = `
|
|
262
|
+
SELECT match_id, timestamp_ms
|
|
263
|
+
FROM matches${where}
|
|
264
|
+
ORDER BY timestamp_ms ${direction}, match_id ${direction}
|
|
265
|
+
LIMIT ?
|
|
266
|
+
`;
|
|
267
|
+
const rows = this.db
|
|
268
|
+
.prepare('SELECT * FROM matches' +
|
|
269
|
+
where +
|
|
270
|
+
` ORDER BY timestamp_ms ${direction}, match_id ${direction} LIMIT ?`)
|
|
271
|
+
.all(...params, limit);
|
|
272
|
+
const players = this.db
|
|
273
|
+
.prepare(`
|
|
274
|
+
WITH selected_matches AS (${selectedMatchesSql})
|
|
275
|
+
SELECT player.*
|
|
276
|
+
FROM selected_matches AS selected
|
|
277
|
+
JOIN match_players AS player ON player.match_id = selected.match_id
|
|
278
|
+
ORDER BY selected.timestamp_ms ${direction}, selected.match_id ${direction}, player.account_id ASC
|
|
279
|
+
`)
|
|
280
|
+
.all(...params, limit);
|
|
281
|
+
const byMatch = new Map();
|
|
282
|
+
for (const player of players) {
|
|
283
|
+
const id = String(player.match_id);
|
|
284
|
+
const bucket = byMatch.get(id) ?? [];
|
|
285
|
+
bucket.push(rowToPlayer(player));
|
|
286
|
+
byMatch.set(id, bucket);
|
|
287
|
+
}
|
|
288
|
+
return rows.map((row) => rowToMatch(row, byMatch.get(String(row.match_id)) ?? []));
|
|
289
|
+
}
|
|
290
|
+
upsertMatches(input, metadata = {}) {
|
|
291
|
+
const normalizedInputs = input.flatMap((item) => normalizeRecords([item]));
|
|
292
|
+
const records = normalizeRecords(input).filter((match) => match.matchId && Number.isFinite(match.timestamp));
|
|
293
|
+
const duplicateInputs = Math.max(0, normalizedInputs.length - records.length);
|
|
294
|
+
const invalidInputs = Math.max(0, input.length - normalizedInputs.length);
|
|
295
|
+
const unique = records;
|
|
296
|
+
const now = metadata.fetchedAt ?? new Date().toISOString();
|
|
297
|
+
let inserted = 0;
|
|
298
|
+
let updated = 0;
|
|
299
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
300
|
+
try {
|
|
301
|
+
for (const match of unique) {
|
|
302
|
+
const existing = this.db
|
|
303
|
+
.prepare('SELECT match_id FROM matches WHERE match_id = ?')
|
|
304
|
+
.get(match.matchId);
|
|
305
|
+
this.db
|
|
306
|
+
.prepare(`
|
|
307
|
+
INSERT INTO matches (
|
|
308
|
+
match_id, shard, created_at, timestamp_ms, match_type, game_mode,
|
|
309
|
+
is_competitive, map_name, duration, patch_version, telemetry_url,
|
|
310
|
+
schema_version, parser_version, fetched_at, source, checked_player_ids_json, raw_json
|
|
311
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
312
|
+
ON CONFLICT(match_id) DO UPDATE SET
|
|
313
|
+
shard=excluded.shard, created_at=excluded.created_at, timestamp_ms=excluded.timestamp_ms,
|
|
314
|
+
match_type=excluded.match_type, game_mode=excluded.game_mode,
|
|
315
|
+
is_competitive=excluded.is_competitive, map_name=excluded.map_name,
|
|
316
|
+
duration=excluded.duration, patch_version=excluded.patch_version,
|
|
317
|
+
telemetry_url=excluded.telemetry_url, schema_version=excluded.schema_version,
|
|
318
|
+
parser_version=excluded.parser_version, fetched_at=excluded.fetched_at,
|
|
319
|
+
source=excluded.source, checked_player_ids_json=excluded.checked_player_ids_json,
|
|
320
|
+
raw_json=excluded.raw_json
|
|
321
|
+
`)
|
|
322
|
+
.run(match.matchId, match.shard, match.createdAt, match.timestamp, match.matchType, match.gameMode, match.isCompetitive ? 1 : 0, match.mapName, match.duration, match.patchVersion, match.telemetryUrl ?? null, match.schemaVersion, metadata.parserVersion ?? null, now, metadata.source ?? 'unknown', JSON.stringify(metadata.checkedPlayerIds ?? match.players.map((player) => player.accountId)), metadata.rawJson === undefined ? null : JSON.stringify(metadata.rawJson));
|
|
323
|
+
this.db.prepare('DELETE FROM match_players WHERE match_id = ?').run(match.matchId);
|
|
324
|
+
const playerStatement = this.db.prepare(`
|
|
325
|
+
INSERT INTO match_players (
|
|
326
|
+
match_id, account_id, player_name, display_name, rank, kills, assists,
|
|
327
|
+
damage, dbnos, revives, headshot_kills, survival_time, longest_kill,
|
|
328
|
+
deaths, death_semantics
|
|
329
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
330
|
+
`);
|
|
331
|
+
for (const player of match.players) {
|
|
332
|
+
playerStatement.run(match.matchId, player.accountId, player.playerName, player.displayName, player.rank, player.kills, player.assists, player.damage, player.dbnos, player.revives, player.headshotKills, player.survivalTime, player.longestKill, player.deaths, player.deathSemantics);
|
|
333
|
+
}
|
|
334
|
+
if (existing)
|
|
335
|
+
updated += 1;
|
|
336
|
+
else
|
|
337
|
+
inserted += 1;
|
|
338
|
+
}
|
|
339
|
+
this.db.exec('COMMIT');
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
this.db.exec('ROLLBACK');
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
return { inserted, updated, duplicateInputs, invalidInputs };
|
|
346
|
+
}
|
|
347
|
+
setSyncState(state) {
|
|
348
|
+
this.db
|
|
349
|
+
.prepare(`
|
|
350
|
+
INSERT INTO sync_state(state_key, checked_at, state_json) VALUES (?, ?, ?)
|
|
351
|
+
ON CONFLICT(state_key) DO UPDATE SET checked_at=excluded.checked_at, state_json=excluded.state_json
|
|
352
|
+
`)
|
|
353
|
+
.run(state.key, state.checkedAt, JSON.stringify(state));
|
|
354
|
+
}
|
|
355
|
+
getSyncState(key) {
|
|
356
|
+
const row = this.db.prepare('SELECT state_json FROM sync_state WHERE state_key = ?').get(key);
|
|
357
|
+
return row ? jsonValue(row.state_json, null) : null;
|
|
358
|
+
}
|
|
359
|
+
async get(key) {
|
|
360
|
+
const row = this.db
|
|
361
|
+
.prepare(`
|
|
362
|
+
SELECT facts_json, created_at FROM telemetry_features
|
|
363
|
+
WHERE match_id = ? AND parser_version = ? AND feature_version = ?
|
|
364
|
+
`)
|
|
365
|
+
.get(key.matchId, key.parserVersion, key.featureVersion);
|
|
366
|
+
if (!row)
|
|
367
|
+
return null;
|
|
368
|
+
const facts = jsonValue(row.facts_json, null);
|
|
369
|
+
if (!facts || typeof facts !== 'object')
|
|
370
|
+
return null;
|
|
371
|
+
return {
|
|
372
|
+
...key,
|
|
373
|
+
facts: facts,
|
|
374
|
+
createdAt: String(row.created_at),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
async set(record) {
|
|
378
|
+
this.db
|
|
379
|
+
.prepare(`
|
|
380
|
+
INSERT INTO telemetry_features(match_id, parser_version, feature_version, facts_json, created_at)
|
|
381
|
+
VALUES (?, ?, ?, ?, ?)
|
|
382
|
+
ON CONFLICT(match_id, parser_version, feature_version) DO UPDATE SET
|
|
383
|
+
facts_json=excluded.facts_json, created_at=excluded.created_at
|
|
384
|
+
`)
|
|
385
|
+
.run(record.matchId, record.parserVersion, record.featureVersion, JSON.stringify(record.facts), record.createdAt);
|
|
386
|
+
}
|
|
387
|
+
getTelemetryPrefetchAttempt(key) {
|
|
388
|
+
const row = this.db
|
|
389
|
+
.prepare(`
|
|
390
|
+
SELECT match_id, parser_version, feature_version, status, attempt_count,
|
|
391
|
+
last_attempted_at, next_retry_at, error, updated_at
|
|
392
|
+
FROM telemetry_prefetch_attempts
|
|
393
|
+
WHERE match_id = ? AND parser_version = ? AND feature_version = ?
|
|
394
|
+
`)
|
|
395
|
+
.get(key.matchId, key.parserVersion, key.featureVersion);
|
|
396
|
+
if (!row)
|
|
397
|
+
return null;
|
|
398
|
+
return {
|
|
399
|
+
matchId: String(row.match_id),
|
|
400
|
+
parserVersion: String(row.parser_version),
|
|
401
|
+
featureVersion: String(row.feature_version),
|
|
402
|
+
status: String(row.status),
|
|
403
|
+
attemptCount: numeric(row.attempt_count),
|
|
404
|
+
lastAttemptedAt: row.last_attempted_at ? String(row.last_attempted_at) : null,
|
|
405
|
+
nextRetryAt: row.next_retry_at ? String(row.next_retry_at) : null,
|
|
406
|
+
...(row.error ? { error: String(row.error) } : {}),
|
|
407
|
+
updatedAt: String(row.updated_at),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
listDueTelemetryPrefetchAttempts(parserVersion, featureVersion, now, limit = 100) {
|
|
411
|
+
const boundedLimit = Math.min(Math.max(Math.trunc(limit), 1), 1000);
|
|
412
|
+
const rows = this.db
|
|
413
|
+
.prepare(`
|
|
414
|
+
SELECT match_id, parser_version, feature_version, status, attempt_count,
|
|
415
|
+
last_attempted_at, next_retry_at, error, updated_at
|
|
416
|
+
FROM telemetry_prefetch_attempts
|
|
417
|
+
WHERE parser_version = ? AND feature_version = ?
|
|
418
|
+
AND status IN ('PENDING', 'UNAVAILABLE')
|
|
419
|
+
AND (next_retry_at IS NULL OR next_retry_at <= ?)
|
|
420
|
+
ORDER BY COALESCE(next_retry_at, '') ASC, updated_at ASC, match_id ASC
|
|
421
|
+
LIMIT ?
|
|
422
|
+
`)
|
|
423
|
+
.all(parserVersion, featureVersion, now.toISOString(), boundedLimit);
|
|
424
|
+
return rows.map((row) => ({
|
|
425
|
+
matchId: String(row.match_id),
|
|
426
|
+
parserVersion: String(row.parser_version),
|
|
427
|
+
featureVersion: String(row.feature_version),
|
|
428
|
+
status: String(row.status),
|
|
429
|
+
attemptCount: numeric(row.attempt_count),
|
|
430
|
+
lastAttemptedAt: row.last_attempted_at ? String(row.last_attempted_at) : null,
|
|
431
|
+
nextRetryAt: row.next_retry_at ? String(row.next_retry_at) : null,
|
|
432
|
+
...(row.error ? { error: String(row.error) } : {}),
|
|
433
|
+
updatedAt: String(row.updated_at),
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
setTelemetryPrefetchAttempt(attempt) {
|
|
437
|
+
this.db
|
|
438
|
+
.prepare(`
|
|
439
|
+
INSERT INTO telemetry_prefetch_attempts(
|
|
440
|
+
match_id, parser_version, feature_version, status, attempt_count,
|
|
441
|
+
last_attempted_at, next_retry_at, error, updated_at
|
|
442
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
443
|
+
ON CONFLICT(match_id, parser_version, feature_version) DO UPDATE SET
|
|
444
|
+
status=excluded.status, attempt_count=excluded.attempt_count,
|
|
445
|
+
last_attempted_at=excluded.last_attempted_at, next_retry_at=excluded.next_retry_at,
|
|
446
|
+
error=excluded.error, updated_at=excluded.updated_at
|
|
447
|
+
`)
|
|
448
|
+
.run(attempt.matchId, attempt.parserVersion, attempt.featureVersion, attempt.status, attempt.attemptCount, attempt.lastAttemptedAt, attempt.nextRetryAt, attempt.error ?? null, attempt.updatedAt);
|
|
449
|
+
}
|
|
450
|
+
recordTelemetryPrefetchRun(run) {
|
|
451
|
+
this.db
|
|
452
|
+
.prepare(`
|
|
453
|
+
INSERT OR REPLACE INTO telemetry_prefetch_runs(
|
|
454
|
+
run_id, report_date, started_at, finished_at, trigger,
|
|
455
|
+
discovered_match_count, new_match_count, candidate_match_count,
|
|
456
|
+
fetched_count, cache_hit_count, unavailable_count, pending_count,
|
|
457
|
+
failed_match_ids_json
|
|
458
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
459
|
+
`)
|
|
460
|
+
.run(run.runId, run.reportDate, run.startedAt, run.finishedAt, run.trigger, run.discoveredMatchCount, run.newMatchCount, run.candidateMatchCount, run.fetchedCount, run.cacheHitCount, run.unavailableCount, run.pendingCount, JSON.stringify([...new Set(run.failedMatchIds)]));
|
|
461
|
+
}
|
|
462
|
+
listTelemetryPrefetchRuns(reportDate) {
|
|
463
|
+
const rows = this.db
|
|
464
|
+
.prepare(`
|
|
465
|
+
SELECT run_id, report_date, started_at, finished_at, trigger,
|
|
466
|
+
discovered_match_count, new_match_count, candidate_match_count,
|
|
467
|
+
fetched_count, cache_hit_count, unavailable_count, pending_count,
|
|
468
|
+
failed_match_ids_json
|
|
469
|
+
FROM telemetry_prefetch_runs
|
|
470
|
+
WHERE report_date = ?
|
|
471
|
+
ORDER BY started_at ASC, run_id ASC
|
|
472
|
+
`)
|
|
473
|
+
.all(reportDate);
|
|
474
|
+
return rows.map((row) => ({
|
|
475
|
+
runId: String(row.run_id),
|
|
476
|
+
reportDate: String(row.report_date),
|
|
477
|
+
startedAt: String(row.started_at),
|
|
478
|
+
finishedAt: String(row.finished_at),
|
|
479
|
+
trigger: String(row.trigger),
|
|
480
|
+
discoveredMatchCount: numeric(row.discovered_match_count),
|
|
481
|
+
newMatchCount: numeric(row.new_match_count),
|
|
482
|
+
candidateMatchCount: numeric(row.candidate_match_count),
|
|
483
|
+
fetchedCount: numeric(row.fetched_count),
|
|
484
|
+
cacheHitCount: numeric(row.cache_hit_count),
|
|
485
|
+
unavailableCount: numeric(row.unavailable_count),
|
|
486
|
+
pendingCount: numeric(row.pending_count),
|
|
487
|
+
failedMatchIds: jsonValue(row.failed_match_ids_json, []),
|
|
488
|
+
}));
|
|
489
|
+
}
|
|
490
|
+
getSessionReview(squadId, sessionKey) {
|
|
491
|
+
const row = this.db
|
|
492
|
+
.prepare(`
|
|
493
|
+
SELECT squad_id, session_key, match_ids_json, last_match_end_ms, status, detail, message, attempts, updated_at
|
|
494
|
+
FROM squad_session_reviews WHERE squad_id = ? AND session_key = ?
|
|
495
|
+
`)
|
|
496
|
+
.get(squadId, sessionKey);
|
|
497
|
+
if (!row)
|
|
498
|
+
return null;
|
|
499
|
+
return {
|
|
500
|
+
squadId: String(row.squad_id),
|
|
501
|
+
sessionKey: String(row.session_key),
|
|
502
|
+
matchIds: jsonValue(row.match_ids_json, []),
|
|
503
|
+
lastMatchEndMs: numeric(row.last_match_end_ms),
|
|
504
|
+
status: String(row.status),
|
|
505
|
+
detail: row.detail === null || row.detail === undefined ? null : String(row.detail),
|
|
506
|
+
message: row.message === null || row.message === undefined ? null : String(row.message),
|
|
507
|
+
attempts: numeric(row.attempts),
|
|
508
|
+
updatedAt: String(row.updated_at),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
putSessionReview(record) {
|
|
512
|
+
this.db
|
|
513
|
+
.prepare(`
|
|
514
|
+
INSERT INTO squad_session_reviews(squad_id, session_key, match_ids_json, last_match_end_ms, status, detail, message, attempts, updated_at)
|
|
515
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
516
|
+
ON CONFLICT(squad_id, session_key) DO UPDATE SET
|
|
517
|
+
match_ids_json = excluded.match_ids_json, last_match_end_ms = excluded.last_match_end_ms, status = excluded.status,
|
|
518
|
+
detail = excluded.detail, message = excluded.message, attempts = excluded.attempts, updated_at = excluded.updated_at
|
|
519
|
+
`)
|
|
520
|
+
.run(record.squadId, record.sessionKey, JSON.stringify(record.matchIds), record.lastMatchEndMs, record.status, record.detail, record.message, record.attempts, record.updatedAt);
|
|
521
|
+
}
|
|
522
|
+
getSessionReviewMeta(key) {
|
|
523
|
+
const row = this.db
|
|
524
|
+
.prepare('SELECT meta_value FROM squad_session_review_meta WHERE meta_key = ?')
|
|
525
|
+
.get(key);
|
|
526
|
+
return row ? String(row.meta_value) : null;
|
|
527
|
+
}
|
|
528
|
+
setSessionReviewMeta(key, value) {
|
|
529
|
+
this.db
|
|
530
|
+
.prepare(`
|
|
531
|
+
INSERT INTO squad_session_review_meta(meta_key, meta_value) VALUES (?, ?)
|
|
532
|
+
ON CONFLICT(meta_key) DO UPDATE SET meta_value = excluded.meta_value
|
|
533
|
+
`)
|
|
534
|
+
.run(key, value);
|
|
535
|
+
}
|
|
536
|
+
putReviewShare(record) {
|
|
537
|
+
this.db
|
|
538
|
+
.prepare(`
|
|
539
|
+
INSERT INTO review_shares(token_hash, kind, squad_id, session_key, match_ids_json, presentation_json, created_at, expires_at, revoked_at)
|
|
540
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
541
|
+
`)
|
|
542
|
+
.run(record.tokenHash, record.kind, record.squadId, record.sessionKey, JSON.stringify(record.matchIds), record.presentationJson, record.createdAt, record.expiresAt, record.revokedAt);
|
|
543
|
+
}
|
|
544
|
+
getReviewShare(tokenHash) {
|
|
545
|
+
const row = this.db.prepare('SELECT * FROM review_shares WHERE token_hash = ?').get(tokenHash);
|
|
546
|
+
return row ? shareRecord(row) : null;
|
|
547
|
+
}
|
|
548
|
+
latestReviewShare(squadId, sessionKey) {
|
|
549
|
+
const row = this.db
|
|
550
|
+
.prepare('SELECT * FROM review_shares WHERE squad_id = ? AND session_key = ? ORDER BY created_at DESC LIMIT 1')
|
|
551
|
+
.get(squadId, sessionKey);
|
|
552
|
+
return row ? shareRecord(row) : null;
|
|
553
|
+
}
|
|
554
|
+
revokeReviewShares(squadId, sessionKey, revokedAt) {
|
|
555
|
+
return Number(this.db
|
|
556
|
+
.prepare('UPDATE review_shares SET revoked_at = ? WHERE squad_id = ? AND session_key = ? AND revoked_at IS NULL')
|
|
557
|
+
.run(revokedAt, squadId, sessionKey).changes);
|
|
558
|
+
}
|
|
559
|
+
/** Every stored share, for readers of a database copy (the Intelligence Lab snapshot). */
|
|
560
|
+
listReviewShares() {
|
|
561
|
+
try {
|
|
562
|
+
return this.db.prepare('SELECT * FROM review_shares').all().map(shareRecord);
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
// A read-only copy made before the table existed.
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
latestPrefetchFinishedMs() {
|
|
570
|
+
const row = this.db
|
|
571
|
+
.prepare('SELECT MAX(finished_at) AS finished_at FROM telemetry_prefetch_runs')
|
|
572
|
+
.get();
|
|
573
|
+
const parsed = row?.finished_at ? Date.parse(String(row.finished_at)) : Number.NaN;
|
|
574
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
575
|
+
}
|
|
576
|
+
setResultSet(result) {
|
|
577
|
+
this.db
|
|
578
|
+
.prepare(`
|
|
579
|
+
INSERT INTO result_sets(session_id, result_set_id, expires_at, result_json)
|
|
580
|
+
VALUES (?, ?, ?, ?)
|
|
581
|
+
ON CONFLICT(session_id, result_set_id) DO UPDATE SET
|
|
582
|
+
expires_at=excluded.expires_at, result_json=excluded.result_json
|
|
583
|
+
`)
|
|
584
|
+
.run(result.sessionId, result.id, result.expiresAt, JSON.stringify(result));
|
|
585
|
+
}
|
|
586
|
+
getResultSet(sessionId, resultSetId, now = new Date()) {
|
|
587
|
+
const row = this.db
|
|
588
|
+
.prepare('SELECT expires_at, result_json FROM result_sets WHERE session_id = ? AND result_set_id = ?')
|
|
589
|
+
.get(sessionId, resultSetId);
|
|
590
|
+
if (!row || Date.parse(String(row.expires_at)) <= now.getTime())
|
|
591
|
+
return null;
|
|
592
|
+
return jsonValue(row.result_json, null);
|
|
593
|
+
}
|
|
594
|
+
setContext(context) {
|
|
595
|
+
this.db
|
|
596
|
+
.prepare(`
|
|
597
|
+
INSERT INTO session_contexts(session_id, expires_at, context_json)
|
|
598
|
+
VALUES (?, ?, ?)
|
|
599
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
600
|
+
expires_at=excluded.expires_at, context_json=excluded.context_json
|
|
601
|
+
`)
|
|
602
|
+
.run(context.sessionId, context.expiresAt, JSON.stringify(context));
|
|
603
|
+
}
|
|
604
|
+
getContext(sessionId, now = new Date()) {
|
|
605
|
+
const row = this.db
|
|
606
|
+
.prepare('SELECT expires_at, context_json FROM session_contexts WHERE session_id = ?')
|
|
607
|
+
.get(sessionId);
|
|
608
|
+
if (!row || Date.parse(String(row.expires_at)) <= now.getTime())
|
|
609
|
+
return null;
|
|
610
|
+
return jsonValue(row.context_json, null);
|
|
611
|
+
}
|
|
612
|
+
cleanupExpired(now = new Date()) {
|
|
613
|
+
const iso = now.toISOString();
|
|
614
|
+
this.db.prepare('DELETE FROM result_sets WHERE expires_at <= ?').run(iso);
|
|
615
|
+
this.db.prepare('DELETE FROM session_contexts WHERE expires_at <= ?').run(iso);
|
|
616
|
+
}
|
|
617
|
+
recordMigration(id, source, report, startedAt, finishedAt) {
|
|
618
|
+
this.db
|
|
619
|
+
.prepare('INSERT OR REPLACE INTO migration_runs(id, source, started_at, finished_at, report_json) VALUES (?, ?, ?, ?, ?)')
|
|
620
|
+
.run(id, source, startedAt, finishedAt, JSON.stringify(report));
|
|
621
|
+
}
|
|
622
|
+
getMigration(id) {
|
|
623
|
+
const row = this.db.prepare('SELECT * FROM migration_runs WHERE id = ?').get(id);
|
|
624
|
+
return row ?? null;
|
|
625
|
+
}
|
|
626
|
+
snapshot() {
|
|
627
|
+
const count = (table) => numeric(this.db.prepare('SELECT COUNT(*) AS count FROM ' + table).get()?.count);
|
|
628
|
+
return {
|
|
629
|
+
matches: count('matches'),
|
|
630
|
+
players: count('match_players'),
|
|
631
|
+
features: count('telemetry_features'),
|
|
632
|
+
resultSets: count('result_sets'),
|
|
633
|
+
contexts: count('session_contexts'),
|
|
634
|
+
prefetchAttempts: count('telemetry_prefetch_attempts'),
|
|
635
|
+
prefetchRuns: count('telemetry_prefetch_runs'),
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function shareRecord(row) {
|
|
640
|
+
return {
|
|
641
|
+
tokenHash: String(row.token_hash),
|
|
642
|
+
kind: String(row.kind) === 'match' ? 'match' : String(row.kind) === 'query' ? 'query' : 'session',
|
|
643
|
+
squadId: String(row.squad_id),
|
|
644
|
+
sessionKey: String(row.session_key),
|
|
645
|
+
matchIds: jsonValue(row.match_ids_json, []),
|
|
646
|
+
presentationJson: String(row.presentation_json),
|
|
647
|
+
createdAt: String(row.created_at),
|
|
648
|
+
expiresAt: String(row.expires_at),
|
|
649
|
+
revokedAt: row.revoked_at === null || row.revoked_at === undefined ? null : String(row.revoked_at),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
//# sourceMappingURL=sqlite-repository.js.map
|