Socket
Socket
Sign inDemoInstall

@ekwoka/spotify-api

Package Overview
Dependencies
0
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.14.0 to 0.15.0

dist/core/cacheKeys.d.ts

2

dist/auth/makeAuthURL.d.ts
export declare const makeAuthURL: (scopes: scope[], clientId?: string, redirectUri?: string) => string;
export declare type scope = 'ugc-image-upload' | 'user-modify-playback-state' | 'user-read-playback-state' | 'user-read-currently-playing' | 'user-library-modify' | 'user-follow-read' | 'user-read-recently-played' | 'user-read-playback-position' | 'user-top-read' | 'playlist-read-collaborative' | 'playlist-modify-public' | 'playlist-read-private' | 'playlist-modify-private' | 'app-remote-control' | 'streaming' | 'user-read-email' | 'user-read-private' | 'user-library-modify' | 'user-library-read';
export type scope = 'ugc-image-upload' | 'user-modify-playback-state' | 'user-read-playback-state' | 'user-read-currently-playing' | 'user-library-modify' | 'user-follow-read' | 'user-read-recently-played' | 'user-read-playback-position' | 'user-top-read' | 'playlist-read-collaborative' | 'playlist-modify-public' | 'playlist-read-private' | 'playlist-modify-private' | 'app-remote-control' | 'streaming' | 'user-read-email' | 'user-read-private' | 'user-library-modify' | 'user-library-read';
//# sourceMappingURL=makeAuthURL.d.ts.map

@@ -9,3 +9,3 @@ /**

export declare const refreshToken: (refreshToken: string, client?: string, secret?: string) => Promise<RefreshedToken>;
export declare type RefreshedToken = {
export type RefreshedToken = {
access_token: string;

@@ -12,0 +12,0 @@ token_type: 'Bearer';

@@ -9,3 +9,3 @@ /**

export declare const tokensFromCode: (code: string, client?: string, secret?: string) => Promise<SpotifyTokens>;
export declare type SpotifyTokens = {
export type SpotifyTokens = {
access_token: string;

@@ -12,0 +12,0 @@ token_type: 'Bearer';

@@ -6,6 +6,6 @@ import { QueryFunction } from './types';

* the passed in property. This is useful for forcing refreshed data.
* @param cacheType string
* @param key string
* @returns void
*/
export declare const resetCache: (cacheType?: string) => QueryFunction;
export declare const resetCache: (key?: string) => QueryFunction;
//# sourceMappingURL=resetCache.d.ts.map

