import Foundation public struct ResourceRequest: Hashable, Sendable { public var url: URL public var headers: [String: String] public var expiresAt: Date? public init(url: URL, headers: [String: String] = [:], expiresAt: Date? = nil) { self.url = url self.headers = headers self.expiresAt = expiresAt } } public struct StreamRequest: Hashable, Sendable { public var resource: ResourceRequest public var format: AudioFormat? public init(resource: ResourceRequest, format: AudioFormat?) { self.resource = resource self.format = format } } public struct Cursor: Hashable, Codable, Sendable { public let raw: String public init(_ raw: String) { self.raw = raw } } public struct Annotation: Hashable, Codable, Sendable { public var favoritedAt: Date? public var playCount: Int public var lastPlayedAt: Date? public init(favoritedAt: Date? = nil, playCount: Int = 0, lastPlayedAt: Date? = nil) { self.favoritedAt = favoritedAt self.playCount = playCount self.lastPlayedAt = lastPlayedAt } } public enum Entity: Sendable { case artist(Artist) case album(Album) case track(Track) case playlist(Playlist, entries: [PlaylistEntry]) case station(RadioStation) } public enum Change: Sendable { case upsert(Entity) case remove(EntityKind, EntityID) case annotate(EntityID, Annotation) case checkpoint(Cursor) } public enum SourceError: Error { case cannotCalculateChanges case notFound } public protocol LibrarySource: Sendable { var id: SourceID { get } var name: String { get } func changes(since cursor: Cursor?) -> AsyncThrowingStream func stream(for track: EntityID) async throws -> StreamRequest func artwork(_ id: EntityID, pixelSize: Int) async throws -> ResourceRequest } public protocol Starring: LibrarySource { func setFavorite(_ isFavorite: Bool, for id: EntityID, kind: EntityKind) async throws } public protocol Scrobbling: LibrarySource { func scrobble(_ track: EntityID, at date: Date, submission: Bool) async throws }