domain: introduce provider-neutral media model (phase 1)
Establish src-tauri/src/domain/ as the single source of truth for the media model, with all Jellyfin translation isolated in from_jellyfin.rs. Adds MediaKind enum and neutral duration_ms/image_id fields to MediaItem as additive, defaulted dual-carry alongside the legacy Jellyfin-named fields, so nothing breaks while the frontend migrates off them. - domain/media.rs: canonical MediaKind (closed enum, replaces stringly item_type), Default = Other so unknown/defaulted items are inert. - domain/from_jellyfin.rs: total, panic-free item_type -> MediaKind classification (all audited types + person subroles) and ticks->ms. - MediaItem gains kind/duration_ms/image_id, populated at both mapping seams (online to_media_item, offline cached_item_to_media_item) and the synthesized-album/person sites. - Regenerated bindings.ts: frontend now HAS the neutral model available. Phase 1 of docs/specs/frontend-domain-model.md. No frontend behaviour change yet; wire shape is a superset of before. Rust 456 tests, frontend 644 tests, check + check:boundary all green.
This commit is contained in:
+95
-4
@@ -1802,7 +1802,22 @@ export type LmsSyncGroup = { masterMac: string; masterName?: string; slaveMacs?:
|
||||
/**
|
||||
* Media item
|
||||
*/
|
||||
export type MediaItem = { id: string; name: string; type: string;
|
||||
export type MediaItem = { id: string; name: string;
|
||||
/**
|
||||
* Legacy Jellyfin item-type string (`"Audio"`, `"MusicAlbum"`, …).
|
||||
*
|
||||
* Dual-carry migration (docs/specs/frontend-domain-model.md): `kind` below
|
||||
* is the neutral replacement. This field stays while the frontend migrates
|
||||
* off it, then is removed in a later phase. New Rust code should read
|
||||
* `kind`, not this.
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Provider-neutral classification — the replacement for `item_type`.
|
||||
* Populated by the Jellyfin mapping; defaults to `Other` for the handful of
|
||||
* construction sites that have not been migrated yet.
|
||||
*/
|
||||
kind?: MediaKind;
|
||||
/**
|
||||
* Whether this item is a folder/container (vs a playable leaf). Used to
|
||||
* decide whether a channel item drills into a list or plays directly.
|
||||
@@ -1812,7 +1827,46 @@ isFolder?: boolean; serverId: string; parentId?: string | null; libraryId?: stri
|
||||
* ISO-8601 release/air date (Jellyfin `PremiereDate`). Used to sort
|
||||
* podcast episodes by release date.
|
||||
*/
|
||||
premiereDate?: string | null; communityRating?: number | null; officialRating?: string | null; runTimeTicks?: number | null; primaryImageTag?: string | null; backdropImageTags?: string[] | null; parentBackdropImageTags?: string[] | null; albumId?: string | null; albumName?: string | null; albumArtist?: string | null; artists?: string[] | null; artistItems?: ArtistItem[] | null; indexNumber?: number | null; parentIndexNumber?: number | null; seriesId?: string | null; seriesName?: string | null; seasonId?: string | null; seasonName?: string | null; userData?: UserData | null; mediaStreams?: MediaStream[] | null; mediaSources?: MediaSource[] | null; people?: Person[] | null }
|
||||
premiereDate?: string | null; communityRating?: number | null; officialRating?: string | null;
|
||||
/**
|
||||
* Legacy Jellyfin duration in ticks (100 ns units). Being replaced by
|
||||
* `duration_ms`; dual-carried while the frontend migrates
|
||||
* (docs/specs/frontend-domain-model.md). New code should read `duration_ms`.
|
||||
*/
|
||||
runTimeTicks?: number | null;
|
||||
/**
|
||||
* Duration in milliseconds — the neutral replacement for `runtime_ticks`.
|
||||
* Ticks never reach the frontend; this does.
|
||||
*/
|
||||
durationMs?: number | null;
|
||||
/**
|
||||
* Legacy Jellyfin primary image tag. Being replaced by `image_id`;
|
||||
* dual-carried while the frontend migrates. New code should read `image_id`.
|
||||
*/
|
||||
primaryImageTag?: string | null;
|
||||
/**
|
||||
* Neutral image identifier the frontend resolves to a URL via the image
|
||||
* command — the replacement for `primary_image_tag`. Same value today
|
||||
* (Jellyfin's tag is the id); the rename removes the provider term.
|
||||
*/
|
||||
imageId?: string | null; backdropImageTags?: string[] | null; parentBackdropImageTags?: string[] | null; albumId?: string | null; albumName?: string | null; albumArtist?: string | null; artists?: string[] | null; artistItems?: ArtistItem[] | null; indexNumber?: number | null; parentIndexNumber?: number | null; seriesId?: string | null; seriesName?: string | null; seasonId?: string | null; seasonName?: string | null; userData?: UserData | null; mediaStreams?: MediaStream[] | null; mediaSources?: MediaSource[] | null; people?: Person[] | null }
|
||||
/**
|
||||
* The kind of a media item — provider-neutral classification.
|
||||
*
|
||||
* Replaces the stringly-typed `item_type` that carried Jellyfin's vocabulary
|
||||
* (`"Audio"`, `"MusicAlbum"`, …) across the boundary. A closed enum means a
|
||||
* typo or an unhandled kind is a compile error on the frontend, not a silent
|
||||
* runtime miss across ~127 comparison sites.
|
||||
*/
|
||||
export type MediaKind = "track" | "album" | "artist" | "playlist" | "movie" | "series" | "season" | "episode" | "person" | "channel" | "folder" |
|
||||
/**
|
||||
* A kind we do not model explicitly. Reached only for provider item types
|
||||
* that map to nothing meaningful; consumers treat it like an opaque
|
||||
* container. The mapping must be *total* — it never panics — so this is the
|
||||
* safe sink for unknown strings. Also the `Default`, so a defaulted
|
||||
* `MediaItem` (see the dual-carry migration) is inert rather than a lie.
|
||||
*/
|
||||
"other"
|
||||
/**
|
||||
* Media session type tracking the high-level playback context
|
||||
*/
|
||||
@@ -2276,7 +2330,22 @@ export type PlaylistEntry =
|
||||
/**
|
||||
* The underlying media item
|
||||
*/
|
||||
({ id: string; name: string; type: string;
|
||||
({ id: string; name: string;
|
||||
/**
|
||||
* Legacy Jellyfin item-type string (`"Audio"`, `"MusicAlbum"`, …).
|
||||
*
|
||||
* Dual-carry migration (docs/specs/frontend-domain-model.md): `kind` below
|
||||
* is the neutral replacement. This field stays while the frontend migrates
|
||||
* off it, then is removed in a later phase. New Rust code should read
|
||||
* `kind`, not this.
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Provider-neutral classification — the replacement for `item_type`.
|
||||
* Populated by the Jellyfin mapping; defaults to `Other` for the handful of
|
||||
* construction sites that have not been migrated yet.
|
||||
*/
|
||||
kind?: MediaKind;
|
||||
/**
|
||||
* Whether this item is a folder/container (vs a playable leaf). Used to
|
||||
* decide whether a channel item drills into a list or plays directly.
|
||||
@@ -2286,7 +2355,29 @@ isFolder?: boolean; serverId: string; parentId?: string | null; libraryId?: stri
|
||||
* ISO-8601 release/air date (Jellyfin `PremiereDate`). Used to sort
|
||||
* podcast episodes by release date.
|
||||
*/
|
||||
premiereDate?: string | null; communityRating?: number | null; officialRating?: string | null; runTimeTicks?: number | null; primaryImageTag?: string | null; backdropImageTags?: string[] | null; parentBackdropImageTags?: string[] | null; albumId?: string | null; albumName?: string | null; albumArtist?: string | null; artists?: string[] | null; artistItems?: ArtistItem[] | null; indexNumber?: number | null; parentIndexNumber?: number | null; seriesId?: string | null; seriesName?: string | null; seasonId?: string | null; seasonName?: string | null; userData?: UserData | null; mediaStreams?: MediaStream[] | null; mediaSources?: MediaSource[] | null; people?: Person[] | null }) & {
|
||||
premiereDate?: string | null; communityRating?: number | null; officialRating?: string | null;
|
||||
/**
|
||||
* Legacy Jellyfin duration in ticks (100 ns units). Being replaced by
|
||||
* `duration_ms`; dual-carried while the frontend migrates
|
||||
* (docs/specs/frontend-domain-model.md). New code should read `duration_ms`.
|
||||
*/
|
||||
runTimeTicks?: number | null;
|
||||
/**
|
||||
* Duration in milliseconds — the neutral replacement for `runtime_ticks`.
|
||||
* Ticks never reach the frontend; this does.
|
||||
*/
|
||||
durationMs?: number | null;
|
||||
/**
|
||||
* Legacy Jellyfin primary image tag. Being replaced by `image_id`;
|
||||
* dual-carried while the frontend migrates. New code should read `image_id`.
|
||||
*/
|
||||
primaryImageTag?: string | null;
|
||||
/**
|
||||
* Neutral image identifier the frontend resolves to a URL via the image
|
||||
* command — the replacement for `primary_image_tag`. Same value today
|
||||
* (Jellyfin's tag is the id); the rename removes the provider term.
|
||||
*/
|
||||
imageId?: string | null; backdropImageTags?: string[] | null; parentBackdropImageTags?: string[] | null; albumId?: string | null; albumName?: string | null; albumArtist?: string | null; artists?: string[] | null; artistItems?: ArtistItem[] | null; indexNumber?: number | null; parentIndexNumber?: number | null; seriesId?: string | null; seriesName?: string | null; seasonId?: string | null; seasonName?: string | null; userData?: UserData | null; mediaStreams?: MediaStream[] | null; mediaSources?: MediaSource[] | null; people?: Person[] | null }) & {
|
||||
/**
|
||||
* The playlist-scoped entry ID (Jellyfin's PlaylistItemId)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user