mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 06:45:51 +00:00
feat(ipc): typed bindings for library catalog/genre browse commands
First vertical slice of the tauri-specta rollout: annotate two psysonic-library browse-metadata commands (library_get_catalog_year_bounds, library_get_genre_album_counts) with #[specta::specta] and derive specta::Type on their DTOs (CatalogYearBoundsDto, GenreAlbumCountDto), then add them to collect_commands!. bindings.ts now carries their typed signatures + DTO shapes (serde camelCase carried through). Non-breaking: generate_handler! stays the live handler and nothing imports the bindings yet. Both DTOs are i64-free (i32/u32/String), so no BigInt handling is needed here — that convention is decided when the first i64 DTO is annotated. Generated src/generated is excluded from eslint (its runtime helper uses `any`); tsc still type-checks bindings.ts.
This commit is contained in:
+4
-1
@@ -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,
|
||||
{
|
||||
|
||||
Generated
+1
@@ -4261,6 +4261,7 @@ dependencies = [
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"tauri",
|
||||
"tokio",
|
||||
"wiremock",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<i32>,
|
||||
@@ -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,
|
||||
|
||||
@@ -87,7 +87,9 @@ fn set_app_user_model_id() {
|
||||
fn specta_builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
tauri_specta::Builder::<tauri::Wry>::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,
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -5,5 +5,33 @@ import { invoke as __TAURI_INVOKE } from "@tauri-apps/api/core";
|
||||
/** Commands */
|
||||
export const commands = {
|
||||
greet: (name: string) => __TAURI_INVOKE<string>("greet", { name }),
|
||||
/** Min/max album years from the local track catalog (for Albums browse filter spinners). */
|
||||
libraryGetCatalogYearBounds: (serverId: string) => typedError<CatalogYearBoundsDto, string>(__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<GenreAlbumCountDto[], string>(__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<T, E>(result: Promise<T>): 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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user