diff --git a/eslint.config.mjs b/eslint.config.mjs index efee700c..0ca26153 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -7,7 +7,10 @@ import tseslint from 'typescript-eslint'; export default tseslint.config( // `scripts/` (Node CI helpers) is intentionally ignored — this config targets the // browser `src/` tree. See `npm run lint:scripts` if scripts ever need a Node-globals lint pass. - { ignores: ['dist', 'coverage', 'src-tauri', 'research', 'scripts'] }, + // `src/generated` holds machine-generated output (release-notes bundle, + // tauri-specta `bindings.ts`) — not linted. `bindings.ts` is still type-checked + // by tsc (the FE↔BE contract); its generated runtime helper uses `any`. + { ignores: ['dist', 'coverage', 'src-tauri', 'research', 'scripts', 'src/generated'] }, eslint.configs.recommended, ...tseslint.configs.recommended, { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a52103be..7dd310f0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4261,6 +4261,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "specta", "tauri", "tokio", "wiremock", diff --git a/src-tauri/crates/psysonic-library/Cargo.toml b/src-tauri/crates/psysonic-library/Cargo.toml index f07adb8a..2cb45aed 100644 --- a/src-tauri/crates/psysonic-library/Cargo.toml +++ b/src-tauri/crates/psysonic-library/Cargo.toml @@ -11,6 +11,7 @@ psysonic-core = { path = "../psysonic-core" } psysonic-integration = { path = "../psysonic-integration" } tauri = { version = "2" } +specta = { version = "=2.0.0-rc.25", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" rusqlite = { version = "0.40", features = ["bundled"] } diff --git a/src-tauri/crates/psysonic-library/src/browse_support.rs b/src-tauri/crates/psysonic-library/src/browse_support.rs index e28cce15..c67d23e6 100644 --- a/src-tauri/crates/psysonic-library/src/browse_support.rs +++ b/src-tauri/crates/psysonic-library/src/browse_support.rs @@ -99,6 +99,7 @@ pub(crate) fn catalog_year_bounds_for_server( /// Min/max album years from the local track catalog (for Albums browse filter spinners). #[tauri::command] +#[specta::specta] pub fn library_get_catalog_year_bounds( runtime: State<'_, LibraryRuntime>, server_id: String, @@ -150,6 +151,7 @@ pub(crate) fn genre_album_counts_for_server( /// Distinct album counts per track genre — same grouping as genre album browse. #[tauri::command] +#[specta::specta] pub fn library_get_genre_album_counts( runtime: State<'_, LibraryRuntime>, server_id: String, diff --git a/src-tauri/crates/psysonic-library/src/dto.rs b/src-tauri/crates/psysonic-library/src/dto.rs index 559138a9..d326bb02 100644 --- a/src-tauri/crates/psysonic-library/src/dto.rs +++ b/src-tauri/crates/psysonic-library/src/dto.rs @@ -382,7 +382,7 @@ pub struct PlaySessionYearBoundsDto { } /// Min/max `year` from indexed tracks for a server (Albums year filter UI). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)] #[serde(rename_all = "camelCase")] pub struct CatalogYearBoundsDto { pub min_year: Option, @@ -390,7 +390,7 @@ pub struct CatalogYearBoundsDto { } /// Per-genre album/track totals from the local track catalog (Genres cloud + browse). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)] #[serde(rename_all = "camelCase")] pub struct GenreAlbumCountDto { pub value: String, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 84b1273a..997fe743 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -87,7 +87,9 @@ fn set_app_user_model_id() { fn specta_builder() -> tauri_specta::Builder { tauri_specta::Builder::::new() .commands(tauri_specta::collect_commands![ - crate::lib_commands::app_api::core::greet + crate::lib_commands::app_api::core::greet, + psysonic_library::browse_support::library_get_catalog_year_bounds, + psysonic_library::browse_support::library_get_genre_album_counts, ]) } diff --git a/src/generated/bindings.ts b/src/generated/bindings.ts index bc1167a7..af8a259c 100644 --- a/src/generated/bindings.ts +++ b/src/generated/bindings.ts @@ -5,5 +5,33 @@ import { invoke as __TAURI_INVOKE } from "@tauri-apps/api/core"; /** Commands */ export const commands = { greet: (name: string) => __TAURI_INVOKE("greet", { name }), + /** Min/max album years from the local track catalog (for Albums browse filter spinners). */ + libraryGetCatalogYearBounds: (serverId: string) => typedError(__TAURI_INVOKE("library_get_catalog_year_bounds", { serverId })), + /** Distinct album counts per track genre — same grouping as genre album browse. */ + libraryGetGenreAlbumCounts: (serverId: string, libraryScope: string | null) => typedError(__TAURI_INVOKE("library_get_genre_album_counts", { serverId, libraryScope })), }; +/* Types */ +/** Min/max `year` from indexed tracks for a server (Albums year filter UI). */ +export type CatalogYearBoundsDto = { + minYear: number | null, + maxYear: number | null, +}; + +/** Per-genre album/track totals from the local track catalog (Genres cloud + browse). */ +export type GenreAlbumCountDto = { + value: string, + albumCount: number, + songCount: number, +}; + +/* Tauri Specta runtime */ +async function typedError(result: Promise): Promise<{ status: "ok"; data: T } | { status: "error"; error: E }> { + try { + return { status: "ok", data: await result }; + } catch (e) { + if (e instanceof Error) throw e; + return { status: "error", error: e as any }; + } +} +