From 80365fe21c13f6b50d724d79b2bd2fc96cab92ca Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 7 Jul 2026 17:42:34 -0600 Subject: [PATCH 1/2] Add active_profile_nexus_files query and fix query-type generator Adds pivot tables (profile_settings_pivot, mod_attributes_pivot, mod_state_pivot) and a registered select that lists gameId/modId/fileId for every installed Nexus mod file of the active profile's game. Fixes three latent bugs in scripts/generate-query-types.ts exposed by the first parameterless select: stale halgari.github.io extension URL (v1.4.4 build, workspace is on v1.5.1), missing View type import, and select rows emitted as interfaces which don't satisfy View's Record constraint. Regenerates queryTypes.ts. --- scripts/generate-query-types.ts | 13 ++++-- src/main/src/store/generated/queryTypes.ts | 49 +++++++++++++++++++++- src/queries/select/nexus_files.sql | 21 ++++++++++ src/queries/setup/tables.sql | 27 ++++++++++++ 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 src/queries/select/nexus_files.sql diff --git a/scripts/generate-query-types.ts b/scripts/generate-query-types.ts index 6e3d9bd0fc..55752cb980 100644 --- a/scripts/generate-query-types.ts +++ b/scripts/generate-query-types.ts @@ -158,7 +158,7 @@ async function main(): Promise { // Install and load level_pivot console.log("Installing level_pivot..."); await connection.run( - "FORCE INSTALL level_pivot FROM 'https://halgari.github.io/duckdb-level-pivot/current_release'", + "FORCE INSTALL level_pivot FROM 'https://nexus-mods.github.io/duckdb-level-pivot/current_release'", ); await connection.run("LOAD level_pivot"); const tmpDbPath = path.join(tmpDir, "gen.db"); @@ -259,6 +259,9 @@ function generateTypeScript( lines.push("// AUTO-GENERATED by scripts/generate-query-types.ts — DO NOT EDIT"); lines.push(""); lines.push('import type { Table } from "../Table";'); + if (selectTypeInfos.some(({ query }) => query.params.length === 0)) { + lines.push('import type { View } from "../View";'); + } lines.push('import type { Database } from "../Database";'); lines.push(""); @@ -291,12 +294,14 @@ function generateTypeScript( } lines.push(""); + // Emitted as a type alias (not an interface) so the row satisfies the + // Record constraint on View. lines.push(`/** Result row for the '${query.name}' query */`); - lines.push(`export interface ${pascal}Row {`); + lines.push(`export type ${pascal}Row = {`); for (const col of columns) { lines.push(` ${col.name}: ${col.tsType};`); } - lines.push("}"); + lines.push("};"); lines.push(""); } @@ -330,7 +335,7 @@ function generateTypeScript( lines.push(` ${propName}: db.createView("${query.name}"),`); } } - lines.push(" } as Models;"); + lines.push(" };"); lines.push("}"); lines.push(""); diff --git a/src/main/src/store/generated/queryTypes.ts b/src/main/src/store/generated/queryTypes.ts index 2a283da7b2..db30f4acc1 100644 --- a/src/main/src/store/generated/queryTypes.ts +++ b/src/main/src/store/generated/queryTypes.ts @@ -2,6 +2,7 @@ import type { Database } from "../Database"; import type { Table } from "../Table"; +import type { View } from "../View"; /** Row type for the 'mods_pivot' pivot table */ export type ModsPivotRow = { @@ -19,20 +20,58 @@ export type ProfilesPivotRow = { lastActivated: bigint; }; +/** Row type for the 'profile_settings_pivot' pivot table */ +export type ProfileSettingsPivotRow = { + section: string; + activeProfileId: string; +}; + +/** Row type for the 'mod_attributes_pivot' pivot table */ +export type ModAttributesPivotRow = { + game_id: string; + vortex_mod_id: string; + source: string; + modId: bigint; + fileId: bigint; + downloadGame: string; +}; + +/** Row type for the 'mod_state_pivot' pivot table */ +export type ModStatePivotRow = { + game_id: string; + vortex_mod_id: string; + state: string; +}; + +/** Parameters for the 'active_profile_nexus_files' query (no parameters) */ +export type ActiveProfileNexusFilesParams = Record; + +/** Result row for the 'active_profile_nexus_files' query */ +export type ActiveProfileNexusFilesRow = { + game_id: string; + mod_id: bigint; + file_id: bigint; + vortex_mod_id: string; +}; + /** Parameters for the 'recently_managed_games' query */ export interface RecentlyManagedGamesParams { current_game_id: string; } /** Result row for the 'recently_managed_games' query */ -export interface RecentlyManagedGamesRow { +export type RecentlyManagedGamesRow = { game_id: string; -} +}; /** Typed model accessors — generated from SQL definitions */ export interface Models { mods: Table; profiles: Table; + profileSettings: Table; + modAttributes: Table; + modState: Table; + activeProfileNexusFiles: View; } /** Create typed model accessors from a Database instance */ @@ -40,16 +79,22 @@ export function createModels(db: Database): Models { return { mods: db.createTable("mods_pivot"), profiles: db.createTable("profiles_pivot"), + profileSettings: db.createTable("profile_settings_pivot"), + modAttributes: db.createTable("mod_attributes_pivot"), + modState: db.createTable("mod_state_pivot"), + activeProfileNexusFiles: db.createView("active_profile_nexus_files"), }; } /** Maps query names to their parameter types */ export interface QueryParamsMap { + active_profile_nexus_files: ActiveProfileNexusFilesParams; recently_managed_games: RecentlyManagedGamesParams; } /** Maps query names to their result row types */ export interface QueryResultMap { + active_profile_nexus_files: ActiveProfileNexusFilesRow; recently_managed_games: RecentlyManagedGamesRow; } diff --git a/src/queries/select/nexus_files.sql b/src/queries/select/nexus_files.sql new file mode 100644 index 0000000000..32f12a3773 --- /dev/null +++ b/src/queries/select/nexus_files.sql @@ -0,0 +1,21 @@ +-- @type select + +-- @name active_profile_nexus_files +-- @description Lists gameId/modId/fileId for every installed Nexus mod file of the currently active profile's game +SELECT + coalesce(ma.downloadGame, ma.game_id) AS game_id, + ma.modId AS mod_id, + ma.fileId AS file_id, + ma.vortex_mod_id +FROM db.profile_settings_pivot ps +JOIN db.profiles_pivot pr + ON pr.profile_id = ps.activeProfileId +JOIN db.mod_attributes_pivot ma + ON ma.game_id = pr.gameId +JOIN db.mod_state_pivot ms + ON ms.game_id = ma.game_id + AND ms.vortex_mod_id = ma.vortex_mod_id +WHERE ps.section = 'profiles' + AND ma.source = 'nexus' + AND ms.state = 'installed' +ORDER BY game_id, mod_id, file_id; diff --git a/src/queries/setup/tables.sql b/src/queries/setup/tables.sql index 6e0287c2ee..f3fdd1a674 100644 --- a/src/queries/setup/tables.sql +++ b/src/queries/setup/tables.sql @@ -17,3 +17,30 @@ CALL level_pivot_create_table( ['profile_id', 'name', 'gameId', 'lastActivated'], column_types := ['VARCHAR', 'JSON VARCHAR', 'JSON VARCHAR', 'JSON BIGINT'] ); + +-- @name profile_settings_pivot +-- @description Creates the profile-settings pivot table (activeProfileId lives at settings###profiles###activeProfileId) +CALL level_pivot_create_table( + 'db', 'profile_settings_pivot', + 'settings###{section}###{attr}', + ['section', 'activeProfileId'], + column_types := ['VARCHAR', 'JSON VARCHAR'] +); + +-- @name mod_attributes_pivot +-- @description Creates the mod-attributes pivot table (mod attributes live under persistent###mods#########attributes###*) +CALL level_pivot_create_table( + 'db', 'mod_attributes_pivot', + 'persistent###mods###{game_id}###{vortex_mod_id}###attributes###{attr}', + ['game_id', 'vortex_mod_id', 'source', 'modId', 'fileId', 'downloadGame'], + column_types := ['VARCHAR', 'VARCHAR', 'JSON VARCHAR', 'JSON BIGINT', 'JSON BIGINT', 'JSON VARCHAR'] +); + +-- @name mod_state_pivot +-- @description Creates the mod-state pivot table (state is a top-level mod field, not an attribute) +CALL level_pivot_create_table( + 'db', 'mod_state_pivot', + 'persistent###mods###{game_id}###{vortex_mod_id}###{attr}', + ['game_id', 'vortex_mod_id', 'state'], + column_types := ['VARCHAR', 'VARCHAR', 'JSON VARCHAR'] +); From 743b442f61d1749e2d082341affbb4dedc4785fe Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 7 Jul 2026 17:56:22 -0600 Subject: [PATCH 2/2] Tidy join formatting in nexus_files.sql --- src/queries/select/nexus_files.sql | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/queries/select/nexus_files.sql b/src/queries/select/nexus_files.sql index 32f12a3773..948eead92c 100644 --- a/src/queries/select/nexus_files.sql +++ b/src/queries/select/nexus_files.sql @@ -8,13 +8,10 @@ SELECT ma.fileId AS file_id, ma.vortex_mod_id FROM db.profile_settings_pivot ps -JOIN db.profiles_pivot pr - ON pr.profile_id = ps.activeProfileId -JOIN db.mod_attributes_pivot ma - ON ma.game_id = pr.gameId -JOIN db.mod_state_pivot ms - ON ms.game_id = ma.game_id - AND ms.vortex_mod_id = ma.vortex_mod_id +JOIN db.profiles_pivot pr ON pr.profile_id = ps.activeProfileId +JOIN db.mod_attributes_pivot ma ON ma.game_id = pr.gameId +JOIN db.mod_state_pivot ms ON ms.game_id = ma.game_id + AND ms.vortex_mod_id = ma.vortex_mod_id WHERE ps.section = 'profiles' AND ma.source = 'nexus' AND ms.state = 'installed'