@@ -5,18 +5,10 @@ /**

* the passed in property. This is useful for forcing refreshed data.
* @param cacheType string
* @param key string
* @returns void
*/
export const resetCache = (cacheType) => (Client) => {
if (!cacheType)
Client.cache = {
albums: {},
artists: {},
saved: {
albums: {},
playlists: {},
tracks: {},
},
};
export const resetCache = (key) => (Client) => {
if (!key)
Client.cache.clear();
else
delete Client.cache[cacheType];
Client.cache.delete(key);
};

@@ -1,3 +0,3 @@

import { SpotifyApiClient } from './types';
export declare function spotifyApiClient(token: string): SpotifyApiClient;
import { SpotifyApiClient, SpotifyApiClientOptions } from './types';
export declare function spotifyApiClient(token: string, options?: SpotifyApiClientOptions): SpotifyApiClient;
//# sourceMappingURL=spotifyApiClient.d.ts.map

@@ -1,2 +0,2 @@

export function spotifyApiClient(token) {
export function spotifyApiClient(token, options = {}) {
if (!token)

@@ -6,11 +6,3 @@ throw new TypeError('Token is required at Spotify API Initialization');

token,
cache: {
albums: {},
artists: {},
saved: {
albums: {},
tracks: {},
playlists: {},
},
},
cache: options.cache ?? new Map(),
};

@@ -17,0 +9,0 @@ return (fn) => {

@@ -1,19 +0,11 @@

import { Album } from '../endpoints/albums';
import { Artist } from '../endpoints/artists';
export declare type PersistentApiProperties = {
export type PersistentApiProperties = {
token: string;
cache: {
albums: Record<string, Album>;
artists: Record<string, Artist>;
saved: {
albums: Record<string, boolean>;
playlists: Record<string, boolean>;
tracks: Record<string, boolean>;
};
[key: string]: unknown;
};
cache: limitedMap;
};
export declare type SpotifyApiClient = <T>(fn: QueryFunction<T>) => T;
export declare type QueryFunction<T = void> = (props: PersistentApiProperties) => T;
export declare type PaginatedList<T> = {
export type SpotifyApiClientOptions = {
cache?: limitedMap;
};
export type SpotifyApiClient = <T>(fn: QueryFunction<T>) => T;
export type QueryFunction<T = void> = (props: PersistentApiProperties) => T;
export type PaginatedList<T> = {
href: string;

@@ -27,3 +19,4 @@ items: T[];

};
export declare type Copyrights = {
export type limitedMap = Pick<Map<string, object>, 'get' | 'set' | 'delete' | 'clear'>;
export type Copyrights = {
text: string;

@@ -30,0 +23,0 @@ type: string;

@@ -10,3 +10,3 @@ import { QueryFunction } from '../../core';

export declare const albumIsSaved: AlbumIsSaved;
declare type AlbumIsSaved = {
type AlbumIsSaved = {
(albumId: string): QueryFunction<Promise<boolean>>;

@@ -13,0 +13,0 @@ (albumIds: string[]): QueryFunction<Promise<boolean[]>>;

@@ -0,1 +1,2 @@

import { AlbumSavedStatus } from '../../core/cacheKeys';
import { batchWrap, spotifyFetch, toURLString, } from '../../utils';

@@ -15,10 +16,6 @@ /**

const cacheAlbumIsSaved = async ({ token, cache }, album) => {
const subCache = cache.saved.albums;
if (subCache[album])
return subCache[album];
const data = await batchAlbumIsSaved(token, album);
subCache[album] = data;
return data;
const cached = cache.get(AlbumSavedStatus);
return cached?.[album] ?? (await batchAlbumIsSaved(token, album, cache));
};
const batchAlbumIsSaved = batchWrap(async (token, ids) => {
const batchAlbumIsSaved = batchWrap(async (token, ids, cache) => {
const endpoint = `me/albums/contains?${toURLString({

@@ -28,3 +25,4 @@ ids: ids.join(','),

const data = await spotifyFetch(endpoint, token);
cache.set(AlbumSavedStatus, ids.reduce((acc, id, idx) => ((acc[id] = data[idx]), acc), cache.get(AlbumSavedStatus) ?? {}));
return data;
});

@@ -12,7 +12,8 @@ import { batchAlbums } from '.';

export const getAlbum = (id, market) => async ({ token, cache }) => {
if (cache.albums[id])
return cache.albums[id];
const cached = cache.get(`album.${id}`);
if (cached)
return cached;
const album = await batchAlbums(token, id, market);
cache.albums[id] = deepFreeze(album);
cache.set(`album.${id}`, deepFreeze(album));
return album;
};

@@ -12,5 +12,5 @@ import { Album } from '.';

export declare const getAlbums: (ids: string[], market?: string) => QueryFunction<Promise<Albums>>;
export declare type Albums = {
export type Albums = {
albums: Album[];
};
//# sourceMappingURL=getAlbums.d.ts.map

@@ -14,3 +14,3 @@ import { Album } from './';

export declare const getSavedAlbums: (options?: SavedAlbumOptions) => QueryFunction<Promise<PaginatedList<SavedAlbum>>>;
declare type SavedAlbumOptions = {
type SavedAlbumOptions = {
limit?: number;

@@ -20,3 +20,3 @@ offset?: number;

};
export declare type SavedAlbum = {
export type SavedAlbum = {
added_at: string;

@@ -23,0 +23,0 @@ album: Album;

import { deepFreeze, spotifyFetch, toURLString } from '../../utils';
import { AlbumSavedStatus } from '../../core/cacheKeys';
/**

@@ -14,7 +15,13 @@ * Retrieves a paginated list of Albums currently saved in the User's Library.

const endpoint = `me/albums?${toURLString(options)}`;
const cachedList = cache.get(endpoint);
if (cachedList)
return cachedList;
const data = await spotifyFetch(endpoint, token);
if (!cache.albums)
cache.albums = {};
data.items.forEach(({ album }) => (cache.albums[album.id] = deepFreeze(album)));
cache.set(endpoint, deepFreeze(data));
cache.set(AlbumSavedStatus, data.items.reduce((acc, { album }) => {
acc[album.id] = true;
cache.set(`album.${album.id}`, album);
return acc;
}, cache.get(AlbumSavedStatus) ?? {}));
return data;
};
import { PaginatedList, QueryFunction } from '../../core';
import { Album } from './types';
export declare const newReleases: (options?: Options) => QueryFunction<Promise<NewReleases>>;
declare type NewReleases = {
type NewReleases = {
albums: PaginatedList<Album>;
};
declare type Options = {
type Options = {
limit?: number;

@@ -9,0 +9,0 @@ offset?: number;

@@ -10,3 +10,3 @@ import { QueryFunction } from '../../core';

export declare const removeAlbums: RemoveAlbums;
declare type RemoveAlbums = {
type RemoveAlbums = {
(albumId: string): QueryFunction<Promise<boolean>>;

@@ -13,0 +13,0 @@ (albumIds: string[]): QueryFunction<Promise<boolean[]>>;

@@ -0,1 +1,2 @@

import { AlbumSavedStatus } from '../../core/cacheKeys';
import { batchWrap, spotifyFetch } from '../../utils';

@@ -11,12 +12,7 @@ /**

if (Array.isArray(ids))
return (client) => Promise.all(ids.map((id) => cacheSavedAlbums(client, id)));
return (client) => cacheSavedAlbums(client, ids);
return ({ token, cache }) => Promise.all(ids.map((id) => batchRemoveAlbums(token, id, cache)));
return ({ token, cache }) => batchRemoveAlbums(token, ids, cache);
});
const cacheSavedAlbums = async ({ token, cache }, album) => {
const data = await batchRemoveAlbums(token, album);
cache.saved.albums[album] = false;
return data;
};
const batchRemoveAlbums = batchWrap(async (token, ids) => {
const endpoint = `me/albums`;
const batchRemoveAlbums = batchWrap(async (token, ids, cache) => {
const endpoint = 'me/albums';
const data = await spotifyFetch(endpoint, token, {

@@ -26,3 +22,4 @@ method: 'DELETE',

});
cache.set(AlbumSavedStatus, ids.reduce((acc, id) => ((acc[id] = false), acc), cache.get(AlbumSavedStatus) ?? {}));
return data;
});

@@ -10,3 +10,3 @@ import { QueryFunction } from '../../core';

export declare const saveAlbums: SaveAlbums;
declare type SaveAlbums = {
type SaveAlbums = {
(albumId: string): QueryFunction<Promise<boolean>>;

@@ -13,0 +13,0 @@ (albumIds: string[]): QueryFunction<Promise<boolean[]>>;

@@ -0,1 +1,2 @@

import { AlbumSavedStatus } from '../../core/cacheKeys';
import { batchWrap, spotifyFetch } from '../../utils';

@@ -11,12 +12,7 @@ /**

if (Array.isArray(ids))
return (client) => Promise.all(ids.map((id) => cacheSavedAlbums(client, id)));
return (client) => cacheSavedAlbums(client, ids);
return ({ token, cache }) => Promise.all(ids.map((id) => batchSaveAlbums(token, id, cache)));
return ({ token, cache }) => batchSaveAlbums(token, ids, cache);
});
const cacheSavedAlbums = async ({ token, cache }, album) => {
const data = await batchSaveAlbums(token, album);
cache.saved.albums[album] = true;
return data;
};
const batchSaveAlbums = batchWrap(async (token, ids) => {
const endpoint = `me/albums`;
const batchSaveAlbums = batchWrap(async (token, ids, cache) => {
const endpoint = 'me/albums';
const data = await spotifyFetch(endpoint, token, {

@@ -26,3 +22,4 @@ method: 'PUT',

});
cache.set(AlbumSavedStatus, ids.reduce((acc, id) => ((acc[id] = true), acc), cache.get(AlbumSavedStatus) ?? {}));
return data;
});

@@ -5,3 +5,3 @@ import { Copyrights } from '../../core/types';

import { TrackStub } from '../tracks/types';
export declare type AlbumStub = {
export type AlbumStub = {
album_type: 'SINGLE' | 'ALBUM' | 'COMPILATION' | 'single' | 'album' | 'compilation';

@@ -23,3 +23,3 @@ artists: ArtistStub[];

};
export declare type Album = AlbumStub & {
export type Album = AlbumStub & {
restrictions?: {

@@ -35,3 +35,3 @@ reason: 'market' | 'product' | 'explicit';

};
export declare type TrackList = {
export type TrackList = {
href: SpotifyAPIURL;

@@ -38,0 +38,0 @@ items: TrackStub[];

@@ -11,7 +11,8 @@ import { batchArtists } from '.';

export const getArtist = (id) => async ({ token, cache }) => {
if (cache.artists[id])
return cache.artists[id];
const cached = cache.get(`artist.${id}`);
if (cached)
return cached;
const artist = await batchArtists(token, id);
cache.artists[id] = deepFreeze(artist);
cache.set(`artist.${id}`, deepFreeze(artist));
return artist;
};

@@ -11,5 +11,5 @@ import { Artist } from '.';

export declare const getArtists: (ids: string[]) => QueryFunction<Promise<Artists>>;
export declare type Artists = {
export type Artists = {
artists: Artist[];
};
//# sourceMappingURL=getArtists.d.ts.map

@@ -8,3 +8,3 @@ import { QueryFunction } from '../../core';

}) => QueryFunction<Promise<FollowedArtists>>;
declare type FollowedArtists = {
type FollowedArtists = {
artists: {

@@ -11,0 +11,0 @@ items: ArtistStub[];

@@ -1,5 +0,12 @@

import { spotifyFetch, toURLString } from '../../utils';
export const getFollowedArtists = (type = 'artist', options = {}) => ({ token }) => {
import { ArtistSavedStatus } from '../../core/cacheKeys';
import { deepFreeze, spotifyFetch, toURLString, } from '../../utils';
export const getFollowedArtists = (type = 'artist', options = {}) => async ({ token, cache }) => {
const endpoint = `me/following?type=${type}&${toURLString(options)}`;
return spotifyFetch(endpoint, token);
const cached = cache.get(endpoint);
if (cached)
return cached;
const followedArtists = await spotifyFetch(endpoint, token);
cache.set(endpoint, deepFreeze(followedArtists));
cache.set(ArtistSavedStatus, followedArtists.artists.items.reduce((acc, { id }) => ((acc[id] = true), acc), cache.get(ArtistSavedStatus) ?? {}));
return followedArtists;
};
import { Image, SpotifyAPIURL, SpotifyPageURL } from '../../utils/SpotifyUtilityTypes';
export declare type Artist = ArtistStub & {
export type Artist = ArtistStub & {
followers: {

@@ -11,3 +11,3 @@ href: null;

};
export declare type ArtistStub = {
export type ArtistStub = {
external_urls: {

@@ -14,0 +14,0 @@ spotify: SpotifyPageURL;

@@ -5,3 +5,3 @@ import { QueryFunction } from '../../core';

export declare const currentlyPlayingTrack: () => QueryFunction<Promise<CurrentlyPlayingTrack>>;
export declare type CurrentlyPlayingTrack = {
export type CurrentlyPlayingTrack = {
device: {

@@ -8,0 +8,0 @@ id: string;

@@ -17,3 +17,3 @@ import { Context } from '.';

export declare const recentlyPlayedTracks: (options?: RecentlyPlayedOptions) => QueryFunction<Promise<RecentlyPlayedTrackList>>;
declare type RecentlyPlayedOptions = {
type RecentlyPlayedOptions = {
after?: UNIXTimeString | UNIXTimeNumber;

@@ -23,3 +23,3 @@ before?: UNIXTimeString | UNIXTimeNumber;

};
export declare type RecentlyPlayedTrackList = {
export type RecentlyPlayedTrackList = {
items: {

@@ -26,0 +26,0 @@ track: Track;

import { SpotifyAPIURL, SpotifyPageURL } from '../../utils';
export declare type Context = {
export type Context = {
external_urls: {

@@ -4,0 +4,0 @@ spotify: SpotifyPageURL;

@@ -12,3 +12,3 @@ import { QueryFunction } from '../../core';

export declare const getPlaylist: (playlistID: string, options?: GetPlaylistOptions) => QueryFunction<Promise<Playlist>>;
declare type GetPlaylistOptions = {
type GetPlaylistOptions = {
fields?: string;

@@ -15,0 +15,0 @@ market?: string;

@@ -1,2 +0,2 @@

import { spotifyFetch, toURLString } from '../../utils';
import { deepFreeze, spotifyFetch, toURLString } from '../../utils';
// TODO: Get Types to work with fields entry

@@ -11,5 +11,10 @@ /**

*/
export const getPlaylist = (playlistID, options) => ({ token }) => {
export const getPlaylist = (playlistID, options) => async ({ token, cache }) => {
const endpoint = `playlists/${playlistID}?${toURLString(options)}`;
return spotifyFetch(endpoint, token);
const cached = cache.get(endpoint);
if (cached)
return cached;
const playlist = await spotifyFetch(endpoint, token);
cache.set(endpoint, deepFreeze(playlist));
return playlist;
};
import { PaginatedList, QueryFunction } from '../../core';
import { PlaylistItem } from './types';
export declare const getPlaylistItems: (playlistID: string, options?: GetPlaylistItemsOptions) => QueryFunction<Promise<PaginatedList<PlaylistItem>>>;
declare type GetPlaylistItemsOptions = {
type GetPlaylistItemsOptions = {
limit?: string | number;

@@ -6,0 +6,0 @@ market?: string;

@@ -1,4 +0,7 @@

import { spotifyFetch, toURLString } from '../../utils';
export const getPlaylistItems = (playlistID, options = {}) => async ({ token }) => {
import { deepFreeze, spotifyFetch, toURLString } from '../../utils';
export const getPlaylistItems = (playlistID, options = {}) => async ({ token, cache }) => {
const makeEndpoint = (options) => `playlists/${playlistID}/tracks?${toURLString(options)}`;
const cached = cache.get(makeEndpoint(options));
if (cached)
return cached;
const endpoint = makeEndpoint({

@@ -10,10 +13,14 @@ ...options,

const limit = Number(options.limit) || 100;
if (!(limit > 100) || firstPage.items.length < 100)
if (!(limit > 100) || firstPage.items.length < 100) {
cache.set(makeEndpoint(options), deepFreeze(firstPage));
return firstPage;
}
const pages = await getRemainingPages(limit, Number(options.offset) || 0, firstPage.total, options, token, makeEndpoint);
return {
const fullResult = deepFreeze({
...firstPage,
items: [...firstPage.items, ...pages.flat()],
limit,
};
});
cache.set(makeEndpoint(options), fullResult);
return fullResult;
};

@@ -20,0 +27,0 @@ const getRemainingPages = (fullLimit, initialOffset, totalItems, options, token, makeEndpoint) => {

@@ -11,3 +11,3 @@ import { PaginatedList, QueryFunction } from '../../core';

export declare const getUsersPlaylists: (options?: UserPlaylistOptions) => QueryFunction<Promise<PaginatedList<PlaylistStub>>>;
declare type UserPlaylistOptions = {
type UserPlaylistOptions = {
limit?: number;

@@ -14,0 +14,0 @@ offset?: number;

@@ -1,2 +0,3 @@

import { spotifyFetch, toURLString } from '../../utils';
import { PlaylistSavedStatus } from '../../core/cacheKeys';
import { deepFreeze, spotifyFetch, toURLString } from '../../utils';
/**

@@ -9,5 +10,11 @@ * Gets a paginated list of the users saved and created playlists. These do

*/
export const getUsersPlaylists = (options) => ({ token }) => {
export const getUsersPlaylists = (options) => async ({ token, cache }) => {
const endpoint = `me/playlists?${toURLString(options)}`;
return spotifyFetch(endpoint, token);
const cached = cache.get(endpoint);
if (cached)
return cached;
const playlists = await spotifyFetch(endpoint, token);
cache.set(endpoint, deepFreeze(playlists));
cache.set(PlaylistSavedStatus, playlists.items.reduce((acc, { id }) => ((acc[id] = true), acc), cache.get(PlaylistSavedStatus) ?? {}));
return playlists;
};

@@ -10,3 +10,3 @@ import { QueryFunction } from '../../core';

export declare const savePlaylists: SavePlaylists;
declare type SavePlaylists = {
type SavePlaylists = {
(playlistID: string): QueryFunction<Promise<boolean>>;

@@ -23,3 +23,3 @@ (playlistIDs: string[]): QueryFunction<Promise<boolean[]>>;

export declare const removePlaylists: RemovePlaylists;
declare type RemovePlaylists = {
type RemovePlaylists = {
(playlistID: string): QueryFunction<Promise<boolean>>;

@@ -26,0 +26,0 @@ (playlistIDs: string[]): QueryFunction<Promise<boolean[]>>;

@@ -1,2 +0,3 @@

import { createMapper, spotifyFetch } from '../../utils';
import { PlaylistSavedStatus } from '../../core/cacheKeys';
import { arrayWrap, createMapper, spotifyFetch, } from '../../utils';
/**

@@ -9,3 +10,3 @@ * Adds a playlist or playlists to the current user's library. Accepts both single

*/
export const savePlaylists = ((ids) => createMapper((id, client) => cacheNewPlaylistState(client, id, true))(ids));
export const savePlaylists = ((ids) => saveOrRemovePlaylist(ids, true));
/**

@@ -18,7 +19,7 @@ * Removes playlists from the current user's library. Accepts both a single ID

*/
export const removePlaylists = ((ids) => createMapper((id, client) => cacheNewPlaylistState(client, id, false))(ids));
const cacheNewPlaylistState = async ({ token, cache }, playlist, state) => {
await (state ? savePlaylist : removePlaylist)(token, playlist);
cache.saved.playlists[playlist] = state;
return state;
export const removePlaylists = ((ids) => saveOrRemovePlaylist(ids, false));
const saveOrRemovePlaylist = (ids, state) => async (client) => {
const results = await createMapper(async (id, client) => (await (state ? savePlaylist : removePlaylist)(client.token, id), state))(ids)(client);
client.cache.set(PlaylistSavedStatus, arrayWrap(ids).reduce((acc, id) => ((acc[id] = state), acc), client.cache.get(PlaylistSavedStatus) ?? {}));
return results;
};

@@ -25,0 +26,0 @@ const createFollowPlaylistCallback = (method) => (token, id) => {

import { PaginatedList } from '../../core';
import { Image, SpotifyAPIURL, SpotifyPageURL } from '../../utils';
import { Track } from '../tracks';
export declare type Playlist = Omit<PlaylistStub, 'tracks'> & {
export type Playlist = Omit<PlaylistStub, 'tracks'> & {
followers: {

@@ -11,3 +11,3 @@ href: null;

};
export declare type PlaylistItem = {
export type PlaylistItem = {
added_at: string;

@@ -33,3 +33,3 @@ added_by: {

};
export declare type PlaylistStub = {
export type PlaylistStub = {
collaborative: boolean;

@@ -36,0 +36,0 @@ description: string;

@@ -13,3 +13,3 @@ import { QueryFunction } from '../../core';

export declare const search: <T extends keyof QueryType>(q: string, maybeTypeArray: T | T[], options?: SearchOptions) => QueryFunction<Promise<SearchResults<T>>>;
declare type SearchOptions = Partial<{
type SearchOptions = Partial<{
limit: number;

@@ -16,0 +16,0 @@ offset: number;

export declare const searchString: (options: queryObject) => string;
declare type queryObject = {
type queryObject = {
q: string;

@@ -4,0 +4,0 @@ artist?: string;

@@ -6,3 +6,3 @@ import { PaginatedList } from '../../core';

import { Track } from '../tracks';
export declare type QueryType = {
export type QueryType = {
album: 'albums';

@@ -15,3 +15,3 @@ artist: 'artists';

};
export declare type PageType = {
export type PageType = {
albums: PaginatedList<Album>;

@@ -24,3 +24,3 @@ artists: PaginatedList<Artist>;

};
export declare type SearchResults<T extends keyof QueryType> = Pick<PageType, QueryType[T]>;
export type SearchResults<T extends keyof QueryType> = Pick<PageType, QueryType[T]>;
//# sourceMappingURL=types.d.ts.map

@@ -12,3 +12,3 @@ import { Track } from '.';

export declare const getRecommendations: (options: RecomendationOptions) => QueryFunction<Promise<Recommendations>>;
declare type RecomendationOptions = {
type RecomendationOptions = {
seed_artists?: string | string[];

@@ -62,7 +62,7 @@ seed_genres?: string | string[];

};
export declare type Recommendations = {
export type Recommendations = {
seeds: Seed[];
tracks: Track[];
};
declare type Seed = {
type Seed = {
initialPoolSize: number;

@@ -69,0 +69,0 @@ afterFilteringSize: number;

@@ -10,3 +10,3 @@ import { QueryFunction } from '../../core';

export declare const saveTracks: SaveTracks;
declare type SaveTracks = {
type SaveTracks = {
(trackID: string): QueryFunction<Promise<boolean>>;

@@ -23,3 +23,3 @@ (trackIDs: string[]): QueryFunction<Promise<boolean[]>>;

export declare const removeTracks: RemoveTracks;
declare type RemoveTracks = {
type RemoveTracks = {
(trackID: string): QueryFunction<Promise<boolean>>;

@@ -26,0 +26,0 @@ (trackIDs: string[]): QueryFunction<Promise<boolean[]>>;

@@ -1,2 +0,3 @@

import { batchWrap, spotifyFetch, createMapper, } from '../../utils';
import { TrackSavedStatus } from '../../core/cacheKeys';
import { batchWrap, spotifyFetch, createMapper, arrayWrap, } from '../../utils';
/**

@@ -9,3 +10,3 @@ * Adds a track or tracks to the current user's library. Accepts both single

*/
export const saveTracks = ((ids) => createMapper((id, client) => cacheNewTrackState(client, id, true))(ids));
export const saveTracks = ((ids) => saveOrRemoveTrack(ids, true));
/**

@@ -18,7 +19,8 @@ * Removes tracks from the current user's library. Accepts both a single ID

*/
export const removeTracks = ((ids) => createMapper((id, client) => cacheNewTrackState(client, id, false))(ids));
const cacheNewTrackState = async ({ token, cache }, track, state) => {
await (state ? batchSaveTrack : batchRemoveTrack)(token, track);
cache.saved.tracks[track] = state;
return state;
export const removeTracks = ((ids) => saveOrRemoveTrack(ids, false));
const saveOrRemoveTrack = (ids, state) => async (client) => {
const results = await createMapper(async (id, client) => (await (state ? batchSaveTrack : batchRemoveTrack)(client.token, id),
state))(ids)(client);
client.cache.set(TrackSavedStatus, arrayWrap(ids).reduce((acc, id) => ((acc[id] = state), acc), client.cache.get(TrackSavedStatus) ?? {}));
return results;
};

@@ -25,0 +27,0 @@ const saveOrRemoveBatchCallback = (method) => (token, ids) => {

@@ -10,3 +10,3 @@ import { QueryFunction } from '../../core';

export declare const trackIsSaved: TrackIsSaved;
declare type TrackIsSaved = {
type TrackIsSaved = {
(trackID: string): QueryFunction<Promise<boolean>>;

@@ -13,0 +13,0 @@ (trackID: string[]): QueryFunction<Promise<boolean[]>>;

@@ -0,1 +1,2 @@

import { TrackSavedStatus } from '../../core/cacheKeys';
import { batchWrap, spotifyFetch, toURLString, } from '../../utils';

@@ -15,10 +16,9 @@ /**

const cacheTrackIsSaved = async ({ token, cache }, track) => {
const subCache = cache.saved.tracks;
if (subCache[track])
return subCache[track];
const data = await batchTrackIsSaved(token, track);
subCache[track] = data;
const cached = cache.get(TrackSavedStatus) ?? {};
if (cached[track])
return cached[track];
const data = await batchTrackIsSaved(token, track, cache);
return data;
};
const batchTrackIsSaved = batchWrap(async (token, ids) => {
const batchTrackIsSaved = batchWrap(async (token, ids, cache) => {
const endpoint = `me/tracks/contains?${toURLString({

@@ -28,3 +28,4 @@ ids: ids.join(','),

const data = await spotifyFetch(endpoint, token);
cache.set(TrackSavedStatus, ids.reduce((acc, id, idx) => ((acc[id] = data[idx]), acc), cache.get(TrackSavedStatus) ?? {}));
return data;
}, 50);
import { SpotifyAPIURL, SpotifyPageURL } from '../../utils/SpotifyUtilityTypes';
import { AlbumStub } from '../albums';
import { ArtistStub } from '../artists/types';
export declare type Track = TrackStub & {
export type Track = TrackStub & {
album: AlbumStub;

@@ -13,3 +13,3 @@ external_ids: {

};
export declare type TrackStub = {
export type TrackStub = {
artists: ArtistStub[];

@@ -16,0 +16,0 @@ available_markets: string[];

@@ -9,3 +9,3 @@ import { QueryFunction } from '../../core';

export declare const getCurrentUser: () => QueryFunction<Promise<User>>;
declare type User = {
type User = {
country: string;

@@ -12,0 +12,0 @@ display_name: string;

@@ -9,9 +9,9 @@ import { deepFreeze, spotifyFetch } from '../../utils';

export const getCurrentUser = () => async ({ token, cache }) => {
if (cache.user)
return cache.user;
const cached = cache.get('user');
if (cached)
return cached;
const endpoint = `me`;
const data = await spotifyFetch(endpoint, token);
deepFreeze(data);
cache.user = data;
cache.set('user', deepFreeze(data));
return data;
};

@@ -5,8 +5,8 @@ import { QueryFunction, PaginatedList } from '../../core';

export declare const getTopItems: GetTopItems;
declare type GetTopItems = <T extends keyof TopItem>(type: T, options?: TopItemOptions) => QueryFunction<Promise<PaginatedList<TopItem[T]>>>;
declare type TopItem = {
type GetTopItems = <T extends keyof TopItem>(type: T, options?: TopItemOptions) => QueryFunction<Promise<PaginatedList<TopItem[T]>>>;
type TopItem = {
tracks: Track;
artists: Artist;
};
declare type TopItemOptions = {
type TopItemOptions = {
limit?: number;

@@ -13,0 +13,0 @@ offset?: number;

@@ -11,3 +11,3 @@ import { QueryFunction } from '../../core';

export declare const getUserProfile: (user_id: string) => QueryFunction<Promise<OtherUser>>;
declare type OtherUser = {
type OtherUser = {
display_name: string;

@@ -14,0 +14,0 @@ external_urls: {

@@ -1,2 +0,2 @@

import { spotifyFetch } from '../../utils';
import { deepFreeze, spotifyFetch } from '../../utils';
/**

@@ -9,6 +9,10 @@ * Consumes the 'user/{user_id}' endpoint to return basic information about

*/
export const getUserProfile = (user_id) => async ({ token }) => {
export const getUserProfile = (user_id) => async ({ token, cache }) => {
const cached = cache.get(`user.${user_id}`);
if (cached)
return cached;
const endpoint = `users/${user_id}`;
const data = await spotifyFetch(endpoint, token);
cache.set(`user.${user_id}`, deepFreeze(data));
return data;
};

@@ -10,4 +10,4 @@ /**

export declare const batchWrap: <T extends string, S>(cb: BatchCallback<T, S>, max?: number) => BatchedFunction<S>;
export declare type BatchedFunction<S> = (tkn: string, id: string, ...rest: unknown[]) => Promise<S>;
export declare type BatchCallback<T extends string, S> = (token: string, data: T[], ...rest: unknown[]) => Promise<S[]>;
export type BatchedFunction<S> = (tkn: string, id: string, ...rest: unknown[]) => Promise<S>;
export type BatchCallback<T extends string, S> = (token: string, data: T[], ...rest: unknown[]) => Promise<S[]>;
//# sourceMappingURL=batchRequests.d.ts.map
import { PersistentApiProperties } from '../core';
export declare const createMapper: <T, S>(cb: (val: T, client: PersistentApiProperties) => Promise<S>) => mappedCallback<T, S>;
declare type mappedCallback<T, S> = {
type mappedCallback<T, S> = {
(vals: T[]): (client: PersistentApiProperties) => Promise<S[]>;
(val: T): (client: PersistentApiProperties) => Promise<S>;
};
export declare type mappedArguments<T, S> = Parameters<mappedCallback<T, S>>;
export type mappedArguments<T, S> = Parameters<mappedCallback<T, S>>;
export {};
//# sourceMappingURL=createMapper.d.ts.map

@@ -1,2 +0,2 @@

export declare type Image = {
export type Image = {
url: string;

@@ -6,7 +6,7 @@ height: number;

};
export declare type SpotifyPageURL = string;
export declare type SpotifyAPIURL = string;
export declare type UNIXTimeNumber = number;
export declare type UNIXTimeString = string;
export declare type ISOTimeString = string;
export type SpotifyPageURL = string;
export type SpotifyAPIURL = string;
export type UNIXTimeNumber = number;
export type UNIXTimeString = string;
export type ISOTimeString = string;
//# sourceMappingURL=SpotifyUtilityTypes.d.ts.map

@@ -1,2 +0,2 @@

export declare type maybeArray<T> = T | T[];
export type maybeArray<T> = T | T[];
//# sourceMappingURL=types.d.ts.map

@@ -16,3 +16,3 @@ {

"license": "MIT",
"version": "0.14.0",
"version": "0.15.0",
"description": "Composable Wrapper for the Spotify Web Api and Spotify Web Playback SDK",

@@ -30,18 +30,18 @@ "repository": "github:ekwoka/spotify-api",

"devDependencies": {
"@types/node": "^18.11.8",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"@vitest/coverage-c8": "^0.24.4",
"esbuild": "^0.15.12",
"eslint": "^8.26.0",
"gzip-size": "^7.0.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"pretty-bytes": "^6.0.0",
"typescript": "^4.8.4",
"undici": "^5.12.0",
"vite": "^3.2.2",
"vitest": "^0.24.4"
"@types/node": "18.11.18",
"@typescript-eslint/eslint-plugin": "5.49.0",
"@typescript-eslint/parser": "5.49.0",
"@vitest/coverage-c8": "0.28.3",
"esbuild": "0.17.5",
"eslint": "8.33.0",
"gzip-size": "7.0.0",
"husky": "8.0.3",
"lint-staged": "13.1.0",
"npm-run-all": "4.1.5",
"prettier": "2.8.3",
"pretty-bytes": "6.0.0",
"typescript": "4.9.4",
"undici": "5.16.0",
"vite": "4.0.4",
"vitest": "0.28.3"
},

@@ -57,3 +57,3 @@ "prettier": {

],
"*.{json,md,mdx,html,css,scss,less,graphql,yml,yaml}": [
"*.{json}": [
"prettier --write"

@@ -60,0 +60,0 @@ ]

# ⚡️A tree-shakable, composable, lightweight wrapper for the multiple Spotify APIs🔥
[<img src="https://img.shields.io/npm/v/@ekwoka/spotify-api?style=for-the-badge">](https://www.npmjs.com/package/@ekwoka/spotify-api)
[<img src="https://img.shields.io/npm/v/@ekwoka/spotify-api?label=%20&style=for-the-badge&logo=pnpm&logoColor=white">](https://www.npmjs.com/package/@ekwoka/spotify-api)
<img src="https://img.shields.io/npm/types/@ekwoka/spotify-api?label=%20&amp;logo=typescript&amp;logoColor=white&amp;style=for-the-badge">
<img src="https://img.shields.io/npm/dt/@ekwoka/spotify-api?style=for-the-badge" >
[<img src="https://img.shields.io/bundlephobia/minzip/@ekwoka/spotify-api?style=for-the-badge">](https://bundlephobia.com/package/@ekwoka/spotify-api)
<img src="https://img.shields.io/badge/coverage-99%25-success?style=for-the-badge&logo=testCafe&logoColor=white" alt="99% test coverage">
<img src="https://img.shields.io/npm/dt/@ekwoka/spotify-api?style=for-the-badge&logo=npm&logoColor=white" >
[<img src="https://img.shields.io/bundlephobia/minzip/@ekwoka/spotify-api?style=for-the-badge&logo=esbuild&logoColor=white">](https://bundlephobia.com/package/@ekwoka/spotify-api)
<img src="https://img.shields.io/badge/coverage-99%25-success?style=for-the-badge&logo=vitest&logoColor=white" alt="99% test coverage">

@@ -141,3 +141,3 @@ Born from my own difficulties using other wrapper libraries for Spotify, this library seeks to be the best possible API wrapper.

Cachekey: `saved.albums[id]`
Cachekey: `saved.albums` (shared)
Batching Limit: 20

@@ -633,3 +633,3 @@

## Batching and Caching
## Batching, Caching, and Limit Breaking

@@ -656,9 +656,58 @@ One of the unique features of this API wrapper is the use of batched requests and intelligent caching. These features serve the purpose of reducing the number of requests make to the Spotify API and generally improving the responsiveness of your application.

While this isa bit contrived, in a component based framework, you might have these actual calls happening in places far away from each other.
While this is a bit contrived, in a component based framework, you might have these actual calls happening in places far away from each other.
In the background, these two ids will be bunched together (if they come in close enough to eachother) sent as a single request to Spotify, and resolved from the return. This also includes combining multiple requests for a single album into only the one reference requested from Spotify.
### Caching
As this data changes infrequently, responses will be cached and reused when the same information is requested again. This works with the above batching, as well. So if you make a bulk request for 10 albums, 3 of which you've already searched for before, those 3 will be returned from cache and the other 7 will be fetched anew, all without any adjustments to how your code behaves.
The default cache strategy is simple strong cache (this takes the least code to implement and is most likely a preferred strategy for most use cases). Values are cached and stored indefinitely, and only cleared when the cache is manually cleared.
However, you can provide your own cache to allow alternative caching strategies (like LRU, TTL, weak, or weak LRU cache). This is done by providing a custom cache object to the `createClient` function.
```js
const client = SpotifyApi('initial_token', {
cache: new Map(),
});
```
The only requirement for the cache object is that it implements a limited set of the `Map` interface. Specifically, it must implement the following methods:
- `get(key)`
- `set(key, value)`
- `delete(key)`
- `clear()`
You can consider checking out [`@ekwoka/weak-lru-cache`](https://www.npmjs.com/package/@ekwoka/weak-lru-cache) for a lightweight weak reference based cache.
### Limit Breaking
Many endpoints offered by Spotify provide limits to how many items can be handled in one request. Retrieving items from a playlist is limited to 100, getting multiple albums in a single request is limited to 20, etc.
This api wrapper, in many places, dynamically breaks these limits.
For example, if you want to get all the songs in a playlist:
```js
const tracks = await client(getPlaylistItems('37i9dQZF1DX5g856aiKiDS'));
// only 100 items
```
This will retrieve up to 100 items from the playlist, and in other wrappers, or working with the API yourself, you'd need to then figure out next requests to send to get more items. This wrapper can allow larger limits (including `Infinity`!) and will make all the necessary requests in the background and return a clean list.
```js
const tracks = await client(
getPlaylistItems('37i9dQZF1DX5g856aiKiDS', { limit: Infinity })
);
// every item in the playlist
```
In the background, this will check how many items are in the playlist, and make all the requests necessary to reconstruct the entire playlist.
The goal is to have every endpoint limit be broken like this.
#### Splitting Large Lists
This system also comes with another nice feature. These multiple item endpoints have limits on the total number of items in a single request. For the above example of `albums` it's 20.
This system also comes with another nice feature. Multiple item endpoints have limits on the total number of items in a single request. For the above example of `albums` it's 20.

@@ -669,6 +718,4 @@ Once again, in the background, as these requests come in, the total number of albums may be greater than 20. Or you may even do a direct request for more than 20 albums.

### Caching
The goal is for this behavior to work on just about every endpoint you might want to access by an id.
As this data changes infrequently, responses will be cached and reused when the same information is requested again. This works with the above batching, as well. So if you make a bulk request for 10 albums, 3 of which you've already searched for before, those 3 will be returned from cache and the other 7 will be fetched anew, all without any adjustments to how your code behaves.
## Special Utilities

@@ -678,4 +725,6 @@

As noted, a major benefit of this API wrapper is the intelligent use of caches. However, caches may not always be accurate, or may introduce other issues in certain contexts. As such, there is a special utility for cache busting.
There can be times when you want to forcefull clean the cache when data has changed. This can be done by using the `resetCache` utility.
You can also consider using an alternative caching strategy as documented above if you wish to provide more automated caching behaviors.
```js

@@ -688,5 +737,5 @@ import { resetCache } from '@ekwoka/spotify-api';

// clears specific cached value
client(resetCache('user')); // should clear user cache only
client(resetCache('album.saved')); // should clear saved albums cache only
```
Where caches are utilized, the documentation for those endpoints will include information about the cache key(s) used.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc