import type { PickKeys } from "ts-essentials" import type { CompareOpKey, ComponentMinimalSelect } from "../backend/common/database/drizzle/drizzleTypes.ts" import type { ClientType, MonitoringStatus } from "./Atomic.ts" import type { SourceType } from "./Atomic.ts" import type { ComponentType, DateLike, ErrorLike, JsonPlayObject, PlayState, QueueName, Replace, SOURCE_SOT_TYPES, SourcePlayerJson } from "./Atomic.ts" import type { Dayjs } from "dayjs" import type { ErrorIsh } from "./ErrorUtils.ts" export interface PlayApiCommon { uid: string componentId: number state: PlayState play: JsonPlayObject compacted: boolean playedAt: string seenAt: string updatedAt: string parentUid?: string // TODO add parent source type/name? } export interface PlayInputApi { id: number data?: object play?: JsonPlayObject createdAt: string } export interface QueueStateApi { id: number queueName: string queueStatus: string retries: number error?: ErrorLike updatedAt: string createdAt: string } export interface PlayApiCommonDetailed extends PlayApiCommon { error?: ErrorIsh input?: PlayInputApi queueStates: QueueStateApi[] } export type ComponentState = 1 | 2 | 3 | 4 | 5 | 6 | 7; export const COMPONENT_STATE = { RUNNING: 1, MUTED: 2, IDLE: 3, STOPPED: 4, INITIALIZING: 5, NOT_READY: 6, ERROR: 7, } as const satisfies Record; export const componentStateToFriendly = (state: ComponentState) => { switch(state) { case 1: return 'Running'; case 2: return 'Ignored'; case 3: return 'Idle'; case 4: return 'Stopped'; case 5: return 'Initializing'; case 6: return 'Not Ready'; case 7: return 'Error'; } } export type ComponentCommonApi = { type: SourceType | ClientType name: string /** General state of the component like Idle, Stopped, Running, Error */ state: ComponentState /** More specific, live activity state like "sleeping", "hydrating historical scrobbles", "processing dead scrobbles", etc... */ status?: string players: Record error?: ErrorIsh warning?: ErrorIsh monitoringStatus?: MonitoringStatus } & Omit export type ComponentCommonApiJson = Replace, string>; export type ComponentDetailedApi = ComponentCommonApi & { hasAuth: boolean; hasAuthInteraction: boolean; authed: boolean initialized: boolean } export type ComponentCientApiBase = { queued: number tracksScrobbled: number deadLetterScrobbles: number deadLetterScrobblesTotal: number supportsNowPlaying: boolean players: Record } export type ComponentClientApi = ComponentCommonApi & ComponentCientApiBase; export type ComponentClientApiJson = Replace, string>; export type ComponentSourceApiBase = { sot: SOURCE_SOT_TYPES supportsUpstreamRecentlyPlayed: boolean; tracksDiscovered: number; wakeAt?: string sleeping: boolean } export type ComponentSourceApi = ComponentCommonApi & ComponentSourceApiBase; export type ComponentSourceApiJson = Replace, string>; export type SubsonicSourceApiJson = ComponentSourceApiJson & { playbackReporting?: boolean } export type ComponentsApiJson = ComponentSourceApiJson | ComponentClientApiJson; export const isComponentSourceApiJson = (data: ComponentCommonApiJson): data is ComponentSourceApiJson => { return data.mode === 'source'; } export const isComponentClientApiJson = (data: ComponentCommonApiJson): data is ComponentClientApiJson => { return data.mode === 'client'; } export type MsSseEventPayload> = { type: SourceType | ClientType name: string componentId: number from: ComponentType data: T event: string } export type MsSseEvent> = { playerUpdate: MsSseEventPayload discovered: MsSseEventPayload scrobbleQueued: MsSseEventPayload scrobbleDequeued: MsSseEventPayload client: T componentUpdate: MsSseEventPayload playInsert: MsSseEventPayload playUpdate: MsSseEventPayload> } export type SortPlaysBy = 'played' | 'seen'; export interface SortPlaysByProps { sortBy: SortPlaysBy } export type PlayStateUI = PlayState | 'dead queued'; export type QueryPlaysOptsJson = { sort?: "playedAt" | "seenAt"; order?: "asc" | "desc"; with?: ("input" | "parent" | "parent-input" | "queues")[]; limit?: number; offset?: number; state?: ("queued" | "discovered" | "discarded" | "scrobbled" | "failed" | "duped")[]; stateNot?: ("queued" | "discovered" | "discarded" | "scrobbled" | "failed" | "duped")[]; componentId?: number; seenAt?: { type: "eq" | "ne" | "gt" | "gte" | "lt" | "lte"; date: string; } | { type: "between"; range: [string, string]; inclusive?: boolean; }; playedAt?: { type: "eq" | "ne" | "gt" | "gte" | "lt" | "lte"; date: string; } | { type: "between"; range: [string, string]; inclusive?: boolean; }; queues?: { queueName: QueueName; queueStatus: ('queued' | 'failed' | 'completed')[] | ('queued' | 'failed' | 'completed'); }[]; uid?: string[]; text?: string[]; } export interface PaginatedQueryResponse { limit: number; offset: number; total?: number; } export interface PaginatedResponse { data: T[]; meta: PaginatedQueryResponse; } export type CompareDateBetween = { type: 'between'; range: [D, D]; inclusive?: boolean; }; export type CompareDateSingle = { type: CompareOpKey; date: D; };