mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +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(
|
export default tseslint.config(
|
||||||
// `scripts/` (Node CI helpers) is intentionally ignored — this config targets the
|
// `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.
|
// 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,
|
eslint.configs.recommended,
|
||||||
...tseslint.configs.recommended,
|
...tseslint.configs.recommended,
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+1
@@ -4261,6 +4261,7 @@ dependencies = [
|
|||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"specta",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tokio",
|
"tokio",
|
||||||
"wiremock",
|
"wiremock",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ psysonic-core = { path = "../psysonic-core" }
|
|||||||
psysonic-integration = { path = "../psysonic-integration" }
|
psysonic-integration = { path = "../psysonic-integration" }
|
||||||
|
|
||||||
tauri = { version = "2" }
|
tauri = { version = "2" }
|
||||||
|
specta = { version = "=2.0.0-rc.25", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
rusqlite = { version = "0.40", features = ["bundled"] }
|
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).
|
/// Min/max album years from the local track catalog (for Albums browse filter spinners).
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn library_get_catalog_year_bounds(
|
pub fn library_get_catalog_year_bounds(
|
||||||
runtime: State<'_, LibraryRuntime>,
|
runtime: State<'_, LibraryRuntime>,
|
||||||
server_id: String,
|
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.
|
/// Distinct album counts per track genre — same grouping as genre album browse.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn library_get_genre_album_counts(
|
pub fn library_get_genre_album_counts(
|
||||||
runtime: State<'_, LibraryRuntime>,
|
runtime: State<'_, LibraryRuntime>,
|
||||||
server_id: String,
|
server_id: String,
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ pub struct PlaySessionYearBoundsDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Min/max `year` from indexed tracks for a server (Albums year filter UI).
|
/// 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")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CatalogYearBoundsDto {
|
pub struct CatalogYearBoundsDto {
|
||||||
pub min_year: Option<i32>,
|
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).
|
/// 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")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GenreAlbumCountDto {
|
pub struct GenreAlbumCountDto {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ fn set_app_user_model_id() {
|
|||||||
fn specta_builder() -> tauri_specta::Builder<tauri::Wry> {
|
fn specta_builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||||
tauri_specta::Builder::<tauri::Wry>::new()
|
tauri_specta::Builder::<tauri::Wry>::new()
|
||||||
.commands(tauri_specta::collect_commands![
|
.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 */
|
/** Commands */
|
||||||
export const commands = {
|
export const commands = {
|
||||||
greet: (name: string) => __TAURI_INVOKE<string>("greet", { name }),
|
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