UNPKG

spotify-api-lib

Version:

A modern, TypeScript-first wrapper for the Spotify Web API with organized endpoint categories and full type safety

1,265 lines (1,256 loc) 33.4 kB
// src/httpClient.ts import axios from "axios"; var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]); var SpotifyHttpClient = class { constructor(accessToken) { this.accessToken = accessToken || ""; this.client = axios.create({ baseURL: "https://api.spotify.com/v1", timeout: 1e4, headers: { "Content-Type": "application/json" } }); this.client.interceptors.request.use((config) => { if (this.accessToken) { config.headers.Authorization = `Bearer ${this.accessToken}`; } return config; }); this.client.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { console.error("Spotify API: Unauthorized - token may be expired"); } else if (error.response?.status === 429) { console.error("Spotify API: Rate limited"); } else if (error.response?.status >= 500) { console.error("Spotify API: Server error"); } return Promise.reject(error); } ); } /** * Set the access token for authentication */ setAccessToken(token) { this.accessToken = token; } /** * Clear the access token */ clearAccessToken() { this.accessToken = ""; } /** * Make an HTTP request to the Spotify API with improved error handling */ async request(endpoint, options) { const maxRetries = options?.retries ?? 3; let attempt = 0; let lastError = null; while (attempt <= maxRetries) { try { const config = { url: endpoint, method: options?.method || "GET", data: options?.data, params: options?.params, headers: options?.headers }; const response = await this.client.request(config); return response.data; } catch (error) { const status = error?.response?.status; lastError = this.createSpotifyError(error); if (attempt === 0 && !this.isRetryableError(status)) { throw lastError; } if (status === 429) { const retryAfter = this.getRetryAfterDelay(error.response.headers?.["retry-after"]); console.warn(`Spotify API rate limited. Retrying after ${retryAfter}ms (attempt ${attempt + 1}/${maxRetries + 1})`); await this.delay(retryAfter); } else if (this.isRetryableError(status)) { const delay = Math.min(1e3 * Math.pow(2, attempt), 1e4) + Math.random() * 1e3; console.warn(`Spotify API error ${status}. Retrying after ${delay}ms (attempt ${attempt + 1}/${maxRetries + 1})`); await this.delay(delay); } else { throw lastError; } attempt++; } } throw lastError || new Error("Spotify API request failed after all retries"); } /** * Check if an HTTP status code should be retried */ isRetryableError(status) { return status ? RETRYABLE_STATUS_CODES.has(status) : false; } /** * Get retry delay from Retry-After header or default */ getRetryAfterDelay(retryAfterHeader) { if (retryAfterHeader) { const parsed = parseInt(retryAfterHeader, 10); if (!isNaN(parsed)) { return parsed * 1e3; } } return 1e3; } /** * Create a standardized error object */ createSpotifyError(error) { const spotifyError = new Error( error?.response?.data?.error?.message || error?.message || "Spotify API request failed" ); spotifyError.status = error?.response?.status; spotifyError.response = error?.response?.data; return spotifyError; } /** * Promise-based delay utility */ delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Get the underlying axios instance for advanced usage */ getClient() { return this.client; } }; // src/baseEndpoint.ts var BaseEndpoint = class { constructor(client) { this.client = client; } // Generic request method async makeRequest(endpoint, options) { return this.client.request(endpoint, options); } // Convenience methods for common HTTP verbs async get(endpoint, params) { return this.makeRequest(endpoint, { method: "GET", params }); } async post(endpoint, data, params) { return this.makeRequest(endpoint, { method: "POST", data, params }); } async put(endpoint, data, params) { return this.makeRequest(endpoint, { method: "PUT", data, params }); } async delete(endpoint, params) { return this.makeRequest(endpoint, { method: "DELETE", params }); } // Standard error handling handleError(error, operation) { const message = error?.response?.data?.error?.message || error?.message || "Unknown error"; throw new Error(`${operation} failed: ${message}`); } }; // src/endpoints/playlists.ts var PlaylistEndpoints = class extends BaseEndpoint { /** * Get current user's playlists */ async getUserPlaylists(options) { let allPlaylists = []; let offset = options?.offset || 0; const limit = options?.limit || 50; try { while (true) { const response = await this.makeRequest("/me/playlists", { params: { limit, offset } }); allPlaylists = allPlaylists.concat(response.items); if (!response.next || response.items.length === 0) { break; } offset += limit; } return allPlaylists; } catch (error) { console.error("Error fetching playlists:", error); throw new Error("Failed to fetch playlists"); } } /** * Create a new playlist */ async create(name, options) { try { const userResponse = await this.makeRequest("/me"); const userId = userResponse.id; const playlistData = { name, description: options?.description || `Created on ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`, public: options?.public ?? false, collaborative: options?.collaborative ?? false }; const response = await this.makeRequest(`/users/${userId}/playlists`, { method: "POST", data: playlistData }); return response; } catch (error) { console.error("Error creating playlist:", error); throw new Error("Failed to create playlist"); } } /** * Add tracks to a playlist */ async addTracks(playlistId, trackUris, options) { try { const batchSize = 100; let lastResponse; for (let i = 0; i < trackUris.length; i += batchSize) { const batch = trackUris.slice(i, i + batchSize); const data = { uris: batch }; if (options?.position !== void 0) { data.position = options.position + i; } lastResponse = await this.makeRequest(`/playlists/${playlistId}/tracks`, { method: "POST", data }); } return lastResponse; } catch (error) { console.error("Error adding tracks to playlist:", error); throw new Error("Failed to add tracks to playlist"); } } /** * Get tracks from a playlist */ async getTracks(playlistId, options) { try { const params = { limit: options?.limit || 50, offset: options?.offset || 0 }; if (options?.fields) { params.fields = options.fields; } const response = await this.makeRequest(`/playlists/${playlistId}/tracks`, { params }); return response; } catch (error) { console.error("Error fetching playlist tracks:", error); throw new Error("Failed to fetch playlist tracks"); } } /** * Get a playlist by ID */ async getById(playlistId, options) { try { const params = {}; if (options?.fields) { params.fields = options.fields; } if (options?.market) { params.market = options.market; } const response = await this.makeRequest(`/playlists/${playlistId}`, { params }); return response; } catch (error) { console.error("Error fetching playlist:", error); throw new Error("Failed to fetch playlist"); } } /** * Update playlist details */ async update(playlistId, options) { try { await this.makeRequest(`/playlists/${playlistId}`, { method: "PUT", data: options }); } catch (error) { console.error("Error updating playlist:", error); throw new Error("Failed to update playlist"); } } /** * Remove tracks from a playlist */ async removeTracks(playlistId, tracks) { try { const response = await this.makeRequest(`/playlists/${playlistId}/tracks`, { method: "DELETE", data: { tracks } }); return response; } catch (error) { console.error("Error removing tracks from playlist:", error); throw new Error("Failed to remove tracks from playlist"); } } }; // src/endpoints/tracks.ts var TrackEndpoints = class extends BaseEndpoint { /** * Get user's saved tracks (liked songs) */ async getSavedTracks(options) { const limit = this.validateLimit(options?.limit, 50); const offset = this.validateOffset(options?.offset); const params = { limit, offset, ...options?.market && { market: options.market } }; return await this.get("/me/tracks", params); } /** * Validate limit parameter */ validateLimit(limit, defaultValue = 20) { if (!limit) return defaultValue; return Math.min(Math.max(1, limit), 50); } /** * Validate offset parameter */ validateOffset(offset) { return Math.max(0, offset || 0); } /** * Get albums from user's liked songs */ async getLikedSongsAlbums(fetchLimit) { const albumsMap = /* @__PURE__ */ new Map(); const singlesMap = /* @__PURE__ */ new Map(); let offset = 0; let fetchedItems = 0; const limit = 50; while (!fetchLimit || fetchedItems < fetchLimit) { const response = await this.get("/me/tracks", { limit, offset }); const items = response.items; if (items.length === 0) break; fetchedItems += items.length; items.forEach((item) => { if (item.track && item.track.album) { const album = item.track.album; if (album.album_type === "single") { singlesMap.set(album.id, { id: album.id, name: album.name, artists: album.artists, artistIds: album.artists.map((a) => a.id), track: item.track.name, images: album.images, release_date: album.release_date, album_type: album.album_type }); } if (!albumsMap.has(album.id)) { albumsMap.set(album.id, { id: album.id, name: album.name, artists: album.artists.map((a) => a.name).join(", "), release_date: album.release_date, total_tracks: album.total_tracks, images: album.images, album_type: album.album_type }); } } }); offset += limit; } return Array.from(albumsMap.values()); } /** * Get track by ID */ async getById(trackId, options) { const params = {}; if (options?.market) { params.market = options.market; } return await this.get(`/tracks/${trackId}`, params); } /** * Get multiple tracks by IDs */ async getByIds(trackIds, options) { const params = { ids: trackIds.join(",") }; if (options?.market) { params.market = options.market; } return await this.get("/tracks", params); } /** * Save tracks for current user */ async saveTracks(trackIds) { await this.put("/me/tracks", { ids: trackIds }); } /** * Remove tracks from current user's saved tracks */ async removeTracks(trackIds) { await this.delete("/me/tracks", { ids: trackIds }); } /** * Check if tracks are saved for current user */ async checkSavedTracks(trackIds) { return await this.get("/me/tracks/contains", { ids: trackIds.join(",") }); } /** * Get audio features for a track */ async getAudioFeatures(trackId) { return await this.get(`/audio-features/${trackId}`); } /** * Get audio analysis for a track */ async getAudioAnalysis(trackId) { return await this.get(`/audio-analysis/${trackId}`); } }; // src/endpoints/albums.ts var AlbumEndpoints = class extends BaseEndpoint { /** * Get album by ID */ async getById(albumId, options) { try { const params = {}; if (options?.market) { params.market = options.market; } const response = await this.makeRequest(`/albums/${albumId}`, { params }); return response; } catch (error) { console.error("Error fetching album:", error); throw new Error("Failed to fetch album"); } } /** * Get multiple albums by IDs */ async getByIds(albumIds, options) { try { const params = { ids: albumIds.join(",") }; if (options?.market) { params.market = options.market; } const response = await this.makeRequest("/albums", { params }); return response; } catch (error) { console.error("Error fetching albums:", error); throw new Error("Failed to fetch albums"); } } /** * Get album tracks */ async getTracks(albumId, options) { try { const params = { limit: options?.limit || 50, offset: options?.offset || 0, ...options?.market && { market: options.market } }; const response = await this.makeRequest(`/albums/${albumId}/tracks`, { params }); return response; } catch (error) { console.error("Error fetching album tracks:", error); throw new Error("Failed to fetch album tracks"); } } /** * Get user's saved albums */ async getSavedAlbums(options) { try { const params = { limit: options?.limit || 50, offset: options?.offset || 0, ...options?.market && { market: options.market } }; const response = await this.makeRequest("/me/albums", { params }); return response; } catch (error) { console.error("Error fetching saved albums:", error); throw new Error("Failed to fetch saved albums"); } } /** * Save albums for current user */ async saveAlbums(albumIds) { try { await this.makeRequest("/me/albums", { method: "PUT", data: { ids: albumIds } }); } catch (error) { console.error("Error saving albums:", error); throw new Error("Failed to save albums"); } } /** * Remove albums from current user's saved albums */ async removeAlbums(albumIds) { try { await this.makeRequest("/me/albums", { method: "DELETE", data: { ids: albumIds } }); } catch (error) { console.error("Error removing albums:", error); throw new Error("Failed to remove albums"); } } /** * Check if albums are saved for current user */ async checkSavedAlbums(albumIds) { try { const response = await this.makeRequest("/me/albums/contains", { params: { ids: albumIds.join(",") } }); return response; } catch (error) { console.error("Error checking saved albums:", error); throw new Error("Failed to check saved albums"); } } /** * Get new album releases */ async getNewReleases(options) { try { const params = { limit: options?.limit || 20, offset: options?.offset || 0, ...options?.country && { country: options.country } }; const response = await this.makeRequest("/browse/new-releases", { params }); return response.albums; } catch (error) { console.error("Error fetching new releases:", error); throw new Error("Failed to fetch new releases"); } } }; // src/endpoints/artists.ts var ArtistEndpoints = class extends BaseEndpoint { /** * Get artist by ID */ async getById(artistId) { try { const response = await this.makeRequest(`/artists/${artistId}`); return response; } catch (error) { console.error("Error fetching artist:", error); throw new Error("Failed to fetch artist"); } } /** * Get multiple artists by IDs */ async getByIds(artistIds) { try { const params = { ids: artistIds.join(",") }; const response = await this.makeRequest("/artists", { params }); return response; } catch (error) { console.error("Error fetching artists:", error); throw new Error("Failed to fetch artists"); } } /** * Get artist's albums */ async getAlbums(artistId, options) { try { const params = { include_groups: options?.include_groups?.join(",") || "album", limit: options?.limit || 50, offset: options?.offset || 0, ...options?.market && { market: options.market } }; const response = await this.makeRequest(`/artists/${artistId}/albums`, { params }); return response; } catch (error) { console.error("Error fetching artist albums:", error); throw new Error("Failed to fetch artist albums"); } } /** * Get artist's top tracks */ async getTopTracks(artistId, options) { try { const params = { market: options?.market || "US" }; const response = await this.makeRequest(`/artists/${artistId}/top-tracks`, { params }); return response; } catch (error) { console.error("Error fetching artist top tracks:", error); throw new Error("Failed to fetch artist top tracks"); } } /** * Get related artists */ async getRelatedArtists(artistId) { try { const response = await this.makeRequest(`/artists/${artistId}/related-artists`); return response; } catch (error) { console.error("Error fetching related artists:", error); throw new Error("Failed to fetch related artists"); } } /** * Follow artists */ async follow(artistIds) { try { await this.makeRequest("/me/following", { method: "PUT", params: { type: "artist" }, data: { ids: artistIds } }); } catch (error) { console.error("Error following artists:", error); throw new Error("Failed to follow artists"); } } /** * Unfollow artists */ async unfollow(artistIds) { try { await this.makeRequest("/me/following", { method: "DELETE", params: { type: "artist" }, data: { ids: artistIds } }); } catch (error) { console.error("Error unfollowing artists:", error); throw new Error("Failed to unfollow artists"); } } /** * Check if user follows artists */ async checkFollowing(artistIds) { try { const response = await this.makeRequest("/me/following/contains", { params: { type: "artist", ids: artistIds.join(",") } }); return response; } catch (error) { console.error("Error checking following:", error); throw new Error("Failed to check following"); } } /** * Get user's followed artists */ async getFollowed(options) { try { const params = { type: "artist", limit: options?.limit || 20, ...options?.after && { after: options.after } }; const response = await this.makeRequest("/me/following", { params }); return response; } catch (error) { console.error("Error fetching followed artists:", error); throw new Error("Failed to fetch followed artists"); } } }; // src/endpoints/search.ts var SearchEndpoints = class extends BaseEndpoint { /** * Search for items */ async search(query, options) { try { const params = { q: query, type: options?.type?.join(",") || "track", limit: options?.limit || 20, offset: options?.offset || 0, ...options?.market && { market: options.market }, ...options?.include_external && { include_external: options.include_external } }; const response = await this.makeRequest("/search", { params }); return response; } catch (error) { console.error("Error searching:", error); throw new Error("Failed to search"); } } /** * Search for tracks */ async tracks(query, options) { return this.search(query, { ...options, type: ["track"] }); } /** * Search for albums */ async albums(query, options) { return this.search(query, { ...options, type: ["album"] }); } /** * Search for artists */ async artists(query, options) { return this.search(query, { ...options, type: ["artist"] }); } /** * Search for playlists */ async playlists(query, options) { return this.search(query, { ...options, type: ["playlist"] }); } /** * Search for shows */ async shows(query, options) { return this.search(query, { ...options, type: ["show"] }); } /** * Search for episodes */ async episodes(query, options) { return this.search(query, { ...options, type: ["episode"] }); } /** * Search for audiobooks */ async audiobooks(query, options) { return this.search(query, { ...options, type: ["audiobook"] }); } /** * Search everything */ async all(query, options) { return this.search(query, { ...options, type: ["track", "album", "artist", "playlist", "show", "episode", "audiobook"] }); } }; // src/endpoints/player.ts var PlayerEndpoints = class extends BaseEndpoint { /** * Get currently playing track */ async getCurrentlyPlaying(options) { try { const params = {}; if (options?.market) { params.market = options.market; } if (options?.additional_types) { params.additional_types = options.additional_types.join(","); } const response = await this.makeRequest("/me/player/currently-playing", { params }); return response; } catch (error) { console.error("Error fetching currently playing:", error); throw new Error("Failed to fetch currently playing track"); } } /** * Get playback state */ async getPlaybackState(options) { try { const params = {}; if (options?.market) { params.market = options.market; } if (options?.additional_types) { params.additional_types = options.additional_types.join(","); } const response = await this.makeRequest("/me/player", { params }); return response; } catch (error) { console.error("Error fetching playback state:", error); throw new Error("Failed to fetch playback state"); } } /** * Transfer playback to a device */ async transferPlayback(deviceIds, play) { try { await this.makeRequest("/me/player", { method: "PUT", data: { device_ids: deviceIds, play: play ?? false } }); } catch (error) { console.error("Error transferring playback:", error); throw new Error("Failed to transfer playback"); } } /** * Get available devices */ async getDevices() { try { const response = await this.makeRequest("/me/player/devices"); return response; } catch (error) { console.error("Error fetching devices:", error); throw new Error("Failed to fetch devices"); } } /** * Start/Resume playback */ async play(options) { try { const params = {}; if (options?.device_id) { params.device_id = options.device_id; } const data = {}; if (options?.context_uri) { data.context_uri = options.context_uri; } if (options?.uris) { data.uris = options.uris; } if (options?.offset) { data.offset = options.offset; } if (options?.position_ms) { data.position_ms = options.position_ms; } await this.makeRequest("/me/player/play", { method: "PUT", params, data: Object.keys(data).length > 0 ? data : void 0 }); } catch (error) { console.error("Error starting playback:", error); throw new Error("Failed to start playback"); } } /** * Pause playback */ async pause(device_id) { try { const params = {}; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/pause", { method: "PUT", params }); } catch (error) { console.error("Error pausing playback:", error); throw new Error("Failed to pause playback"); } } /** * Skip to next track */ async next(device_id) { try { const params = {}; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/next", { method: "POST", params }); } catch (error) { console.error("Error skipping to next:", error); throw new Error("Failed to skip to next track"); } } /** * Skip to previous track */ async previous(device_id) { try { const params = {}; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/previous", { method: "POST", params }); } catch (error) { console.error("Error skipping to previous:", error); throw new Error("Failed to skip to previous track"); } } /** * Seek to position in track */ async seek(position_ms, device_id) { try { const params = { position_ms }; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/seek", { method: "PUT", params }); } catch (error) { console.error("Error seeking:", error); throw new Error("Failed to seek"); } } /** * Set repeat mode */ async setRepeat(state, device_id) { try { const params = { state }; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/repeat", { method: "PUT", params }); } catch (error) { console.error("Error setting repeat:", error); throw new Error("Failed to set repeat mode"); } } /** * Set shuffle mode */ async setShuffle(state, device_id) { try { const params = { state }; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/shuffle", { method: "PUT", params }); } catch (error) { console.error("Error setting shuffle:", error); throw new Error("Failed to set shuffle mode"); } } /** * Set volume */ async setVolume(volume_percent, device_id) { try { const params = { volume_percent }; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/volume", { method: "PUT", params }); } catch (error) { console.error("Error setting volume:", error); throw new Error("Failed to set volume"); } } /** * Add item to playback queue */ async addToQueue(uri, device_id) { try { const params = { uri }; if (device_id) { params.device_id = device_id; } await this.makeRequest("/me/player/queue", { method: "POST", params }); } catch (error) { console.error("Error adding to queue:", error); throw new Error("Failed to add to queue"); } } /** * Get the user's queue */ async getQueue() { try { const response = await this.makeRequest("/me/player/queue"); return response; } catch (error) { console.error("Error fetching queue:", error); throw new Error("Failed to fetch queue"); } } /** * Get recently played tracks */ async getRecentlyPlayed(options) { try { const params = { limit: options?.limit || 20 }; if (options?.after) { params.after = options.after; } if (options?.before) { params.before = options.before; } const response = await this.makeRequest("/me/player/recently-played", { params }); return response; } catch (error) { console.error("Error fetching recently played:", error); throw new Error("Failed to fetch recently played tracks"); } } }; // src/endpoints/user.ts var UserEndpoints = class extends BaseEndpoint { /** * Get current user's profile */ async getCurrentUser() { try { const response = await this.makeRequest("/me"); return response; } catch (error) { console.error("Error fetching current user:", error); throw new Error("Failed to fetch current user"); } } /** * Get user's profile by ID */ async getById(userId) { try { const response = await this.makeRequest(`/users/${userId}`); return response; } catch (error) { console.error("Error fetching user:", error); throw new Error("Failed to fetch user"); } } /** * Get user's top items (tracks or artists) */ async getTopItems(type, options) { try { const params = { time_range: options?.time_range || "medium_term", limit: options?.limit || 20, offset: options?.offset || 0 }; const response = await this.makeRequest(`/me/top/${type}`, { params }); return response; } catch (error) { console.error(`Error fetching top ${type}:`, error); throw new Error(`Failed to fetch top ${type}`); } } /** * Get user's top tracks */ async getTopTracks(options) { return this.getTopItems("tracks", options); } /** * Get user's top artists */ async getTopArtists(options) { return this.getTopItems("artists", options); } /** * Follow a user */ async followUser(userId) { try { await this.makeRequest("/me/following", { method: "PUT", params: { type: "user" }, data: { ids: [userId] } }); } catch (error) { console.error("Error following user:", error); throw new Error("Failed to follow user"); } } /** * Unfollow a user */ async unfollowUser(userId) { try { await this.makeRequest("/me/following", { method: "DELETE", params: { type: "user" }, data: { ids: [userId] } }); } catch (error) { console.error("Error unfollowing user:", error); throw new Error("Failed to unfollow user"); } } /** * Check if current user follows users */ async checkFollowingUsers(userIds) { try { const response = await this.makeRequest("/me/following/contains", { params: { type: "user", ids: userIds.join(",") } }); return response; } catch (error) { console.error("Error checking following users:", error); throw new Error("Failed to check following users"); } } }; // src/main.ts var ENDPOINT_CLASSES = { playlists: PlaylistEndpoints, tracks: TrackEndpoints, albums: AlbumEndpoints, artists: ArtistEndpoints, search: SearchEndpoints, player: PlayerEndpoints, user: UserEndpoints }; var SpotifyApi = class { constructor(accessToken) { if (accessToken && !this.isValidTokenFormat(accessToken)) { console.warn("SpotifyApi: Access token format appears invalid"); } this.httpClient = new SpotifyHttpClient(accessToken); this.initializeEndpoints(); } /** * Initialize all endpoint groups */ initializeEndpoints() { for (const [name, EndpointClass] of Object.entries(ENDPOINT_CLASSES)) { ; this[name] = new EndpointClass(this.httpClient); } } /** * Basic token format validation */ isValidTokenFormat(token) { return typeof token === "string" && token.length > 50 && /^[A-Za-z0-9_-]+$/.test(token); } /** * Set access token for API requests */ setAccessToken(token) { this.httpClient.setAccessToken(token); } /** * Clear the access token */ clearAccessToken() { this.httpClient.clearAccessToken(); } /** * Make a raw API request */ async request(endpoint, options) { return this.httpClient.request(endpoint, options); } /** * Get the HTTP client instance for advanced usage */ getHttpClient() { return this.httpClient; } /** * Clean up resources and clear tokens */ destroy() { this.clearAccessToken(); Object.values(ENDPOINT_CLASSES).forEach((_, key) => { const endpoint = this[Object.keys(ENDPOINT_CLASSES)[key]]; if (endpoint && typeof endpoint.cleanup === "function") { endpoint.cleanup(); } }); } }; export { AlbumEndpoints, ArtistEndpoints, PlayerEndpoints, PlaylistEndpoints, SearchEndpoints, SpotifyApi, SpotifyHttpClient, TrackEndpoints, UserEndpoints, SpotifyApi as default }; //# sourceMappingURL=index.mjs.map