Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+274 -80
View File
@@ -28,9 +28,15 @@ let optimizeTimer: NodeJS.Timeout | undefined;
// IF NOT EXISTS in schema.sql is a no-op on existing tables, so it can't
// backfill columns; this can.
const SERVERS_COLUMNS: Array<{ name: string; ddl: string }> = [
{ name: 'container_checked_at', ddl: 'ALTER TABLE servers ADD COLUMN container_checked_at TEXT' },
{
name: 'container_checked_at',
ddl: 'ALTER TABLE servers ADD COLUMN container_checked_at TEXT'
},
{ name: 'container_state', ddl: 'ALTER TABLE servers ADD COLUMN container_state TEXT' },
{ name: 'desired_state', ddl: "ALTER TABLE servers ADD COLUMN desired_state TEXT NOT NULL DEFAULT 'running' CHECK (desired_state IN ('running','stopped'))" },
{
name: 'desired_state',
ddl: "ALTER TABLE servers ADD COLUMN desired_state TEXT NOT NULL DEFAULT 'running' CHECK (desired_state IN ('running','stopped'))"
},
// Base columns that shipped in the very first schema stay here for parity
// with fresh installs — no ALTER is issued if they're already present.
{ name: 'game_port', ddl: 'ALTER TABLE servers ADD COLUMN game_port INTEGER' },
@@ -43,7 +49,10 @@ const SERVERS_COLUMNS: Array<{ name: string; ddl: string }> = [
{ name: 'last_roster', ddl: 'ALTER TABLE servers ADD COLUMN last_roster TEXT' },
{ name: 'last_seen_at', ddl: 'ALTER TABLE servers ADD COLUMN last_seen_at TEXT' },
{ name: 'last_error', ddl: 'ALTER TABLE servers ADD COLUMN last_error TEXT' },
{ name: 'rcon_failures', ddl: 'ALTER TABLE servers ADD COLUMN rcon_failures INTEGER NOT NULL DEFAULT 0' },
{
name: 'rcon_failures',
ddl: 'ALTER TABLE servers ADD COLUMN rcon_failures INTEGER NOT NULL DEFAULT 0'
},
{ name: 'last_reply_snippet', ddl: 'ALTER TABLE servers ADD COLUMN last_reply_snippet TEXT' },
// Script-scope columns — static per server, populated via server-edit UI.
{ name: 'game', ddl: 'ALTER TABLE servers ADD COLUMN game TEXT' },
@@ -78,7 +87,10 @@ function migrateServers(handle: Database.Database): void {
const retired = new Set(['komodo_type', 'komodo_name', 'komodo_server']);
handle.transaction(() => {
for (const column of rows) {
if (retired.has(column.name)) handle.exec(`ALTER TABLE servers DROP COLUMN "${column.name.replaceAll('"', '""')}"`);
if (retired.has(column.name))
handle.exec(
`ALTER TABLE servers DROP COLUMN "${column.name.replaceAll('"', '""')}"`
);
}
})();
}
@@ -98,8 +110,14 @@ const SCRIPT_VERSIONS_COLUMNS: Array<{ name: string; ddl: string }> = [
// the authoritative "promoted" signal), and any version already gated
// through a ready-promote beta will be re-marked next time that state
// machine fires.
{ name: 'state', ddl: "ALTER TABLE script_versions ADD COLUMN state TEXT NOT NULL DEFAULT 'untested'" },
{ name: 'kind', ddl: "ALTER TABLE script_versions ADD COLUMN kind TEXT NOT NULL DEFAULT 'script'" }
{
name: 'state',
ddl: "ALTER TABLE script_versions ADD COLUMN state TEXT NOT NULL DEFAULT 'untested'"
},
{
name: 'kind',
ddl: "ALTER TABLE script_versions ADD COLUMN kind TEXT NOT NULL DEFAULT 'script'"
}
];
const BETA_SCRIPTS_COLUMNS: Array<{ name: string; ddl: string }> = [
@@ -107,18 +125,40 @@ const BETA_SCRIPTS_COLUMNS: Array<{ name: string; ddl: string }> = [
];
const DEPLOYMENT_SCRIPTS_COLUMNS: Array<{ name: string; ddl: string }> = [
{ name: 'kind', ddl: "ALTER TABLE deployment_scripts ADD COLUMN kind TEXT NOT NULL DEFAULT 'script'" }
{
name: 'kind',
ddl: "ALTER TABLE deployment_scripts ADD COLUMN kind TEXT NOT NULL DEFAULT 'script'"
}
];
const DEPLOYMENTS_COLUMNS: Array<{ name: string; ddl: string }> = [
{ name: 'script_id', ddl: 'ALTER TABLE deployments ADD COLUMN script_id INTEGER REFERENCES scripts(id)' },
{ name: 'script_version_id', ddl: 'ALTER TABLE deployments ADD COLUMN script_version_id INTEGER REFERENCES script_versions(id)' },
{ name: 'prior_version_id', ddl: 'ALTER TABLE deployments ADD COLUMN prior_version_id INTEGER REFERENCES script_versions(id)' },
{ name: 'beta_id', ddl: 'ALTER TABLE deployments ADD COLUMN beta_id INTEGER REFERENCES betas(id)' },
{ name: 'rolled_back_by', ddl: 'ALTER TABLE deployments ADD COLUMN rolled_back_by TEXT REFERENCES deployments(id)' }
{
name: 'script_id',
ddl: 'ALTER TABLE deployments ADD COLUMN script_id INTEGER REFERENCES scripts(id)'
},
{
name: 'script_version_id',
ddl: 'ALTER TABLE deployments ADD COLUMN script_version_id INTEGER REFERENCES script_versions(id)'
},
{
name: 'prior_version_id',
ddl: 'ALTER TABLE deployments ADD COLUMN prior_version_id INTEGER REFERENCES script_versions(id)'
},
{
name: 'beta_id',
ddl: 'ALTER TABLE deployments ADD COLUMN beta_id INTEGER REFERENCES betas(id)'
},
{
name: 'rolled_back_by',
ddl: 'ALTER TABLE deployments ADD COLUMN rolled_back_by TEXT REFERENCES deployments(id)'
}
];
function migrateTable(handle: Database.Database, table: string, cols: Array<{ name: string; ddl: string }>): void {
function migrateTable(
handle: Database.Database,
table: string,
cols: Array<{ name: string; ddl: string }>
): void {
const rows = handle.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
const existing = new Set(rows.map((r) => r.name));
for (const col of cols) {
@@ -169,14 +209,16 @@ function backfillMultiScriptJoins(handle: Database.Database): void {
// operator can promote/rollback in the old app before upgrading.
function dropLegacyBetaTables(handle: Database.Database): void {
const legacy = handle
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('betas','beta_scripts','beta_deploys')")
.prepare(
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('betas','beta_scripts','beta_deploys')"
)
.all() as Array<{ name: string }>;
if (legacy.length === 0) return;
const legacyNames = legacy.map((r) => r.name);
// The old `betas` table had a `slug` column; the new one has `id` + `script_id`.
// If we see `slug`, we know it's the old shape.
const betasCols = handle.prepare("PRAGMA table_info(betas)").all() as Array<{ name: string }>;
const betasCols = handle.prepare('PRAGMA table_info(betas)').all() as Array<{ name: string }>;
const isLegacyShape = betasCols.some((c) => c.name === 'slug');
if (!isLegacyShape) return;
@@ -205,8 +247,7 @@ function dropLegacyBetaTables(handle: Database.Database): void {
// active betas aren't blocked.
function assertSnapshotSchemeSafe(handle: Database.Database): void {
const done = handle.prepare("SELECT value FROM settings WHERE key = 'snapshots_v1'").get() as
| { value: string }
| undefined;
{ value: string } | undefined;
if (done) return;
const active = handle
.prepare("SELECT id FROM betas WHERE state IN ('running','ready-promote') LIMIT 1")
@@ -227,19 +268,36 @@ function migrateGameTelemetry(handle: Database.Database): void {
handle.transaction(() => {
const convert = (raw: string): string => {
const plan = JSON.parse(raw) as BetaPlan;
plan.requirements = plan.requirements.map(r => {
plan.requirements = plan.requirements.map((r) => {
const { event: _event, ...rest } = r as BetaRequirement & { event?: string };
return ['feature', 'restart-budget', 'clean-logs'].includes(r.kind)
? { ...requirement('manual', r.id), label: `Review: ${r.label}`.slice(0, 200) } : rest;
? { ...requirement('manual', r.id), label: `Review: ${r.label}`.slice(0, 200) }
: rest;
});
return JSON.stringify(plan);
};
const plans = handle.prepare('SELECT beta_id, plan FROM beta_test_plans').all() as Array<{ beta_id: number; plan: string }>;
for (const row of plans) handle.prepare('UPDATE beta_test_plans SET plan = ? WHERE beta_id = ?').run(convert(row.plan), row.beta_id);
const presets = handle.prepare('SELECT name, plan FROM beta_test_presets').all() as Array<{ name: string; plan: string }>;
for (const row of presets) handle.prepare('UPDATE beta_test_presets SET plan = ? WHERE name = ?').run(convert(row.plan), row.name);
handle.prepare(`UPDATE beta_test_runs SET metrics = ?, cursors = '{}' WHERE retired_at IS NULL
AND beta_id IN (SELECT id FROM betas WHERE state IN ('running','ready-promote'))`).run(JSON.stringify(emptyMetrics()));
const plans = handle.prepare('SELECT beta_id, plan FROM beta_test_plans').all() as Array<{
beta_id: number;
plan: string;
}>;
for (const row of plans)
handle
.prepare('UPDATE beta_test_plans SET plan = ? WHERE beta_id = ?')
.run(convert(row.plan), row.beta_id);
const presets = handle.prepare('SELECT name, plan FROM beta_test_presets').all() as Array<{
name: string;
plan: string;
}>;
for (const row of presets)
handle
.prepare('UPDATE beta_test_presets SET plan = ? WHERE name = ?')
.run(convert(row.plan), row.name);
handle
.prepare(
`UPDATE beta_test_runs SET metrics = ?, cursors = '{}' WHERE retired_at IS NULL
AND beta_id IN (SELECT id FROM betas WHERE state IN ('running','ready-promote'))`
)
.run(JSON.stringify(emptyMetrics()));
handle.exec(`UPDATE beta_test_plans SET outcome = 'active', reason = 'Gameplay telemetry updated; collecting fresh script events'
WHERE outcome IN ('active','passed') AND beta_id IN (SELECT id FROM betas WHERE state IN ('running','ready-promote'));
UPDATE script_versions SET state = 'untested' WHERE state = 'tested'
@@ -254,54 +312,96 @@ function migrateGameTelemetry(handle: Database.Database): void {
}
function migrateProvisioningActions(handle: Database.Database): void {
const table = handle.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'server_deployments'").get() as { sql: string };
const table = handle
.prepare(
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'server_deployments'"
)
.get() as { sql: string };
if (table.sql.includes("'rebuild'")) return;
// Rebuild the CHECK constraint without cascading into events, recipes, or
// collection members. Preserve rowids: they order same-server operations.
handle.pragma('foreign_keys = OFF');
try {
handle.transaction(() => {
handle.exec(table.sql.replace('server_deployments', 'server_deployments_new')
.replace("'create', 'destroy'", "'create', 'start', 'restart', 'rebuild', 'stop', 'destroy'"));
const columns = (handle.prepare('PRAGMA table_info(server_deployments)').all() as { name: string }[])
.map(column => `"${column.name.replaceAll('"', '""')}"`).join(', ');
handle.exec(
table.sql
.replace('server_deployments', 'server_deployments_new')
.replace(
"'create', 'destroy'",
"'create', 'start', 'restart', 'rebuild', 'stop', 'destroy'"
)
);
const columns = (
handle.prepare('PRAGMA table_info(server_deployments)').all() as { name: string }[]
)
.map((column) => `"${column.name.replaceAll('"', '""')}"`)
.join(', ');
handle.exec(`INSERT INTO server_deployments_new (rowid, ${columns})
SELECT rowid, ${columns} FROM server_deployments;
DROP TABLE server_deployments;
ALTER TABLE server_deployments_new RENAME TO server_deployments;`);
if ((handle.pragma('foreign_key_check') as unknown[]).length) throw new Error('Provisioning migration found broken references');
if ((handle.pragma('foreign_key_check') as unknown[]).length)
throw new Error('Provisioning migration found broken references');
})();
} finally { handle.pragma('foreign_keys = ON'); }
} finally {
handle.pragma('foreign_keys = ON');
}
}
/** Rebuild constrained tables while preserving links, indexes, triggers, and row order. */
function migrateWatchdogConstraints(handle: Database.Database): void {
for (const name of ['provisioning_sources', 'deployment_servers']) {
const { sql } = handle.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(name) as { sql: string };
const replacement = name === 'provisioning_sources'
? (sql.includes("'autoscaling'") ? sql : sql.replace(/'campaign','beta','maintenance'(?:,'watchdog')?/, "'campaign','beta','maintenance','watchdog','autoscaling'"))
: sql.replace(/server\s+TEXT NOT NULL REFERENCES servers\(name\)/, 'server TEXT NOT NULL');
const { sql } = handle
.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?")
.get(name) as { sql: string };
const replacement =
name === 'provisioning_sources'
? sql.includes("'autoscaling'")
? sql
: sql.replace(
/'campaign','beta','maintenance'(?:,'watchdog')?/,
"'campaign','beta','maintenance','watchdog','autoscaling'"
)
: sql.replace(
/server\s+TEXT NOT NULL REFERENCES servers\(name\)/,
'server TEXT NOT NULL'
);
if (sql === replacement) continue;
const dependent = handle.prepare("SELECT sql FROM sqlite_master WHERE tbl_name = ? AND type IN ('index','trigger') AND sql IS NOT NULL")
const dependent = handle
.prepare(
"SELECT sql FROM sqlite_master WHERE tbl_name = ? AND type IN ('index','trigger') AND sql IS NOT NULL"
)
.all(name) as { sql: string }[];
handle.pragma('foreign_keys = OFF');
try {
handle.transaction(() => {
handle.exec(replacement.replace(name, name + '_watchdog_new'));
handle.exec(`INSERT INTO ${name}_watchdog_new (rowid, ${
(handle.prepare(`PRAGMA table_info(${name})`).all() as { name: string }[]).map(col => col.name).join(', ')
}) SELECT rowid, ${
(handle.prepare(`PRAGMA table_info(${name})`).all() as { name: string }[]).map(col => col.name).join(', ')
} FROM ${name}; DROP TABLE ${name}; ALTER TABLE ${name}_watchdog_new RENAME TO ${name};`);
handle.exec(
`INSERT INTO ${name}_watchdog_new (rowid, ${(
handle.prepare(`PRAGMA table_info(${name})`).all() as { name: string }[]
)
.map((col) => col.name)
.join(', ')}) SELECT rowid, ${(
handle.prepare(`PRAGMA table_info(${name})`).all() as { name: string }[]
)
.map((col) => col.name)
.join(
', '
)} FROM ${name}; DROP TABLE ${name}; ALTER TABLE ${name}_watchdog_new RENAME TO ${name};`
);
for (const object of dependent) handle.exec(object.sql);
if ((handle.pragma('foreign_key_check') as unknown[]).length) throw new Error('Watchdog migration found broken references');
if ((handle.pragma('foreign_key_check') as unknown[]).length)
throw new Error('Watchdog migration found broken references');
})();
} finally { handle.pragma('foreign_keys = ON'); }
} finally {
handle.pragma('foreign_keys = ON');
}
}
}
function backfillDesiredState(handle: Database.Database): void {
if (handle.prepare("SELECT 1 FROM settings WHERE key = 'watchdog_desired_state_v1'").get()) return;
if (handle.prepare("SELECT 1 FROM settings WHERE key = 'watchdog_desired_state_v1'").get())
return;
handle.transaction(() => {
// A failed stop may have reached the host agent. Preserve that explicit intent.
handle.exec(`UPDATE servers SET desired_state = 'stopped' WHERE 'stop' = (
@@ -331,35 +431,80 @@ export function db(): Database.Database {
migrateProvisioningActions(handle);
migrateWatchdogConstraints(handle);
migrateTable(handle, 'deployment_files', [
{ name: 'snapshot_pending', ddl: 'ALTER TABLE deployment_files ADD COLUMN snapshot_pending INTEGER NOT NULL DEFAULT 0' }
{
name: 'snapshot_pending',
ddl: 'ALTER TABLE deployment_files ADD COLUMN snapshot_pending INTEGER NOT NULL DEFAULT 0'
}
]);
migrateTable(handle, 'restart_rollout', [
{
name: 'operation_id',
ddl: 'ALTER TABLE restart_rollout ADD COLUMN operation_id TEXT'
}
]);
migrateTable(handle, 'restart_rollout', [{ name: 'operation_id', ddl: 'ALTER TABLE restart_rollout ADD COLUMN operation_id TEXT' }]);
migrateTable(handle, 'server_deployments', [
{ name: 'requested_by', ddl: 'ALTER TABLE server_deployments ADD COLUMN requested_by TEXT' },
{ name: 'submitted_at', ddl: 'ALTER TABLE server_deployments ADD COLUMN submitted_at TEXT' },
{ name: 'checked_at', ddl: 'ALTER TABLE server_deployments ADD COLUMN checked_at TEXT' },
{
name: 'requested_by',
ddl: 'ALTER TABLE server_deployments ADD COLUMN requested_by TEXT'
},
{
name: 'submitted_at',
ddl: 'ALTER TABLE server_deployments ADD COLUMN submitted_at TEXT'
},
{
name: 'checked_at',
ddl: 'ALTER TABLE server_deployments ADD COLUMN checked_at TEXT'
},
{ name: 'players', ddl: 'ALTER TABLE server_deployments ADD COLUMN players INTEGER' },
{ name: 'phase_key', ddl: 'ALTER TABLE server_deployments ADD COLUMN phase_key TEXT' },
{ name: 'phase_started_at', ddl: 'ALTER TABLE server_deployments ADD COLUMN phase_started_at TEXT' },
{ name: 'observed_at', ddl: 'ALTER TABLE server_deployments ADD COLUMN observed_at TEXT' },
{ name: 'observation_json', ddl: 'ALTER TABLE server_deployments ADD COLUMN observation_json TEXT' }
{
name: 'phase_started_at',
ddl: 'ALTER TABLE server_deployments ADD COLUMN phase_started_at TEXT'
},
{
name: 'observed_at',
ddl: 'ALTER TABLE server_deployments ADD COLUMN observed_at TEXT'
},
{
name: 'observation_json',
ddl: 'ALTER TABLE server_deployments ADD COLUMN observation_json TEXT'
}
]);
migrateTable(handle, 'server_deployment_events', [
{ name: 'kind', ddl: 'ALTER TABLE server_deployment_events ADD COLUMN kind TEXT' },
{ name: 'phase_key', ddl: 'ALTER TABLE server_deployment_events ADD COLUMN phase_key TEXT' },
{ name: 'details_json', ddl: 'ALTER TABLE server_deployment_events ADD COLUMN details_json TEXT' }
{
name: 'phase_key',
ddl: 'ALTER TABLE server_deployment_events ADD COLUMN phase_key TEXT'
},
{
name: 'details_json',
ddl: 'ALTER TABLE server_deployment_events ADD COLUMN details_json TEXT'
}
]);
for (const table of ['deployment_servers', 'beta_servers', 'maintenance_jobs']) {
migrateTable(handle, table, [{ name: 'provisioning_id', ddl: `ALTER TABLE ${table} ADD COLUMN provisioning_id TEXT` }]);
migrateTable(handle, table, [
{
name: 'provisioning_id',
ddl: `ALTER TABLE ${table} ADD COLUMN provisioning_id TEXT`
}
]);
}
migrateTable(handle, 'maintenance_jobs', [
{ name: 'kind', ddl: "ALTER TABLE maintenance_jobs ADD COLUMN kind TEXT NOT NULL DEFAULT 'legacy'" },
{ name: 'origin', ddl: "ALTER TABLE maintenance_jobs ADD COLUMN origin TEXT NOT NULL DEFAULT 'manual'" }
{
name: 'kind',
ddl: "ALTER TABLE maintenance_jobs ADD COLUMN kind TEXT NOT NULL DEFAULT 'legacy'"
},
{
name: 'origin',
ddl: "ALTER TABLE maintenance_jobs ADD COLUMN origin TEXT NOT NULL DEFAULT 'manual'"
}
]);
migrateServers(handle);
// Old snippets may contain player addresses, including hex-encoded bytes.
// Discard them once; newly retained diagnostics are sanitized failure replies only.
if (!handle.prepare("SELECT 1 FROM settings WHERE key='rcon_private_diagnostics_v1'").get()) {
if (
!handle.prepare("SELECT 1 FROM settings WHERE key='rcon_private_diagnostics_v1'").get()
) {
handle.transaction(() => {
handle.exec(`UPDATE servers SET last_reply_snippet=NULL;
UPDATE servers SET last_error='bad-reply:unparseable print reply'
@@ -374,25 +519,52 @@ export function db(): Database.Database {
migrateTable(handle, 'scripts', SCRIPTS_COLUMNS);
migrateTable(handle, 'script_versions', SCRIPT_VERSIONS_COLUMNS);
migrateTable(handle, 'beta_scripts', BETA_SCRIPTS_COLUMNS);
migrateTable(handle, 'beta_test_runs', [{ name: 'revision', ddl: 'ALTER TABLE beta_test_runs ADD COLUMN revision INTEGER NOT NULL DEFAULT 1' }]);
migrateTable(handle, 'beta_test_runs', [
{
name: 'revision',
ddl: 'ALTER TABLE beta_test_runs ADD COLUMN revision INTEGER NOT NULL DEFAULT 1'
}
]);
migrateTable(handle, 'deployment_scripts', DEPLOYMENT_SCRIPTS_COLUMNS);
migrateTable(handle, 'deployments', DEPLOYMENTS_COLUMNS);
// Older databases gain rolled_back_by above, after schema.sql runs.
handle.exec('CREATE INDEX IF NOT EXISTS idx_deployments_rolled_back_by ON deployments(rolled_back_by)');
handle.exec(
'CREATE INDEX IF NOT EXISTS idx_deployments_rolled_back_by ON deployments(rolled_back_by)'
);
migrateTable(handle, 'server_launch_collections', [
{ name: 'cancelled_at', ddl: 'ALTER TABLE server_launch_collections ADD COLUMN cancelled_at TEXT' },
{ name: 'mode', ddl: "ALTER TABLE server_launch_collections ADD COLUMN mode TEXT NOT NULL DEFAULT 'fill'" },
{ name: 'retained_servers', ddl: "ALTER TABLE server_launch_collections ADD COLUMN retained_servers TEXT NOT NULL DEFAULT '[]'" }
{
name: 'cancelled_at',
ddl: 'ALTER TABLE server_launch_collections ADD COLUMN cancelled_at TEXT'
},
{
name: 'mode',
ddl: "ALTER TABLE server_launch_collections ADD COLUMN mode TEXT NOT NULL DEFAULT 'fill'"
},
{
name: 'retained_servers',
ddl: "ALTER TABLE server_launch_collections ADD COLUMN retained_servers TEXT NOT NULL DEFAULT '[]'"
}
]);
migrateTable(handle, 'server_launch_recipes', [
{ name: 'recipe_summary', ddl: 'ALTER TABLE server_launch_recipes ADD COLUMN recipe_summary TEXT' }
{
name: 'recipe_summary',
ddl: 'ALTER TABLE server_launch_recipes ADD COLUMN recipe_summary TEXT'
}
]);
migrateServerHistory(handle);
migrateTable(handle, 'server_collection_previews', [
{ name: 'mode', ddl: "ALTER TABLE server_collection_previews ADD COLUMN mode TEXT NOT NULL DEFAULT 'fill'" },
{ name: 'fleet_snapshot', ddl: 'ALTER TABLE server_collection_previews ADD COLUMN fleet_snapshot TEXT' }
{
name: 'mode',
ddl: "ALTER TABLE server_collection_previews ADD COLUMN mode TEXT NOT NULL DEFAULT 'fill'"
},
{
name: 'fleet_snapshot',
ddl: 'ALTER TABLE server_collection_previews ADD COLUMN fleet_snapshot TEXT'
}
]);
migrateTable(handle, 'server_telemetry', [
{ name: 'payload', ddl: 'ALTER TABLE server_telemetry ADD COLUMN payload TEXT' }
]);
migrateTable(handle, 'server_telemetry', [{ name: 'payload', ddl: 'ALTER TABLE server_telemetry ADD COLUMN payload TEXT' }]);
// Kind-referencing indexes: safe here because migrateTable above just
// ALTER'd the column into existence if it was missing.
handle.exec(
@@ -403,14 +575,27 @@ export function db(): Database.Database {
backfillMultiScriptJoins(handle);
assertSnapshotSchemeSafe(handle);
migrateGameTelemetry(handle);
migrateTable(handle, 'leaderboard_matches', [{ name: 'start_players', ddl: 'ALTER TABLE leaderboard_matches ADD COLUMN start_players INTEGER' }]);
migrateTable(handle, 'leaderboard_matches', [
{
name: 'start_players',
ddl: 'ALTER TABLE leaderboard_matches ADD COLUMN start_players INTEGER'
}
]);
migrateTable(handle, 'leaderboard_match_players', [
{ name: 'first_observed_round', ddl: 'ALTER TABLE leaderboard_match_players ADD COLUMN first_observed_round INTEGER' },
{ name: 'present_at_start', ddl: 'ALTER TABLE leaderboard_match_players ADD COLUMN present_at_start INTEGER' }
{
name: 'first_observed_round',
ddl: 'ALTER TABLE leaderboard_match_players ADD COLUMN first_observed_round INTEGER'
},
{
name: 'present_at_start',
ddl: 'ALTER TABLE leaderboard_match_players ADD COLUMN present_at_start INTEGER'
}
]);
backfillLeaderboard(handle);
initializeFleetSettings(handle);
migrationStep('native instance schema and file plans', () => migrateNativeInstances(handle));
migrationStep('native instance schema and file plans', () =>
migrateNativeInstances(handle)
);
migrationStep('per-game server key pools', () => migrateServerKeys(handle));
migrationStep('autoscaling schema', () => ensureAutoscalingSchema(handle));
migrationStep('rebalancing schema', () => ensureRebalancingSchema(handle));
@@ -436,12 +621,18 @@ export function db(): Database.Database {
ON server_deployments(id) WHERE state NOT IN ('done','warning','cancelled');`);
installPerformanceIndexes(handle);
installReportRevisions(handle);
migrationStep('retention clocks and indexes', () => retentionClocks(handle, {
deployments: ['started_at', 'finished_at'], server_deployments: ['started_at', 'finished_at'],
maintenance_jobs: ['requested_at', 'finished_at'], watchdog_incidents: ['first_seen_at', 'finished_at'],
leaderboard_matches: ['started_at', 'last_received_at', 'ended_at'],
betas: ['opened_at', 'closed_at'], script_versions: ['uploaded_at'], retention_controls: ['resolved_at']
}));
migrationStep('retention clocks and indexes', () =>
retentionClocks(handle, {
deployments: ['started_at', 'finished_at'],
server_deployments: ['started_at', 'finished_at'],
maintenance_jobs: ['requested_at', 'finished_at'],
watchdog_incidents: ['first_seen_at', 'finished_at'],
leaderboard_matches: ['started_at', 'last_received_at', 'ended_at'],
betas: ['opened_at', 'closed_at'],
script_versions: ['uploaded_at'],
retention_controls: ['resolved_at']
})
);
// Existing databases require one rebuild to gain the incremental-vacuum page map.
enableIncrementalVacuum(handle);
handle.pragma('optimize = 0x10002');
@@ -472,8 +663,11 @@ export function db(): Database.Database {
_db = handle;
optimizeTimer = setInterval(() => {
try { handle.pragma('optimize'); }
catch (error) { console.error('[database] optimize failed:', error); }
try {
handle.pragma('optimize');
} catch (error) {
console.error('[database] optimize failed:', error);
}
}, 60 * 60_000);
optimizeTimer.unref();
return _db;