From c75bf4dda200f3f486197c64c6ad1b74bd46d644 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 26 Feb 2024 11:13:24 +0100 Subject: [PATCH 01/18] chore: add jsdoc annotations for premium only endpoints --- .vscode/settings.json | 5 +++ README.md | 40 +++++++++++---------- endpoints/audiobook/audiobook.types.ts | 2 +- endpoints/episode/episode.endpoints.ts | 5 ++- endpoints/episode/episode.types.ts | 2 +- endpoints/genre/genre.endpoints.ts | 4 ++- endpoints/market/market.endpoints.ts | 4 ++- endpoints/player/player.endpoints.ts | 48 +++++++++++++++++--------- tsconfig.json | 2 +- 9 files changed, 71 insertions(+), 41 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..047daf3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "[typescript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + } +} diff --git a/README.md b/README.md index c7fb883..620bc24 100644 --- a/README.md +++ b/README.md @@ -214,12 +214,13 @@ import { PageIterator } from "@soundify/web-api/pagination"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); const playlistIter = new PageIterator( - (offset) => getPlaylistTracks(client, "37i9dQZEVXbMDoHDwVN2tF", { - // you can find the max limit for specific endpoint - // in spotify docs or in the jsdoc comments of this property - limit: 50, - offset, - }), + (offset) => + getPlaylistTracks(client, "37i9dQZEVXbMDoHDwVN2tF", { + // you can find the max limit for specific endpoint + // in spotify docs or in the jsdoc comments of this property + limit: 50, + offset, + }), ); // iterate over all tracks in the playlist @@ -233,12 +234,13 @@ console.log(allTracks.length); // Want to get the last 100 items? No problem const lastHundredTracks = new PageIterator( - (offset) => getPlaylistTracks( - client, - "37i9dQZEVXbMDoHDwVN2tF", - { limit: 50, offset } - ), - { initialOffset: -100 }, // this will work just as `Array.slice(-100)` + (offset) => + getPlaylistTracks( + client, + "37i9dQZEVXbMDoHDwVN2tF", + { limit: 50, offset }, + ), + { initialOffset: -100 }, // this will work just as `Array.slice(-100)` ).collect(); ``` @@ -249,21 +251,23 @@ import { CursorPageIterator } from "@soundify/web-api/pagination"; const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); // loop over all followed artists -for await (const artist of new CursorPageIterator( - opts => getFollowedArtists(client, { limit: 50, after: opts.after }) -)) { +for await ( + const artist of new CursorPageIterator( + (opts) => getFollowedArtists(client, { limit: 50, after: opts.after }), + ) +) { console.log(artist.name); } // or collect all followed artists into an array const artists = await new CursorPageIterator( - opts => getFollowedArtists(client, { limit: 50, after: opts.after }) + (opts) => getFollowedArtists(client, { limit: 50, after: opts.after }), ).collect(); // get all followed artists starting from Radiohead const artists = await new CursorPageIterator( - opts => getFollowedArtists(client, { limit: 50, after: opts.after }), - { initialAfter: "4Z8W4fKeB5YxbusRsdQVPb" } // let's start from Radiohead + (opts) => getFollowedArtists(client, { limit: 50, after: opts.after }), + { initialAfter: "4Z8W4fKeB5YxbusRsdQVPb" }, // let's start from Radiohead ).collect(); ``` diff --git a/endpoints/audiobook/audiobook.types.ts b/endpoints/audiobook/audiobook.types.ts index 8cf465c..e158a15 100644 --- a/endpoints/audiobook/audiobook.types.ts +++ b/endpoints/audiobook/audiobook.types.ts @@ -100,4 +100,4 @@ export type SavedAudiobook = { * Information about the audiobook. */ audiobook: SimplifiedAudiobook; -}; \ No newline at end of file +}; diff --git a/endpoints/episode/episode.endpoints.ts b/endpoints/episode/episode.endpoints.ts index 1e4909b..8aeef5d 100644 --- a/endpoints/episode/episode.endpoints.ts +++ b/endpoints/episode/episode.endpoints.ts @@ -86,7 +86,10 @@ export const saveEpisodes = ( * @param client Spotify HTTPClient * @param episodeId The Spotify ID of the episode */ -export const saveEpisode = (client: HTTPClient, episodeId: string): Promise => { +export const saveEpisode = ( + client: HTTPClient, + episodeId: string, +): Promise => { return saveEpisodes(client, [episodeId]); }; diff --git a/endpoints/episode/episode.types.ts b/endpoints/episode/episode.types.ts index 1418547..fdbbf4d 100644 --- a/endpoints/episode/episode.types.ts +++ b/endpoints/episode/episode.types.ts @@ -105,4 +105,4 @@ export type SavedEpisode = { * Information about the episode. */ episode: Episode; -} +}; diff --git a/endpoints/genre/genre.endpoints.ts b/endpoints/genre/genre.endpoints.ts index 4f5a0d4..c58c5a2 100644 --- a/endpoints/genre/genre.endpoints.ts +++ b/endpoints/genre/genre.endpoints.ts @@ -5,7 +5,9 @@ import type { HTTPClient } from "../../client.ts"; * * @param client Spotify HTTPClient */ -export const getAvailableGenreSeeds = async (client: HTTPClient): Promise => { +export const getAvailableGenreSeeds = async ( + client: HTTPClient, +): Promise => { const res = await client.fetch("/v1/recommendations/available-genre-seeds"); return ((await res.json()) as { genres: string[] }).genres; }; diff --git a/endpoints/market/market.endpoints.ts b/endpoints/market/market.endpoints.ts index 34822ef..d35698c 100644 --- a/endpoints/market/market.endpoints.ts +++ b/endpoints/market/market.endpoints.ts @@ -5,7 +5,9 @@ import type { HTTPClient } from "../../client.ts"; * * @param client Spotify HTTPClient */ -export const getAvailableMarkets = async (client: HTTPClient): Promise => { +export const getAvailableMarkets = async ( + client: HTTPClient, +): Promise => { const res = await client.fetch("/v1/markets"); return (await res.json() as { markets: string[] }).markets; }; diff --git a/endpoints/player/player.endpoints.ts b/endpoints/player/player.endpoints.ts index d305d96..dcb7d5d 100644 --- a/endpoints/player/player.endpoints.ts +++ b/endpoints/player/player.endpoints.ts @@ -22,7 +22,8 @@ export type GetPlaybackStateOpts = { /** * Get information about the user’s current playback state, including track or episode, progress, and active device. * - * @requires `user-read-playback-state` + * @requires `user-read-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param options Additional options for request @@ -58,7 +59,8 @@ export type TransferPlaybackBody = { /** * Transfer playback to a new device and optionally begin playback. The order of execution is not guaranteed when you use this API with other Player API endpoints. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** */ export const transferPlayback = ( client: HTTPClient, @@ -70,13 +72,14 @@ export const transferPlayback = ( /** * Get information about a user’s available Spotify Connect devices. Some device models are not supported and will not be listed in the API response. * - * @requires `user-read-playback-state` + * @requires `user-read-playback-state` \ + * **The user must have a Spotify Premium subscription.** */ export const getAvailableDevices = async ( client: HTTPClient, ): Promise => { const res = await client.fetch("/v1/me/player/devices"); - return (await res.json() as { devices: Device[] }).devices; + return ((await res.json()) as { devices: Device[] }).devices; }; export type GetCurrentPlayingTrackOpts = { @@ -93,7 +96,8 @@ export type GetCurrentPlayingTrackOpts = { /** * Get the object currently being played on the user's Spotify account. * - * @requires `user-read-currently-playing` + * @requires `user-read-currently-playing` \ + * **The user must have a Spotify Premium subscription.** */ export const getCurrentPlayingTrack = async ( client: HTTPClient, @@ -149,7 +153,8 @@ export type StartResumePlaybackBody = { /** * Start a new context or resume current playback on the user’s active device. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** */ export const startPlayback = ( client: HTTPClient, @@ -166,14 +171,16 @@ export const startPlayback = ( /** * Start a new context or resume current playback on the user’s active device. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** */ export const resumePlayback = startPlayback; /** * Pause playback on the user’s account. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param deviceId The id of the device this command is targeting. If not supplied, the user's currently active device is the target. @@ -191,7 +198,8 @@ export const pausePlayback = ( /** * Skips to next track in the user’s queue. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param deviceId The id of the device this command is targeting. If not supplied, the user's currently active device is the target. @@ -209,7 +217,8 @@ export const skipToNext = ( /** * Skips to previous track in the user’s queue. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param deviceId The id of the device this command is targeting. If not supplied, the user's currently active device is the target. @@ -227,7 +236,8 @@ export const skipToPrevious = ( /** * Seeks to the given position in the user’s currently playing track. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param positionMs The position in milliseconds to seek to. Must be a positive number. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. @@ -247,7 +257,8 @@ export const seekToPosition = ( /** * Set the repeat mode for the user's playback. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param state @@ -270,7 +281,8 @@ export const setRepeatMode = ( /** * Toggle shuffle on or off for user’s playback. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param state `true` to turn shuffle on, `false` to turn it off. @@ -308,7 +320,8 @@ export type GetRecentlyPlayedTracksOpts = { /** * Get tracks from the current user's recently played tracks. * - * @requires `user-read-recently-played` + * @requires `user-read-recently-played` \ + * **The user must have a Spotify Premium subscription.** */ export const getRecentPlayedTracks = async ( client: HTTPClient, @@ -323,8 +336,8 @@ export const getRecentPlayedTracks = async ( /** * Get the list of objects that make up the user's queue. * - * @requires `user-read-currently-playing`, -`user-read-playback-state` + * @requires `user-read-currently-playing`, `user-read-playback-state` \ + * **The user must have a Spotify Premium subscription.** */ export const getUserQueue = async (client: HTTPClient): Promise => { const res = await client.fetch("/v1/me/player/queue"); @@ -334,7 +347,8 @@ export const getUserQueue = async (client: HTTPClient): Promise => { /** * Add an item to the end of the user's current playback queue. * - * @requires `user-modify-playback-state` + * @requires `user-modify-playback-state` \ + * **The user must have a Spotify Premium subscription.** * * @param client Spotify HTTPClient * @param uri The uri of the item to add to the queue. Must be a track or an episode uri. diff --git a/tsconfig.json b/tsconfig.json index 2d7cdc8..0d41db1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,4 +27,4 @@ "allowUnusedLabels": false, "skipLibCheck": true } -} \ No newline at end of file +} From c9c737eaf4fb040ab05c04fa4eebd524052dc48b Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Sun, 3 Mar 2024 11:54:54 +0100 Subject: [PATCH 02/18] feat: handle multiple paralel refreshes and add ability to create client with refresher only --- README.md | 21 +++-- client.test.ts | 211 ++++++++++++++++++++++++++++++++++++++++++++++++- client.ts | 164 ++++++++++++++++++++++++++++---------- deno.json | 3 +- mod.test.ts | 2 - mod.ts | 11 ++- package.json | 13 ++- shared.ts | 10 --- test_client.ts | 21 ++--- 9 files changed, 370 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 620bc24..600de61 100644 --- a/README.md +++ b/README.md @@ -180,21 +180,20 @@ authorization. ```ts import { getCurrentUser, SpotifyClient } from "@soundify/web-api"; -const refresher = () => { - // This function should return a new access token - // You can use any library you want to refresh the token - // Or even make it yourself, we don't care - return Promise.resolve("YOUR_NEW_ACCESS_TOKEN"); -}; - -const accessToken = await refresher(); -const client = new SpotifyClient(accessToken, { refresher }); +// if you don't have access token yet, you can pass null to first argument +const client = new SpotifyClient(null, { + // but you have to provide a function that will return a new access token + refresher: () => { + return Promise.resolve("YOUR_NEW_ACCESS_TOKEN"); + } +}); const me = await getCurrentUser(client); +// client will call your refresher to get the token +// and only then make the request console.log(me); -// wait some time to expire the token ... -// 2000 YEARS LATER 🧽 +// let's wait some time to expire the token ... const me = await getCurrentUser(client); // client will receive 401 and call your refresher to get new token diff --git a/client.test.ts b/client.test.ts index ba12b71..2ea82a3 100644 --- a/client.test.ts +++ b/client.test.ts @@ -1,6 +1,11 @@ -import { SpotifyClient, SpotifyError } from "./client.ts"; +import { createSpotifyError, SpotifyClient, SpotifyError } from "./client.ts"; import { sandbox } from "mock_fetch"; -import { assert, assertEquals, assertInstanceOf } from "std/assert/mod.ts"; +import { + assert, + assertEquals, + assertInstanceOf, + assertThrows, +} from "std/assert/mod.ts"; Deno.test("SpotifyClient: basic", async () => { const { mock, fetch } = sandbox(); @@ -44,3 +49,205 @@ Deno.test("SpotifyClient: error handling", async () => { }); } }); + +Deno.test("createSpotifyError: regular error object", async () => { + const response = new Response( + JSON.stringify({ error: { status: 400, message: "Invalid request" } }), + { + status: 400, + statusText: "Bad Request", + headers: { "Content-Type": "application/json" }, + }, + ); + + const error = await createSpotifyError(response); + assertInstanceOf(error, SpotifyError); + assert(error.message === "400 Bad Request : Invalid request"); + assertEquals(error.body, { + error: { status: 400, message: "Invalid request" }, + }); +}); + +Deno.test("createSpotifyError: response with URL query", async () => { + const response = new Response("Please, provide access token", { + status: 401, + statusText: "Unauthorized", + }); + + Object.defineProperty( + response, + "url", + { get: () => "https://api.spotify.com/v1/me" }, + ); + + const error = await createSpotifyError(response); + assertInstanceOf(error, SpotifyError); + assert( + error.message === + "401 Unauthorized (https://api.spotify.com/v1/me) : Please, provide access token", + ); + assertEquals(error.body, "Please, provide access token"); +}); + +Deno.test("SpotifyClient: fetch - basic", async () => { + const { mock, fetch } = sandbox(); + + mock("GET@/v1/me", (req) => { + assert(req.url === "https://api.spotify.com/v1/me"); + assert(req.headers.get("Accept") === "application/json"); + assert(req.headers.get("Authorization") === "Bearer TOKEN"); + return Response.json({ id: "123" }); + }); + + const client = new SpotifyClient("TOKEN", { fetch }); + const res = await client.fetch("/v1/me"); + assert(res.status === 200); + assertEquals(await res.json(), { id: "123" }); +}); + +Deno.test("SpotifyClient: fetch - with query parameters", async () => { + const { mock, fetch } = sandbox(); + + mock("GET@/v1/search", (req) => { + assert(req.url === "https://api.spotify.com/v1/search?q=test&type=track"); + assert(req.headers.get("Accept") === "application/json"); + assert(req.headers.get("Authorization") === "Bearer TOKEN"); + return Response.json({ results: [] }); + }); + + const client = new SpotifyClient("TOKEN", { fetch }); + const res = await client.fetch("/v1/search", { + query: { q: "test", type: "track" }, + }); + assert(res.status === 200); + assertEquals(await res.json(), { results: [] }); +}); + +Deno.test("SpotifyClient: fetch - with JSON body", async () => { + const { mock, fetch } = sandbox(); + + mock("POST@/v1/playlists", async (req) => { + assert(req.url === "https://api.spotify.com/v1/playlists"); + assert(req.headers.get("Accept") === "application/json"); + assert(req.headers.get("Authorization") === "Bearer TOKEN"); + assert(req.headers.get("Content-Type") === "application/json"); + assertEquals(await req.json(), { name: "My Playlist" }); + return Response.json({ id: "playlist123" }); + }); + + const client = new SpotifyClient("TOKEN", { fetch }); + const res = await client.fetch("/v1/playlists", { + method: "POST", + body: { name: "My Playlist" }, + }); + assert(res.status === 200); + assertEquals(await res.json(), { id: "playlist123" }); +}); + +Deno.test("SpotifyClient: fetch - with access token refresher", async () => { + const { mock, fetch } = sandbox(); + + mock("GET@/v1/me", (req) => { + assert(req.url === "https://api.spotify.com/v1/me"); + assert(req.headers.get("Accept") === "application/json"); + assert(req.headers.get("Authorization") === "Bearer NEW_TOKEN"); + return Response.json({ id: "123" }); + }); + + const client = new SpotifyClient(null, { + fetch, + refresher: () => Promise.resolve("NEW_TOKEN"), + }); + + const res = await client.fetch("/v1/me"); + assert(res.status === 200); + assertEquals(await res.json(), { id: "123" }); +}); + +Deno.test("SpotifyClient: fetch - rate limit handling", async () => { + const { mock, fetch } = sandbox(); + + let isRateLimited = false; + + mock("GET@/v1/search", () => { + if (isRateLimited) { + return Response.json({ results: [] }); + } + isRateLimited = true; + return new Response(null, { + status: 429, + headers: { "Retry-After": "3" }, + }); + }); + + const client = new SpotifyClient("TOKEN", { + fetch, + waitForRateLimit: (retryAfter) => { + assert(retryAfter === 3); + return true; + }, + }); + + const start = Date.now(); + const res2 = await client.fetch("/v1/search", { + query: { q: "test", type: "track" }, + }); + const end = Date.now(); + assert(res2.status === 200); + assertEquals(await res2.json(), { results: [] }); + assert(end - start >= 3000); +}); + +Deno.test("SpotifyClient: fetch - error handling", async () => { + const { mock, fetch } = sandbox(); + + mock("GET@/v1/me", () => { + return new Response(null, { + status: 500, + statusText: "Internal Server Error", + }); + }); + + const client = new SpotifyClient("TOKEN", { fetch }); + + try { + await client.fetch("/v1/me"); + assert(false, "should throw an error"); + } catch (error) { + assertInstanceOf(error, SpotifyError); + assert(error.message === "500 Internal Server Error"); + assertEquals(error.body, ""); + } +}); + +Deno.test("SpotifyClient: constructor - without access token and refresher", () => { + // @ts-expect-error - testing invalid input + assertThrows(() => new SpotifyClient(null)); + assertThrows(() => new SpotifyClient("")); +}); + +Deno.test("SpotifyClient: multiple refreshes at the same time", async () => { + const { mock, fetch } = sandbox(); + + mock("GET@/v1/me", (req) => { + assert(req.headers.get("Authorization") === "Bearer NEW_TOKEN_1"); + return Response.json({ id: "123" }); + }); + + let refresherCalls = 0; + + const client = new SpotifyClient(null, { + fetch, + refresher: async () => { + await new Promise((res) => setTimeout(res, 500)); + refresherCalls++; + return Promise.resolve("NEW_TOKEN_" + refresherCalls); + }, + }); + + await Promise.all([ + client.fetch("/v1/me"), + client.fetch("/v1/me"), + client.fetch("/v1/me"), + ]); +}); diff --git a/client.ts b/client.ts index bec91dc..09abd69 100644 --- a/client.ts +++ b/client.ts @@ -1,4 +1,12 @@ -import type { SearchParams } from "./shared.ts"; +type SearchParam = + | string + | number + | boolean + | string[] + | number[] + | boolean[] + | undefined; +type SearchParams = Record; /** * @see https://developer.spotify.com/documentation/web-api/concepts/api-calls#regular-error-object @@ -11,42 +19,53 @@ export type RegularErrorObject = { }; }; +const isRegularErrorObject = ( + obj: unknown, +): obj is RegularErrorObject => { + if (typeof obj !== "object" || obj === null) return false; + const error = (obj as RegularErrorObject).error; + if (typeof error !== "object" || error === null) return false; + return ( + typeof error.message === "string" && + typeof error.status === "number" && + (error.reason === undefined || typeof error.reason === "string") + ); +}; + export class SpotifyError extends Error { name = "SpotifyError"; constructor( message: string, public readonly response: Response, - public readonly body: RegularErrorObject | string | null, + public readonly body: RegularErrorObject | string, options?: ErrorOptions, ) { super(message, options); } - get url(): string { - return this.response.url; - } - + /** + * Shorthand for `error.response.status` + */ get status(): number { return this.response.status; } } const APP_JSON = "application/json"; -const CONTENT_TYPE = "Content-Type"; -const getBodyMessage = (body: RegularErrorObject | string | null) => { - if (body === null) return null; +const getBodyMessage = ( + body: RegularErrorObject | string, +): string => { if (typeof body === "string") return body; - return ( - body.error.message + (body.error.reason ? ` (${body.error.reason})` : "") - ); + return body.error.message + + (body.error.reason ? ` (${body.error.reason})` : ""); }; -const createSpotifyError = async ( +export async function createSpotifyError( response: Response, options?: ErrorOptions, -) => { +): Promise { let message = response.statusText ? `${response.status} ${response.statusText}` : response.status.toString(); @@ -56,21 +75,19 @@ const createSpotifyError = async ( message += ` (${urlWithoutQuery})`; } - let body: RegularErrorObject | string | null = null; + let body: RegularErrorObject | string = ""; if (response.body && response.type !== "opaque") { - try { - body = await response.text(); - const contentType = response.headers.get(CONTENT_TYPE); - - if ( - contentType && - (contentType === APP_JSON || contentType.split(";")[0] === APP_JSON) - ) { - body = JSON.parse(body); + const _body = await response.text().catch(() => null); + if (_body) { + try { + const json = JSON.parse(_body); + if (isRegularErrorObject(json)) { + body = json; + } + } catch (_) { + body = _body; } - } catch (_) { - /* Ignore errors */ } const bodyMessage = getBodyMessage(body); @@ -80,18 +97,23 @@ const createSpotifyError = async ( } return new SpotifyError(message, response, body, options); -}; +} export interface FetchLikeOptions extends Omit { query?: SearchParams; body?: BodyInit | null | Record | unknown[]; } -type FetchLike = ( - resource: URL, - options: FetchLikeOptions, +interface MiddlewareOptions extends Omit { + query?: SearchParams; + headers: Headers; +} + +type MiddlewareHandler = ( + url: URL, + options: MiddlewareOptions, ) => Promise; -export type Middleware = (next: FetchLike) => FetchLike; +export type Middleware = (next: MiddlewareHandler) => MiddlewareHandler; /** * Interface for making HTTP requests to the Spotify API. @@ -124,19 +146,53 @@ export type SpotifyClinetOptions = { */ refresher?: () => Promise; /** + * Weather to wait for rate limit or not. \ + * Function can be used to decide dynamically based on the `retryAfter` time in seconds. + * * @default false */ - waitForRateLimit?: boolean | ((retryAfter: number) => boolean); + waitForRateLimit?: + | boolean + | ((retryAfter: number) => boolean); + /** + * @example + * ```ts + * const middleware: Middleware = (next) => (url, options) => { + * options.headers.set("X-Custom-Header", "custom-value"); + * return next(url, options); + * } + * + * const client = new SpotifyClient("YOUR_ACCESS_TOKEN", { + * middlewares: [middleware] + * }); + * ``` + */ middlewares?: Middleware[]; }; +const createFailedToAuthorizeError = () => + new Error( + "[SpotifyClient] accessToken or refresher is required to make requests.", + ); + export class SpotifyClient implements HTTPClient { private readonly baseUrl: string; + private refreshInProgress: Promise | null = null; + constructor(accessToken: string, options?: SpotifyClinetOptions); + constructor( + accessToken: null, + options: + & SpotifyClinetOptions + & Required>, + ); constructor( - private accessToken: string, + private accessToken: string | null, private readonly options: SpotifyClinetOptions = {}, ) { + if (!accessToken && !options.refresher) { + throw createFailedToAuthorizeError(); + } this.baseUrl = options.baseUrl ? options.baseUrl : "https://api.spotify.com/"; @@ -158,36 +214,61 @@ export class SpotifyClient implements HTTPClient { const isBodyJSON = !!opts.body && (isPlainObject(opts.body) || Array.isArray(opts.body)); if (isBodyJSON) { - headers.set(CONTENT_TYPE, APP_JSON); + headers.set("Content-Type", APP_JSON); } const body = isBodyJSON ? JSON.stringify(opts.body) : (opts.body as BodyInit | null | undefined); - let isRefreshed = false; - const wrappedFetch = (this.options.middlewares || []).reduceRight( (next, mw) => mw(next), - (this.options.fetch || globalThis.fetch) as FetchLike, + (this.options.fetch || globalThis.fetch) as MiddlewareHandler, ); + let isRefreshed = false; + + if (!this.accessToken && !this.options.refresher) { + throw createFailedToAuthorizeError(); + } + + const getRefreshPromise = () => { + if (!this.options.refresher) return null; + if (this.refreshInProgress === null) { + this.refreshInProgress = new Promise((res, rej) => { + this.options.refresher!() + .then((newAccessToken) => { + this.accessToken = newAccessToken; + res(); + }) + .catch(rej) + .finally(() => this.refreshInProgress = null); + }); + } + + return this.refreshInProgress; + }; + const recursiveFetch = async (): Promise => { + if (!this.accessToken && !isRefreshed) { + await getRefreshPromise(); + isRefreshed = true; + } headers.set("Authorization", "Bearer " + this.accessToken); const res = await wrappedFetch(url, { ...opts, body, headers }); - if (res.ok) return res; if (res.status === 401 && this.options.refresher && !isRefreshed) { - this.accessToken = await this.options.refresher(); + await getRefreshPromise(); isRefreshed = true; return recursiveFetch(); } if (res.status === 429) { // time in seconds - const retryAfter = Number(res.headers.get("Retry-After")) || undefined; + const value = res.headers.get("Retry-After"); + const retryAfter = value ? parseInt(value) || undefined : undefined; if (retryAfter) { const waitForRateLimit = @@ -199,9 +280,8 @@ export class SpotifyClient implements HTTPClient { await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000) ); + return recursiveFetch(); } - - return recursiveFetch(); } } diff --git a/deno.json b/deno.json index 2b5b4a7..48d1f69 100644 --- a/deno.json +++ b/deno.json @@ -3,7 +3,8 @@ "version": "1.0.0", "exports": { ".": "./mod.ts", - "./pagination": "./pagination.ts" + "./pagination": "./pagination.ts", + "./auth": "./auth.ts" }, "imports": { "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", diff --git a/mod.test.ts b/mod.test.ts index 9327442..e9d15b6 100644 --- a/mod.test.ts +++ b/mod.test.ts @@ -7,7 +7,6 @@ Deno.test("test endpoint exports", async () => { new Set([ ...EXPECTED_ENDPOINTS_MODULE_EXPORTS, ...EXPECTED_CLIENT_MODULE_EXPORTS, - ...EXPECTED_AUTH_MODULE_EXPORTS, ]), ); }); @@ -148,4 +147,3 @@ const EXPECTED_ENDPOINTS_MODULE_EXPORTS = [ ]; const EXPECTED_CLIENT_MODULE_EXPORTS = ["SpotifyClient", "SpotifyError"]; -const EXPECTED_AUTH_MODULE_EXPORTS = ["SPOTIFY_AUTH_URL", "OAUTH_SCOPES"]; diff --git a/mod.ts b/mod.ts index 1373673..600af49 100644 --- a/mod.ts +++ b/mod.ts @@ -1,3 +1,10 @@ -export * from "./auth.ts"; -export * from "./client.ts"; +export { + type FetchLikeOptions, + type HTTPClient, + type Middleware, + type RegularErrorObject, + SpotifyClient, + type SpotifyClinetOptions, + SpotifyError, +} from "./client.ts"; export * from "./endpoints/mod.ts"; diff --git a/package.json b/package.json index 9dd30bf..83eb445 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,22 @@ "exports": { ".": { "import": "./dist/mod.js", - "require": "./dist/mod.cjs" + "require": "./dist/mod.cjs", + "types": "./dist/mod.d.ts" }, "./pagination": { "import": "./dist/pagination.js", - "require": "./dist/pagination.cjs" + "require": "./dist/pagination.cjs", + "types": "./dist/pagination.d.ts" + }, + "./auth": { + "import": "./dist/auth.js", + "require": "./dist/auth.cjs", + "types": "./dist/auth.d.ts" } }, "scripts": { - "build": "tsup ./mod.ts ./pagination.ts --format esm,cjs --minify --dts --out-dir dist && rm ./dist/*.d.cts" + "build": "tsup ./mod.ts ./pagination.ts ./auth.ts --format esm,cjs --minify --dts --out-dir dist && rm ./dist/*.d.cts" }, "devDependencies": { "all-contributors-cli": "6.26.1", diff --git a/shared.ts b/shared.ts index 1609428..50797b4 100644 --- a/shared.ts +++ b/shared.ts @@ -8,16 +8,6 @@ export type RequireAtLeastOne = { & Partial>>; }[keyof T]; -export type SearchParam = - | string - | number - | boolean - | string[] - | number[] - | boolean[] - | undefined; -export type SearchParams = Record; - export type Prettify = & { [K in keyof T]: T[K]; diff --git a/test_client.ts b/test_client.ts index 8ec8139..9f753db 100644 --- a/test_client.ts +++ b/test_client.ts @@ -47,18 +47,13 @@ const refresher = async () => { return data.access_token; }; -const refreshOrGetCachedToken = async () => { - try { - const token = await Deno.readTextFile("/tmp/soundify_test_cache.txt"); - return token; - } catch (_) { - return await refresher(); - } -}; - -const accessToken = await refreshOrGetCachedToken(); - -export const client = new SpotifyClient(accessToken, { +export const client = new SpotifyClient(null, { waitForRateLimit: true, - refresher, + refresher: () => { + try { + return Deno.readTextFile("/tmp/soundify_test_cache.txt"); + } catch (_) { + return refresher(); + } + }, }); From 657463198941478d4374a246b2f1410204f130fd Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Sun, 3 Mar 2024 11:57:24 +0100 Subject: [PATCH 03/18] fix(tests): invalid refresher --- test_client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_client.ts b/test_client.ts index 9f753db..1a45ffb 100644 --- a/test_client.ts +++ b/test_client.ts @@ -49,9 +49,9 @@ const refresher = async () => { export const client = new SpotifyClient(null, { waitForRateLimit: true, - refresher: () => { + refresher: async () => { try { - return Deno.readTextFile("/tmp/soundify_test_cache.txt"); + return await Deno.readTextFile("/tmp/soundify_test_cache.txt"); } catch (_) { return refresher(); } From 428231da2b4c3e1a9a510acd4ae172a8ffe1729f Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Wed, 6 Mar 2024 00:14:31 +0100 Subject: [PATCH 04/18] chore: imporove pkce example --- README.md | 8 +- client.ts | 8 +- deno.json | 2 + endpoints/album/album.types.ts | 4 +- endpoints/audiobook/audiobook.types.ts | 4 +- endpoints/category/category.types.ts | 4 +- endpoints/episode/episode.types.ts | 4 +- endpoints/general.types.ts | 20 +- endpoints/player/player.types.ts | 31 +- endpoints/playlist/playlist.types.ts | 9 +- endpoints/search/search.endpoints.ts | 20 +- endpoints/show/show.types.ts | 4 +- endpoints/track/track.types.ts | 8 +- endpoints/user/user.types.ts | 4 +- examples/oauth4webapi/auth_code.ts | 2 +- examples/oauth4webapi/client_credentials.ts | 13 +- examples/oauth4webapi/pkce_code.ts | 197 +- spotify-openapi.ts | 4769 +++++++++++++++++++ 18 files changed, 4988 insertions(+), 123 deletions(-) create mode 100644 spotify-openapi.ts diff --git a/README.md b/README.md index 600de61..55e072c 100644 --- a/README.md +++ b/README.md @@ -181,15 +181,15 @@ authorization. import { getCurrentUser, SpotifyClient } from "@soundify/web-api"; // if you don't have access token yet, you can pass null to first argument -const client = new SpotifyClient(null, { +const client = new SpotifyClient(null, { // but you have to provide a function that will return a new access token refresher: () => { - return Promise.resolve("YOUR_NEW_ACCESS_TOKEN"); - } + return Promise.resolve("YOUR_NEW_ACCESS_TOKEN"); + }, }); const me = await getCurrentUser(client); -// client will call your refresher to get the token +// client will call your refresher to get the token // and only then make the request console.log(me); diff --git a/client.ts b/client.ts index 09abd69..9d7a6b4 100644 --- a/client.ts +++ b/client.ts @@ -11,13 +11,13 @@ type SearchParams = Record; /** * @see https://developer.spotify.com/documentation/web-api/concepts/api-calls#regular-error-object */ -export type RegularErrorObject = { +export interface RegularErrorObject { error: { message: string; status: number; reason?: string; }; -}; +} const isRegularErrorObject = ( obj: unknown, @@ -132,7 +132,7 @@ const isPlainObject = (obj: unknown): obj is Record => { ); }; -export type SpotifyClinetOptions = { +export interface SpotifyClinetOptions { /** * Use this option to provide a custom fetch function. */ @@ -168,7 +168,7 @@ export type SpotifyClinetOptions = { * ``` */ middlewares?: Middleware[]; -}; +} const createFailedToAuthorizeError = () => new Error( diff --git a/deno.json b/deno.json index 48d1f69..d36032a 100644 --- a/deno.json +++ b/deno.json @@ -12,6 +12,8 @@ "oak": "https://deno.land/x/oak@v12.5.0/mod.ts", "std/": "https://deno.land/std@0.215.0/", "@soundify/web-api": "./mod.ts", + "@soundify/web-api/pagination": "./pagination.ts", + "@soundify/web-api/auth": "./auth.ts", "mock_fetch": "https://deno.land/x/mock_fetch@0.3.0/mod.ts" }, "fmt": { "useTabs": true } diff --git a/endpoints/album/album.types.ts b/endpoints/album/album.types.ts index 347ac52..57178a7 100644 --- a/endpoints/album/album.types.ts +++ b/endpoints/album/album.types.ts @@ -106,11 +106,11 @@ export interface Album extends AlbumBase { tracks: PagingObject; } -export type SavedAlbum = { +export interface SavedAlbum { /** * The date and time the album was saved. * Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. */ added_at: string; album: Album; -}; +} diff --git a/endpoints/audiobook/audiobook.types.ts b/endpoints/audiobook/audiobook.types.ts index e158a15..cc66711 100644 --- a/endpoints/audiobook/audiobook.types.ts +++ b/endpoints/audiobook/audiobook.types.ts @@ -91,7 +91,7 @@ export interface Audiobook extends SimplifiedAudiobook { chapters: PagingObject; } -export type SavedAudiobook = { +export interface SavedAudiobook { /** * The date and time the audiobook was saved Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. */ @@ -100,4 +100,4 @@ export type SavedAudiobook = { * Information about the audiobook. */ audiobook: SimplifiedAudiobook; -}; +} diff --git a/endpoints/category/category.types.ts b/endpoints/category/category.types.ts index 3069c03..6ad4b8a 100644 --- a/endpoints/category/category.types.ts +++ b/endpoints/category/category.types.ts @@ -1,7 +1,7 @@ import type { NonNullableObject } from "../../shared.ts"; import type { Image } from "../general.types.ts"; -export type Category = { +export interface Category { /** * A link to the Web API endpoint returning full details of the category. */ @@ -18,4 +18,4 @@ export type Category = { * The name of the category. */ name: string; -}; +} diff --git a/endpoints/episode/episode.types.ts b/endpoints/episode/episode.types.ts index fdbbf4d..ec65a27 100644 --- a/endpoints/episode/episode.types.ts +++ b/endpoints/episode/episode.types.ts @@ -96,7 +96,7 @@ export interface Episode extends SimplifiedEpisode { show: SimplifiedShow; } -export type SavedEpisode = { +export interface SavedEpisode { /** * The date and time the episode was saved Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. */ @@ -105,4 +105,4 @@ export type SavedEpisode = { * Information about the episode. */ episode: Episode; -}; +} diff --git a/endpoints/general.types.ts b/endpoints/general.types.ts index d73b418..7a64cb3 100644 --- a/endpoints/general.types.ts +++ b/endpoints/general.types.ts @@ -1,4 +1,4 @@ -export type PagingObject = { +export interface PagingObject { /** * A link to the Web API endpoint returning the full result of the request. */ @@ -24,9 +24,9 @@ export type PagingObject = { */ total: number; items: TItem[]; -}; +} -export type CursorPagingObject = { +export interface CursorPagingObject { /** * A link to the Web API endpoint returning the full result of the request. */ @@ -57,9 +57,9 @@ export type CursorPagingObject = { */ total: number; items: TItem[]; -}; +} -export type PagingOptions = { +export interface PagingOptions { /** * The maximum number of items to return. Minimum: 1. Maximum: 50. * @default 20 @@ -70,7 +70,7 @@ export type PagingOptions = { * @default 0 (the first item) */ offset?: number; -}; +} /** * The reason for the restriction. @@ -95,7 +95,7 @@ export type Restrictions = { */ export type ReleaseDatePrecision = "year" | "month" | "day"; -export type Image = { +export interface Image { /** * The image height in pixels. */ @@ -108,7 +108,7 @@ export type Image = { * The image width in pixels. */ width: number | null; -}; +} export type ResumePoint = { /** @@ -150,7 +150,7 @@ export type ExternalUrls = { spotify: string; }; -export type ExternalIds = { +export interface ExternalIds { /** * [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code). */ @@ -163,7 +163,7 @@ export type ExternalIds = { * [Universal Product Code](http://en.wikipedia.org/wiki/Universal_Product_Code). */ upc?: string; -}; +} /** * The copyright object contains the type and the name of copyright. diff --git a/endpoints/player/player.types.ts b/endpoints/player/player.types.ts index 328ecae..3d36bc3 100644 --- a/endpoints/player/player.types.ts +++ b/endpoints/player/player.types.ts @@ -2,7 +2,7 @@ import type { ExternalUrls } from "../general.types.ts"; import type { Track } from "../track/track.types.ts"; import type { Episode } from "../episode/episode.types.ts"; -export type Device = { +export interface Device { /** * The device ID. This ID is unique and persistent to some extent. However, this is not guaranteed and any cached device_id should periodically be cleared out and refetched as necessary. */ @@ -17,6 +17,7 @@ export type Device = { is_restricted: boolean; /** * A human-readable name for the device. Some devices have a name that the user can configure (e.g. "Loudest speaker") and some devices have a generic name associated with the manufacturer or device model. + * @example Kitchen speaker */ name: string; /** @@ -31,9 +32,9 @@ export type Device = { * If this device can be used to set the volume. */ supports_volume: boolean; -}; +} -export type Actions = { +export interface Disallows { /** Interrupting playback. */ interrupting_playback?: boolean; /** Pausing. */ @@ -54,12 +55,10 @@ export type Actions = { toggling_repeat_track?: boolean; /** Transfering playback between devices. */ transferring_playback?: boolean; -}; +} -export type Context = { - /** - * The object type, e.g. "artist", "playlist", "album", "show". - */ +export interface Context { + /** The object type, e.g. "artist", "playlist", "album", "show". */ type: string; /** A link to the Web API endpoint providing full details of the track. */ href: string; @@ -67,7 +66,7 @@ export type Context = { external_urls: ExternalUrls; /** The Spotify URI for the context. */ uri: string; -}; +} /** * "track" - repeat the current track. \ @@ -76,7 +75,7 @@ export type Context = { */ export type RepeatMode = "off" | "track" | "context"; -export type PlaybackState = { +export interface PlaybackState { /** The device that is currently active. */ device: Device; repeat_state: RepeatMode; @@ -96,22 +95,22 @@ export type PlaybackState = { * Allows to update the user interface based on which playback actions are available within the current context. */ actions: { - disallows: Actions; + disallows: Disallows; }; -}; +} -export type Queue = { +export interface Queue { /** The currently playing track or episode. */ currently_playing: Track | Episode | null; /** The tracks or episodes in the queue. Can be empty. */ queue: (Track | Episode)[]; -}; +} -export type PlayHistoryObject = { +export interface PlayHistoryObject { /** The track the user listened to. */ track: Track; /** The date and time the track was played. */ played_at: string; /** The context the track was played from. */ context: Context; -}; +} diff --git a/endpoints/playlist/playlist.types.ts b/endpoints/playlist/playlist.types.ts index 7059d4a..e158426 100644 --- a/endpoints/playlist/playlist.types.ts +++ b/endpoints/playlist/playlist.types.ts @@ -23,7 +23,6 @@ export interface SimplifiedPlaylist { * Known external URLs for this playlist. */ external_urls: ExternalUrls; - /** * A link to the Web API endpoint providing full details of the playlist. */ @@ -62,14 +61,14 @@ export interface SimplifiedPlaylist { tracks: TracksReference; } -export type TracksReference = { +export interface TracksReference { /** * A link to the Web API endpoint where full details of the playlist’s tracks can be retrieved. */ href: string; /** The total number of tracks in playlist. */ total: number; -}; +} /** * The structure containing the details of the Spotify Track in the playlist. @@ -122,11 +121,11 @@ export interface Playlist extends SimplifiedPlaylist { tracks: PagingObject; } -export type FeaturedPlaylists = { +export interface FeaturedPlaylists { /** The message from the featured playlists. */ message: string; /** * The list of the featured playlists wrapped in Paging object. */ playlists: PagingObject; -}; +} diff --git a/endpoints/search/search.endpoints.ts b/endpoints/search/search.endpoints.ts index f3b32fa..cd33430 100644 --- a/endpoints/search/search.endpoints.ts +++ b/endpoints/search/search.endpoints.ts @@ -17,7 +17,7 @@ export type ItemType = | "episode" | "audiobook"; -type ItemTypeToResultKey = { +interface ItemTypeToResultKey { album: "albums"; artist: "artists"; playlist: "playlists"; @@ -25,14 +25,14 @@ type ItemTypeToResultKey = { show: "shows"; episode: "episodes"; audiobook: "audiobooks"; -}; +} type ItemTypesToSearchResultKeys = T extends ItemType[] ? Pick[T[number]] : T extends ItemType ? ItemTypeToResultKey[T] : never; -export type SearchResponse = { +export interface SearchResponse { tracks: PagingObject; artists: PagingObject; albums: PagingObject; @@ -40,9 +40,9 @@ export type SearchResponse = { shows: PagingObject; audiobooks: PagingObject; episodes: PagingObject; -}; +} -export type SearchQueries = { +export interface SearchQueries { q?: string; track?: string; artist?: string; @@ -56,9 +56,9 @@ export type SearchQueries = { * The `tag:new` filter will return albums released in the past two weeks and `tag:hipster` can be used to return only albums with the lowest 10% popularity. */ tag?: "hipster" | "new"; -}; +} -type ItemTypeQueries = { +interface ItemTypeQueries { artists: "q" | "artist" | "genre" | "year"; tracks: "q" | "artist" | "album" | "genre" | "isrc" | "year"; albums: "q" | "artist" | "album" | "tag" | "year" | "upc"; @@ -66,14 +66,14 @@ type ItemTypeQueries = { shows: "q"; audiobooks: "q"; episodes: "q"; -}; +} type SearchQueriesFromItemTypes = Pick< SearchQueries, ItemTypeQueries[ItemTypesToSearchResultKeys] >; -export type SearchOptions = { +export interface SearchOptions { /** * If `include_external=audio` is specified it signals that the client can play externally hosted audio content, and marks the content as playable in the response. * @@ -99,7 +99,7 @@ export type SearchOptions = { * @default 0 */ offset?: number; -}; +} /** * Get Spotify catalog information about albums, artists, playlists, tracks, shows, episodes or audiobooks that match a keyword string. diff --git a/endpoints/show/show.types.ts b/endpoints/show/show.types.ts index 032e773..7f027a7 100644 --- a/endpoints/show/show.types.ts +++ b/endpoints/show/show.types.ts @@ -81,7 +81,7 @@ export interface Show extends SimplifiedShow { episodes: PagingObject; } -export type SavedShow = { +export interface SavedShow { /** * The date and time the album was saved Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. */ @@ -90,4 +90,4 @@ export type SavedShow = { * Information about the show. */ show: Show; -}; +} diff --git a/endpoints/track/track.types.ts b/endpoints/track/track.types.ts index 430e0ac..7ca2500 100644 --- a/endpoints/track/track.types.ts +++ b/endpoints/track/track.types.ts @@ -7,7 +7,7 @@ import type { } from "../general.types.ts"; import type { RequireAtLeastOne } from "../../shared.ts"; -export type LinkedTrack = { +export interface LinkedTrack { /** * A map of url name and the url. */ @@ -25,7 +25,7 @@ export type LinkedTrack = { * The Spotify URI for the track. */ uri: string; -}; +} export interface SimplifiedTrack { /** @@ -672,10 +672,10 @@ export interface RecommendationSeed { type: "artist" | "track" | "genre"; } -export type Recomendations = { +export interface Recomendations { seeds: RecommendationSeed[]; /** * An array of track object ordered according to the parameters supplied. */ tracks: Track[]; -}; +} diff --git a/endpoints/user/user.types.ts b/endpoints/user/user.types.ts index 1cc589f..90c1599 100644 --- a/endpoints/user/user.types.ts +++ b/endpoints/user/user.types.ts @@ -44,7 +44,7 @@ export type UserProductType = "free" | "open" | "premium"; /** * The spotify api object containing the information of explicit content. */ -export type ExplicitContentSettings = { +export interface ExplicitContentSettings { /** * When true, indicates that explicit content should not be played. */ @@ -54,7 +54,7 @@ export type ExplicitContentSettings = { * and can't be changed by the user. */ filter_locked: boolean; -}; +} /** * The spotify api object containing details of a user's public and private details. diff --git a/examples/oauth4webapi/auth_code.ts b/examples/oauth4webapi/auth_code.ts index 76a594f..bdf7b16 100644 --- a/examples/oauth4webapi/auth_code.ts +++ b/examples/oauth4webapi/auth_code.ts @@ -1,5 +1,5 @@ import * as oauth from "oauth4webapi"; -import { OAUTH_SCOPES, SPOTIFY_AUTH_URL } from "@soundify/web-api"; +import { OAUTH_SCOPES, SPOTIFY_AUTH_URL } from "@soundify/web-api/auth"; import { z } from "zod"; import { load } from "std/dotenv/mod.ts"; import { encodeBase64Url } from "std/encoding/base64url.ts"; diff --git a/examples/oauth4webapi/client_credentials.ts b/examples/oauth4webapi/client_credentials.ts index 9c368dc..5bc9018 100644 --- a/examples/oauth4webapi/client_credentials.ts +++ b/examples/oauth4webapi/client_credentials.ts @@ -1,5 +1,6 @@ import * as oauth from "oauth4webapi"; -import { search, SPOTIFY_AUTH_URL, SpotifyClient } from "@soundify/web-api"; +import { search, SpotifyClient } from "@soundify/web-api"; +import { SPOTIFY_AUTH_URL } from "@soundify/web-api/auth"; import { z } from "zod"; import { load } from "std/dotenv/mod.ts"; @@ -30,7 +31,6 @@ const refresher = async () => { oauthClient, {}, ); - const result = await oauth.processClientCredentialsResponse( authServer, oauthClient, @@ -38,17 +38,14 @@ const refresher = async () => { ); if (oauth.isOAuth2Error(result)) { throw new Error( - result.error + result.error_description - ? " : " + result.error_description - : "", + result.error + + (result.error_description ? " : " + result.error_description : ""), ); } return result.access_token; }; -const accessToken = await refresher(); - -const spotifyClient = new SpotifyClient(accessToken); +const spotifyClient = new SpotifyClient(null, { refresher }); const result = await search(spotifyClient, "track", "Never Gonna Give You Up"); console.log(result.tracks.items.at(0)); diff --git a/examples/oauth4webapi/pkce_code.ts b/examples/oauth4webapi/pkce_code.ts index abe3e2d..4b8e34b 100644 --- a/examples/oauth4webapi/pkce_code.ts +++ b/examples/oauth4webapi/pkce_code.ts @@ -1,10 +1,5 @@ import * as oauth from "oauth4webapi"; -import { - getCurrentUser, - OAUTH_SCOPES, - SPOTIFY_AUTH_URL, - SpotifyClient, -} from "@soundify/web-api"; +import { OAUTH_SCOPES } from "@soundify/web-api/auth"; import { z } from "zod"; import { load } from "std/dotenv/mod.ts"; import { Application, Router } from "oak"; @@ -18,7 +13,7 @@ const env = z }) .parse(Deno.env.toObject()); -const issuer = new URL(SPOTIFY_AUTH_URL); +const issuer = new URL("https://accounts.spotify.com/"); const authServer = await oauth.processDiscoveryResponse( issuer, await oauth.discoveryRequest(issuer), @@ -26,7 +21,7 @@ const authServer = await oauth.processDiscoveryResponse( const oauthClient: oauth.Client = { client_id: env.SPOTIFY_CLIENT_ID, - token_endpoint_auth_method: "client_secret_basic", + token_endpoint_auth_method: "none", }; class OAuthError extends Error { @@ -38,78 +33,182 @@ class OAuthError extends Error { } } +async function createAuthUrl(codeVerifier: string, state: string) { + const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier); + + const authUrl = new URL(authServer.authorization_endpoint!); + for ( + const [key, value] of Object.entries({ + client_id: env.SPOTIFY_CLIENT_ID, + redirect_uri: env.SPOTIFY_REDIRECT_URI, + response_type: "code", + code_challenge: codeChallenge, + code_challenge_method: "S256", + state, + scope: Object.values(OAUTH_SCOPES).join(" "), + }) + ) { + authUrl.searchParams.set(key, value); + } + + return authUrl; +} + +async function processOAuthCallback( + url: URL, + codeVerifier: string, + expectedState: string, +) { + const params = oauth.validateAuthResponse( + authServer, + oauthClient, + url, + expectedState, + ); + + if (oauth.isOAuth2Error(params)) { + throw new OAuthError(params); + } + + const response = await oauth.authorizationCodeGrantRequest( + authServer, + oauthClient, + params, + env.SPOTIFY_REDIRECT_URI, + codeVerifier, + ); + const result = await oauth.processAuthorizationCodeOAuth2Response( + authServer, + oauthClient, + response, + ); + + if (oauth.isOAuth2Error(result)) { + throw new OAuthError(result); + } + + return result; +} + +async function processRefresh(refreshToken: string) { + const response = await oauth.refreshTokenGrantRequest( + authServer, + oauthClient, + refreshToken, + ); + const result = await oauth.processRefreshTokenResponse( + authServer, + oauthClient, + response, + ); + + if (oauth.isOAuth2Error(result)) { + throw new OAuthError(result); + } + + return result; +} + const app = new Application(); const router = new Router(); router.get("/login", async (ctx) => { const codeVerifier = oauth.generateRandomCodeVerifier(); - const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier); - await ctx.cookies.set("code_verifier", codeVerifier, { httpOnly: true, path: "/callback", }); - const authUrl = new URL(authServer.authorization_endpoint!); - authUrl.searchParams.set("client_id", env.SPOTIFY_CLIENT_ID); - authUrl.searchParams.set("redirect_uri", env.SPOTIFY_REDIRECT_URI); - authUrl.searchParams.set("response_type", "code"); - authUrl.searchParams.set("code_challenge", codeChallenge); - authUrl.searchParams.set("code_challenge_method", "S256"); - authUrl.searchParams.set("scope", Object.values(OAUTH_SCOPES).join(" ")); + const state = oauth.generateRandomState(); + await ctx.cookies.set("state", state, { + httpOnly: true, + path: "/callback", + }); + const authUrl = await createAuthUrl(codeVerifier, state); return ctx.response.redirect(authUrl); }); router.get("/callback", async (ctx) => { + const codeVerifier = await ctx.cookies.get("code_verifier"); + if (!codeVerifier) { + ctx.response.status = 400; + ctx.response.body = "Missing code_verifier"; + return; + } + const state = await ctx.cookies.get("state"); + if (!state) { + ctx.response.status = 400; + ctx.response.body = "Missing state"; + return; + } + try { - const params = oauth.validateAuthResponse( - authServer, - oauthClient, + const result = await processOAuthCallback( ctx.request.url, - oauth.expectNoState, - ); - if (oauth.isOAuth2Error(params)) { - throw new OAuthError(params); - } - - const codeVerifier = await ctx.cookies.get("code_verifier"); - if (!codeVerifier) { - throw new Error("no code verifier"); - } - - const response = await oauth.authorizationCodeGrantRequest( - authServer, - oauthClient, - params, - env.SPOTIFY_REDIRECT_URI, codeVerifier, + state, ); - const result = await oauth.processAuthorizationCodeOAuth2Response( - authServer, - oauthClient, - response, - ); - console.log(result); - if (oauth.isOAuth2Error(result)) { - throw new OAuthError(result); + if (!result.refresh_token) { + throw new Error("Missing refresh_token"); } - const spotifyClient = new SpotifyClient(result.access_token); - const user = await getCurrentUser(spotifyClient); + await ctx.cookies.set("refresh_token", result.refresh_token, { + httpOnly: true, + path: "/refresh", + }); + await ctx.cookies.set("access_token", result.access_token, { + httpOnly: true, + }); ctx.response.type = "application/json"; - ctx.response.body = JSON.stringify(user); + ctx.response.body = JSON.stringify(result); ctx.response.status = 200; } catch (error) { console.log(error); ctx.response.status = 500; ctx.response.body = error.message; } finally { - await ctx.cookies.set("code_verifier", null, { + await ctx.cookies.delete("code_verifier", { httpOnly: true, path: "/callback", }); + await ctx.cookies.delete("state", { + httpOnly: true, + path: "/callback", + }); + } +}); + +router.get("/refresh", async (ctx) => { + const refreshToken = await ctx.cookies.get("refresh_token"); + if (!refreshToken) { + ctx.response.status = 400; + ctx.response.body = "Missing refresh token"; + return; + } + + try { + const result = await processRefresh(refreshToken); + if (!result.refresh_token) { + throw new Error("Missing refresh_token"); + } + + await ctx.cookies.set("refresh_token", result.refresh_token, { + httpOnly: true, + path: "/refresh", + }); + await ctx.cookies.set("access_token", result.access_token, { + httpOnly: true, + }); + + ctx.response.type = "application/json"; + ctx.response.body = JSON.stringify(result); + ctx.response.status = 200; + } catch (error) { + console.log(error); + ctx.response.status = 500; + ctx.response.body = error.message; } }); @@ -118,6 +217,6 @@ app.use(router.allowedMethods()); app.addEventListener( "listen", - (event) => console.log(`http://${event.hostname}:${event.port}/login`), + ({ hostname, port }) => console.log(`http://${hostname}:${port}/login`), ); await app.listen({ port: 3000 }); diff --git a/spotify-openapi.ts b/spotify-openapi.ts new file mode 100644 index 0000000..d287969 --- /dev/null +++ b/spotify-openapi.ts @@ -0,0 +1,4769 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +/** WithRequired type helpers */ +type WithRequired = T & { [P in K]-?: T[P] }; + +export interface paths { + "/albums/{id}": { + /** + * Get Album + * + * @description Get Spotify catalog information for a single album. + */ + get: operations["get-an-album"]; + }; + "/albums": { + /** + * Get Several Albums + * + * @description Get Spotify catalog information for multiple albums identified by their Spotify IDs. + */ + get: operations["get-multiple-albums"]; + }; + "/albums/{id}/tracks": { + /** + * Get Album Tracks + * + * @description Get Spotify catalog information about an album’s tracks. + * Optional parameters can be used to limit the number of tracks returned. + */ + get: operations["get-an-albums-tracks"]; + }; + "/artists/{id}": { + /** + * Get Artist + * + * @description Get Spotify catalog information for a single artist identified by their unique Spotify ID. + */ + get: operations["get-an-artist"]; + }; + "/artists": { + /** + * Get Several Artists + * + * @description Get Spotify catalog information for several artists based on their Spotify IDs. + */ + get: operations["get-multiple-artists"]; + }; + "/artists/{id}/albums": { + /** + * Get Artist's Albums + * + * @description Get Spotify catalog information about an artist's albums. + */ + get: operations["get-an-artists-albums"]; + }; + "/artists/{id}/top-tracks": { + /** + * Get Artist's Top Tracks + * + * @description Get Spotify catalog information about an artist's top tracks by country. + */ + get: operations["get-an-artists-top-tracks"]; + }; + "/artists/{id}/related-artists": { + /** + * Get Artist's Related Artists + * + * @description Get Spotify catalog information about artists similar to a given artist. Similarity is based on analysis of the Spotify community's listening history. + */ + get: operations["get-an-artists-related-artists"]; + }; + "/shows/{id}": { + /** + * Get Show + * + * @description Get Spotify catalog information for a single show identified by its + * unique Spotify ID. + */ + get: operations["get-a-show"]; + }; + "/shows": { + /** + * Get Several Shows + * + * @description Get Spotify catalog information for several shows based on their Spotify IDs. + */ + get: operations["get-multiple-shows"]; + }; + "/shows/{id}/episodes": { + /** + * Get Show Episodes + * + * @description Get Spotify catalog information about an show’s episodes. Optional parameters can be used to limit the number of episodes returned. + */ + get: operations["get-a-shows-episodes"]; + }; + "/episodes/{id}": { + /** + * Get Episode + * + * @description Get Spotify catalog information for a single episode identified by its + * unique Spotify ID. + */ + get: operations["get-an-episode"]; + }; + "/episodes": { + /** + * Get Several Episodes + * + * @description Get Spotify catalog information for several episodes based on their Spotify IDs. + */ + get: operations["get-multiple-episodes"]; + }; + "/audiobooks/{id}": { + /** + * Get an Audiobook + * + * @description Get Spotify catalog information for a single audiobook. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + get: operations["get-an-audiobook"]; + }; + "/audiobooks": { + /** + * Get Several Audiobooks + * + * @description Get Spotify catalog information for several audiobooks identified by their Spotify IDs. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + get: operations["get-multiple-audiobooks"]; + }; + "/audiobooks/{id}/chapters": { + /** + * Get Audiobook Chapters + * + * @description Get Spotify catalog information about an audiobook's chapters. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + get: operations["get-audiobook-chapters"]; + }; + "/me/audiobooks": { + /** + * Get User's Saved Audiobooks + * + * @description Get a list of the audiobooks saved in the current Spotify user's 'Your Music' library. + */ + get: operations["get-users-saved-audiobooks"]; + /** + * Save Audiobooks for Current User + * + * @description Save one or more audiobooks to the current Spotify user's library. + */ + put: operations["save-audiobooks-user"]; + /** + * Remove User's Saved Audiobooks + * + * @description Remove one or more audiobooks from the Spotify user's library. + */ + delete: operations["remove-audiobooks-user"]; + }; + "/me/audiobooks/contains": { + /** + * Check User's Saved Audiobooks + * + * @description Check if one or more audiobooks are already saved in the current Spotify user's library. + */ + get: operations["check-users-saved-audiobooks"]; + }; + "/chapters/{id}": { + /** + * Get a Chapter + * + * @description Get Spotify catalog information for a single audiobook chapter. Chapters are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + get: operations["get-a-chapter"]; + }; + "/chapters": { + /** + * Get Several Chapters + * + * @description Get Spotify catalog information for several audiobook chapters identified by their Spotify IDs. Chapters are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + get: operations["get-several-chapters"]; + }; + "/tracks/{id}": { + /** + * Get Track + * + * @description Get Spotify catalog information for a single track identified by its + * unique Spotify ID. + */ + get: operations["get-track"]; + }; + "/tracks": { + /** + * Get Several Tracks + * + * @description Get Spotify catalog information for multiple tracks based on their Spotify IDs. + */ + get: operations["get-several-tracks"]; + }; + "/search": { + /** + * Search for Item + * + * @description Get Spotify catalog information about albums, artists, playlists, tracks, shows, episodes or audiobooks + * that match a keyword string. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + get: operations["search"]; + }; + "/me": { + /** + * Get Current User's Profile + * + * @description Get detailed profile information about the current user (including the + * current user's username). + */ + get: operations["get-current-users-profile"]; + }; + "/playlists/{playlist_id}": { + /** + * Get Playlist + * + * @description Get a playlist owned by a Spotify user. + */ + get: operations["get-playlist"]; + /** + * Change Playlist Details + * + * @description Change a playlist's name and public/private state. (The user must, of + * course, own the playlist.) + */ + put: operations["change-playlist-details"]; + }; + "/playlists/{playlist_id}/tracks": { + /** + * Get Playlist Items + * + * @description Get full details of the items of a playlist owned by a Spotify user. + */ + get: operations["get-playlists-tracks"]; + /** + * Update Playlist Items + * + * @description Either reorder or replace items in a playlist depending on the request's parameters. + * To reorder items, include `range_start`, `insert_before`, `range_length` and `snapshot_id` in the request's body. + * To replace items, include `uris` as either a query parameter or in the request's body. + * Replacing items in a playlist will overwrite its existing items. This operation can be used for replacing or clearing items in a playlist. + *
+ * **Note**: Replace and reorder are mutually exclusive operations which share the same endpoint, but have different parameters. + * These operations can't be applied together in a single request. + */ + put: operations["reorder-or-replace-playlists-tracks"]; + /** + * Add Items to Playlist + * + * @description Add one or more items to a user's playlist. + */ + post: operations["add-tracks-to-playlist"]; + /** + * Remove Playlist Items + * + * @description Remove one or more items from a user's playlist. + */ + delete: operations["remove-tracks-playlist"]; + }; + "/me/playlists": { + /** + * Get Current User's Playlists + * + * @description Get a list of the playlists owned or followed by the current Spotify + * user. + */ + get: operations["get-a-list-of-current-users-playlists"]; + }; + "/me/albums": { + /** + * Get User's Saved Albums + * + * @description Get a list of the albums saved in the current Spotify user's 'Your Music' library. + */ + get: operations["get-users-saved-albums"]; + /** + * Save Albums for Current User + * + * @description Save one or more albums to the current user's 'Your Music' library. + */ + put: operations["save-albums-user"]; + /** + * Remove Users' Saved Albums + * + * @description Remove one or more albums from the current user's 'Your Music' library. + */ + delete: operations["remove-albums-user"]; + }; + "/me/albums/contains": { + /** + * Check User's Saved Albums + * + * @description Check if one or more albums is already saved in the current Spotify user's 'Your Music' library. + */ + get: operations["check-users-saved-albums"]; + }; + "/me/tracks": { + /** + * Get User's Saved Tracks + * + * @description Get a list of the songs saved in the current Spotify user's 'Your Music' library. + */ + get: operations["get-users-saved-tracks"]; + /** + * Save Tracks for Current User + * + * @description Save one or more tracks to the current user's 'Your Music' library. + */ + put: operations["save-tracks-user"]; + /** + * Remove User's Saved Tracks + * + * @description Remove one or more tracks from the current user's 'Your Music' library. + */ + delete: operations["remove-tracks-user"]; + }; + "/me/tracks/contains": { + /** + * Check User's Saved Tracks + * + * @description Check if one or more tracks is already saved in the current Spotify user's 'Your Music' library. + */ + get: operations["check-users-saved-tracks"]; + }; + "/me/episodes": { + /** + * Get User's Saved Episodes + * + * @description Get a list of the episodes saved in the current Spotify user's library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer). + */ + get: operations["get-users-saved-episodes"]; + /** + * Save Episodes for Current User + * + * @description Save one or more episodes to the current user's library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer). + */ + put: operations["save-episodes-user"]; + /** + * Remove User's Saved Episodes + * + * @description Remove one or more episodes from the current user's library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer). + */ + delete: operations["remove-episodes-user"]; + }; + "/me/episodes/contains": { + /** + * Check User's Saved Episodes + * + * @description Check if one or more episodes is already saved in the current Spotify user's 'Your Episodes' library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer).. + */ + get: operations["check-users-saved-episodes"]; + }; + "/me/shows": { + /** + * Get User's Saved Shows + * + * @description Get a list of shows saved in the current Spotify user's library. Optional parameters can be used to limit the number of shows returned. + */ + get: operations["get-users-saved-shows"]; + /** + * Save Shows for Current User + * + * @description Save one or more shows to current Spotify user's library. + */ + put: operations["save-shows-user"]; + /** + * Remove User's Saved Shows + * + * @description Delete one or more shows from current Spotify user's library. + */ + delete: operations["remove-shows-user"]; + }; + "/me/shows/contains": { + /** + * Check User's Saved Shows + * + * @description Check if one or more shows is already saved in the current Spotify user's library. + */ + get: operations["check-users-saved-shows"]; + }; + "/me/top/{type}": { + /** + * Get User's Top Items + * + * @description Get the current user's top artists or tracks based on calculated affinity. + */ + get: operations["get-users-top-artists-and-tracks"]; + }; + "/users/{user_id}": { + /** + * Get User's Profile + * + * @description Get public profile information about a Spotify user. + */ + get: operations["get-users-profile"]; + }; + "/users/{user_id}/playlists": { + /** + * Get User's Playlists + * + * @description Get a list of the playlists owned or followed by a Spotify user. + */ + get: operations["get-list-users-playlists"]; + /** + * Create Playlist + * + * @description Create a playlist for a Spotify user. (The playlist will be empty until + * you [add tracks](/documentation/web-api/reference/add-tracks-to-playlist).) + * Each user is generally limited to a maximum of 11000 playlists. + */ + post: operations["create-playlist"]; + }; + "/playlists/{playlist_id}/followers": { + /** + * Follow Playlist + * + * @description Add the current user as a follower of a playlist. + */ + put: operations["follow-playlist"]; + /** + * Unfollow Playlist + * + * @description Remove the current user as a follower of a playlist. + */ + delete: operations["unfollow-playlist"]; + }; + "/browse/featured-playlists": { + /** + * Get Featured Playlists + * + * @description Get a list of Spotify featured playlists (shown, for example, on a Spotify player's 'Browse' tab). + */ + get: operations["get-featured-playlists"]; + }; + "/browse/categories": { + /** + * Get Several Browse Categories + * + * @description Get a list of categories used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). + */ + get: operations["get-categories"]; + }; + "/browse/categories/{category_id}": { + /** + * Get Single Browse Category + * + * @description Get a single category used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). + */ + get: operations["get-a-category"]; + }; + "/browse/categories/{category_id}/playlists": { + /** + * Get Category's Playlists + * + * @description Get a list of Spotify playlists tagged with a particular category. + */ + get: operations["get-a-categories-playlists"]; + }; + "/playlists/{playlist_id}/images": { + /** + * Get Playlist Cover Image + * + * @description Get the current image associated with a specific playlist. + */ + get: operations["get-playlist-cover"]; + /** + * Add Custom Playlist Cover Image + * + * @description Replace the image used to represent a specific playlist. + */ + put: operations["upload-custom-playlist-cover"]; + }; + "/browse/new-releases": { + /** + * Get New Releases + * + * @description Get a list of new album releases featured in Spotify (shown, for example, on a Spotify player’s “Browse” tab). + */ + get: operations["get-new-releases"]; + }; + "/me/following": { + /** + * Get Followed Artists + * + * @description Get the current user's followed artists. + */ + get: operations["get-followed"]; + /** + * Follow Artists or Users + * + * @description Add the current user as a follower of one or more artists or other Spotify users. + */ + put: operations["follow-artists-users"]; + /** + * Unfollow Artists or Users + * + * @description Remove the current user as a follower of one or more artists or other Spotify users. + */ + delete: operations["unfollow-artists-users"]; + }; + "/me/following/contains": { + /** + * Check If User Follows Artists or Users + * + * @description Check to see if the current user is following one or more artists or other Spotify users. + */ + get: operations["check-current-user-follows"]; + }; + "/playlists/{playlist_id}/followers/contains": { + /** + * Check if Users Follow Playlist + * + * @description Check to see if one or more Spotify users are following a specified playlist. + */ + get: operations["check-if-user-follows-playlist"]; + }; + "/audio-features": { + /** + * Get Several Tracks' Audio Features + * + * @description Get audio features for multiple tracks based on their Spotify IDs. + */ + get: operations["get-several-audio-features"]; + }; + "/audio-features/{id}": { + /** + * Get Track's Audio Features + * + * @description Get audio feature information for a single track identified by its unique + * Spotify ID. + */ + get: operations["get-audio-features"]; + }; + "/audio-analysis/{id}": { + /** + * Get Track's Audio Analysis + * + * @description Get a low-level audio analysis for a track in the Spotify catalog. The audio analysis describes the track’s structure and musical content, including rhythm, pitch, and timbre. + */ + get: operations["get-audio-analysis"]; + }; + "/recommendations": { + /** + * Get Recommendations + * + * @description Recommendations are generated based on the available information for a given seed entity and matched against similar artists and tracks. If there is sufficient information about the provided seeds, a list of tracks will be returned together with pool size details. + * + * For artists and tracks that are very new or obscure there might not be enough data to generate a list of tracks. + */ + get: operations["get-recommendations"]; + }; + "/recommendations/available-genre-seeds": { + /** + * Get Available Genre Seeds + * + * @description Retrieve a list of available genres seed parameter values for [recommendations](/documentation/web-api/reference/get-recommendations). + */ + get: operations["get-recommendation-genres"]; + }; + "/me/player": { + /** + * Get Playback State + * + * @description Get information about the user’s current playback state, including track or episode, progress, and active device. + */ + get: operations["get-information-about-the-users-current-playback"]; + /** + * Transfer Playback + * + * @description Transfer playback to a new device and optionally begin playback. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["transfer-a-users-playback"]; + }; + "/me/player/devices": { + /** + * Get Available Devices + * + * @description Get information about a user’s available Spotify Connect devices. Some device models are not supported and will not be listed in the API response. + */ + get: operations["get-a-users-available-devices"]; + }; + "/me/player/currently-playing": { + /** + * Get Currently Playing Track + * + * @description Get the object currently being played on the user's Spotify account. + */ + get: operations["get-the-users-currently-playing-track"]; + }; + "/me/player/play": { + /** + * Start/Resume Playback + * + * @description Start a new context or resume current playback on the user's active device. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["start-a-users-playback"]; + }; + "/me/player/pause": { + /** + * Pause Playback + * + * @description Pause playback on the user's account. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["pause-a-users-playback"]; + }; + "/me/player/next": { + /** + * Skip To Next + * + * @description Skips to next track in the user’s queue. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + post: operations["skip-users-playback-to-next-track"]; + }; + "/me/player/previous": { + /** + * Skip To Previous + * + * @description Skips to previous track in the user’s queue. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + post: operations["skip-users-playback-to-previous-track"]; + }; + "/me/player/seek": { + /** + * Seek To Position + * + * @description Seeks to the given position in the user’s currently playing track. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["seek-to-position-in-currently-playing-track"]; + }; + "/me/player/repeat": { + /** + * Set Repeat Mode + * + * @description Set the repeat mode for the user's playback. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["set-repeat-mode-on-users-playback"]; + }; + "/me/player/volume": { + /** + * Set Playback Volume + * + * @description Set the volume for the user’s current playback device. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["set-volume-for-users-playback"]; + }; + "/me/player/shuffle": { + /** + * Toggle Playback Shuffle + * + * @description Toggle shuffle on or off for user’s playback. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + put: operations["toggle-shuffle-for-users-playback"]; + }; + "/me/player/recently-played": { + /** + * Get Recently Played Tracks + * + * @description Get tracks from the current user's recently played tracks. + * _**Note**: Currently doesn't support podcast episodes._ + */ + get: operations["get-recently-played"]; + }; + "/me/player/queue": { + /** + * Get the User's Queue + * + * @description Get the list of objects that make up the user's queue. + */ + get: operations["get-queue"]; + /** + * Add Item to Playback Queue + * + * @description Add an item to the end of the user's current playback queue. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + post: operations["add-to-queue"]; + }; + "/markets": { + /** + * Get Available Markets + * + * @description Get the list of markets where Spotify is available. + */ + get: operations["get-available-markets"]; + }; +} + +export type webhooks = Record; + +export interface components { + schemas: { + LinkedTrackObject: { + /** @description Known external URLs for this track. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the track. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the track. */ + id?: string; + /** @description The object type: "track". */ + type?: string; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the track. */ + uri?: string; + }; + TrackRestrictionObject: { + /** + * @description The reason for the restriction. Supported values: + * - `market` - The content item is not available in the given market. + * - `product` - The content item is not available for the user's subscription type. + * - `explicit` - The content item is explicit and the user's account is set to not play explicit content. + * + * Additional reasons may be added in the future. + * **Note**: If you use this field, make sure that your application safely handles unknown values. + */ + reason?: string; + }; + AlbumRestrictionObject: { + /** + * @description The reason for the restriction. Albums may be restricted if the content is not available in a given market, to the user's subscription type, or when the user's account is set to not play explicit content. + * Additional reasons may be added in the future. + * + * @enum {string} + */ + reason?: "market" | "product" | "explicit"; + }; + EpisodeRestrictionObject: { + /** + * @description The reason for the restriction. Supported values: + * - `market` - The content item is not available in the given market. + * - `product` - The content item is not available for the user's subscription type. + * - `explicit` - The content item is explicit and the user's account is set to not play explicit content. + * + * Additional reasons may be added in the future. + * **Note**: If you use this field, make sure that your application safely handles unknown values. + */ + reason?: string; + }; + ChapterRestrictionObject: { + /** + * @description The reason for the restriction. Supported values: + * - `market` - The content item is not available in the given market. + * - `product` - The content item is not available for the user's subscription type. + * - `explicit` - The content item is explicit and the user's account is set to not play explicit content. + * - `payment_required` - Payment is required to play the content item. + * + * Additional reasons may be added in the future. + * **Note**: If you use this field, make sure that your application safely handles unknown values. + */ + reason?: string; + }; + ArtistObject: { + type: "ArtistObject"; + /** @description Known external URLs for this artist. */ + external_urls?: { + type: "ArtistObject"; + } & components["schemas"]["ExternalUrlObject"]; + /** @description Information about the followers of the artist. */ + followers?: { + type: "ArtistObject"; + } & components["schemas"]["FollowersObject"]; + /** + * @description A list of the genres the artist is associated with. If not yet classified, the array is empty. + * + * @example [ + * "Prog rock", + * "Grunge" + * ] + */ + genres?: string[]; + /** @description A link to the Web API endpoint providing full details of the artist. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the artist. */ + id?: string; + /** @description Images of the artist in various sizes, widest first. */ + images?: components["schemas"]["ImageObject"][]; + /** @description The name of the artist. */ + name?: string; + /** @description The popularity of the artist. The value will be between 0 and 100, with 100 being the most popular. The artist's popularity is calculated from the popularity of all the artist's tracks. */ + popularity?: number; + /** + * @description The object type. + * + * @enum {string} + */ + type?: "artist"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the artist. */ + uri?: string; + }; + SimplifiedArtistObject: { + /** @description Known external URLs for this artist. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the artist. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the artist. */ + id?: string; + /** @description The name of the artist. */ + name?: string; + /** + * @description The object type. + * + * @enum {string} + */ + type?: "artist"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the artist. */ + uri?: string; + }; + PlayHistoryObject: { + /** @description The track the user listened to. */ + track?: components["schemas"]["TrackObject"]; + /** + * Format: date-time + * @description The date and time the track was played. + */ + played_at?: string; + /** @description The context the track was played from. */ + context?: components["schemas"]["ContextObject"]; + }; + PlaylistTrackObject: { + /** + * Format: date-time + * @description The date and time the track or episode was added. _**Note**: some very old playlists may return `null` in this field._ + */ + added_at?: string; + /** @description The Spotify user who added the track or episode. _**Note**: some very old playlists may return `null` in this field._ */ + added_by?: components["schemas"]["PlaylistUserObject"]; + /** @description Whether this track or episode is a [local file](/documentation/web-api/concepts/playlists/#local-files) or not. */ + is_local?: boolean; + /** @description Information about the track or episode. */ + track?: + | components["schemas"]["TrackObject"] + | components["schemas"]["EpisodeObject"]; + }; + QueueObject: { + /** @description The currently playing track or episode. Can be `null`. */ + currently_playing?: + | components["schemas"]["TrackObject"] + | components["schemas"]["EpisodeObject"]; + /** @description The tracks or episodes in the queue. Can be empty. */ + queue?: ( + | components["schemas"]["TrackObject"] + | components["schemas"]["EpisodeObject"] + )[]; + }; + CurrentlyPlayingContextObject: { + /** @description The device that is currently active. */ + device?: components["schemas"]["DeviceObject"]; + /** @description off, track, context */ + repeat_state?: string; + /** @description If shuffle is on or off. */ + shuffle_state?: boolean; + /** @description A Context Object. Can be `null`. */ + context?: components["schemas"]["ContextObject"]; + /** @description Unix Millisecond Timestamp when data was fetched. */ + timestamp?: number; + /** @description Progress into the currently playing track or episode. Can be `null`. */ + progress_ms?: number; + /** @description If something is currently playing, return `true`. */ + is_playing?: boolean; + /** @description The currently playing track or episode. Can be `null`. */ + item?: + | components["schemas"]["TrackObject"] + | components["schemas"]["EpisodeObject"]; + /** @description The object type of the currently playing item. Can be one of `track`, `episode`, `ad` or `unknown`. */ + currently_playing_type?: string; + /** @description Allows to update the user interface based on which playback actions are available within the current context. */ + actions?: components["schemas"]["DisallowsObject"]; + }; + DisallowsObject: { + /** @description Interrupting playback. Optional field. */ + interrupting_playback?: boolean; + /** @description Pausing. Optional field. */ + pausing?: boolean; + /** @description Resuming. Optional field. */ + resuming?: boolean; + /** @description Seeking playback location. Optional field. */ + seeking?: boolean; + /** @description Skipping to the next context. Optional field. */ + skipping_next?: boolean; + /** @description Skipping to the previous context. Optional field. */ + skipping_prev?: boolean; + /** @description Toggling repeat context flag. Optional field. */ + toggling_repeat_context?: boolean; + /** @description Toggling shuffle flag. Optional field. */ + toggling_shuffle?: boolean; + /** @description Toggling repeat track flag. Optional field. */ + toggling_repeat_track?: boolean; + /** @description Transfering playback between devices. Optional field. */ + transferring_playback?: boolean; + }; + ErrorObject: { + /** @description The HTTP status code (also returned in the response header; see [Response Status Codes](/documentation/web-api/concepts/api-calls#response-status-codes) for more information). */ + status: number; + /** @description A short description of the cause of the error. */ + message: string; + }; + PrivateUserObject: { + /** @description The country of the user, as set in the user's account profile. An [ISO 3166-1 alpha-2 country code](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). _This field is only available when the current user has granted access to the [user-read-private](/documentation/web-api/concepts/scopes/#list-of-scopes) scope._ */ + country?: string; + /** @description The name displayed on the user's profile. `null` if not available. */ + display_name?: string; + /** @description The user's email address, as entered by the user when creating their account. _**Important!** This email address is unverified; there is no proof that it actually belongs to the user._ _This field is only available when the current user has granted access to the [user-read-email](/documentation/web-api/concepts/scopes/#list-of-scopes) scope._ */ + email?: string; + /** @description The user's explicit content settings. _This field is only available when the current user has granted access to the [user-read-private](/documentation/web-api/concepts/scopes/#list-of-scopes) scope._ */ + explicit_content?: components["schemas"]["ExplicitContentSettingsObject"]; + /** @description Known external URLs for this user. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description Information about the followers of the user. */ + followers?: components["schemas"]["FollowersObject"]; + /** @description A link to the Web API endpoint for this user. */ + href?: string; + /** @description The [Spotify user ID](/documentation/web-api/concepts/spotify-uris-ids) for the user. */ + id?: string; + /** @description The user's profile image. */ + images?: components["schemas"]["ImageObject"][]; + /** @description The user's Spotify subscription level: "premium", "free", etc. (The subscription level "open" can be considered the same as "free".) _This field is only available when the current user has granted access to the [user-read-private](/documentation/web-api/concepts/scopes/#list-of-scopes) scope._ */ + product?: string; + /** @description The object type: "user" */ + type?: string; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the user. */ + uri?: string; + }; + PublicUserObject: { + /** @description The name displayed on the user's profile. `null` if not available. */ + display_name?: string | null; + /** @description Known public external URLs for this user. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description Information about the followers of this user. */ + followers?: components["schemas"]["FollowersObject"]; + /** @description A link to the Web API endpoint for this user. */ + href?: string; + /** @description The [Spotify user ID](/documentation/web-api/concepts/spotify-uris-ids) for this user. */ + id?: string; + /** @description The user's profile image. */ + images?: components["schemas"]["ImageObject"][]; + /** + * @description The object type. + * + * @enum {string} + */ + type?: "user"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for this user. */ + uri?: string; + }; + AudioAnalysisObject: { + meta?: { + /** + * @description The version of the Analyzer used to analyze this track. + * @example 4.0.0 + */ + analyzer_version?: string; + /** + * @description The platform used to read the track's audio data. + * @example Linux + */ + platform?: string; + /** + * @description A detailed status code for this track. If analysis data is missing, this code may explain why. + * @example OK + */ + detailed_status?: string; + /** + * @description The return code of the analyzer process. 0 if successful, 1 if any errors occurred. + * @example 0 + */ + status_code?: number; + /** + * @description The Unix timestamp (in seconds) at which this track was analyzed. + * @example 1495193577 + */ + timestamp?: number; + /** + * @description The amount of time taken to analyze this track. + * @example 6.93906 + */ + analysis_time?: number; + /** + * @description The method used to read the track's audio data. + * @example libvorbisfile L+R 44100->22050 + */ + input_process?: string; + }; + track?: { + /** + * @description The exact number of audio samples analyzed from this track. See also `analysis_sample_rate`. + * @example 4585515 + */ + num_samples?: number; + /** + * @description Length of the track in seconds. + * @example 207.95985 + */ + duration?: number; + /** @description This field will always contain the empty string. */ + sample_md5?: string; + /** + * @description An offset to the start of the region of the track that was analyzed. (As the entire track is analyzed, this should always be 0.) + * @example 0 + */ + offset_seconds?: number; + /** + * @description The length of the region of the track was analyzed, if a subset of the track was analyzed. (As the entire track is analyzed, this should always be 0.) + * @example 0 + */ + window_seconds?: number; + /** + * @description The sample rate used to decode and analyze this track. May differ from the actual sample rate of this track available on Spotify. + * @example 22050 + */ + analysis_sample_rate?: number; + /** + * @description The number of channels used for analysis. If 1, all channels are summed together to mono before analysis. + * @example 1 + */ + analysis_channels?: number; + /** + * @description The time, in seconds, at which the track's fade-in period ends. If the track has no fade-in, this will be 0.0. + * @example 0 + */ + end_of_fade_in?: number; + /** + * @description The time, in seconds, at which the track's fade-out period starts. If the track has no fade-out, this should match the track's length. + * @example 201.13705 + */ + start_of_fade_out?: number; + loudness?: components["schemas"]["Loudness"]; + tempo?: components["schemas"]["Tempo"]; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the `tempo`. + * @example 0.73 + */ + tempo_confidence?: number; + time_signature?: components["schemas"]["TimeSignature"]; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the `time_signature`. + * @example 0.994 + */ + time_signature_confidence?: number; + key?: components["schemas"]["Key"]; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the `key`. + * @example 0.408 + */ + key_confidence?: number; + mode?: components["schemas"]["Mode"]; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the `mode`. + * @example 0.485 + */ + mode_confidence?: number; + /** @description An [Echo Nest Musical Fingerprint (ENMFP)](https://academiccommons.columbia.edu/doi/10.7916/D8Q248M4) codestring for this track. */ + codestring?: string; + /** + * @description A version number for the Echo Nest Musical Fingerprint format used in the codestring field. + * @example 3.15 + */ + code_version?: number; + /** @description An [EchoPrint](https://github.com/spotify/echoprint-codegen) codestring for this track. */ + echoprintstring?: string; + /** + * @description A version number for the EchoPrint format used in the echoprintstring field. + * @example 4.15 + */ + echoprint_version?: number; + /** @description A [Synchstring](https://github.com/echonest/synchdata) for this track. */ + synchstring?: string; + /** + * @description A version number for the Synchstring used in the synchstring field. + * @example 1 + */ + synch_version?: number; + /** @description A Rhythmstring for this track. The format of this string is similar to the Synchstring. */ + rhythmstring?: string; + /** + * @description A version number for the Rhythmstring used in the rhythmstring field. + * @example 1 + */ + rhythm_version?: number; + }; + /** @description The time intervals of the bars throughout the track. A bar (or measure) is a segment of time defined as a given number of beats. */ + bars?: components["schemas"]["TimeIntervalObject"][]; + /** @description The time intervals of beats throughout the track. A beat is the basic time unit of a piece of music; for example, each tick of a metronome. Beats are typically multiples of tatums. */ + beats?: components["schemas"]["TimeIntervalObject"][]; + /** @description Sections are defined by large variations in rhythm or timbre, e.g. chorus, verse, bridge, guitar solo, etc. Each section contains its own descriptions of tempo, key, mode, time_signature, and loudness. */ + sections?: components["schemas"]["SectionObject"][]; + /** @description Each segment contains a roughly conisistent sound throughout its duration. */ + segments?: components["schemas"]["SegmentObject"][]; + /** @description A tatum represents the lowest regular pulse train that a listener intuitively infers from the timing of perceived musical events (segments). */ + tatums?: components["schemas"]["TimeIntervalObject"][]; + }; + TimeIntervalObject: { + /** + * @description The starting point (in seconds) of the time interval. + * @example 0.49567 + */ + start?: number; + /** + * @description The duration (in seconds) of the time interval. + * @example 2.18749 + */ + duration?: number; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the interval. + * @example 0.925 + */ + confidence?: number; + }; + SectionObject: { + /** + * @description The starting point (in seconds) of the section. + * @example 0 + */ + start?: number; + /** + * @description The duration (in seconds) of the section. + * @example 6.97092 + */ + duration?: number; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the section's "designation". + * @example 1 + */ + confidence?: number; + /** + * @description The overall loudness of the section in decibels (dB). Loudness values are useful for comparing relative loudness of sections within tracks. + * @example -14.938 + */ + loudness?: number; + /** + * @description The overall estimated tempo of the section in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a given piece and derives directly from the average beat duration. + * @example 113.178 + */ + tempo?: number; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the tempo. Some tracks contain tempo changes or sounds which don't contain tempo (like pure speech) which would correspond to a low value in this field. + * @example 0.647 + */ + tempo_confidence?: number; + /** + * @description The estimated overall key of the section. The values in this field ranging from 0 to 11 mapping to pitches using standard Pitch Class notation (E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on). If no key was detected, the value is -1. + * @example 9 + */ + key?: number; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the key. Songs with many key changes may correspond to low values in this field. + * @example 0.297 + */ + key_confidence?: number; + /** + * @description Indicates the modality (major or minor) of a section, the type of scale from which its melodic content is derived. This field will contain a 0 for "minor", a 1 for "major", or a -1 for no result. Note that the major key (e.g. C major) could more likely be confused with the minor key at 3 semitones lower (e.g. A minor) as both keys carry the same pitches. + * @enum {number} + */ + mode?: -1 | 0 | 1; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the `mode`. + * @example 0.471 + */ + mode_confidence?: number; + time_signature?: components["schemas"]["TimeSignature"]; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the `time_signature`. Sections with time signature changes may correspond to low values in this field. + * @example 1 + */ + time_signature_confidence?: number; + }; + SegmentObject: { + /** + * @description The starting point (in seconds) of the segment. + * @example 0.70154 + */ + start?: number; + /** + * @description The duration (in seconds) of the segment. + * @example 0.19891 + */ + duration?: number; + /** + * @description The confidence, from 0.0 to 1.0, of the reliability of the segmentation. Segments of the song which are difficult to logically segment (e.g: noise) may correspond to low values in this field. + * + * @example 0.435 + */ + confidence?: number; + /** + * @description The onset loudness of the segment in decibels (dB). Combined with `loudness_max` and `loudness_max_time`, these components can be used to describe the "attack" of the segment. + * @example -23.053 + */ + loudness_start?: number; + /** + * @description The peak loudness of the segment in decibels (dB). Combined with `loudness_start` and `loudness_max_time`, these components can be used to describe the "attack" of the segment. + * @example -14.25 + */ + loudness_max?: number; + /** + * @description The segment-relative offset of the segment peak loudness in seconds. Combined with `loudness_start` and `loudness_max`, these components can be used to desctibe the "attack" of the segment. + * @example 0.07305 + */ + loudness_max_time?: number; + /** + * @description The offset loudness of the segment in decibels (dB). This value should be equivalent to the loudness_start of the following segment. + * @example 0 + */ + loudness_end?: number; + /** + * @description Pitch content is given by a “chroma” vector, corresponding to the 12 pitch classes C, C#, D to B, with values ranging from 0 to 1 that describe the relative dominance of every pitch in the chromatic scale. For example a C Major chord would likely be represented by large values of C, E and G (i.e. classes 0, 4, and 7). + * + * Vectors are normalized to 1 by their strongest dimension, therefore noisy sounds are likely represented by values that are all close to 1, while pure tones are described by one value at 1 (the pitch) and others near 0. + * As can be seen below, the 12 vector indices are a combination of low-power spectrum values at their respective pitch frequencies. + * ![pitch vector](https://developer.spotify.com/assets/audio/Pitch_vector.png) + * + * @example [ + * 0.212, + * 0.141, + * 0.294 + * ] + */ + pitches?: number[]; + /** + * @description Timbre is the quality of a musical note or sound that distinguishes different types of musical instruments, or voices. It is a complex notion also referred to as sound color, texture, or tone quality, and is derived from the shape of a segment’s spectro-temporal surface, independently of pitch and loudness. The timbre feature is a vector that includes 12 unbounded values roughly centered around 0. Those values are high level abstractions of the spectral surface, ordered by degree of importance. + * + * For completeness however, the first dimension represents the average loudness of the segment; second emphasizes brightness; third is more closely correlated to the flatness of a sound; fourth to sounds with a stronger attack; etc. See an image below representing the 12 basis functions (i.e. template segments). + * ![timbre basis functions](https://developer.spotify.com/assets/audio/Timbre_basis_functions.png) + * + * The actual timbre of the segment is best described as a linear combination of these 12 basis functions weighted by the coefficient values: timbre = c1 x b1 + c2 x b2 + ... + c12 x b12, where c1 to c12 represent the 12 coefficients and b1 to b12 the 12 basis functions as displayed below. Timbre vectors are best used in comparison with each other. + * + * @example [ + * 42.115, + * 64.373, + * -0.233 + * ] + */ + timbre?: number[]; + }; + /** + * @description An estimated time signature. The time signature (meter) is a notational convention to specify how many beats are in each bar (or measure). The time signature ranges from 3 to 7 indicating time signatures of "3/4", to "7/4". + * @example 4 + */ + TimeSignature: number; + /** + * Format: float + * @description The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a given piece and derives directly from the average beat duration. + * + * @example 118.211 + */ + Tempo: number; + /** + * Format: float + * @description The overall loudness of a track in decibels (dB). Loudness values are averaged across the entire track and are useful for comparing relative loudness of tracks. Loudness is the quality of a sound that is the primary psychological correlate of physical strength (amplitude). Values typically range between -60 and 0 db. + * + * @example -5.883 + */ + Loudness: number; + /** + * @description The key the track is in. Integers map to pitches using standard [Pitch Class notation](https://en.wikipedia.org/wiki/Pitch_class). E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on. If no key was detected, the value is -1. + * + * @example 9 + */ + Key: number; + /** + * @description Mode indicates the modality (major or minor) of a track, the type of scale from which its melodic content is derived. Major is represented by 1 and minor is 0. + * + * @example 0 + */ + Mode: number; + AudioFeaturesObject: { + /** + * Format: float + * @description A confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 represents high confidence the track is acoustic. + * + * @example 0.00242 + */ + acousticness?: number; + /** + * @description A URL to access the full audio analysis of this track. An access token is required to access this data. + * + * @example https://api.spotify.com/v1/audio-analysis/2takcwOaAZWiXQijPHIx7B + */ + analysis_url?: string; + /** + * Format: float + * @description Danceability describes how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is least danceable and 1.0 is most danceable. + * + * @example 0.585 + */ + danceability?: number; + /** + * @description The duration of the track in milliseconds. + * + * @example 237040 + */ + duration_ms?: number; + /** + * Format: float + * @description Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity. Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy, while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, and general entropy. + * + * @example 0.842 + */ + energy?: number; + /** + * @description The Spotify ID for the track. + * + * @example 2takcwOaAZWiXQijPHIx7B + */ + id?: string; + /** + * Format: float + * @description Predicts whether a track contains no vocals. "Ooh" and "aah" sounds are treated as instrumental in this context. Rap or spoken word tracks are clearly "vocal". The closer the instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the value approaches 1.0. + * + * @example 0.00686 + */ + instrumentalness?: number; + key?: components["schemas"]["Key"]; + /** + * Format: float + * @description Detects the presence of an audience in the recording. Higher liveness values represent an increased probability that the track was performed live. A value above 0.8 provides strong likelihood that the track is live. + * + * @example 0.0866 + */ + liveness?: number; + loudness?: components["schemas"]["Loudness"]; + mode?: components["schemas"]["Mode"]; + /** + * Format: float + * @description Speechiness detects the presence of spoken words in a track. The more exclusively speech-like the recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values above 0.66 describe tracks that are probably made entirely of spoken words. Values between 0.33 and 0.66 describe tracks that may contain both music and speech, either in sections or layered, including such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like tracks. + * + * @example 0.0556 + */ + speechiness?: number; + tempo?: components["schemas"]["Tempo"]; + time_signature?: components["schemas"]["TimeSignature"]; + /** + * @description A link to the Web API endpoint providing full details of the track. + * + * @example https://api.spotify.com/v1/tracks/2takcwOaAZWiXQijPHIx7B + */ + track_href?: string; + /** + * @description The object type. + * + * @enum {string} + */ + type?: "audio_features"; + /** + * @description The Spotify URI for the track. + * + * @example spotify:track:2takcwOaAZWiXQijPHIx7B + */ + uri?: string; + /** + * Format: float + * @description A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry). + * + * @example 0.428 + */ + valence?: number; + }; + SimplifiedTrackObject: { + /** @description The artists who performed the track. Each artist object includes a link in `href` to more detailed information about the artist. */ + artists?: components["schemas"]["SimplifiedArtistObject"][]; + /** @description A list of the countries in which the track can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. */ + available_markets?: string[]; + /** @description The disc number (usually `1` unless the album consists of more than one disc). */ + disc_number?: number; + /** @description The track length in milliseconds. */ + duration_ms?: number; + /** @description Whether or not the track has explicit lyrics ( `true` = yes it does; `false` = no it does not OR unknown). */ + explicit?: boolean; + /** @description External URLs for this track. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the track. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the track. */ + id?: string; + /** @description Part of the response when [Track Relinking](/documentation/web-api/concepts/track-relinking/) is applied. If `true`, the track is playable in the given market. Otherwise `false`. */ + is_playable?: boolean; + /** @description Part of the response when [Track Relinking](/documentation/web-api/concepts/track-relinking/) is applied and is only part of the response if the track linking, in fact, exists. The requested track has been replaced with a different track. The track in the `linked_from` object contains information about the originally requested track. */ + linked_from?: components["schemas"]["LinkedTrackObject"]; + /** @description Included in the response when a content restriction is applied. */ + restrictions?: components["schemas"]["TrackRestrictionObject"]; + /** @description The name of the track. */ + name?: string; + /** @description A URL to a 30 second preview (MP3 format) of the track. */ + preview_url?: string | null; + /** @description The number of the track. If an album has several discs, the track number is the number on the specified disc. */ + track_number?: number; + /** @description The object type: "track". */ + type?: string; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the track. */ + uri?: string; + /** @description Whether or not the track is from a local file. */ + is_local?: boolean; + }; + DeviceObject: { + /** @description The device ID. This ID is unique and persistent to some extent. However, this is not guaranteed and any cached `device_id` should periodically be cleared out and refetched as necessary. */ + id?: string | null; + /** @description If this device is the currently active device. */ + is_active?: boolean; + /** @description If this device is currently in a private session. */ + is_private_session?: boolean; + /** @description Whether controlling this device is restricted. At present if this is "true" then no Web API commands will be accepted by this device. */ + is_restricted?: boolean; + /** + * @description A human-readable name for the device. Some devices have a name that the user can configure (e.g. \"Loudest speaker\") and some devices have a generic name associated with the manufacturer or device model. + * @example Kitchen speaker + */ + name?: string; + /** + * @description Device type, such as "computer", "smartphone" or "speaker". + * @example computer + */ + type?: string; + /** + * @description The current volume in percent. + * @example 59 + */ + volume_percent?: number | null; + /** @description If this device can be used to set the volume. */ + supports_volume?: boolean; + }; + CursorObject: { + /** @description The cursor to use as key to find the next page of items. */ + after?: string; + /** @description The cursor to use as key to find the previous page of items. */ + before?: string; + }; + CursorPagingObject: { + /** @description A link to the Web API endpoint returning the full result of the request. */ + href?: string; + /** @description The maximum number of items in the response (as set in the query or by default). */ + limit?: number; + /** @description URL to the next page of items. ( `null` if none) */ + next?: string; + /** @description The cursors used to find the next set of items. */ + cursors?: components["schemas"]["CursorObject"]; + /** @description The total number of items available to return. */ + total?: number; + }; + CursorPagingPlayHistoryObject: + & components["schemas"]["CursorPagingObject"] + & { + items?: components["schemas"]["PlayHistoryObject"][]; + }; + CursorPagingSimplifiedArtistObject: + & components["schemas"]["CursorPagingObject"] + & { + items?: components["schemas"]["ArtistObject"][]; + }; + PagingObject: { + /** + * @description A link to the Web API endpoint returning the full result of the request + * + * @example https://api.spotify.com/v1/me/shows?offset=0&limit=20 + */ + href: string; + /** + * @description The maximum number of items in the response (as set in the query or by default). + * + * @example 20 + */ + limit: number; + /** + * @description URL to the next page of items. ( `null` if none) + * + * @example https://api.spotify.com/v1/me/shows?offset=1&limit=1 + */ + next: string | null; + /** + * @description The offset of the items returned (as set in the query or by default) + * + * @example 0 + */ + offset: number; + /** + * @description URL to the previous page of items. ( `null` if none) + * + * @example https://api.spotify.com/v1/me/shows?offset=1&limit=1 + */ + previous: string | null; + /** + * @description The total number of items available to return. + * + * @example 4 + */ + total: number; + }; + PagingPlaylistObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedPlaylistObject"][]; + }; + PagingFeaturedPlaylistObject: { + /** + * @description The localized message of a playlist. + * + * @example Popular Playlists + */ + message?: string; + playlists?: components["schemas"]["PagingPlaylistObject"]; + }; + PagingArtistDiscographyAlbumObject: + & components["schemas"]["PagingObject"] + & { + items?: components["schemas"]["ArtistDiscographyAlbumObject"][]; + }; + PagingSimplifiedAlbumObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedAlbumObject"][]; + }; + PagingSavedAlbumObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SavedAlbumObject"][]; + }; + PagingSimplifiedTrackObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedTrackObject"][]; + }; + PagingSavedTrackObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SavedTrackObject"][]; + }; + PagingTrackObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["TrackObject"][]; + }; + PagingPlaylistTrackObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["PlaylistTrackObject"][]; + }; + PagingSimplifiedShowObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedShowObject"][]; + }; + PagingSavedShowObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SavedShowObject"][]; + }; + PagingSimplifiedEpisodeObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedEpisodeObject"][]; + }; + PagingSavedEpisodeObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SavedEpisodeObject"][]; + }; + PagingSimplifiedAudiobookObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedAudiobookObject"][]; + }; + PagingArtistObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["ArtistObject"][]; + }; + PagingSimplifiedChapterObject: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["SimplifiedChapterObject"][]; + }; + RecommendationsObject: { + /** @description An array of recommendation seed objects. */ + seeds: components["schemas"]["RecommendationSeedObject"][]; + /** @description An array of track object (simplified) ordered according to the parameters supplied. */ + tracks: components["schemas"]["TrackObject"][]; + }; + RecommendationSeedObject: { + /** @description The number of tracks available after min\_\* and max\_\* filters have been applied. */ + afterFilteringSize?: number; + /** @description The number of tracks available after relinking for regional availability. */ + afterRelinkingSize?: number; + /** @description A link to the full track or artist data for this seed. For tracks this will be a link to a Track Object. For artists a link to an Artist Object. For genre seeds, this value will be `null`. */ + href?: string; + /** @description The id used to select this seed. This will be the same as the string used in the `seed_artists`, `seed_tracks` or `seed_genres` parameter. */ + id?: string; + /** @description The number of recommended tracks available for this seed. */ + initialPoolSize?: number; + /** @description The entity type of this seed. One of `artist`, `track` or `genre`. */ + type?: string; + }; + SavedAlbumObject: { + /** + * Format: date-time + * @description The date and time the album was saved + * Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. + * If the time is imprecise (for example, the date/time of an album release), an additional field indicates the precision; see for example, release_date in an album object. + */ + added_at?: string; + /** @description Information about the album. */ + album?: components["schemas"]["AlbumObject"]; + }; + SavedTrackObject: { + /** + * Format: date-time + * @description The date and time the track was saved. + * Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. + * If the time is imprecise (for example, the date/time of an album release), an additional field indicates the precision; see for example, release_date in an album object. + */ + added_at?: string; + /** @description Information about the track. */ + track?: components["schemas"]["TrackObject"]; + }; + SavedEpisodeObject: { + /** + * Format: date-time + * @description The date and time the episode was saved. + * Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. + */ + added_at?: string; + /** @description Information about the episode. */ + episode?: components["schemas"]["EpisodeObject"]; + }; + SavedShowObject: { + /** + * Format: date-time + * @description The date and time the show was saved. + * Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. + * If the time is imprecise (for example, the date/time of an album release), an additional field indicates the precision; see for example, release_date in an album object. + */ + added_at?: string; + /** @description Information about the show. */ + show?: components["schemas"]["SimplifiedShowObject"]; + }; + PlaylistObject: { + /** @description `true` if the owner allows other users to modify the playlist. */ + collaborative?: boolean; + /** @description The playlist description. _Only returned for modified, verified playlists, otherwise_ `null`. */ + description?: string | null; + /** @description Known external URLs for this playlist. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description Information about the followers of the playlist. */ + followers?: components["schemas"]["FollowersObject"]; + /** @description A link to the Web API endpoint providing full details of the playlist. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the playlist. */ + id?: string; + /** @description Images for the playlist. The array may be empty or contain up to three images. The images are returned by size in descending order. See [Working with Playlists](/documentation/web-api/concepts/playlists). _**Note**: If returned, the source URL for the image (`url`) is temporary and will expire in less than a day._ */ + images?: components["schemas"]["ImageObject"][]; + /** @description The name of the playlist. */ + name?: string; + /** @description The user who owns the playlist */ + owner?: components["schemas"]["PlaylistOwnerObject"]; + /** @description The playlist's public/private status: `true` the playlist is public, `false` the playlist is private, `null` the playlist status is not relevant. For more about public/private status, see [Working with Playlists](/documentation/web-api/concepts/playlists) */ + public?: boolean; + /** @description The version identifier for the current playlist. Can be supplied in other requests to target a specific playlist version */ + snapshot_id?: string; + /** @description The tracks of the playlist. */ + tracks?: components["schemas"]["PagingPlaylistTrackObject"]; + /** @description The object type: "playlist" */ + type?: string; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the playlist. */ + uri?: string; + }; + SimplifiedPlaylistObject: { + /** @description `true` if the owner allows other users to modify the playlist. */ + collaborative?: boolean; + /** @description The playlist description. _Only returned for modified, verified playlists, otherwise_ `null`. */ + description?: string; + /** @description Known external URLs for this playlist. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the playlist. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the playlist. */ + id?: string; + /** @description Images for the playlist. The array may be empty or contain up to three images. The images are returned by size in descending order. See [Working with Playlists](/documentation/web-api/concepts/playlists). _**Note**: If returned, the source URL for the image (`url`) is temporary and will expire in less than a day._ */ + images?: components["schemas"]["ImageObject"][]; + /** @description The name of the playlist. */ + name?: string; + /** @description The user who owns the playlist */ + owner?: components["schemas"]["PlaylistOwnerObject"]; + /** @description The playlist's public/private status: `true` the playlist is public, `false` the playlist is private, `null` the playlist status is not relevant. For more about public/private status, see [Working with Playlists](/documentation/web-api/concepts/playlists) */ + public?: boolean; + /** @description The version identifier for the current playlist. Can be supplied in other requests to target a specific playlist version */ + snapshot_id?: string; + /** @description A collection containing a link ( `href` ) to the Web API endpoint where full details of the playlist's tracks can be retrieved, along with the `total` number of tracks in the playlist. Note, a track object may be `null`. This can happen if a track is no longer available. */ + tracks?: components["schemas"]["PlaylistTracksRefObject"]; + /** @description The object type: "playlist" */ + type?: string; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the playlist. */ + uri?: string; + }; + PlaylistTracksRefObject: { + /** @description A link to the Web API endpoint where full details of the playlist's tracks can be retrieved. */ + href?: string; + /** @description Number of tracks in the playlist. */ + total?: number; + }; + PlaylistUserObject: { + /** @description Known public external URLs for this user. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description Information about the followers of this user. */ + followers?: components["schemas"]["FollowersObject"]; + /** @description A link to the Web API endpoint for this user. */ + href?: string; + /** @description The [Spotify user ID](/documentation/web-api/concepts/spotify-uris-ids) for this user. */ + id?: string; + /** + * @description The object type. + * + * @enum {string} + */ + type?: "user"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for this user. */ + uri?: string; + }; + PlaylistOwnerObject: + & components["schemas"]["PlaylistUserObject"] + & ({ + /** @description The name displayed on the user's profile. `null` if not available. */ + display_name?: string | null; + }); + CategoryObject: { + /** @description A link to the Web API endpoint returning full details of the category. */ + href: string; + /** @description The category icon, in various sizes. */ + icons: components["schemas"]["ImageObject"][]; + /** + * @description The [Spotify category ID](/documentation/web-api/concepts/spotify-uris-ids) of the category. + * + * @example equal + */ + id: string; + /** + * @description The name of the category. + * + * @example EQUAL + */ + name: string; + }; + TrackObject: { + type: "TrackObject"; + /** @description The album on which the track appears. The album object includes a link in `href` to full information about the album. */ + album?: { + type: "TrackObject"; + } & components["schemas"]["SimplifiedAlbumObject"]; + /** @description The artists who performed the track. Each artist object includes a link in `href` to more detailed information about the artist. */ + artists?: components["schemas"]["ArtistObject"][]; + /** @description A list of the countries in which the track can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. */ + available_markets?: string[]; + /** @description The disc number (usually `1` unless the album consists of more than one disc). */ + disc_number?: number; + /** @description The track length in milliseconds. */ + duration_ms?: number; + /** @description Whether or not the track has explicit lyrics ( `true` = yes it does; `false` = no it does not OR unknown). */ + explicit?: boolean; + /** @description Known external IDs for the track. */ + external_ids?: { + type: "TrackObject"; + } & components["schemas"]["ExternalIdObject"]; + /** @description Known external URLs for this track. */ + external_urls?: { + type: "TrackObject"; + } & components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the track. */ + href?: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the track. */ + id?: string; + /** @description Part of the response when [Track Relinking](/documentation/web-api/concepts/track-relinking) is applied. If `true`, the track is playable in the given market. Otherwise `false`. */ + is_playable?: boolean; + /** @description Part of the response when [Track Relinking](/documentation/web-api/concepts/track-relinking) is applied, and the requested track has been replaced with different track. The track in the `linked_from` object contains information about the originally requested track. */ + linked_from?: { + type: "TrackObject"; + }; + /** @description Included in the response when a content restriction is applied. */ + restrictions?: { + type: "TrackObject"; + } & components["schemas"]["TrackRestrictionObject"]; + /** @description The name of the track. */ + name?: string; + /** @description The popularity of the track. The value will be between 0 and 100, with 100 being the most popular.
The popularity of a track is a value between 0 and 100, with 100 being the most popular. The popularity is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how recent those plays are.
Generally speaking, songs that are being played a lot now will have a higher popularity than songs that were played a lot in the past. Duplicate tracks (e.g. the same track from a single and an album) are rated independently. Artist and album popularity is derived mathematically from track popularity. _**Note**: the popularity value may lag actual popularity by a few days: the value is not updated in real time._ */ + popularity?: number; + /** @description A link to a 30 second preview (MP3 format) of the track. Can be `null` */ + preview_url?: string | null; + /** @description The number of the track. If an album has several discs, the track number is the number on the specified disc. */ + track_number?: number; + /** + * @description The object type: "track". + * + * @enum {string} + */ + type?: "track"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the track. */ + uri?: string; + /** @description Whether or not the track is from a local file. */ + is_local?: boolean; + }; + EpisodeObject: + & { + type: "EpisodeObject"; + } + & components["schemas"]["EpisodeBase"] + & ({ + type: "EpisodeObject"; + /** @description The show on which the episode belongs. */ + show: { + type: "EpisodeObject"; + } & components["schemas"]["SimplifiedShowObject"]; + }); + SimplifiedEpisodeObject: + & components["schemas"]["EpisodeBase"] + & Record; + EpisodeBase: { + /** + * @description A URL to a 30 second preview (MP3 format) of the episode. `null` if not available. + * + * @example https://p.scdn.co/mp3-preview/2f37da1d4221f40b9d1a98cd191f4d6f1646ad17 + */ + audio_preview_url: string | null; + /** + * @description A description of the episode. HTML tags are stripped away from this field, use `html_description` field in case HTML tags are needed. + * + * @example A Spotify podcast sharing fresh insights on important topics of the moment—in a way only Spotify can. You’ll hear from experts in the music, podcast and tech industries as we discover and uncover stories about our work and the world around us. + */ + description: string; + /** + * @description A description of the episode. This field may contain HTML tags. + * + * @example

A Spotify podcast sharing fresh insights on important topics of the moment—in a way only Spotify can. You’ll hear from experts in the music, podcast and tech industries as we discover and uncover stories about our work and the world around us.

+ */ + html_description: string; + /** + * @description The episode length in milliseconds. + * + * @example 1686230 + */ + duration_ms: number; + /** @description Whether or not the episode has explicit content (true = yes it does; false = no it does not OR unknown). */ + explicit: boolean; + /** @description External URLs for this episode. */ + external_urls: components["schemas"]["ExternalUrlObject"]; + /** + * @description A link to the Web API endpoint providing full details of the episode. + * + * @example https://api.spotify.com/v1/episodes/5Xt5DXGzch68nYYamXrNxZ + */ + href: string; + /** + * @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the episode. + * + * @example 5Xt5DXGzch68nYYamXrNxZ + */ + id: string; + /** @description The cover art for the episode in various sizes, widest first. */ + images: components["schemas"]["ImageObject"][]; + /** @description True if the episode is hosted outside of Spotify's CDN. */ + is_externally_hosted: boolean; + /** @description True if the episode is playable in the given market. Otherwise false. */ + is_playable: boolean; + /** + * @deprecated + * @description The language used in the episode, identified by a [ISO 639](https://en.wikipedia.org/wiki/ISO_639) code. This field is deprecated and might be removed in the future. Please use the `languages` field instead. + * + * @example en + */ + language?: string; + /** + * @description A list of the languages used in the episode, identified by their [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639) code. + * + * @example [ + * "fr", + * "en" + * ] + */ + languages: string[]; + /** + * @description The name of the episode. + * + * @example Starting Your Own Podcast: Tips, Tricks, and Advice From Anchor Creators + */ + name: string; + /** + * @description The date the episode was first released, for example `"1981-12-15"`. Depending on the precision, it might be shown as `"1981"` or `"1981-12"`. + * + * @example 1981-12-15 + */ + release_date: string; + /** + * @description The precision with which `release_date` value is known. + * + * @example day + * @enum {string} + */ + release_date_precision: "year" | "month" | "day"; + /** @description The user's most recent position in the episode. Set if the supplied access token is a user token and has the scope 'user-read-playback-position'. */ + resume_point: components["schemas"]["ResumePointObject"]; + /** + * @description The object type. + * + * @enum {string} + */ + type: "episode"; + /** + * @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the episode. + * + * @example spotify:episode:0zLhl3WsOCQHbe1BPTiHgr + */ + uri: string; + /** @description Included in the response when a content restriction is applied. */ + restrictions?: components["schemas"]["EpisodeRestrictionObject"]; + }; + ResumePointObject: { + /** @description Whether or not the episode has been fully played by the user. */ + fully_played?: boolean; + /** @description The user's most recent position in the episode in milliseconds. */ + resume_position_ms?: number; + }; + ShowBase: { + /** @description A list of the countries in which the show can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. */ + available_markets: string[]; + /** @description The copyright statements of the show. */ + copyrights: components["schemas"]["CopyrightObject"][]; + /** @description A description of the show. HTML tags are stripped away from this field, use `html_description` field in case HTML tags are needed. */ + description: string; + /** @description A description of the show. This field may contain HTML tags. */ + html_description: string; + /** @description Whether or not the show has explicit content (true = yes it does; false = no it does not OR unknown). */ + explicit: boolean; + /** @description External URLs for this show. */ + external_urls: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the show. */ + href: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the show. */ + id: string; + /** @description The cover art for the show in various sizes, widest first. */ + images: components["schemas"]["ImageObject"][]; + /** @description True if all of the shows episodes are hosted outside of Spotify's CDN. This field might be `null` in some cases. */ + is_externally_hosted: boolean; + /** @description A list of the languages used in the show, identified by their [ISO 639](https://en.wikipedia.org/wiki/ISO_639) code. */ + languages: string[]; + /** @description The media type of the show. */ + media_type: string; + /** @description The name of the episode. */ + name: string; + /** @description The publisher of the show. */ + publisher: string; + /** + * @description The object type. + * + * @enum {string} + */ + type: "show"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the show. */ + uri: string; + /** @description The total number of episodes in the show. */ + total_episodes: number; + }; + ShowObject: components["schemas"]["ShowBase"] & { + /** @description The episodes of the show. */ + episodes: components["schemas"]["PagingSimplifiedEpisodeObject"]; + }; + SimplifiedShowObject: + & components["schemas"]["ShowBase"] + & Record; + AudiobookBase: { + /** @description The author(s) for the audiobook. */ + authors: components["schemas"]["AuthorObject"][]; + /** @description A list of the countries in which the audiobook can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. */ + available_markets: string[]; + /** @description The copyright statements of the audiobook. */ + copyrights: components["schemas"]["CopyrightObject"][]; + /** @description A description of the audiobook. HTML tags are stripped away from this field, use `html_description` field in case HTML tags are needed. */ + description: string; + /** @description A description of the audiobook. This field may contain HTML tags. */ + html_description: string; + /** + * @description The edition of the audiobook. + * + * @example Unabridged + */ + edition?: string; + /** @description Whether or not the audiobook has explicit content (true = yes it does; false = no it does not OR unknown). */ + explicit: boolean; + /** @description External URLs for this audiobook. */ + external_urls: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the audiobook. */ + href: string; + /** @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the audiobook. */ + id: string; + /** @description The cover art for the audiobook in various sizes, widest first. */ + images: components["schemas"]["ImageObject"][]; + /** @description A list of the languages used in the audiobook, identified by their [ISO 639](https://en.wikipedia.org/wiki/ISO_639) code. */ + languages: string[]; + /** @description The media type of the audiobook. */ + media_type: string; + /** @description The name of the audiobook. */ + name: string; + /** @description The narrator(s) for the audiobook. */ + narrators: components["schemas"]["NarratorObject"][]; + /** @description The publisher of the audiobook. */ + publisher: string; + /** + * @description The object type. + * + * @enum {string} + */ + type: "audiobook"; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the audiobook. */ + uri: string; + /** @description The number of chapters in this audiobook. */ + total_chapters: number; + }; + AudiobookObject: components["schemas"]["AudiobookBase"] & { + /** @description The chapters of the audiobook. */ + chapters: components["schemas"]["PagingSimplifiedChapterObject"]; + }; + SimplifiedAudiobookObject: + & components["schemas"]["AudiobookBase"] + & Record; + AlbumBase: { + /** + * @description The type of the album. + * + * @example compilation + * @enum {string} + */ + album_type: "album" | "single" | "compilation"; + /** + * @description The number of tracks in the album. + * @example 9 + */ + total_tracks: number; + /** + * @description The markets in which the album is available: [ISO 3166-1 alpha-2 country codes](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). _**NOTE**: an album is considered available in a market when at least 1 of its tracks is available in that market._ + * + * @example [ + * "CA", + * "BR", + * "IT" + * ] + */ + available_markets: string[]; + /** @description Known external URLs for this album. */ + external_urls: components["schemas"]["ExternalUrlObject"]; + /** @description A link to the Web API endpoint providing full details of the album. */ + href: string; + /** + * @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the album. + * + * @example 2up3OPMp9Tb4dAKM2erWXQ + */ + id: string; + /** @description The cover art for the album in various sizes, widest first. */ + images: components["schemas"]["ImageObject"][]; + /** @description The name of the album. In case of an album takedown, the value may be an empty string. */ + name: string; + /** + * @description The date the album was first released. + * + * @example 1981-12 + */ + release_date: string; + /** + * @description The precision with which `release_date` value is known. + * + * @example year + * @enum {string} + */ + release_date_precision: "year" | "month" | "day"; + /** @description Included in the response when a content restriction is applied. */ + restrictions?: components["schemas"]["AlbumRestrictionObject"]; + /** + * @description The object type. + * + * @enum {string} + */ + type: "album"; + /** + * @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the album. + * + * @example spotify:album:2up3OPMp9Tb4dAKM2erWXQ + */ + uri: string; + }; + SimplifiedAlbumObject: components["schemas"]["AlbumBase"] & { + /** @description The artists of the album. Each artist object includes a link in `href` to more detailed information about the artist. */ + artists: components["schemas"]["SimplifiedArtistObject"][]; + }; + ArtistDiscographyAlbumObject: + & components["schemas"]["SimplifiedAlbumObject"] + & ({ + /** + * @description This field describes the relationship between the artist and the album. + * + * @example compilation + * @enum {string} + */ + album_group: "album" | "single" | "compilation" | "appears_on"; + }); + ChapterObject: components["schemas"]["ChapterBase"] & { + /** @description The audiobook for which the chapter belongs. */ + audiobook: components["schemas"]["SimplifiedAudiobookObject"]; + }; + SimplifiedChapterObject: + & components["schemas"]["ChapterBase"] + & Record; + ChapterBase: { + /** + * @description A URL to a 30 second preview (MP3 format) of the chapter. `null` if not available. + * + * @example https://p.scdn.co/mp3-preview/2f37da1d4221f40b9d1a98cd191f4d6f1646ad17 + */ + audio_preview_url: string | null; + /** @description A list of the countries in which the chapter can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. */ + available_markets?: string[]; + /** + * @description The number of the chapter + * + * @example 1 + */ + chapter_number: number; + /** + * @description A description of the chapter. HTML tags are stripped away from this field, use `html_description` field in case HTML tags are needed. + * + * @example We kept on ascending, with occasional periods of quick descent, but in the main always ascending. Suddenly, I became conscious of the fact that the driver was in the act of pulling up the horses in the courtyard of a vast ruined castle, from whose tall black windows came no ray of light, and whose broken battlements showed a jagged line against the moonlit sky. + */ + description: string; + /** + * @description A description of the chapter. This field may contain HTML tags. + * + * @example

We kept on ascending, with occasional periods of quick descent, but in the main always ascending. Suddenly, I became conscious of the fact that the driver was in the act of pulling up the horses in the courtyard of a vast ruined castle, from whose tall black windows came no ray of light, and whose broken battlements showed a jagged line against the moonlit sky.

+ */ + html_description: string; + /** + * @description The chapter length in milliseconds. + * + * @example 1686230 + */ + duration_ms: number; + /** @description Whether or not the chapter has explicit content (true = yes it does; false = no it does not OR unknown). */ + explicit: boolean; + /** @description External URLs for this chapter. */ + external_urls: components["schemas"]["ExternalUrlObject"]; + /** + * @description A link to the Web API endpoint providing full details of the chapter. + * + * @example https://api.spotify.com/v1/episodes/5Xt5DXGzch68nYYamXrNxZ + */ + href: string; + /** + * @description The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the chapter. + * + * @example 5Xt5DXGzch68nYYamXrNxZ + */ + id: string; + /** @description The cover art for the chapter in various sizes, widest first. */ + images: components["schemas"]["ImageObject"][]; + /** @description True if the chapter is playable in the given market. Otherwise false. */ + is_playable: boolean; + /** + * @description A list of the languages used in the chapter, identified by their [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639) code. + * + * @example [ + * "fr", + * "en" + * ] + */ + languages: string[]; + /** + * @description The name of the chapter. + * + * @example Starting Your Own Podcast: Tips, Tricks, and Advice From Anchor Creators + */ + name: string; + /** + * @description The date the chapter was first released, for example `"1981-12-15"`. Depending on the precision, it might be shown as `"1981"` or `"1981-12"`. + * + * @example 1981-12-15 + */ + release_date: string; + /** + * @description The precision with which `release_date` value is known. + * + * @example day + * @enum {string} + */ + release_date_precision: "year" | "month" | "day"; + /** @description The user's most recent position in the chapter. Set if the supplied access token is a user token and has the scope 'user-read-playback-position'. */ + resume_point: components["schemas"]["ResumePointObject"]; + /** + * @description The object type. + * + * @enum {string} + */ + type: "episode"; + /** + * @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the chapter. + * + * @example spotify:episode:0zLhl3WsOCQHbe1BPTiHgr + */ + uri: string; + /** @description Included in the response when a content restriction is applied. */ + restrictions?: components["schemas"]["ChapterRestrictionObject"]; + }; + AlbumObject: WithRequired< + components["schemas"]["AlbumBase"] & { + /** @description The artists of the album. Each artist object includes a link in `href` to more detailed information about the artist. */ + artists?: components["schemas"]["SimplifiedArtistObject"][]; + /** @description The tracks of the album. */ + tracks?: components["schemas"]["PagingSimplifiedTrackObject"]; + /** @description The copyright statements of the album. */ + copyrights?: components["schemas"]["CopyrightObject"][]; + /** @description Known external IDs for the album. */ + external_ids?: components["schemas"]["ExternalIdObject"]; + /** + * @description A list of the genres the album is associated with. If not yet classified, the array is empty. + * + * @example [ + * "Egg punk", + * "Noise rock" + * ] + */ + genres?: string[]; + /** @description The label associated with the album. */ + label?: string; + /** @description The popularity of the album. The value will be between 0 and 100, with 100 being the most popular. */ + popularity?: number; + }, + | "artists" + | "tracks" + | "copyrights" + | "external_ids" + | "genres" + | "label" + | "popularity" + >; + ContextObject: { + /** @description The object type, e.g. "artist", "playlist", "album", "show". */ + type?: string; + /** @description A link to the Web API endpoint providing full details of the track. */ + href?: string; + /** @description External URLs for this context. */ + external_urls?: components["schemas"]["ExternalUrlObject"]; + /** @description The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the context. */ + uri?: string; + }; + CopyrightObject: { + /** @description The copyright text for this content. */ + text?: string; + /** @description The type of copyright: `C` = the copyright, `P` = the sound recording (performance) copyright. */ + type?: string; + }; + AuthorObject: { + /** @description The name of the author. */ + name?: string; + }; + NarratorObject: { + /** @description The name of the Narrator. */ + name?: string; + }; + ExternalIdObject: { + /** @description [International Standard Recording Code](http://en.wikipedia.org/wiki/International_Standard_Recording_Code) */ + isrc?: string; + /** @description [International Article Number](http://en.wikipedia.org/wiki/International_Article_Number_%28EAN%29) */ + ean?: string; + /** @description [Universal Product Code](http://en.wikipedia.org/wiki/Universal_Product_Code) */ + upc?: string; + }; + ExternalUrlObject: { + /** @description The [Spotify URL](/documentation/web-api/concepts/spotify-uris-ids) for the object. */ + spotify?: string; + }; + FollowersObject: { + /** @description This will always be set to null, as the Web API does not support it at the moment. */ + href?: string | null; + /** @description The total number of followers. */ + total?: number; + }; + ImageObject: { + /** + * @description The source URL of the image. + * + * @example https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228 + */ + url: string; + /** + * @description The image height in pixels. + * + * @example 300 + */ + height: number | null; + /** + * @description The image width in pixels. + * + * @example 300 + */ + width: number | null; + }; + ExplicitContentSettingsObject: { + /** @description When `true`, indicates that explicit content should not be played. */ + filter_enabled?: boolean; + /** @description When `true`, indicates that the explicit content setting is locked and can't be changed by the user. */ + filter_locked?: boolean; + }; + }; + responses: { + /** + * @description Bad or expired token. This can happen if the user revoked a token or + * the access token has expired. You should re-authenticate the user. + */ + Unauthorized: { + content: { + "application/json": { + error: components["schemas"]["ErrorObject"]; + }; + }; + }; + /** + * @description Bad OAuth request (wrong consumer key, bad nonce, expired + * timestamp...). Unfortunately, re-authenticating the user won't help here. + */ + Forbidden: { + content: { + "application/json": { + error: components["schemas"]["ErrorObject"]; + }; + }; + }; + /** @description The requested resource cannot be found. */ + NotFound: { + content: { + "application/json": { + error: components["schemas"]["ErrorObject"]; + }; + }; + }; + /** @description The request contains malformed data in path, query parameters, or body. */ + BadRequest: { + content: { + "application/json": { + error: components["schemas"]["ErrorObject"]; + }; + }; + }; + /** @description The app has exceeded its rate limits. */ + TooManyRequests: { + content: { + "application/json": { + error: components["schemas"]["ErrorObject"]; + }; + }; + }; + /** @description A set of albums */ + ManyAlbums: { + content: { + "application/json": { + albums: components["schemas"]["AlbumObject"][]; + }; + }; + }; + /** @description A set of audiobooks. If one of the requested audiobooks is unavailable then you'll find a `null` item in the `audiobooks` array where the audiobook object would otherwise be. */ + ManyAudiobooks: { + content: { + "application/json": { + audiobooks: components["schemas"]["AudiobookObject"][]; + }; + }; + }; + /** @description A set of chapters */ + ManyChapters: { + content: { + "application/json": { + chapters: components["schemas"]["ChapterObject"][]; + }; + }; + }; + /** @description A set of devices */ + ManyDevices: { + content: { + "application/json": { + devices: components["schemas"]["DeviceObject"][]; + }; + }; + }; + /** @description A paged set of albums */ + PagedAlbums: { + content: { + "application/json": { + albums: components["schemas"]["PagingSimplifiedAlbumObject"]; + }; + }; + }; + /** @description A paged set of playlists */ + PagedPlaylists: { + content: { + "application/json": components["schemas"]["PagingPlaylistObject"]; + }; + }; + /** @description A paged set of playlists */ + PagedFeaturedPlaylists: { + content: { + "application/json": + components["schemas"]["PagingFeaturedPlaylistObject"]; + }; + }; + /** @description A paged set of categories */ + PagedCategories: { + content: { + "application/json": { + categories: components["schemas"]["PagingObject"] & { + items?: components["schemas"]["CategoryObject"][]; + }; + }; + }; + }; + /** @description A paged set of artists */ + CursorPagedArtists: { + content: { + "application/json": { + artists: components["schemas"]["CursorPagingSimplifiedArtistObject"]; + }; + }; + }; + /** @description A paged set of tracks */ + CursorPagedPlayHistory: { + content: { + "application/json": + components["schemas"]["CursorPagingPlayHistoryObject"]; + }; + }; + /** @description A set of artists */ + ManyArtists: { + content: { + "application/json": { + artists: components["schemas"]["ArtistObject"][]; + }; + }; + }; + /** @description A set of audio features */ + ManyAudioFeatures: { + content: { + "application/json": { + audio_features: components["schemas"]["AudioFeaturesObject"][]; + }; + }; + }; + /** @description A set of episodes */ + ManyEpisodes: { + content: { + "application/json": { + episodes: components["schemas"]["EpisodeObject"][]; + }; + }; + }; + /** @description A set of genres */ + ManyGenres: { + content: { + "application/json": { + /** + * @example [ + * "alternative", + * "samba" + * ] + */ + genres: string[]; + }; + }; + }; + /** @description An episode */ + OneEpisode: { + content: { + "application/json": components["schemas"]["EpisodeObject"]; + }; + }; + /** @description A Chapter */ + OneChapter: { + content: { + "application/json": components["schemas"]["ChapterObject"]; + }; + }; + /** @description An Audiobook */ + OneAudiobook: { + content: { + "application/json": components["schemas"]["AudiobookObject"]; + }; + }; + /** @description An album */ + OneAlbum: { + content: { + "application/json": components["schemas"]["AlbumObject"]; + }; + }; + /** @description A set of images */ + ArrayOfImages: { + content: { + "application/json": components["schemas"]["ImageObject"][]; + }; + }; + /** @description A user */ + OnePrivateUser: { + content: { + "application/json": components["schemas"]["PrivateUserObject"]; + }; + }; + /** @description A user */ + OnePublicUser: { + content: { + "application/json": components["schemas"]["PublicUserObject"]; + }; + }; + /** @description A track */ + OneTrack: { + content: { + "application/json": components["schemas"]["TrackObject"]; + }; + }; + /** @description A show */ + OneShow: { + content: { + "application/json": components["schemas"]["ShowObject"]; + }; + }; + /** @description A category */ + OneCategory: { + content: { + "application/json": components["schemas"]["CategoryObject"]; + }; + }; + /** @description A playlist */ + OnePlaylist: { + content: { + "application/json": components["schemas"]["PlaylistObject"]; + }; + }; + /** @description Audio features for one track */ + OneAudioFeatures: { + content: { + "application/json": components["schemas"]["AudioFeaturesObject"]; + }; + }; + /** @description Audio analysis for one track */ + OneAudioAnalysis: { + content: { + "application/json": components["schemas"]["AudioAnalysisObject"]; + }; + }; + /** @description An artist */ + OneArtist: { + content: { + "application/json": components["schemas"]["ArtistObject"]; + }; + }; + /** @description A set of tracks */ + ManyTracks: { + content: { + "application/json": { + tracks: components["schemas"]["TrackObject"][]; + }; + }; + }; + /** @description A set of shows */ + ManySimplifiedShows: { + content: { + "application/json": { + shows: components["schemas"]["SimplifiedShowObject"][]; + }; + }; + }; + /** @description Pages of tracks */ + PagingSimplifiedTrackObject: { + content: { + "application/json": + components["schemas"]["PagingSimplifiedTrackObject"]; + }; + }; + /** @description Pages of tracks */ + PagingSavedTrackObject: { + content: { + "application/json": components["schemas"]["PagingSavedTrackObject"]; + }; + }; + /** @description Pages of tracks */ + PagingPlaylistTrackObject: { + content: { + "application/json": components["schemas"]["PagingPlaylistTrackObject"]; + }; + }; + /** @description Pages of albums */ + PagingArtistDiscographyAlbumObject: { + content: { + "application/json": + components["schemas"]["PagingArtistDiscographyAlbumObject"]; + }; + }; + /** @description Pages of albums */ + PagingSavedAlbumObject: { + content: { + "application/json": components["schemas"]["PagingSavedAlbumObject"]; + }; + }; + /** @description Pages of shows */ + PagingSavedShowObject: { + content: { + "application/json": components["schemas"]["PagingSavedShowObject"]; + }; + }; + /** @description Pages of episodes */ + PagingSimplifiedEpisodeObject: { + content: { + "application/json": + components["schemas"]["PagingSimplifiedEpisodeObject"]; + }; + }; + /** @description Pages of episodes */ + PagingSavedEpisodeObject: { + content: { + "application/json": components["schemas"]["PagingSavedEpisodeObject"]; + }; + }; + /** @description Pages of audiobooks */ + PagingSimplifiedAudiobookObject: { + content: { + "application/json": + components["schemas"]["PagingSimplifiedAudiobookObject"]; + }; + }; + /** @description Pages of chapters */ + PagingSimplifiedChapterObject: { + content: { + "application/json": + components["schemas"]["PagingSimplifiedChapterObject"]; + }; + }; + /** @description Pages of artists or tracks */ + PagingArtistOrTrackObject: { + content: { + "application/json": + & components["schemas"]["PagingObject"] + & ({ + items?: ( + | components["schemas"]["ArtistObject"] + | components["schemas"]["TrackObject"] + )[]; + }); + }; + }; + /** @description Search response */ + SearchItems: { + content: { + "application/json": { + tracks?: components["schemas"]["PagingTrackObject"]; + artists?: components["schemas"]["PagingArtistObject"]; + albums?: components["schemas"]["PagingSimplifiedAlbumObject"]; + playlists?: components["schemas"]["PagingPlaylistObject"]; + shows?: components["schemas"]["PagingSimplifiedShowObject"]; + episodes?: components["schemas"]["PagingSimplifiedEpisodeObject"]; + audiobooks?: components["schemas"]["PagingSimplifiedAudiobookObject"]; + }; + }; + }; + /** @description A set of recommendations */ + OneRecommendations: { + content: { + "application/json": components["schemas"]["RecommendationsObject"]; + }; + }; + /** @description Array of booleans */ + ArrayOfBooleans: { + content: { + "application/json": boolean[]; + }; + }; + /** @description Information about the queue */ + Queue: { + content: { + "application/json": components["schemas"]["QueueObject"]; + }; + }; + /** @description Information about playback */ + OneCurrentlyPlaying: { + content: { + "application/json": + components["schemas"]["CurrentlyPlayingContextObject"]; + }; + }; + /** @description Information about the currently playing track */ + OneCurrentlyPlayingTrack: { + content: { + "application/json": + components["schemas"]["CurrentlyPlayingContextObject"]; + }; + }; + /** @description A snapshot ID for the playlist */ + PlaylistSnapshotId: { + content: { + "application/json": { + /** @example abc */ + snapshot_id?: string; + }; + }; + }; + }; + parameters: { + PathAlbumId: string; + PathPlaylistId: string; + QueryMarket?: string; + QueryLimit?: number; + QueryOffset?: number; + QueryAdditionalTypes?: string; + QueryAlbumIds: string; + PathArtistId: string; + PathShowId: string; + PathAudiobookId: string; + QueryAudiobookIds: string; + PathChapterId: string; + QueryChapterIds: string; + QueryTrackIds: string; + QueryIncludeGroups?: string; + QueryShowIds: string; + PathUserId: string; + }; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type $defs = Record; + +export interface external { + "../policies.yaml": unknown; +} + +export interface operations { + /** + * Get Album + * + * @description Get Spotify catalog information for a single album. + */ + "get-an-album": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: components["parameters"]["PathAlbumId"]; + }; + }; + responses: { + 200: components["responses"]["OneAlbum"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Albums + * + * @description Get Spotify catalog information for multiple albums identified by their Spotify IDs. + */ + "get-multiple-albums": { + parameters: { + query: { + ids: components["parameters"]["QueryAlbumIds"]; + market?: components["parameters"]["QueryMarket"]; + }; + }; + responses: { + 200: components["responses"]["ManyAlbums"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Album Tracks + * + * @description Get Spotify catalog information about an album’s tracks. + * Optional parameters can be used to limit the number of tracks returned. + */ + "get-an-albums-tracks": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + path: { + id: components["parameters"]["PathAlbumId"]; + }; + }; + responses: { + 200: components["responses"]["PagingSimplifiedTrackObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Artist + * + * @description Get Spotify catalog information for a single artist identified by their unique Spotify ID. + */ + "get-an-artist": { + parameters: { + path: { + id: components["parameters"]["PathArtistId"]; + }; + }; + responses: { + 200: components["responses"]["OneArtist"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Artists + * + * @description Get Spotify catalog information for several artists based on their Spotify IDs. + */ + "get-multiple-artists": { + parameters: { + query: { + ids: string; + }; + }; + responses: { + 200: components["responses"]["ManyArtists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Artist's Albums + * + * @description Get Spotify catalog information about an artist's albums. + */ + "get-an-artists-albums": { + parameters: { + query?: { + include_groups?: components["parameters"]["QueryIncludeGroups"]; + market?: components["parameters"]["QueryMarket"]; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + path: { + id: components["parameters"]["PathArtistId"]; + }; + }; + responses: { + 200: components["responses"]["PagingArtistDiscographyAlbumObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Artist's Top Tracks + * + * @description Get Spotify catalog information about an artist's top tracks by country. + */ + "get-an-artists-top-tracks": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: components["parameters"]["PathArtistId"]; + }; + }; + responses: { + 200: components["responses"]["ManyTracks"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Artist's Related Artists + * + * @description Get Spotify catalog information about artists similar to a given artist. Similarity is based on analysis of the Spotify community's listening history. + */ + "get-an-artists-related-artists": { + parameters: { + path: { + id: components["parameters"]["PathArtistId"]; + }; + }; + responses: { + 200: components["responses"]["ManyArtists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Show + * + * @description Get Spotify catalog information for a single show identified by its + * unique Spotify ID. + */ + "get-a-show": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: components["parameters"]["PathShowId"]; + }; + }; + responses: { + 200: components["responses"]["OneShow"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Shows + * + * @description Get Spotify catalog information for several shows based on their Spotify IDs. + */ + "get-multiple-shows": { + parameters: { + query: { + market?: components["parameters"]["QueryMarket"]; + ids: components["parameters"]["QueryShowIds"]; + }; + }; + responses: { + 200: components["responses"]["ManySimplifiedShows"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Show Episodes + * + * @description Get Spotify catalog information about an show’s episodes. Optional parameters can be used to limit the number of episodes returned. + */ + "get-a-shows-episodes": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + path: { + id: components["parameters"]["PathShowId"]; + }; + }; + responses: { + 200: components["responses"]["PagingSimplifiedEpisodeObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Episode + * + * @description Get Spotify catalog information for a single episode identified by its + * unique Spotify ID. + */ + "get-an-episode": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: string; + }; + }; + responses: { + 200: components["responses"]["OneEpisode"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Episodes + * + * @description Get Spotify catalog information for several episodes based on their Spotify IDs. + */ + "get-multiple-episodes": { + parameters: { + query: { + ids: string; + market?: components["parameters"]["QueryMarket"]; + }; + }; + responses: { + 200: components["responses"]["ManyEpisodes"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get an Audiobook + * + * @description Get Spotify catalog information for a single audiobook. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + "get-an-audiobook": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: components["parameters"]["PathAudiobookId"]; + }; + }; + responses: { + 200: components["responses"]["OneAudiobook"]; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Audiobooks + * + * @description Get Spotify catalog information for several audiobooks identified by their Spotify IDs. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + "get-multiple-audiobooks": { + parameters: { + query: { + ids: components["parameters"]["QueryAudiobookIds"]; + market?: components["parameters"]["QueryMarket"]; + }; + }; + responses: { + 200: components["responses"]["ManyAudiobooks"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Audiobook Chapters + * + * @description Get Spotify catalog information about an audiobook's chapters. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + "get-audiobook-chapters": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + path: { + id: components["parameters"]["PathAudiobookId"]; + }; + }; + responses: { + 200: components["responses"]["PagingSimplifiedChapterObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Saved Audiobooks + * + * @description Get a list of the audiobooks saved in the current Spotify user's 'Your Music' library. + */ + "get-users-saved-audiobooks": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagingSimplifiedAudiobookObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Save Audiobooks for Current User + * + * @description Save one or more audiobooks to the current Spotify user's library. + */ + "save-audiobooks-user": { + parameters: { + query: { + ids: components["parameters"]["QueryAudiobookIds"]; + }; + }; + responses: { + /** @description Audiobook(s) are saved to the library */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Remove User's Saved Audiobooks + * + * @description Remove one or more audiobooks from the Spotify user's library. + */ + "remove-audiobooks-user": { + parameters: { + query: { + ids: components["parameters"]["QueryAudiobookIds"]; + }; + }; + responses: { + /** @description Audiobook(s) have been removed from the library */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check User's Saved Audiobooks + * + * @description Check if one or more audiobooks are already saved in the current Spotify user's library. + */ + "check-users-saved-audiobooks": { + parameters: { + query: { + ids: components["parameters"]["QueryAudiobookIds"]; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get a Chapter + * + * @description Get Spotify catalog information for a single audiobook chapter. Chapters are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + "get-a-chapter": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: components["parameters"]["PathChapterId"]; + }; + }; + responses: { + 200: components["responses"]["OneChapter"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Chapters + * + * @description Get Spotify catalog information for several audiobook chapters identified by their Spotify IDs. Chapters are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + "get-several-chapters": { + parameters: { + query: { + ids: components["parameters"]["QueryChapterIds"]; + market?: components["parameters"]["QueryMarket"]; + }; + }; + responses: { + 200: components["responses"]["ManyChapters"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Track + * + * @description Get Spotify catalog information for a single track identified by its + * unique Spotify ID. + */ + "get-track": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + }; + path: { + id: string; + }; + }; + responses: { + 200: components["responses"]["OneTrack"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Tracks + * + * @description Get Spotify catalog information for multiple tracks based on their Spotify IDs. + */ + "get-several-tracks": { + parameters: { + query: { + market?: components["parameters"]["QueryMarket"]; + ids: string; + }; + }; + responses: { + 200: components["responses"]["ManyTracks"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Search for Item + * + * @description Get Spotify catalog information about albums, artists, playlists, tracks, shows, episodes or audiobooks + * that match a keyword string. Audiobooks are only available within the US, UK, Canada, Ireland, New Zealand and Australia markets. + */ + search: { + parameters: { + query: { + q: string; + type: ( + | "album" + | "artist" + | "playlist" + | "track" + | "show" + | "episode" + | "audiobook" + )[]; + market?: components["parameters"]["QueryMarket"]; + limit?: number; + offset?: number; + include_external?: "audio"; + }; + }; + responses: { + 200: components["responses"]["SearchItems"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Current User's Profile + * + * @description Get detailed profile information about the current user (including the + * current user's username). + */ + "get-current-users-profile": { + responses: { + 200: components["responses"]["OnePrivateUser"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Playlist + * + * @description Get a playlist owned by a Spotify user. + */ + "get-playlist": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + fields?: string; + additional_types?: components["parameters"]["QueryAdditionalTypes"]; + }; + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + responses: { + 200: components["responses"]["OnePlaylist"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Change Playlist Details + * + * @description Change a playlist's name and public/private state. (The user must, of + * course, own the playlist.) + */ + "change-playlist-details": { + parameters: { + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description The new name for the playlist, for example `"My New Playlist Title"` */ + name?: string; + /** @description If `true` the playlist will be public, if `false` it will be private. */ + public?: boolean; + /** + * @description If `true`, the playlist will become collaborative and other users will be able to modify the playlist in their Spotify client.
+ * _**Note**: You can only set `collaborative` to `true` on non-public playlists._ + */ + collaborative?: boolean; + /** @description Value for playlist description as displayed in Spotify Clients and in the Web API. */ + description?: string; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Playlist updated */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Playlist Items + * + * @description Get full details of the items of a playlist owned by a Spotify user. + */ + "get-playlists-tracks": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + fields?: string; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + additional_types?: components["parameters"]["QueryAdditionalTypes"]; + }; + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + responses: { + 200: components["responses"]["PagingPlaylistTrackObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Update Playlist Items + * + * @description Either reorder or replace items in a playlist depending on the request's parameters. + * To reorder items, include `range_start`, `insert_before`, `range_length` and `snapshot_id` in the request's body. + * To replace items, include `uris` as either a query parameter or in the request's body. + * Replacing items in a playlist will overwrite its existing items. This operation can be used for replacing or clearing items in a playlist. + *
+ * **Note**: Replace and reorder are mutually exclusive operations which share the same endpoint, but have different parameters. + * These operations can't be applied together in a single request. + */ + "reorder-or-replace-playlists-tracks": { + parameters: { + query?: { + uris?: string; + }; + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + requestBody?: { + content: { + "application/json": { + uris?: string[]; + /** @description The position of the first item to be reordered. */ + range_start?: number; + /** @description The position where the items should be inserted.
To reorder the items to the end of the playlist, simply set _insert_before_ to the position after the last item.
Examples:
To reorder the first item to the last position in a playlist with 10 items, set _range_start_ to 0, and _insert_before_ to 10.
To reorder the last item in a playlist with 10 items to the start of the playlist, set _range_start_ to 9, and _insert_before_ to 0. */ + insert_before?: number; + /** @description The amount of items to be reordered. Defaults to 1 if not set.
The range of items to be reordered begins from the _range_start_ position, and includes the _range_length_ subsequent items.
Example:
To move the items at index 9-10 to the start of the playlist, _range_start_ is set to 9, and _range_length_ is set to 2. */ + range_length?: number; + /** @description The playlist's snapshot ID against which you want to make the changes. */ + snapshot_id?: string; + [key: string]: unknown; + }; + }; + }; + responses: { + 200: components["responses"]["PlaylistSnapshotId"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Add Items to Playlist + * + * @description Add one or more items to a user's playlist. + */ + "add-tracks-to-playlist": { + parameters: { + query?: { + position?: number; + uris?: string; + }; + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify URIs](/documentation/web-api/concepts/spotify-uris-ids) to add. For example: `{"uris": ["spotify:track:4iV5W9uYEdYUVa79Axb7Rh","spotify:track:1301WleyT98MSxVHPZCA6M", "spotify:episode:512ojhOuo1ktJprKbVcKyQ"]}`
A maximum of 100 items can be added in one request. _**Note**: if the `uris` parameter is present in the query string, any URIs listed here in the body will be ignored._ */ + uris?: string[]; + /** @description The position to insert the items, a zero-based index. For example, to insert the items in the first position: `position=0` ; to insert the items in the third position: `position=2`. If omitted, the items will be appended to the playlist. Items are added in the order they appear in the uris array. For example: `{"uris": ["spotify:track:4iV5W9uYEdYUVa79Axb7Rh","spotify:track:1301WleyT98MSxVHPZCA6M"], "position": 3}` */ + position?: number; + [key: string]: unknown; + }; + }; + }; + responses: { + 201: components["responses"]["PlaylistSnapshotId"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Remove Playlist Items + * + * @description Remove one or more items from a user's playlist. + */ + "remove-tracks-playlist": { + parameters: { + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description An array of objects containing [Spotify URIs](/documentation/web-api/concepts/spotify-uris-ids) of the tracks or episodes to remove. + * For example: `{ "tracks": [{ "uri": "spotify:track:4iV5W9uYEdYUVa79Axb7Rh" },{ "uri": "spotify:track:1301WleyT98MSxVHPZCA6M" }] }`. A maximum of 100 objects can be sent at once. + */ + tracks: { + /** @description Spotify URI */ + uri?: string; + }[]; + /** + * @description The playlist's snapshot ID against which you want to make the changes. + * The API will validate that the specified items exist and in the specified positions and make the changes, + * even if more recent changes have been made to the playlist. + */ + snapshot_id?: string; + }; + }; + }; + responses: { + 200: components["responses"]["PlaylistSnapshotId"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Current User's Playlists + * + * @description Get a list of the playlists owned or followed by the current Spotify + * user. + */ + "get-a-list-of-current-users-playlists": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: number; + }; + }; + responses: { + 200: components["responses"]["PagedPlaylists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Saved Albums + * + * @description Get a list of the albums saved in the current Spotify user's 'Your Music' library. + */ + "get-users-saved-albums": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + market?: components["parameters"]["QueryMarket"]; + }; + }; + responses: { + 200: components["responses"]["PagingSavedAlbumObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Save Albums for Current User + * + * @description Save one or more albums to the current user's 'Your Music' library. + */ + "save-albums-user": { + parameters: { + query: { + ids: components["parameters"]["QueryAlbumIds"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids). For example: `["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"]`
A maximum of 50 items can be specified in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description The album is saved */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Remove Users' Saved Albums + * + * @description Remove one or more albums from the current user's 'Your Music' library. + */ + "remove-albums-user": { + parameters: { + query: { + ids: components["parameters"]["QueryAlbumIds"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids). For example: `["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"]`
A maximum of 50 items can be specified in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Album(s) have been removed from the library */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check User's Saved Albums + * + * @description Check if one or more albums is already saved in the current Spotify user's 'Your Music' library. + */ + "check-users-saved-albums": { + parameters: { + query: { + ids: components["parameters"]["QueryAlbumIds"]; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Saved Tracks + * + * @description Get a list of the songs saved in the current Spotify user's 'Your Music' library. + */ + "get-users-saved-tracks": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagingSavedTrackObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Save Tracks for Current User + * + * @description Save one or more tracks to the current user's 'Your Music' library. + */ + "save-tracks-user": { + parameters: { + query: { + ids: components["parameters"]["QueryTrackIds"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids). For example: `["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"]`
A maximum of 50 items can be specified in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Track saved */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Remove User's Saved Tracks + * + * @description Remove one or more tracks from the current user's 'Your Music' library. + */ + "remove-tracks-user": { + parameters: { + query: { + ids: components["parameters"]["QueryTrackIds"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids). For example: `["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"]`
A maximum of 50 items can be specified in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Track removed */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check User's Saved Tracks + * + * @description Check if one or more tracks is already saved in the current Spotify user's 'Your Music' library. + */ + "check-users-saved-tracks": { + parameters: { + query: { + ids: components["parameters"]["QueryTrackIds"]; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Saved Episodes + * + * @description Get a list of the episodes saved in the current Spotify user's library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer). + */ + "get-users-saved-episodes": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagingSavedEpisodeObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Save Episodes for Current User + * + * @description Save one or more episodes to the current user's library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer). + */ + "save-episodes-user": { + parameters: { + query: { + ids: string; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids).
A maximum of 50 items can be specified in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Episode saved */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Remove User's Saved Episodes + * + * @description Remove one or more episodes from the current user's library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer). + */ + "remove-episodes-user": { + parameters: { + query: { + ids: components["parameters"]["QueryTrackIds"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids).
A maximum of 50 items can be specified in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Episode removed */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check User's Saved Episodes + * + * @description Check if one or more episodes is already saved in the current Spotify user's 'Your Episodes' library.
+ * This API endpoint is in __beta__ and could change without warning. Please share any feedback that you have, or issues that you discover, in our [developer community forum](https://community.spotify.com/t5/Spotify-for-Developers/bd-p/Spotify_Developer).. + */ + "check-users-saved-episodes": { + parameters: { + query: { + ids: string; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Saved Shows + * + * @description Get a list of shows saved in the current Spotify user's library. Optional parameters can be used to limit the number of shows returned. + */ + "get-users-saved-shows": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagingSavedShowObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Save Shows for Current User + * + * @description Save one or more shows to current Spotify user's library. + */ + "save-shows-user": { + parameters: { + query: { + ids: components["parameters"]["QueryShowIds"]; + }; + }; + responses: { + /** @description Show saved */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Remove User's Saved Shows + * + * @description Delete one or more shows from current Spotify user's library. + */ + "remove-shows-user": { + parameters: { + query: { + ids: components["parameters"]["QueryShowIds"]; + market?: components["parameters"]["QueryMarket"]; + }; + }; + responses: { + /** @description Show removed */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check User's Saved Shows + * + * @description Check if one or more shows is already saved in the current Spotify user's library. + */ + "check-users-saved-shows": { + parameters: { + query: { + ids: components["parameters"]["QueryShowIds"]; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Top Items + * + * @description Get the current user's top artists or tracks based on calculated affinity. + */ + "get-users-top-artists-and-tracks": { + parameters: { + query?: { + time_range?: string; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + path: { + type: "artists" | "tracks"; + }; + }; + responses: { + 200: components["responses"]["PagingArtistOrTrackObject"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Profile + * + * @description Get public profile information about a Spotify user. + */ + "get-users-profile": { + parameters: { + path: { + user_id: components["parameters"]["PathUserId"]; + }; + }; + responses: { + 200: components["responses"]["OnePublicUser"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get User's Playlists + * + * @description Get a list of the playlists owned or followed by a Spotify user. + */ + "get-list-users-playlists": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: number; + }; + path: { + user_id: components["parameters"]["PathUserId"]; + }; + }; + responses: { + 200: components["responses"]["PagedPlaylists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Create Playlist + * + * @description Create a playlist for a Spotify user. (The playlist will be empty until + * you [add tracks](/documentation/web-api/reference/add-tracks-to-playlist).) + * Each user is generally limited to a maximum of 11000 playlists. + */ + "create-playlist": { + parameters: { + path: { + user_id: components["parameters"]["PathUserId"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description The name for the new playlist, for example `"Your Coolest Playlist"`. This name does not need to be unique; a user may have several playlists with the same name. */ + name: string; + /** @description Defaults to `true`. If `true` the playlist will be public, if `false` it will be private. To be able to create private playlists, the user must have granted the `playlist-modify-private` [scope](/documentation/web-api/concepts/scopes/#list-of-scopes) */ + public?: boolean; + /** @description Defaults to `false`. If `true` the playlist will be collaborative. _**Note**: to create a collaborative playlist you must also set `public` to `false`. To create collaborative playlists you must have granted `playlist-modify-private` and `playlist-modify-public` [scopes](/documentation/web-api/concepts/scopes/#list-of-scopes)._ */ + collaborative?: boolean; + /** @description value for playlist description as displayed in Spotify Clients and in the Web API. */ + description?: string; + [key: string]: unknown; + }; + }; + }; + responses: { + 201: components["responses"]["OnePlaylist"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Follow Playlist + * + * @description Add the current user as a follower of a playlist. + */ + "follow-playlist": { + parameters: { + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description Defaults to `true`. If `true` the playlist will be included in user's public playlists, if `false` it will remain private. */ + public?: boolean; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Playlist followed */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Unfollow Playlist + * + * @description Remove the current user as a follower of a playlist. + */ + "unfollow-playlist": { + parameters: { + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + responses: { + /** @description Playlist unfollowed */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Featured Playlists + * + * @description Get a list of Spotify featured playlists (shown, for example, on a Spotify player's 'Browse' tab). + */ + "get-featured-playlists": { + parameters: { + query?: { + locale?: string; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagedFeaturedPlaylists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Browse Categories + * + * @description Get a list of categories used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). + */ + "get-categories": { + parameters: { + query?: { + locale?: string; + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagedCategories"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Single Browse Category + * + * @description Get a single category used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). + */ + "get-a-category": { + parameters: { + query?: { + locale?: string; + }; + path: { + category_id: string; + }; + }; + responses: { + 200: components["responses"]["OneCategory"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Category's Playlists + * + * @description Get a list of Spotify playlists tagged with a particular category. + */ + "get-a-categories-playlists": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + path: { + category_id: string; + }; + }; + responses: { + 200: components["responses"]["PagedFeaturedPlaylists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Playlist Cover Image + * + * @description Get the current image associated with a specific playlist. + */ + "get-playlist-cover": { + parameters: { + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + responses: { + 200: components["responses"]["ArrayOfImages"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Add Custom Playlist Cover Image + * + * @description Replace the image used to represent a specific playlist. + */ + "upload-custom-playlist-cover": { + parameters: { + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + requestBody?: { + content: { + "image/jpeg": string; + }; + }; + responses: { + /** @description Image uploaded */ + 202: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get New Releases + * + * @description Get a list of new album releases featured in Spotify (shown, for example, on a Spotify player’s “Browse” tab). + */ + "get-new-releases": { + parameters: { + query?: { + limit?: components["parameters"]["QueryLimit"]; + offset?: components["parameters"]["QueryOffset"]; + }; + }; + responses: { + 200: components["responses"]["PagedAlbums"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Followed Artists + * + * @description Get the current user's followed artists. + */ + "get-followed": { + parameters: { + query: { + type: "artist"; + after?: string; + limit?: number; + }; + }; + responses: { + 200: components["responses"]["CursorPagedArtists"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Follow Artists or Users + * + * @description Add the current user as a follower of one or more artists or other Spotify users. + */ + "follow-artists-users": { + parameters: { + query: { + type: "artist" | "user"; + ids: string; + }; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description A JSON array of the artist or user [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids). + * For example: `{ids:["74ASZWbe4lXaubB36ztrGX", "08td7MxkoHQkXnWAYD8d6Q"]}`. A maximum of 50 IDs can be sent in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ + */ + ids: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Artist or user followed */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Unfollow Artists or Users + * + * @description Remove the current user as a follower of one or more artists or other Spotify users. + */ + "unfollow-artists-users": { + parameters: { + query: { + type: "artist" | "user"; + ids: string; + }; + }; + requestBody?: { + content: { + "application/json": { + /** @description A JSON array of the artist or user [Spotify IDs](/documentation/web-api/concepts/spotify-uris-ids). For example: `{ids:["74ASZWbe4lXaubB36ztrGX", "08td7MxkoHQkXnWAYD8d6Q"]}`. A maximum of 50 IDs can be sent in one request. _**Note**: if the `ids` parameter is present in the query string, any IDs listed here in the body will be ignored._ */ + ids?: string[]; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Artist or user unfollowed */ + 200: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check If User Follows Artists or Users + * + * @description Check to see if the current user is following one or more artists or other Spotify users. + */ + "check-current-user-follows": { + parameters: { + query: { + type: "artist" | "user"; + ids: string; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Check if Users Follow Playlist + * + * @description Check to see if one or more Spotify users are following a specified playlist. + */ + "check-if-user-follows-playlist": { + parameters: { + query: { + ids: string; + }; + path: { + playlist_id: components["parameters"]["PathPlaylistId"]; + }; + }; + responses: { + 200: components["responses"]["ArrayOfBooleans"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Several Tracks' Audio Features + * + * @description Get audio features for multiple tracks based on their Spotify IDs. + */ + "get-several-audio-features": { + parameters: { + query: { + ids: string; + }; + }; + responses: { + 200: components["responses"]["ManyAudioFeatures"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Track's Audio Features + * + * @description Get audio feature information for a single track identified by its unique + * Spotify ID. + */ + "get-audio-features": { + parameters: { + path: { + id: string; + }; + }; + responses: { + 200: components["responses"]["OneAudioFeatures"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Track's Audio Analysis + * + * @description Get a low-level audio analysis for a track in the Spotify catalog. The audio analysis describes the track’s structure and musical content, including rhythm, pitch, and timbre. + */ + "get-audio-analysis": { + parameters: { + path: { + id: string; + }; + }; + responses: { + 200: components["responses"]["OneAudioAnalysis"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Recommendations + * + * @description Recommendations are generated based on the available information for a given seed entity and matched against similar artists and tracks. If there is sufficient information about the provided seeds, a list of tracks will be returned together with pool size details. + * + * For artists and tracks that are very new or obscure there might not be enough data to generate a list of tracks. + */ + "get-recommendations": { + parameters: { + query: { + limit?: number; + market?: components["parameters"]["QueryMarket"]; + seed_artists: string; + seed_genres: string; + seed_tracks: string; + min_acousticness?: number; + max_acousticness?: number; + target_acousticness?: number; + min_danceability?: number; + max_danceability?: number; + target_danceability?: number; + min_duration_ms?: number; + max_duration_ms?: number; + target_duration_ms?: number; + min_energy?: number; + max_energy?: number; + target_energy?: number; + min_instrumentalness?: number; + max_instrumentalness?: number; + target_instrumentalness?: number; + min_key?: number; + max_key?: number; + target_key?: number; + min_liveness?: number; + max_liveness?: number; + target_liveness?: number; + min_loudness?: number; + max_loudness?: number; + target_loudness?: number; + min_mode?: number; + max_mode?: number; + target_mode?: number; + min_popularity?: number; + max_popularity?: number; + target_popularity?: number; + min_speechiness?: number; + max_speechiness?: number; + target_speechiness?: number; + min_tempo?: number; + max_tempo?: number; + target_tempo?: number; + min_time_signature?: number; + max_time_signature?: number; + target_time_signature?: number; + min_valence?: number; + max_valence?: number; + target_valence?: number; + }; + }; + responses: { + 200: components["responses"]["OneRecommendations"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Available Genre Seeds + * + * @description Retrieve a list of available genres seed parameter values for [recommendations](/documentation/web-api/reference/get-recommendations). + */ + "get-recommendation-genres": { + responses: { + 200: components["responses"]["ManyGenres"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Playback State + * + * @description Get information about the user’s current playback state, including track or episode, progress, and active device. + */ + "get-information-about-the-users-current-playback": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + additional_types?: components["parameters"]["QueryAdditionalTypes"]; + }; + }; + responses: { + 200: components["responses"]["OneCurrentlyPlaying"]; + /** @description Playback not available or active */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Transfer Playback + * + * @description Transfer playback to a new device and optionally begin playback. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "transfer-a-users-playback": { + requestBody?: { + content: { + "application/json": { + /** @description A JSON array containing the ID of the device on which playback should be started/transferred.
For example:`{device_ids:["74ASZWbe4lXaubB36ztrGX"]}`
_**Note**: Although an array is accepted, only a single device_id is currently supported. Supplying more than one will return `400 Bad Request`_ */ + device_ids: string[]; + /** @description **true**: ensure playback happens on new device.
**false** or not provided: keep the current playback state. */ + play?: boolean; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Playback transferred */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Available Devices + * + * @description Get information about a user’s available Spotify Connect devices. Some device models are not supported and will not be listed in the API response. + */ + "get-a-users-available-devices": { + responses: { + 200: components["responses"]["ManyDevices"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Currently Playing Track + * + * @description Get the object currently being played on the user's Spotify account. + */ + "get-the-users-currently-playing-track": { + parameters: { + query?: { + market?: components["parameters"]["QueryMarket"]; + additional_types?: components["parameters"]["QueryAdditionalTypes"]; + }; + }; + responses: { + 200: components["responses"]["OneCurrentlyPlayingTrack"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Start/Resume Playback + * + * @description Start a new context or resume current playback on the user's active device. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "start-a-users-playback": { + parameters: { + query?: { + device_id?: string; + }; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description Optional. Spotify URI of the context to play. + * Valid contexts are albums, artists & playlists. + * `{context_uri:"spotify:album:1Je1IMUlBXcx1Fz0WE7oPT"}` + */ + context_uri?: string; + /** + * @description Optional. A JSON array of the Spotify track URIs to play. + * For example: `{"uris": ["spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M"]}` + */ + uris?: string[]; + /** + * @description Optional. Indicates from where in the context playback should start. Only available when context_uri corresponds to an album or playlist object + * "position" is zero based and can’t be negative. Example: `"offset": {"position": 5}` + * "uri" is a string representing the uri of the item to start at. Example: `"offset": {"uri": "spotify:track:1301WleyT98MSxVHPZCA6M"}` + */ + offset?: { + [key: string]: unknown; + }; + /** @description integer */ + position_ms?: number; + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Playback started */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Pause Playback + * + * @description Pause playback on the user's account. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "pause-a-users-playback": { + parameters: { + query?: { + device_id?: string; + }; + }; + responses: { + /** @description Playback paused */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Skip To Next + * + * @description Skips to next track in the user’s queue. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "skip-users-playback-to-next-track": { + parameters: { + query?: { + device_id?: string; + }; + }; + responses: { + /** @description Command sent */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Skip To Previous + * + * @description Skips to previous track in the user’s queue. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "skip-users-playback-to-previous-track": { + parameters: { + query?: { + device_id?: string; + }; + }; + responses: { + /** @description Command sent */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Seek To Position + * + * @description Seeks to the given position in the user’s currently playing track. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "seek-to-position-in-currently-playing-track": { + parameters: { + query: { + position_ms: number; + device_id?: string; + }; + }; + responses: { + /** @description Command sent */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Set Repeat Mode + * + * @description Set the repeat mode for the user's playback. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "set-repeat-mode-on-users-playback": { + parameters: { + query: { + state: string; + device_id?: string; + }; + }; + responses: { + /** @description Command sent */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Set Playback Volume + * + * @description Set the volume for the user’s current playback device. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "set-volume-for-users-playback": { + parameters: { + query: { + volume_percent: number; + device_id?: string; + }; + }; + responses: { + /** @description Command sent */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Toggle Playback Shuffle + * + * @description Toggle shuffle on or off for user’s playback. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "toggle-shuffle-for-users-playback": { + parameters: { + query: { + state: boolean; + device_id?: string; + }; + }; + responses: { + /** @description Command sent */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Recently Played Tracks + * + * @description Get tracks from the current user's recently played tracks. + * _**Note**: Currently doesn't support podcast episodes._ + */ + "get-recently-played": { + parameters: { + query?: { + limit?: number; + after?: number; + before?: number; + }; + }; + responses: { + 200: components["responses"]["CursorPagedPlayHistory"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get the User's Queue + * + * @description Get the list of objects that make up the user's queue. + */ + "get-queue": { + responses: { + 200: components["responses"]["Queue"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Add Item to Playback Queue + * + * @description Add an item to the end of the user's current playback queue. This API only works for users who have Spotify Premium. The order of execution is not guaranteed when you use this API with other Player API endpoints. + */ + "add-to-queue": { + parameters: { + query: { + uri: string; + device_id?: string; + }; + }; + responses: { + /** @description Command received */ + 204: { + content: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; + /** + * Get Available Markets + * + * @description Get the list of markets where Spotify is available. + */ + "get-available-markets": { + responses: { + /** @description A markets object with an array of country codes */ + 200: { + content: { + "application/json": { + /** + * @example [ + * "CA", + * "BR", + * "IT" + * ] + */ + markets?: string[]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 429: components["responses"]["TooManyRequests"]; + }; + }; +} From 86e66485d56bb43c1bec62874a251dbf47e0d29b Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Wed, 6 Mar 2024 00:18:17 +0100 Subject: [PATCH 05/18] fix: tests --- endpoints/general.types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/endpoints/general.types.ts b/endpoints/general.types.ts index 7a64cb3..7a865ea 100644 --- a/endpoints/general.types.ts +++ b/endpoints/general.types.ts @@ -59,7 +59,7 @@ export interface CursorPagingObject { items: TItem[]; } -export interface PagingOptions { +export type PagingOptions = { /** * The maximum number of items to return. Minimum: 1. Maximum: 50. * @default 20 @@ -70,7 +70,7 @@ export interface PagingOptions { * @default 0 (the first item) */ offset?: number; -} +}; /** * The reason for the restriction. From 1425962d4ab4557d52ea21fb2dbb304b462b65f0 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Sat, 9 Mar 2024 16:38:02 +0100 Subject: [PATCH 06/18] chore: update pkce code example with ssr preact --- .vscode/settings.json | 7 +- client.ts | 2 +- deno.json | 12 +- deno.lock | 12 +- examples/{oauth4webapi => }/auth_code.ts | 0 .../{oauth4webapi => }/client_credentials.ts | 0 examples/oauth4webapi/pkce_code.ts | 222 ------------------ examples/pkce/auth.ts | 128 ++++++++++ examples/pkce/env.ts | 13 + examples/pkce/main.tsx | 150 ++++++++++++ tsconfig.json | 2 +- 11 files changed, 318 insertions(+), 230 deletions(-) rename examples/{oauth4webapi => }/auth_code.ts (100%) rename examples/{oauth4webapi => }/client_credentials.ts (100%) delete mode 100644 examples/oauth4webapi/pkce_code.ts create mode 100644 examples/pkce/auth.ts create mode 100644 examples/pkce/env.ts create mode 100644 examples/pkce/main.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 047daf3..6b0e0c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" - } -} + }, + "[typescriptreact]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, +} \ No newline at end of file diff --git a/client.ts b/client.ts index 9d7a6b4..3d9d42b 100644 --- a/client.ts +++ b/client.ts @@ -181,7 +181,7 @@ export class SpotifyClient implements HTTPClient { constructor(accessToken: string, options?: SpotifyClinetOptions); constructor( - accessToken: null, + accessToken: string | null, options: & SpotifyClinetOptions & Required>, diff --git a/deno.json b/deno.json index d36032a..eeb7203 100644 --- a/deno.json +++ b/deno.json @@ -10,11 +10,19 @@ "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", "oauth4webapi": "https://deno.land/x/oauth4webapi@v2.10.3/mod.ts", "oak": "https://deno.land/x/oak@v12.5.0/mod.ts", + "oak/deps.ts": "https://deno.land/x/oak@v12.5.0/deps.ts", "std/": "https://deno.land/std@0.215.0/", "@soundify/web-api": "./mod.ts", "@soundify/web-api/pagination": "./pagination.ts", "@soundify/web-api/auth": "./auth.ts", - "mock_fetch": "https://deno.land/x/mock_fetch@0.3.0/mod.ts" + "mock_fetch": "https://deno.land/x/mock_fetch@0.3.0/mod.ts", + "preact": "https://esm.sh/preact@10.19.6", + "preact/": "https://esm.sh/preact@10.19.6/", + "preact-render-to-string": "https://esm.sh/preact-render-to-string@6.4.0?external=preact" }, - "fmt": { "useTabs": true } + "fmt": { "useTabs": true }, + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } } diff --git a/deno.lock b/deno.lock index e15dc05..13ad738 100644 --- a/deno.lock +++ b/deno.lock @@ -1,7 +1,9 @@ { "version": "3", "redirects": { - "https://crux.land/router@0.0.5": "https://crux.land/api/get/2KNRVU.ts" + "https://crux.land/router@0.0.5": "https://crux.land/api/get/2KNRVU.ts", + "https://esm.sh/preact": "https://esm.sh/preact@10.19.6", + "https://esm.sh/preact-render-to-string?external=preact": "https://esm.sh/preact-render-to-string@6.4.0?external=preact" }, "remote": { "https://crux.land/api/get/2KNRVU.ts": "6a77d55844aba78d01520c5ff0b2f0af7f24cc1716a0de8b3bb6bd918c47b5ba", @@ -180,7 +182,13 @@ "https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", - "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e" + "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", + "https://esm.sh/preact-render-to-string@6.4.0?external=preact": "67ecc6b1144314e3f8544dd1fc3bcdcdcfa3a892ad5662ceb8d3b8e62b857617", + "https://esm.sh/preact@10.19.6": "281b115a9b79918c6b73382b4e818d6f9e48db9064b7fd4f75a45a4edee54145", + "https://esm.sh/preact@10.19.6/jsx-runtime": "f4c9e5f72ef18435eab846ae02e315a63da724f64fb08d806d2ddeaf08d99bd8", + "https://esm.sh/stable/preact@10.19.6/denonext/jsx-runtime.js": "97e9eeb40443de53a8cb3b70f264d04db2bbfc5c2298fdb15d1c471283ccd6aa", + "https://esm.sh/stable/preact@10.19.6/denonext/preact.mjs": "68491395d287895f4d697e403ded5b0ebb8fed0494f9e870c422bc017e5e52f5", + "https://esm.sh/v135/preact-render-to-string@6.4.0/X-ZS9wcmVhY3Q/denonext/preact-render-to-string.mjs": "441c6360fc6adf7c6a60f4eb61ae3a44ed6668819cb0b145d03d52514c1ca1f5" }, "workspace": { "packageJson": { diff --git a/examples/oauth4webapi/auth_code.ts b/examples/auth_code.ts similarity index 100% rename from examples/oauth4webapi/auth_code.ts rename to examples/auth_code.ts diff --git a/examples/oauth4webapi/client_credentials.ts b/examples/client_credentials.ts similarity index 100% rename from examples/oauth4webapi/client_credentials.ts rename to examples/client_credentials.ts diff --git a/examples/oauth4webapi/pkce_code.ts b/examples/oauth4webapi/pkce_code.ts deleted file mode 100644 index 4b8e34b..0000000 --- a/examples/oauth4webapi/pkce_code.ts +++ /dev/null @@ -1,222 +0,0 @@ -import * as oauth from "oauth4webapi"; -import { OAUTH_SCOPES } from "@soundify/web-api/auth"; -import { z } from "zod"; -import { load } from "std/dotenv/mod.ts"; -import { Application, Router } from "oak"; - -await load({ export: true }); - -const env = z - .object({ - SPOTIFY_CLIENT_ID: z.string(), - SPOTIFY_REDIRECT_URI: z.string().url(), - }) - .parse(Deno.env.toObject()); - -const issuer = new URL("https://accounts.spotify.com/"); -const authServer = await oauth.processDiscoveryResponse( - issuer, - await oauth.discoveryRequest(issuer), -); - -const oauthClient: oauth.Client = { - client_id: env.SPOTIFY_CLIENT_ID, - token_endpoint_auth_method: "none", -}; - -class OAuthError extends Error { - constructor(public readonly params: oauth.OAuth2Error) { - super( - params.error + - (params.error_description ? " : " + params.error_description : ""), - ); - } -} - -async function createAuthUrl(codeVerifier: string, state: string) { - const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier); - - const authUrl = new URL(authServer.authorization_endpoint!); - for ( - const [key, value] of Object.entries({ - client_id: env.SPOTIFY_CLIENT_ID, - redirect_uri: env.SPOTIFY_REDIRECT_URI, - response_type: "code", - code_challenge: codeChallenge, - code_challenge_method: "S256", - state, - scope: Object.values(OAUTH_SCOPES).join(" "), - }) - ) { - authUrl.searchParams.set(key, value); - } - - return authUrl; -} - -async function processOAuthCallback( - url: URL, - codeVerifier: string, - expectedState: string, -) { - const params = oauth.validateAuthResponse( - authServer, - oauthClient, - url, - expectedState, - ); - - if (oauth.isOAuth2Error(params)) { - throw new OAuthError(params); - } - - const response = await oauth.authorizationCodeGrantRequest( - authServer, - oauthClient, - params, - env.SPOTIFY_REDIRECT_URI, - codeVerifier, - ); - const result = await oauth.processAuthorizationCodeOAuth2Response( - authServer, - oauthClient, - response, - ); - - if (oauth.isOAuth2Error(result)) { - throw new OAuthError(result); - } - - return result; -} - -async function processRefresh(refreshToken: string) { - const response = await oauth.refreshTokenGrantRequest( - authServer, - oauthClient, - refreshToken, - ); - const result = await oauth.processRefreshTokenResponse( - authServer, - oauthClient, - response, - ); - - if (oauth.isOAuth2Error(result)) { - throw new OAuthError(result); - } - - return result; -} - -const app = new Application(); -const router = new Router(); - -router.get("/login", async (ctx) => { - const codeVerifier = oauth.generateRandomCodeVerifier(); - await ctx.cookies.set("code_verifier", codeVerifier, { - httpOnly: true, - path: "/callback", - }); - - const state = oauth.generateRandomState(); - await ctx.cookies.set("state", state, { - httpOnly: true, - path: "/callback", - }); - - const authUrl = await createAuthUrl(codeVerifier, state); - return ctx.response.redirect(authUrl); -}); - -router.get("/callback", async (ctx) => { - const codeVerifier = await ctx.cookies.get("code_verifier"); - if (!codeVerifier) { - ctx.response.status = 400; - ctx.response.body = "Missing code_verifier"; - return; - } - const state = await ctx.cookies.get("state"); - if (!state) { - ctx.response.status = 400; - ctx.response.body = "Missing state"; - return; - } - - try { - const result = await processOAuthCallback( - ctx.request.url, - codeVerifier, - state, - ); - if (!result.refresh_token) { - throw new Error("Missing refresh_token"); - } - - await ctx.cookies.set("refresh_token", result.refresh_token, { - httpOnly: true, - path: "/refresh", - }); - await ctx.cookies.set("access_token", result.access_token, { - httpOnly: true, - }); - - ctx.response.type = "application/json"; - ctx.response.body = JSON.stringify(result); - ctx.response.status = 200; - } catch (error) { - console.log(error); - ctx.response.status = 500; - ctx.response.body = error.message; - } finally { - await ctx.cookies.delete("code_verifier", { - httpOnly: true, - path: "/callback", - }); - await ctx.cookies.delete("state", { - httpOnly: true, - path: "/callback", - }); - } -}); - -router.get("/refresh", async (ctx) => { - const refreshToken = await ctx.cookies.get("refresh_token"); - if (!refreshToken) { - ctx.response.status = 400; - ctx.response.body = "Missing refresh token"; - return; - } - - try { - const result = await processRefresh(refreshToken); - if (!result.refresh_token) { - throw new Error("Missing refresh_token"); - } - - await ctx.cookies.set("refresh_token", result.refresh_token, { - httpOnly: true, - path: "/refresh", - }); - await ctx.cookies.set("access_token", result.access_token, { - httpOnly: true, - }); - - ctx.response.type = "application/json"; - ctx.response.body = JSON.stringify(result); - ctx.response.status = 200; - } catch (error) { - console.log(error); - ctx.response.status = 500; - ctx.response.body = error.message; - } -}); - -app.use(router.routes()); -app.use(router.allowedMethods()); - -app.addEventListener( - "listen", - ({ hostname, port }) => console.log(`http://${hostname}:${port}/login`), -); -await app.listen({ port: 3000 }); diff --git a/examples/pkce/auth.ts b/examples/pkce/auth.ts new file mode 100644 index 0000000..148102b --- /dev/null +++ b/examples/pkce/auth.ts @@ -0,0 +1,128 @@ +import * as oauth from "oauth4webapi"; +import { OAuthScope } from "@soundify/web-api/auth"; +import { env } from "./env.ts"; + +const issuer = new URL("https://accounts.spotify.com/"); +const authServer = await oauth.processDiscoveryResponse( + issuer, + await oauth.discoveryRequest(issuer), +); + +const oauthClient: oauth.Client = { + client_id: env.SPOTIFY_CLIENT_ID, + token_endpoint_auth_method: "none", +}; + +export class OAuthError extends Error { + constructor(public readonly params: oauth.OAuth2Error) { + super( + params.error + + (params.error_description ? " : " + params.error_description : ""), + ); + } +} + +export function createAuthUrl( + codeChallenge: string, + scopes?: OAuthScope[], + state?: string, +) { + const authUrl = new URL(authServer.authorization_endpoint!); + + for ( + const [key, value] of Object.entries({ + client_id: env.SPOTIFY_CLIENT_ID, + redirect_uri: env.SPOTIFY_REDIRECT_URI, + response_type: "code", + code_challenge: codeChallenge, + code_challenge_method: "S256", + }) + ) { + authUrl.searchParams.set(key, value); + } + + if (scopes && scopes.length > 0) { + authUrl.searchParams.set("scope", scopes.join(" ")); + } + + if (state) { + authUrl.searchParams.set("state", state); + } + + return authUrl; +} + +export async function processCallback( + url: URL, + codeVerifier: string, + expectedState?: string, +) { + const params = oauth.validateAuthResponse( + authServer, + oauthClient, + url, + expectedState, + ); + + if (oauth.isOAuth2Error(params)) { + throw new OAuthError(params); + } + + const response = await oauth.authorizationCodeGrantRequest( + authServer, + oauthClient, + params, + env.SPOTIFY_REDIRECT_URI, + codeVerifier, + ); + const result = await oauth.processAuthorizationCodeOAuth2Response( + authServer, + oauthClient, + response, + ); + + if (oauth.isOAuth2Error(result)) { + throw new OAuthError(result); + } + + if (!result.refresh_token) { + throw new Error("No refresh token in response"); + } + + return result as { + refresh_token: string; + access_token: string; + token_type: string; + expires_in?: number; + scope?: string; + }; +} + +export async function refresh(refreshToken: string) { + const response = await oauth.refreshTokenGrantRequest( + authServer, + oauthClient, + refreshToken, + ); + const result = await oauth.processRefreshTokenResponse( + authServer, + oauthClient, + response, + ); + + if (oauth.isOAuth2Error(result)) { + throw new OAuthError(result); + } + + if (!result.refresh_token) { + throw new Error("No refresh token in response"); + } + + return result as { + refresh_token: string; + access_token: string; + token_type: string; + expires_in?: number; + scope?: string; + }; +} diff --git a/examples/pkce/env.ts b/examples/pkce/env.ts new file mode 100644 index 0000000..63966ea --- /dev/null +++ b/examples/pkce/env.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { load } from "std/dotenv/mod.ts"; + +await load({ export: true }); + +export const env = z + .object({ + SPOTIFY_CLIENT_ID: z.string(), + // we don't need the client secret for PKCE + // so this is safe to use in the browser + SPOTIFY_REDIRECT_URI: z.string().url(), + }) + .parse(Deno.env.toObject()); diff --git a/examples/pkce/main.tsx b/examples/pkce/main.tsx new file mode 100644 index 0000000..64984d2 --- /dev/null +++ b/examples/pkce/main.tsx @@ -0,0 +1,150 @@ +import { Application, Router } from "oak"; +import { renderToString } from "preact-render-to-string"; +import { createAuthUrl, processCallback, refresh } from "./auth.ts"; +import { + calculatePKCECodeChallenge, + generateRandomCodeVerifier, + generateRandomState, +} from "oauth4webapi"; +import { OAUTH_SCOPES } from "@soundify/web-api/auth"; +import { getCurrentUser, SpotifyClient } from "@soundify/web-api"; +import type { SecureCookieMap } from "oak/deps.ts"; + +const app = new Application(); +const router = new Router(); + +router.get("/callback", async (ctx) => { + const codeVerifier = await ctx.cookies.get("code_verifier"); + if (!codeVerifier) { + ctx.response.status = 400; + ctx.response.body = "Missing code_verifier"; + return; + } + const state = await ctx.cookies.get("state"); + if (!state) { + ctx.response.status = 400; + ctx.response.body = "Missing state"; + return; + } + + try { + const result = await processCallback( + ctx.request.url, + codeVerifier, + state, + ); + if (!result.refresh_token) { + throw new Error("Missing refresh_token"); + } + + await ctx.cookies.set("refresh_token", result.refresh_token, { + httpOnly: true, + }); + await ctx.cookies.set("access_token", result.access_token, { + httpOnly: true, + }); + + ctx.response.redirect("/"); + } catch (error) { + console.log(error); + ctx.response.status = 500; + ctx.response.body = error.message; + } finally { + await ctx.cookies.delete("code_verifier", { + httpOnly: true, + path: "/callback", + }); + await ctx.cookies.delete("state", { + httpOnly: true, + path: "/callback", + }); + } +}); + +const createRefresher = (cookies: SecureCookieMap) => { + return async () => { + const refreshToken = await cookies.get("refresh_token"); + if (!refreshToken) { + throw new Error("Missing refresh token"); + } + + const result = await refresh(refreshToken); + if (!result.refresh_token) { + throw new Error("Missing refresh_token"); + } + + await cookies.set("refresh_token", result.refresh_token, { + httpOnly: true, + }); + await cookies.set("access_token", result.access_token, { + httpOnly: true, + }); + + return result.access_token; + }; +}; + +router.get("/", async (ctx) => { + try { + const accessToken = await ctx.cookies.get("access_token") || null; + const spotifyClient = new SpotifyClient(accessToken, { + refresher: createRefresher(ctx.cookies), + }); + + const user = await getCurrentUser(spotifyClient); + + ctx.response.type = "text/html"; + ctx.response.body = renderToString( + + + Soundify PKCE example + + +

Soundify PKCE example

+

{`Hello, ${user.display_name}!`}

+ + , + ); + } catch (error) { + const codeVerifier = generateRandomCodeVerifier(); + await ctx.cookies.set("code_verifier", codeVerifier, { + httpOnly: true, + path: "/callback", + }); + + const state = generateRandomState(); + await ctx.cookies.set("state", state, { + httpOnly: true, + path: "/callback", + }); + + const codeChallenge = await calculatePKCECodeChallenge(codeVerifier); + const authUrl = createAuthUrl( + codeChallenge, + Object.values(OAUTH_SCOPES), + state, + ); + + ctx.response.type = "text/html"; + ctx.response.body = renderToString( + + + Soundify PKCE example + + +

Soundify PKCE example

+ Login with Spotify + + , + ); + } +}); + +app.use(router.routes()); +app.use(router.allowedMethods()); + +app.addEventListener( + "listen", + ({ hostname, port }) => console.log(`http://${hostname}:${port}/`), +); +await app.listen({ port: 3000 }); diff --git a/tsconfig.json b/tsconfig.json index 0d41db1..2d7cdc8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,4 +27,4 @@ "allowUnusedLabels": false, "skipLibCheck": true } -} +} \ No newline at end of file From 1523a829d59f8ac540d68906cd102a0599bf7683 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Fri, 22 Mar 2024 10:44:06 +0100 Subject: [PATCH 07/18] chore: add more docs comments --- auth.ts | 3 ++ client.ts | 50 ++++++++++++++++++++---- endpoints/category/category.types.ts | 3 +- endpoints/general.types.ts | 28 ++++++------- endpoints/mod.ts | 6 +++ endpoints/playlist/playlist.endpoints.ts | 6 +-- mod.ts | 43 ++++++++++++++++++++ pagination.ts | 4 +- shared.ts | 4 -- 9 files changed, 115 insertions(+), 32 deletions(-) diff --git a/auth.ts b/auth.ts index 44106d3..c6388dc 100644 --- a/auth.ts +++ b/auth.ts @@ -1,5 +1,8 @@ export const SPOTIFY_AUTH_URL = "https://accounts.spotify.com/"; +/** + * @see https://developer.spotify.com/documentation/web-api/concepts/scopes + */ export const OAUTH_SCOPES = { /** * Write access to user-provided images. diff --git a/client.ts b/client.ts index 3d9d42b..2cc6631 100644 --- a/client.ts +++ b/client.ts @@ -52,8 +52,6 @@ export class SpotifyError extends Error { } } -const APP_JSON = "application/json"; - const getBodyMessage = ( body: RegularErrorObject | string, ): string => { @@ -109,11 +107,11 @@ interface MiddlewareOptions extends Omit { headers: Headers; } -type MiddlewareHandler = ( +type NextMiddleware = ( url: URL, options: MiddlewareOptions, ) => Promise; -export type Middleware = (next: MiddlewareHandler) => MiddlewareHandler; +export type Middleware = (next: NextMiddleware) => NextMiddleware; /** * Interface for making HTTP requests to the Spotify API. @@ -142,12 +140,19 @@ export interface SpotifyClinetOptions { */ baseUrl?: string; /** - * @returns new access token + * Function that will be called when the access token is expired. + * @returns New access token. */ refresher?: () => Promise; /** * Weather to wait for rate limit or not. \ * Function can be used to decide dynamically based on the `retryAfter` time in seconds. + * ```ts + * { + * waitForRateLimit: (retryAfter) => retryAfter < 10, + * } + * ``` + * **Be aware that this can cause a long delay in the response.** * * @default false */ @@ -175,6 +180,37 @@ const createFailedToAuthorizeError = () => "[SpotifyClient] accessToken or refresher is required to make requests.", ); +const APP_JSON = "application/json"; + +/** + * The main class that provides fetch method for making requests with build-in functionality for token refreshing, error handling and more. + * + * @example + * ```ts + * import { SpotifyClient } from "@soundify/web-api"; + * + * // with access token + * const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); + * + * // with automatic token refreshing + * const client = new SpotifyClient(null, { + * // your custom refresher here + * refresher: () => Promise.resolve("NEW_ACCESS_TOKEN"), + * }); + * + * // with custom options + * const client = new SpotifyClient("YOUR_ACCESS_TOKEN", { + * waitForRateLimit: true, + * middlewares: [(next) => (url, options) => { + * options.headers.set("X-Custom-Header", "custom-value"); + * return next(url, options); + * }], + * }) + * + * const res = await client.fetch("/v1/me"); + * const user = await res.json(); + * ``` + */ export class SpotifyClient implements HTTPClient { private readonly baseUrl: string; private refreshInProgress: Promise | null = null; @@ -203,7 +239,7 @@ export class SpotifyClient implements HTTPClient { if (opts.query) { for (const key in opts.query) { const value = opts.query[key]; - if (typeof value !== "undefined") { + if (value !== undefined) { url.searchParams.set(key, value.toString()); } } @@ -223,7 +259,7 @@ export class SpotifyClient implements HTTPClient { const wrappedFetch = (this.options.middlewares || []).reduceRight( (next, mw) => mw(next), - (this.options.fetch || globalThis.fetch) as MiddlewareHandler, + (this.options.fetch || globalThis.fetch) as NextMiddleware, ); let isRefreshed = false; diff --git a/endpoints/category/category.types.ts b/endpoints/category/category.types.ts index 6ad4b8a..49824c9 100644 --- a/endpoints/category/category.types.ts +++ b/endpoints/category/category.types.ts @@ -1,4 +1,3 @@ -import type { NonNullableObject } from "../../shared.ts"; import type { Image } from "../general.types.ts"; export interface Category { @@ -9,7 +8,7 @@ export interface Category { /** * The category icon, in various sizes. */ - icons: NonNullableObject[]; + icons: Image[]; /** * The Spotify category ID of the category. */ diff --git a/endpoints/general.types.ts b/endpoints/general.types.ts index 7a865ea..c8dcf93 100644 --- a/endpoints/general.types.ts +++ b/endpoints/general.types.ts @@ -81,14 +81,14 @@ export type PagingOptions = { */ export type RestrictionsReason = "market" | "product" | "explicit"; -export type Restrictions = { +export interface Restrictions { /** * The reason for the restriction. * * Episodes may be restricted if the content is not available in a given market, to the user's subscription type, or when the user's account is set to not play explicit content. */ reason: RestrictionsReason; -}; +} /** * The precision with which `release_date` value is known. @@ -110,7 +110,7 @@ export interface Image { width: number | null; } -export type ResumePoint = { +export interface ResumePoint { /** * Whether or not the episode has been fully played by the user. */ @@ -119,9 +119,9 @@ export type ResumePoint = { * The user's most recent position in the episode in milliseconds */ resume_position_ms: number; -}; +} -export type Followers = { +export interface Followers { /** * This will always be set to null, as the Web API does not support it at the moment. */ @@ -130,25 +130,25 @@ export type Followers = { * The total number of followers. */ total: number; -}; +} -export type Author = { +export interface Author { /** * The name of the author. */ name: string; -}; +} -export type Narrator = { +export interface Narrator { /** * The name of the narrator. */ name: string; -}; +} -export type ExternalUrls = { +export interface ExternalUrls { spotify: string; -}; +} export interface ExternalIds { /** @@ -168,7 +168,7 @@ export interface ExternalIds { /** * The copyright object contains the type and the name of copyright. */ -export type Copyright = { +export interface Copyright { /** * The copyright text for this content. */ @@ -179,4 +179,4 @@ export type Copyright = { * P = the sound recording (performance) copyright */ type: "C" | "P"; -}; +} diff --git a/endpoints/mod.ts b/endpoints/mod.ts index 0171e67..8309b2b 100644 --- a/endpoints/mod.ts +++ b/endpoints/mod.ts @@ -1,3 +1,9 @@ +/** + * @module + * This module contains all the endpoints and types for the Spotify Web API. + * + * @see https://developer.spotify.com/documentation/web-api + */ export * from "./user/user.types.ts"; export * from "./user/user.endpoints.ts"; export * from "./track/track.types.ts"; diff --git a/endpoints/playlist/playlist.endpoints.ts b/endpoints/playlist/playlist.endpoints.ts index ff1ebce..c658bfb 100644 --- a/endpoints/playlist/playlist.endpoints.ts +++ b/endpoints/playlist/playlist.endpoints.ts @@ -1,4 +1,4 @@ -import type { NonNullableObject, Prettify } from "../../shared.ts"; +import type { Prettify } from "../../shared.ts"; import type { Image, PagingObject, PagingOptions } from "../general.types.ts"; import type { FeaturedPlaylists, @@ -397,9 +397,9 @@ export const getCategoryPlaylists = async ( export const getPlaylistCoverImage = async ( client: HTTPClient, playlistId: string, -): Promise[]> => { +): Promise => { const res = await client.fetch(`/v1/playlists/${playlistId}/images`); - return res.json() as Promise[]>; + return res.json() as Promise; }; /** diff --git a/mod.ts b/mod.ts index 600af49..a5b0de2 100644 --- a/mod.ts +++ b/mod.ts @@ -1,3 +1,46 @@ +/** + * @module + * HTTP Client library that provides a simple interface to the Spotify Web API. + * + * ## SpotifyClient + * The main class that provides fetch method for making requests with build-in functionality for token refreshing, error handling and more. + * + * @example + * ```ts + * import { SpotifyClient } from "@soundify/web-api"; + * + * // with access token + * const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); + * + * // with automatic token refreshing + * const client = new SpotifyClient(null, { + * // your custom refresher here + * refresher: () => Promise.resolve("NEW_ACCESS_TOKEN"), + * }); + * + * const res = await client.fetch("/v1/me"); + * const user = await res.json(); + * ``` + * + * ## Endpoints + * Functions that utilize the `SpotifyClient` to make requests to the Spotify Web API. + * + * @example + * ``` + * import { SpotifyClient, getCurrentUser } from "@soundify/web-api"; + * + * const client = new SpotifyClient("YOUR_ACCESS_TOKEN"); + * const user = await getCurrentUser(client); + * + * // How endpoint functions are built + * const getCurrentUser = async (client: SpotifyClient) => { + * const res = await client.fetch("/v1/me"); + * return await res.json(); + * } + * ``` + * + * @see https://developer.spotify.com/documentation/web-api + */ export { type FetchLikeOptions, type HTTPClient, diff --git a/pagination.ts b/pagination.ts index 452c93a..911be4a 100644 --- a/pagination.ts +++ b/pagination.ts @@ -1,4 +1,4 @@ -export type PageIteratorOptions = { +export interface PageIteratorOptions { /** * The Spotify API does not allow you to use a negative offset, but you can do so with this property. This will be useful when, for example, you want to get the last 100 elements. * @@ -7,7 +7,7 @@ export type PageIteratorOptions = { * @default 0 */ initialOffset?: number; -}; +} /** * A helper class which allows you to iterate over items in a paginated API response with javascript async iterators. diff --git a/shared.ts b/shared.ts index 50797b4..20cc8cb 100644 --- a/shared.ts +++ b/shared.ts @@ -1,7 +1,3 @@ -export type NonNullableObject = { - [K in keyof T]: NonNullable; -}; - export type RequireAtLeastOne = { [K in keyof T]-?: & Required> From 31bcace71062667a4fb5cb666811019902308605 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Fri, 22 Mar 2024 10:54:39 +0100 Subject: [PATCH 08/18] chore: add jsr badge --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 55e072c..08c30c7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ Github stars + + jsr registry score +

@@ -31,11 +34,7 @@ The package doesn't depend on runtime specific apis, so you should be able to use it without any problems everywhere. ```bash -pnpm add @soundify/web-api -``` - -```bash -bun install @soundify/web-api +npm install @soundify/web-api ``` ```jsonc @@ -47,6 +46,12 @@ bun install @soundify/web-api } ``` +Install from [JSR registry](https://jsr.io/@soundify/web-api) + +```bash +deno add @soundify/web-api +``` + ## Getting Started Soundify has a very simple structure. It consists of a `SpotifyClient` capable From 8dbb1284e786fb9b42ecf81edfa8ad9fca59a078 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Fri, 22 Mar 2024 11:02:16 +0100 Subject: [PATCH 09/18] chore: release 1.1.0 --- deno.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deno.json b/deno.json index eeb7203..977c4fc 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.0.0", + "version": "1.1.0", "exports": { ".": "./mod.ts", "./pagination": "./pagination.ts", diff --git a/package.json b/package.json index 83eb445..7ea7dde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.0.0", + "version": "1.1.0", "description": "🎧 Spotify Web API client for js/ts runtime environments", "type": "module", "main": "dist/mod.cjs", From b3d070eef47f04baa19333fe0816f12a7ac789e7 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Fri, 22 Mar 2024 11:07:34 +0100 Subject: [PATCH 10/18] chore: fix github release workflow --- .github/workflows/release.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3650876..1e90a9d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,8 +1,5 @@ name: Release -permissions: - contents: write - on: push: tags: @@ -13,8 +10,7 @@ jobs: if: github.event.base_ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: - contents: read - id-token: write + contents: write steps: - uses: actions/checkout@v3 with: From 34d6dbe53494447f1e8b8a89fd8e0c56700bba27 Mon Sep 17 00:00:00 2001 From: Brayden Babbitt Date: Mon, 15 Jul 2024 05:04:26 -0600 Subject: [PATCH 11/18] fix: fixed inaccurate saved tracks return type (#49) --- endpoints/track/track.endpoints.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/endpoints/track/track.endpoints.ts b/endpoints/track/track.endpoints.ts index e94b7c3..39dd42c 100644 --- a/endpoints/track/track.endpoints.ts +++ b/endpoints/track/track.endpoints.ts @@ -68,11 +68,13 @@ export type GetSavedTracksOpts = Prettify< export const getSavedTracks = async ( client: HTTPClient, options: GetSavedTracksOpts, -): Promise> => { +): Promise> => { const res = await client.fetch("/v1/me/tracks", { query: options, }); - return res.json() as Promise>; + return res.json() as Promise< + PagingObject<{ added_at: string; track: Track }> + >; }; /** From 018c02e06115255638ea3176c42ba7daf3b391c5 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 15 Jul 2024 13:19:02 +0200 Subject: [PATCH 12/18] chore: update dependencies and package version to 1.1.1 --- .vscode/settings.json | 5 +- deno.lock | 4 +- package.json | 9 +- pnpm-lock.yaml | 3646 ++++++++++++++++++++++------------------- 4 files changed, 2003 insertions(+), 1661 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b0e0c6..f52bc50 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,9 @@ { + "deno.enable": true, "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" }, "[typescriptreact]": { "editor.defaultFormatter": "denoland.vscode-deno" - }, -} \ No newline at end of file + } +} diff --git a/deno.lock b/deno.lock index 13ad738..0b9d3de 100644 --- a/deno.lock +++ b/deno.lock @@ -194,8 +194,8 @@ "packageJson": { "dependencies": [ "npm:all-contributors-cli@6.26.1", - "npm:tsup@8.0.2", - "npm:typescript@5.3.3" + "npm:tsup@8.1.0", + "npm:typescript@5.5.3" ] } } diff --git a/package.json b/package.json index 7ea7dde..150c894 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.0", + "version": "1.1.1", "description": "🎧 Spotify Web API client for js/ts runtime environments", "type": "module", "main": "dist/mod.cjs", @@ -36,8 +36,8 @@ }, "devDependencies": { "all-contributors-cli": "6.26.1", - "typescript": "5.3.3", - "tsup": "8.0.2" + "typescript": "5.5.3", + "tsup": "8.1.0" }, "author": { "name": "Artem Melnyk", @@ -58,5 +58,6 @@ "music", "sdk", "soundify" - ] + ], + "packageManager": "pnpm@9.5.0+sha256.dbdf5961c32909fb030595a9daa1dae720162e658609a8f92f2fa99835510ca5" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed1fd74..ee54154 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true @@ -12,11 +12,11 @@ importers: specifier: 6.26.1 version: 6.26.1 tsup: - specifier: 8.0.2 - version: 8.0.2(typescript@5.3.3) + specifier: 8.1.0 + version: 8.1.0(postcss@8.4.23)(typescript@5.5.3) typescript: - specifier: 5.3.3 - version: 5.3.3 + specifier: 5.5.3 + version: 5.5.3 examples/next-ssr: dependencies: @@ -146,935 +146,566 @@ importers: packages: - /@ampproject/remapping@2.2.1: + '@ampproject/remapping@2.2.1': resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - dev: true - /@babel/code-frame@7.21.4: + '@babel/code-frame@7.21.4': resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - dev: true - /@babel/compat-data@7.21.7: + '@babel/compat-data@7.21.7': resolution: {integrity: sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==} engines: {node: '>=6.9.0'} - dev: true - /@babel/core@7.21.5: + '@babel/core@7.21.5': resolution: {integrity: sha512-9M398B/QH5DlfCOTKDZT1ozXr0x8uBEeFd+dJraGUZGiaNpGCDVGCc14hZexsMblw3XxltJ+6kSvogp9J+5a9g==} engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.5) - '@babel/helper-module-transforms': 7.21.5 - '@babel/helpers': 7.21.5 - '@babel/parser': 7.21.5 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/generator@7.21.5: + '@babel/generator@7.21.5': resolution: {integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - dev: true - /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.5): + '@babel/helper-compilation-targets@7.21.5': resolution: {integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.21.7 - '@babel/core': 7.21.5 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.5 - lru-cache: 5.1.1 - semver: 6.3.0 - dev: true - /@babel/helper-environment-visitor@7.21.5: + '@babel/helper-environment-visitor@7.21.5': resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-function-name@7.21.0: + '@babel/helper-function-name@7.21.0': resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.21.5 - dev: true - /@babel/helper-hoist-variables@7.18.6: + '@babel/helper-hoist-variables@7.18.6': resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-module-imports@7.21.4: + '@babel/helper-module-imports@7.21.4': resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.4 - dev: true - /@babel/helper-module-transforms@7.21.5: + '@babel/helper-module-transforms@7.21.5': resolution: {integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-module-imports': 7.21.4 - '@babel/helper-simple-access': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-plugin-utils@7.20.2: + '@babel/helper-plugin-utils@7.20.2': resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-simple-access@7.21.5: + '@babel/helper-simple-access@7.21.5': resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-split-export-declaration@7.18.6: + '@babel/helper-split-export-declaration@7.18.6': resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/helper-string-parser@7.19.4: + '@babel/helper-string-parser@7.19.4': resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-string-parser@7.21.5: + '@babel/helper-string-parser@7.21.5': resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-identifier@7.19.1: + '@babel/helper-validator-identifier@7.19.1': resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-option@7.21.0: + '@babel/helper-validator-option@7.21.0': resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helpers@7.21.5: + '@babel/helpers@7.21.5': resolution: {integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/highlight@7.18.6: + '@babel/highlight@7.18.6': resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - /@babel/parser@7.21.5: + '@babel/parser@7.21.5': resolution: {integrity: sha512-J+IxH2IsxV4HbnTrSWgMAQj0UEo61hDA4Ny8h8PCX0MLXiibqHbqIOVneqdocemSBc22VpBKxt4J6FQzy9HarQ==} engines: {node: '>=6.0.0'} hasBin: true - dependencies: - '@babel/types': 7.21.5 - dev: true - /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.5): + '@babel/plugin-transform-react-jsx-self@7.21.0': resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.5 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.5): + '@babel/plugin-transform-react-jsx-source@7.19.6': resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.5 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/runtime@7.21.5: + '@babel/runtime@7.21.5': resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.11 - dev: true - /@babel/template@7.20.7: + '@babel/template@7.20.7': resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/parser': 7.21.5 - '@babel/types': 7.21.5 - dev: true - /@babel/traverse@7.21.5: + '@babel/traverse@7.21.5': resolution: {integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.5 - '@babel/types': 7.21.5 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/types@7.21.4: + '@babel/types@7.21.4': resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - dev: true - /@babel/types@7.21.5: + '@babel/types@7.21.5': resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - dev: true - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.17.15: + '@esbuild/android-arm64@0.17.15': resolution: {integrity: sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.17.15: + '@esbuild/android-arm@0.17.15': resolution: {integrity: sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.17.15: + '@esbuild/android-x64@0.17.15': resolution: {integrity: sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.17.15: + '@esbuild/darwin-arm64@0.17.15': resolution: {integrity: sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.17.15: + '@esbuild/darwin-x64@0.17.15': resolution: {integrity: sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.17.15: + '@esbuild/freebsd-arm64@0.17.15': resolution: {integrity: sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.17.15: + '@esbuild/freebsd-x64@0.17.15': resolution: {integrity: sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.17.15: + '@esbuild/linux-arm64@0.17.15': resolution: {integrity: sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.17.15: + '@esbuild/linux-arm@0.17.15': resolution: {integrity: sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.17.15: + '@esbuild/linux-ia32@0.17.15': resolution: {integrity: sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.17.15: + '@esbuild/linux-loong64@0.17.15': resolution: {integrity: sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.17.15: + '@esbuild/linux-mips64el@0.17.15': resolution: {integrity: sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.17.15: + '@esbuild/linux-ppc64@0.17.15': resolution: {integrity: sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.17.15: + '@esbuild/linux-riscv64@0.17.15': resolution: {integrity: sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.17.15: + '@esbuild/linux-s390x@0.17.15': resolution: {integrity: sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.17.15: + '@esbuild/linux-x64@0.17.15': resolution: {integrity: sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.17.15: + '@esbuild/netbsd-x64@0.17.15': resolution: {integrity: sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.17.15: + '@esbuild/openbsd-x64@0.17.15': resolution: {integrity: sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.17.15: + '@esbuild/sunos-x64@0.17.15': resolution: {integrity: sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.17.15: + '@esbuild/win32-arm64@0.17.15': resolution: {integrity: sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.17.15: + '@esbuild/win32-ia32@0.17.15': resolution: {integrity: sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.17.15: + '@esbuild/win32-x64@0.17.15': resolution: {integrity: sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@isaacs/cliui@8.0.2: + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true - /@jridgewell/gen-mapping@0.3.3: + '@jridgewell/gen-mapping@0.3.3': resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.18 - dev: true - /@jridgewell/resolve-uri@3.1.0: + '@jridgewell/resolve-uri@3.1.0': resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/set-array@1.1.2: + '@jridgewell/set-array@1.1.2': resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/sourcemap-codec@1.4.14: + '@jridgewell/sourcemap-codec@1.4.14': resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true - /@jridgewell/trace-mapping@0.3.18: + '@jridgewell/trace-mapping@0.3.18': resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - /@next/env@13.4.6: + '@next/env@13.4.6': resolution: {integrity: sha512-nqUxEtvDqFhmV1/awSg0K2XHNwkftNaiUqCYO9e6+MYmqNObpKVl7OgMkGaQ2SZnFx5YqF0t60ZJTlyJIDAijg==} - dev: false - /@next/swc-darwin-arm64@13.4.6: + '@next/swc-darwin-arm64@13.4.6': resolution: {integrity: sha512-ahi6VP98o4HV19rkOXPSUu+ovfHfUxbJQ7VVJ7gL2FnZRr7onEFC1oGQ6NQHpm8CxpIzSSBW79kumlFMOmZVjg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-darwin-x64@13.4.6: + '@next/swc-darwin-x64@13.4.6': resolution: {integrity: sha512-13cXxKFsPJIJKzUqrU5XB1mc0xbUgYsRcdH6/rB8c4NMEbWGdtD4QoK9ShN31TZdePpD4k416Ur7p+deMIxnnA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-gnu@13.4.6: + '@next/swc-linux-arm64-gnu@13.4.6': resolution: {integrity: sha512-Ti+NMHEjTNktCVxNjeWbYgmZvA2AqMMI2AMlzkXsU7W4pXCMhrryAmAIoo+7YdJbsx01JQWYVxGe62G6DoCLaA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-musl@13.4.6: + '@next/swc-linux-arm64-musl@13.4.6': resolution: {integrity: sha512-OHoC6gO7XfjstgwR+z6UHKlvhqJfyMtNaJidjx3sEcfaDwS7R2lqR5AABi8PuilGgi0BO0O0sCXqLlpp3a0emQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-gnu@13.4.6: + '@next/swc-linux-x64-gnu@13.4.6': resolution: {integrity: sha512-zHZxPGkUlpfNJCboUrFqwlwEX5vI9LSN70b8XEb0DYzzlrZyCyOi7hwDp/+3Urm9AB7YCAJkgR5Sp1XBVjHdfQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-musl@13.4.6: + '@next/swc-linux-x64-musl@13.4.6': resolution: {integrity: sha512-K/Y8lYGTwTpv5ME8PSJxwxLolaDRdVy+lOd9yMRMiQE0BLUhtxtCWC9ypV42uh9WpLjoaD0joOsB9Q6mbrSGJg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-arm64-msvc@13.4.6: + '@next/swc-win32-arm64-msvc@13.4.6': resolution: {integrity: sha512-U6LtxEUrjBL2tpW+Kr1nHCSJWNeIed7U7l5o7FiKGGwGgIlFi4UHDiLI6TQ2lxi20fAU33CsruV3U0GuzMlXIw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-ia32-msvc@13.4.6: + '@next/swc-win32-ia32-msvc@13.4.6': resolution: {integrity: sha512-eEBeAqpCfhdPSlCZCayjCiyIllVqy4tcqvm1xmg3BgJG0G5ITiMM4Cw2WVeRSgWDJqQGRyyb+q8Y2ltzhXOWsQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-x64-msvc@13.4.6: + '@next/swc-win32-x64-msvc@13.4.6': resolution: {integrity: sha512-OrZs94AuO3ZS5tnqlyPRNgfWvboXaDQCi5aXGve3o3C+Sj0ctMUV9+Do+0zMvvLRumR8E0PTWKvtz9n5vzIsWw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - requiresBuild: true - dev: false - optional: true - /@nodelib/fs.scandir@2.1.5: + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - /@nodelib/fs.stat@2.0.5: + '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true - /@nodelib/fs.walk@1.2.8: + '@nodelib/fs.walk@1.2.8': resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - dev: true - /@pkgjs/parseargs@0.11.0: + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - requiresBuild: true - dev: true - optional: true - /@remix-run/router@1.6.3: + '@remix-run/router@1.6.3': resolution: {integrity: sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==} engines: {node: '>=14'} - dev: false - /@rollup/rollup-android-arm-eabi@4.12.0: + '@rollup/rollup-android-arm-eabi@4.12.0': resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.12.0: + '@rollup/rollup-android-arm64@4.12.0': resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.12.0: + '@rollup/rollup-darwin-arm64@4.12.0': resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.12.0: + '@rollup/rollup-darwin-x64@4.12.0': resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.12.0: + '@rollup/rollup-linux-arm-gnueabihf@4.12.0': resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.12.0: + '@rollup/rollup-linux-arm64-gnu@4.12.0': resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.12.0: + '@rollup/rollup-linux-arm64-musl@4.12.0': resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-gnu@4.12.0: + '@rollup/rollup-linux-riscv64-gnu@4.12.0': resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.12.0: + '@rollup/rollup-linux-x64-gnu@4.12.0': resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.12.0: + '@rollup/rollup-linux-x64-musl@4.12.0': resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.12.0: + '@rollup/rollup-win32-arm64-msvc@4.12.0': resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.12.0: + '@rollup/rollup-win32-ia32-msvc@4.12.0': resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.12.0: + '@rollup/rollup-win32-x64-msvc@4.12.0': resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@swc/helpers@0.5.1: + '@swc/helpers@0.5.1': resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} - dependencies: - tslib: 2.5.0 - dev: false - /@tanstack/query-core@4.29.14: + '@tanstack/query-core@4.29.14': resolution: {integrity: sha512-ElEAahtLWA7Y7c2Haw10KdEf2tx+XZl/Z8dmyWxZehxWK3TPL5qtXtb7kUEhvt/8u2cSP62fLxgh2qqzMMGnDQ==} - dev: false - /@tanstack/react-query@4.29.14(react-dom@18.2.0)(react@18.2.0): + '@tanstack/react-query@4.29.14': resolution: {integrity: sha512-wh4bd/QIy85YgTDBtj/7/9ZkpRX41QdZuUL8xKoSzuMCukXvAE1/oJ4p0F15lqQq9W3g2pgcbr2Aa+CNvqABhg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -1085,458 +716,256 @@ packages: optional: true react-native: optional: true - dependencies: - '@tanstack/query-core': 4.29.14 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - /@types/body-parser@1.19.2: + '@types/body-parser@1.19.2': resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} - dependencies: - '@types/connect': 3.4.35 - '@types/node': 20.3.1 - dev: true - /@types/connect@3.4.35: + '@types/connect@3.4.35': resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} - dependencies: - '@types/node': 20.3.1 - dev: true - /@types/cookie-parser@1.4.3: + '@types/cookie-parser@1.4.3': resolution: {integrity: sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==} - dependencies: - '@types/express': 4.17.17 - dev: true - /@types/cookie@0.4.1: + '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} - dev: false - /@types/estree@1.0.5: + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true - /@types/express-serve-static-core@4.17.33: + '@types/express-serve-static-core@4.17.33': resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} - dependencies: - '@types/node': 20.3.1 - '@types/qs': 6.9.7 - '@types/range-parser': 1.2.4 - dev: true - /@types/express@4.17.17: + '@types/express@4.17.17': resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} - dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.33 - '@types/qs': 6.9.7 - '@types/serve-static': 1.15.1 - dev: true - /@types/mime@3.0.1: + '@types/mime@3.0.1': resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} - dev: true - /@types/node@16.18.23: + '@types/node@16.18.23': resolution: {integrity: sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==} - dev: false - /@types/node@20.3.1: + '@types/node@20.3.1': resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} - /@types/prop-types@15.7.5: + '@types/prop-types@15.7.5': resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - /@types/qs@6.9.7: + '@types/qs@6.9.7': resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - dev: true - /@types/range-parser@1.2.4: + '@types/range-parser@1.2.4': resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - dev: true - /@types/react-dom@18.0.11: + '@types/react-dom@18.0.11': resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} - dependencies: - '@types/react': 18.2.13 - /@types/react@18.2.13: + '@types/react@18.2.13': resolution: {integrity: sha512-vJ+zElvi/Zn9cVXB5slX2xL8PZodPCwPRDpittQdw43JR2AJ5k3vKdgJJyneV/cYgIbLQUwXa9JVDvUZXGba+Q==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 - /@types/scheduler@0.16.3: + '@types/scheduler@0.16.3': resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} - /@types/serve-static@1.15.1: + '@types/serve-static@1.15.1': resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} - dependencies: - '@types/mime': 3.0.1 - '@types/node': 20.3.1 - dev: true - /@vitejs/plugin-react@3.1.0(vite@4.3.9): + '@vitejs/plugin-react@3.1.0': resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.1.0-beta.0 - dependencies: - '@babel/core': 7.21.5 - '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.5) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.5) - magic-string: 0.27.0 - react-refresh: 0.14.0 - vite: 4.3.9(@types/node@20.3.1) - transitivePeerDependencies: - - supports-color - dev: true - /accepts@1.3.8: + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - dev: false - /all-contributors-cli@6.26.1: + all-contributors-cli@6.26.1: resolution: {integrity: sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==} engines: {node: '>=4'} hasBin: true - dependencies: - '@babel/runtime': 7.21.5 - async: 3.2.4 - chalk: 4.1.2 - didyoumean: 1.2.2 - inquirer: 7.3.3 - json-fixer: 1.6.15 - lodash: 4.17.21 - node-fetch: 2.6.7 - pify: 5.0.0 - yargs: 15.4.1 - optionalDependencies: - prettier: 2.8.8 - transitivePeerDependencies: - - encoding - dev: true - /ansi-escapes@4.3.2: + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - dependencies: - type-fest: 0.21.3 - dev: true - /ansi-regex@5.0.1: + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true - /ansi-regex@6.0.1: + ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true - /ansi-styles@3.2.1: + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: true - /ansi-styles@4.3.0: + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - /ansi-styles@6.2.1: + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true - /any-promise@1.3.0: + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true - /anymatch@3.1.3: + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - /array-flatten@1.1.1: + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - dev: false - /array-union@2.1.0: + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - dev: true - /async@3.2.4: + async@3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - dev: true - /balanced-match@1.0.2: + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - /binary-extensions@2.2.0: + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - dev: true - /body-parser@1.20.1: + body-parser@1.20.1: resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - dev: false - /brace-expansion@2.0.1: + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - dev: true - /braces@3.0.2: + braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - /browserslist@4.21.5: + browserslist@4.21.5: resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - dependencies: - caniuse-lite: 1.0.30001476 - electron-to-chromium: 1.4.356 - node-releases: 2.0.10 - update-browserslist-db: 1.0.10(browserslist@4.21.5) - dev: true - /bundle-require@4.0.2(esbuild@0.19.12): + bundle-require@4.0.2: resolution: {integrity: sha512-jwzPOChofl67PSTW2SGubV9HBQAhhR2i6nskiOThauo9dzwDUgOWQScFVaJkjEfYX+UXiD+LEx8EblQMc2wIag==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.17' - dependencies: - esbuild: 0.19.12 - load-tsconfig: 0.2.5 - dev: true - /busboy@1.6.0: + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} - dependencies: - streamsearch: 1.1.0 - dev: false - /bytes@3.1.2: + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - dev: false - /cac@6.7.14: + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - dev: true - /call-bind@1.0.2: + call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.0 - dev: false - /camelcase@5.3.1: + camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - dev: true - /caniuse-lite@1.0.30001476: + caniuse-lite@1.0.30001476: resolution: {integrity: sha512-JmpktFppVSvyUN4gsLS0bShY2L9ZUslHLE72vgemBkS43JD2fOvKTKs+GtRwuxrtRGnwJFW0ye7kWRRlLJS9vQ==} - /chalk@2.4.2: + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: true - /chalk@4.1.2: + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - /chardet@0.7.0: + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: true - /chokidar@3.6.0: + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /cli-cursor@3.1.0: + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - dependencies: - restore-cursor: 3.1.0 - dev: true - /cli-width@3.0.0: + cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} - dev: true - /client-only@0.0.1: + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false - /cliui@6.0.0: + cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: true - /color-convert@1.9.3: + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: true - /color-convert@2.0.1: + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - /color-name@1.1.3: + color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true - /color-name@1.1.4: + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - /commander@4.1.1: + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: true - /content-disposition@0.5.4: + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - dependencies: - safe-buffer: 5.2.1 - dev: false - /content-type@1.0.5: + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - dev: false - /convert-source-map@1.9.0: + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true - /cookie-parser@1.4.6: + cookie-parser@1.4.6: resolution: {integrity: sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==} engines: {node: '>= 0.8.0'} - dependencies: - cookie: 0.4.1 - cookie-signature: 1.0.6 - dev: false - /cookie-signature@1.0.6: + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - dev: false - /cookie@0.4.1: + cookie@0.4.1: resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} engines: {node: '>= 0.6'} - dev: false - /cookie@0.5.0: + cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - dev: false - /cookies-next@2.1.2: + cookies-next@2.1.2: resolution: {integrity: sha512-czxcfqVaQlo0Q/3xMgp/2jpspsuLJrIm6D37wlmibP3DAcYT315c8UxQmDMohhAT/GRWpaHzpDEFANBjzTFQGg==} - dependencies: - '@types/cookie': 0.4.1 - '@types/node': 16.18.23 - cookie: 0.4.1 - dev: false - /cross-spawn@7.0.3: + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - /csstype@3.1.2: + csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - /debug@2.6.9: + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.0.0 - dev: false - /debug@4.3.4: + debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} peerDependencies: @@ -1544,663 +973,367 @@ packages: peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.2 - dev: true - /decamelize@1.2.0: + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} - dev: true - /depd@2.0.0: + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dev: false - /destroy@1.2.0: + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dev: false - /didyoumean@1.2.2: + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true - /dir-glob@3.0.1: + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - /eastasianwidth@0.2.0: + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /ee-first@1.1.1: + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - dev: false - /electron-to-chromium@1.4.356: + electron-to-chromium@1.4.356: resolution: {integrity: sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==} - dev: true - /emoji-regex@8.0.0: + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true - /emoji-regex@9.2.2: + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true - /encodeurl@1.0.2: + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} - dev: false - /esbuild@0.17.15: + esbuild@0.17.15: resolution: {integrity: sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.17.15 - '@esbuild/android-arm64': 0.17.15 - '@esbuild/android-x64': 0.17.15 - '@esbuild/darwin-arm64': 0.17.15 - '@esbuild/darwin-x64': 0.17.15 - '@esbuild/freebsd-arm64': 0.17.15 - '@esbuild/freebsd-x64': 0.17.15 - '@esbuild/linux-arm': 0.17.15 - '@esbuild/linux-arm64': 0.17.15 - '@esbuild/linux-ia32': 0.17.15 - '@esbuild/linux-loong64': 0.17.15 - '@esbuild/linux-mips64el': 0.17.15 - '@esbuild/linux-ppc64': 0.17.15 - '@esbuild/linux-riscv64': 0.17.15 - '@esbuild/linux-s390x': 0.17.15 - '@esbuild/linux-x64': 0.17.15 - '@esbuild/netbsd-x64': 0.17.15 - '@esbuild/openbsd-x64': 0.17.15 - '@esbuild/sunos-x64': 0.17.15 - '@esbuild/win32-arm64': 0.17.15 - '@esbuild/win32-ia32': 0.17.15 - '@esbuild/win32-x64': 0.17.15 - dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - dev: true - - /escalade@3.1.1: + + escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} - dev: true - /escape-html@1.0.3: + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: false - /escape-string-regexp@1.0.5: + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true - /etag@1.8.1: + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - dev: false - /execa@5.1.1: + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true - /express@4.18.2: + express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.5.0 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.2.0 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.1 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.7 - proxy-addr: 2.0.7 - qs: 6.11.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - dev: false - /external-editor@3.1.0: + external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - dev: true - /fast-glob@3.3.2: + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - /fastq@1.17.1: + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - dependencies: - reusify: 1.0.4 - dev: true - /figures@3.2.0: + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - dependencies: - escape-string-regexp: 1.0.5 - dev: true - /fill-range@7.0.1: + fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - /finalhandler@1.2.0: + finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - dev: false - /find-up@4.1.0: + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - dev: true - /foreground-child@3.1.1: + foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.0.1 - dev: true - /forwarded@0.2.0: + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - dev: false - /fresh@0.5.2: + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - dev: false - /fsevents@2.3.2: + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - requiresBuild: true - dev: true - optional: true - /function-bind@1.1.1: + function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: false - /gensync@1.0.0-beta.2: + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - dev: true - /get-caller-file@2.0.5: + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true - /get-intrinsic@1.2.0: + get-intrinsic@1.2.0: resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 - dev: false - /get-stream@6.0.1: + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - dev: true - /glob-parent@5.1.2: + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - /glob-to-regexp@0.4.1: + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - dev: false - /glob@10.3.10: + glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.1 - minipass: 5.0.0 - path-scurry: 1.10.1 - dev: true - /globals@11.12.0: + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - dev: true - /globby@11.1.0: + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - /graceful-fs@4.2.11: + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false - /has-flag@3.0.0: + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true - /has-flag@4.0.0: + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true - /has-symbols@1.0.3: + has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: false - /has@1.0.3: + has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: false - /http-errors@2.0.0: + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - dev: false - /human-signals@2.1.0: + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - dev: true - /iconv-lite@0.4.24: + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - /ignore@5.3.1: + ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} - dev: true - /inherits@2.0.4: + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - /inquirer@7.3.3: + inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - run-async: 2.4.1 - rxjs: 6.6.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - dev: true - /ipaddr.js@1.9.1: + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - dev: false - /is-binary-path@2.1.0: + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - /is-extglob@2.1.1: + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true - /is-fullwidth-code-point@3.0.0: + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true - /is-glob@4.0.3: + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - /is-number@7.0.0: + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true - /is-stream@2.0.1: + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: true - /isexe@2.0.0: + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - /jackspeak@2.3.6: + jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - dev: true - /joycon@3.1.1: + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - dev: true - /js-tokens@4.0.0: + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /jsesc@2.5.2: + jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true - dev: true - /json-fixer@1.6.15: + json-fixer@1.6.15: resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} engines: {node: '>=10'} - dependencies: - '@babel/runtime': 7.21.5 - chalk: 4.1.2 - pegjs: 0.10.0 - dev: true - /json5@2.2.3: + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - dev: true - /lilconfig@3.1.0: + lilconfig@3.1.0: resolution: {integrity: sha512-p3cz0JV5vw/XeouBU3Ldnp+ZkBjE+n8ydJ4mcwBrOiXXPqNlrzGBqWs9X4MWF7f+iKUBu794Y8Hh8yawiJbCjw==} engines: {node: '>=14'} - dev: true + deprecated: This version contains a security issue. Please upgrade to 3.1.1 or later. - /lines-and-columns@1.2.4: + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - /load-tsconfig@0.2.5: + load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /locate-path@5.0.0: + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - dev: true - /lodash.sortby@4.7.0: + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - dev: true - /lodash@4.17.21: + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true - /loose-envify@1.4.0: + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: false - /lru-cache@5.1.1: + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 - dev: true - /lru-cache@9.1.1: + lru-cache@9.1.1: resolution: {integrity: sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==} engines: {node: 14 || >=16.14} - dev: true - /magic-string@0.27.0: + magic-string@0.27.0: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - /media-typer@0.3.0: + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - dev: false - /merge-descriptors@1.0.1: + merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - dev: false - /merge-stream@2.0.0: + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true - /merge2@1.4.1: + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true - /methods@1.1.2: + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - dev: false - /micromatch@4.0.5: + micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: true - /mime-db@1.52.0: + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: false - /mime-types@2.1.35: + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: false - /mime@1.6.0: + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} hasBin: true - dev: false - /mimic-fn@2.1.0: + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: true - /minimatch@9.0.1: + minimatch@9.0.1: resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.1 - dev: true - /minipass@5.0.0: + minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} - dev: true - /ms@2.0.0: + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - dev: false - /ms@2.1.2: + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - /ms@2.1.3: + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: false - /mute-stream@0.0.8: + mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - dev: true - /mz@2.7.0: + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: true - /nanoid@3.3.6: + nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - /negotiator@0.6.3: + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - dev: false - /next@13.4.6(react-dom@18.2.0)(react@18.2.0): + next@13.4.6: resolution: {integrity: sha512-sjVqjxU+U2aXZnYt4Ud6CTLNNwWjdSfMgemGpIQJcN3Z7Jni9xRWbR0ie5fQzCg87aLqQVhKA2ud2gPoqJ9lGw==} engines: {node: '>=16.8.0'} hasBin: true @@ -2217,33 +1350,8 @@ packages: optional: true sass: optional: true - dependencies: - '@next/env': 13.4.6 - '@swc/helpers': 0.5.1 - busboy: 1.6.0 - caniuse-lite: 1.0.30001476 - postcss: 8.4.14 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(react@18.2.0) - watchpack: 2.4.0 - zod: 3.21.4 - optionalDependencies: - '@next/swc-darwin-arm64': 13.4.6 - '@next/swc-darwin-x64': 13.4.6 - '@next/swc-linux-arm64-gnu': 13.4.6 - '@next/swc-linux-arm64-musl': 13.4.6 - '@next/swc-linux-x64-gnu': 13.4.6 - '@next/swc-linux-x64-musl': 13.4.6 - '@next/swc-win32-arm64-msvc': 13.4.6 - '@next/swc-win32-ia32-msvc': 13.4.6 - '@next/swc-win32-x64-msvc': 13.4.6 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - /node-fetch@2.6.7: + node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -2251,130 +1359,93 @@ packages: peerDependenciesMeta: encoding: optional: true - dependencies: - whatwg-url: 5.0.0 - dev: true - /node-releases@2.0.10: + node-releases@2.0.10: resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} - dev: true - /normalize-path@3.0.0: + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true - /npm-run-path@4.0.1: + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - dev: true - /object-assign@4.1.1: + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true - /object-inspect@1.12.3: + object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: false - /on-finished@2.4.1: + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} - dependencies: - ee-first: 1.1.1 - dev: false - /onetime@5.1.2: + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: true - /os-tmpdir@1.0.2: + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} - dev: true - /p-limit@2.3.0: + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 - dev: true - /p-locate@4.1.0: + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 - dev: true - /p-try@2.2.0: + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true - /parseurl@1.3.3: + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - dev: false - /path-exists@4.0.0: + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - dev: true - /path-key@3.1.1: + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true - /path-scurry@1.10.1: + path-scurry@1.10.1: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} - dependencies: - lru-cache: 9.1.1 - minipass: 5.0.0 - dev: true - /path-to-regexp@0.1.7: + path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - dev: false - /path-type@4.0.0: + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true - /pegjs@0.10.0: + pegjs@0.10.0: resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} engines: {node: '>=0.10'} hasBin: true - dev: true - /picocolors@1.0.0: + picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /picomatch@2.3.1: + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true - /pify@5.0.0: + pify@5.0.0: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} engines: {node: '>=10'} - dev: true - /pirates@4.0.6: + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - dev: true - /postcss-load-config@4.0.2: + postcss-load-config@4.0.2: resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} peerDependencies: @@ -2385,360 +1456,202 @@ packages: optional: true ts-node: optional: true - dependencies: - lilconfig: 3.1.0 - yaml: 2.3.4 - dev: true - /postcss@8.4.14: + postcss@8.4.14: resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: false - /postcss@8.4.23: + postcss@8.4.23: resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - /prettier@2.8.8: + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - requiresBuild: true - dev: true - optional: true - /proxy-addr@2.0.7: + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - dev: false - /punycode@2.3.1: + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true - /qs@6.11.0: + qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} - dependencies: - side-channel: 1.0.4 - dev: false - /queue-microtask@1.2.3: + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - /range-parser@1.2.1: + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - dev: false - /raw-body@2.5.1: + raw-body@2.5.1: resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} engines: {node: '>= 0.8'} - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - dev: false - /react-dom@18.2.0(react@18.2.0): + react-dom@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: false - /react-refresh@0.14.0: + react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} - dev: true - /react-router-dom@6.13.0(react-dom@18.2.0)(react@18.2.0): + react-router-dom@6.13.0: resolution: {integrity: sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - dependencies: - '@remix-run/router': 1.6.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.13.0(react@18.2.0) - dev: false - /react-router@6.13.0(react@18.2.0): + react-router@6.13.0: resolution: {integrity: sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' - dependencies: - '@remix-run/router': 1.6.3 - react: 18.2.0 - dev: false - /react@18.2.0: + react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - /readdirp@3.6.0: + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - /regenerator-runtime@0.13.11: + regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: true - /require-directory@2.1.1: + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true - /require-main-filename@2.0.0: + require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true - /resolve-from@5.0.0: + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - dev: true - /restore-cursor@3.1.0: + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - dev: true - /reusify@1.0.4: + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - /rollup@3.21.2: + rollup@3.21.2: resolution: {integrity: sha512-c4vC+JZ3bbF4Kqq2TtM7zSKtSyMybFOjqmomFax3xpfYaPZDZ4iz8NMIuBRMjnXOcKYozw7bC6vhJjiWD6JpzQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - /rollup@4.12.0: + rollup@4.12.0: resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.12.0 - '@rollup/rollup-android-arm64': 4.12.0 - '@rollup/rollup-darwin-arm64': 4.12.0 - '@rollup/rollup-darwin-x64': 4.12.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.12.0 - '@rollup/rollup-linux-arm64-gnu': 4.12.0 - '@rollup/rollup-linux-arm64-musl': 4.12.0 - '@rollup/rollup-linux-riscv64-gnu': 4.12.0 - '@rollup/rollup-linux-x64-gnu': 4.12.0 - '@rollup/rollup-linux-x64-musl': 4.12.0 - '@rollup/rollup-win32-arm64-msvc': 4.12.0 - '@rollup/rollup-win32-ia32-msvc': 4.12.0 - '@rollup/rollup-win32-x64-msvc': 4.12.0 - fsevents: 2.3.2 - dev: true - /run-async@2.4.1: + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} - dev: true - /run-parallel@1.2.0: + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - /rxjs@6.6.7: + rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} - dependencies: - tslib: 1.14.1 - dev: true - /safe-buffer@5.2.1: + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false - /safer-buffer@2.1.2: + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /scheduler@0.23.0: + scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - dev: false - /semver@6.3.0: + semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true - dev: true - /send@0.18.0: + send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - dev: false - /serve-static@1.15.0: + serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.18.0 - transitivePeerDependencies: - - supports-color - dev: false - /set-blocking@2.0.0: + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true - /setprototypeof@1.2.0: + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - dev: false - /shebang-command@2.0.0: + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true - /side-channel@1.0.4: + side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - object-inspect: 1.12.3 - dev: false - /signal-exit@3.0.7: + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true - /signal-exit@4.0.1: + signal-exit@4.0.1: resolution: {integrity: sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==} engines: {node: '>=14'} - dev: true - /slash@3.0.0: + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - dev: true - /source-map-js@1.0.2: + source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - /source-map@0.8.0-beta.0: + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - dependencies: - whatwg-url: 7.1.0 - dev: true - /statuses@2.0.1: + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - dev: false - /streamsearch@1.1.0: + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: false - /string-width@4.2.3: + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - dev: true - /string-width@5.1.2: + string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - dev: true - /strip-ansi@6.0.1: + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - /strip-ansi@7.1.0: + strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - dependencies: - ansi-regex: 6.0.1 - dev: true - /strip-final-newline@2.0.0: + strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - dev: true - /styled-jsx@5.1.1(react@18.2.0): + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} peerDependencies: @@ -2750,109 +1663,67 @@ packages: optional: true babel-plugin-macros: optional: true - dependencies: - client-only: 0.0.1 - react: 18.2.0 - dev: false - /sucrase@3.35.0: + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - commander: 4.1.1 - glob: 10.3.10 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - dev: true - /supports-color@5.5.0: + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: true - /supports-color@7.2.0: + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - /thenify-all@1.6.0: + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: true - /thenify@3.3.1: + thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: true - /through@2.3.8: + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true - /tmp@0.0.33: + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} - dependencies: - os-tmpdir: 1.0.2 - dev: true - /to-fast-properties@2.0.0: + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - dev: true - /to-regex-range@5.0.1: + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - /toidentifier@1.0.1: + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - dev: false - /tr46@0.0.3: + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true - /tr46@1.0.1: + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - dependencies: - punycode: 2.3.1 - dev: true - /tree-kill@1.2.2: + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - dev: true - /ts-interface-checker@0.1.13: + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true - /tslib@1.14.1: + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - /tslib@2.5.0: + tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - dev: false - /tsup@8.0.2(typescript@5.3.3): - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + tsup@8.1.0: + resolution: {integrity: sha512-UFdfCAXukax+U6KzeTNO2kAARHcWxmKsnvSPXUcfA1D+kU05XDccCrkffCQpFaWDsZfV0jMyTsxU39VfCp6EOg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -2869,86 +1740,49 @@ packages: optional: true typescript: optional: true - dependencies: - bundle-require: 4.0.2(esbuild@0.19.12) - cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.4 - esbuild: 0.19.12 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.2 - resolve-from: 5.0.0 - rollup: 4.12.0 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tree-kill: 1.2.2 - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - /type-fest@0.21.3: + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: true - /type-is@1.6.18: + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - dev: false - /typescript@5.1.3: + typescript@5.1.3: resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} engines: {node: '>=14.17'} hasBin: true - /typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} hasBin: true - dev: true - /unpipe@1.0.0: + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - dev: false - /update-browserslist-db@1.0.10(browserslist@4.21.5): + update-browserslist-db@1.0.10: resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.5 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - /use-sync-external-store@1.2.0(react@18.2.0): + use-sync-external-store@1.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /utils-merge@1.0.1: + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - dev: false - /vary@1.1.2: + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - dev: false - /vite@4.3.9(@types/node@20.3.1): + vite@4.3.9: resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -2972,123 +1806,1629 @@ packages: optional: true terser: optional: true - dependencies: - '@types/node': 20.3.1 - esbuild: 0.17.15 - postcss: 8.4.23 - rollup: 3.21.2 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /watchpack@2.4.0: + watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - dev: false - /webidl-conversions@3.0.1: + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true - /webidl-conversions@4.0.2: + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - dev: true - /whatwg-url@5.0.0: + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - dev: true - /whatwg-url@7.1.0: + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - dev: true - /which-module@2.0.1: + which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - dev: true - /which@2.0.2: + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - /wrap-ansi@6.2.0: + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /wrap-ansi@7.0.0: + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /wrap-ansi@8.1.0: + wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - dev: true - /y18n@4.0.3: + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true - /yallist@3.1.1: + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: true - /yaml@2.3.4: + yaml@2.3.4: resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} engines: {node: '>= 14'} - dev: true - /yargs-parser@18.1.3: + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - /yargs@15.4.1: + yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - dev: true - /zod@3.21.4: + zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} - dev: false + +snapshots: + + '@ampproject/remapping@2.2.1': + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.18 + + '@babel/code-frame@7.21.4': + dependencies: + '@babel/highlight': 7.18.6 + + '@babel/compat-data@7.21.7': {} + + '@babel/core@7.21.5': + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.21.5 + '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.5) + '@babel/helper-module-transforms': 7.21.5 + '@babel/helpers': 7.21.5 + '@babel/parser': 7.21.5 + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.21.5': + dependencies: + '@babel/types': 7.21.5 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.18 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.5)': + dependencies: + '@babel/compat-data': 7.21.7 + '@babel/core': 7.21.5 + '@babel/helper-validator-option': 7.21.0 + browserslist: 4.21.5 + lru-cache: 5.1.1 + semver: 6.3.0 + + '@babel/helper-environment-visitor@7.21.5': {} + + '@babel/helper-function-name@7.21.0': + dependencies: + '@babel/template': 7.20.7 + '@babel/types': 7.21.5 + + '@babel/helper-hoist-variables@7.18.6': + dependencies: + '@babel/types': 7.21.5 + + '@babel/helper-module-imports@7.21.4': + dependencies: + '@babel/types': 7.21.4 + + '@babel/helper-module-transforms@7.21.5': + dependencies: + '@babel/helper-environment-visitor': 7.21.5 + '@babel/helper-module-imports': 7.21.4 + '@babel/helper-simple-access': 7.21.5 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.19.1 + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.20.2': {} + + '@babel/helper-simple-access@7.21.5': + dependencies: + '@babel/types': 7.21.5 + + '@babel/helper-split-export-declaration@7.18.6': + dependencies: + '@babel/types': 7.21.5 + + '@babel/helper-string-parser@7.19.4': {} + + '@babel/helper-string-parser@7.21.5': {} + + '@babel/helper-validator-identifier@7.19.1': {} + + '@babel/helper-validator-option@7.21.0': {} + + '@babel/helpers@7.21.5': + dependencies: + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 + transitivePeerDependencies: + - supports-color + + '@babel/highlight@7.18.6': + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + + '@babel/parser@7.21.5': + dependencies: + '@babel/types': 7.21.5 + + '@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.5)': + dependencies: + '@babel/core': 7.21.5 + '@babel/helper-plugin-utils': 7.20.2 + + '@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.5)': + dependencies: + '@babel/core': 7.21.5 + '@babel/helper-plugin-utils': 7.20.2 + + '@babel/runtime@7.21.5': + dependencies: + regenerator-runtime: 0.13.11 + + '@babel/template@7.20.7': + dependencies: + '@babel/code-frame': 7.21.4 + '@babel/parser': 7.21.5 + '@babel/types': 7.21.5 + + '@babel/traverse@7.21.5': + dependencies: + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.21.5 + '@babel/helper-environment-visitor': 7.21.5 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.21.5 + '@babel/types': 7.21.5 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.21.4': + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + + '@babel/types@7.21.5': + dependencies: + '@babel/helper-string-parser': 7.21.5 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.17.15': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.17.15': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.17.15': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.17.15': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.17.15': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.17.15': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.17.15': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.17.15': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.17.15': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.17.15': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.17.15': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.17.15': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.17.15': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.17.15': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.17.15': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.17.15': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.17.15': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.17.15': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.17.15': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.17.15': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.17.15': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.17.15': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.3': + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping': 0.3.18 + + '@jridgewell/resolve-uri@3.1.0': {} + + '@jridgewell/set-array@1.1.2': {} + + '@jridgewell/sourcemap-codec@1.4.14': {} + + '@jridgewell/trace-mapping@0.3.18': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + + '@next/env@13.4.6': {} + + '@next/swc-darwin-arm64@13.4.6': + optional: true + + '@next/swc-darwin-x64@13.4.6': + optional: true + + '@next/swc-linux-arm64-gnu@13.4.6': + optional: true + + '@next/swc-linux-arm64-musl@13.4.6': + optional: true + + '@next/swc-linux-x64-gnu@13.4.6': + optional: true + + '@next/swc-linux-x64-musl@13.4.6': + optional: true + + '@next/swc-win32-arm64-msvc@13.4.6': + optional: true + + '@next/swc-win32-ia32-msvc@13.4.6': + optional: true + + '@next/swc-win32-x64-msvc@13.4.6': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@remix-run/router@1.6.3': {} + + '@rollup/rollup-android-arm-eabi@4.12.0': + optional: true + + '@rollup/rollup-android-arm64@4.12.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.12.0': + optional: true + + '@rollup/rollup-darwin-x64@4.12.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.12.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.12.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.12.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.12.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.12.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.12.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.12.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.12.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.12.0': + optional: true + + '@swc/helpers@0.5.1': + dependencies: + tslib: 2.5.0 + + '@tanstack/query-core@4.29.14': {} + + '@tanstack/react-query@4.29.14(react-dom@18.2.0)(react@18.2.0)': + dependencies: + '@tanstack/query-core': 4.29.14 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + use-sync-external-store: 1.2.0(react@18.2.0) + + '@types/body-parser@1.19.2': + dependencies: + '@types/connect': 3.4.35 + '@types/node': 20.3.1 + + '@types/connect@3.4.35': + dependencies: + '@types/node': 20.3.1 + + '@types/cookie-parser@1.4.3': + dependencies: + '@types/express': 4.17.17 + + '@types/cookie@0.4.1': {} + + '@types/estree@1.0.5': {} + + '@types/express-serve-static-core@4.17.33': + dependencies: + '@types/node': 20.3.1 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + + '@types/express@4.17.17': + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.33 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.1 + + '@types/mime@3.0.1': {} + + '@types/node@16.18.23': {} + + '@types/node@20.3.1': {} + + '@types/prop-types@15.7.5': {} + + '@types/qs@6.9.7': {} + + '@types/range-parser@1.2.4': {} + + '@types/react-dom@18.0.11': + dependencies: + '@types/react': 18.2.13 + + '@types/react@18.2.13': + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.3 + csstype: 3.1.2 + + '@types/scheduler@0.16.3': {} + + '@types/serve-static@1.15.1': + dependencies: + '@types/mime': 3.0.1 + '@types/node': 20.3.1 + + '@vitejs/plugin-react@3.1.0(vite@4.3.9)': + dependencies: + '@babel/core': 7.21.5 + '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.5) + '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.5) + magic-string: 0.27.0 + react-refresh: 0.14.0 + vite: 4.3.9(@types/node@20.3.1) + transitivePeerDependencies: + - supports-color + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + all-contributors-cli@6.26.1: + dependencies: + '@babel/runtime': 7.21.5 + async: 3.2.4 + chalk: 4.1.2 + didyoumean: 1.2.2 + inquirer: 7.3.3 + json-fixer: 1.6.15 + lodash: 4.17.21 + node-fetch: 2.6.7 + pify: 5.0.0 + yargs: 15.4.1 + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - encoding + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + array-flatten@1.1.1: {} + + array-union@2.1.0: {} + + async@3.2.4: {} + + balanced-match@1.0.2: {} + + binary-extensions@2.2.0: {} + + body-parser@1.20.1: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.2: + dependencies: + fill-range: 7.0.1 + + browserslist@4.21.5: + dependencies: + caniuse-lite: 1.0.30001476 + electron-to-chromium: 1.4.356 + node-releases: 2.0.10 + update-browserslist-db: 1.0.10(browserslist@4.21.5) + + bundle-require@4.0.2(esbuild@0.21.5): + dependencies: + esbuild: 0.21.5 + load-tsconfig: 0.2.5 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + cac@6.7.14: {} + + call-bind@1.0.2: + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.0 + + camelcase@5.3.1: {} + + caniuse-lite@1.0.30001476: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@0.7.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-width@3.0.0: {} + + client-only@0.0.1: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@4.1.1: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@1.9.0: {} + + cookie-parser@1.4.6: + dependencies: + cookie: 0.4.1 + cookie-signature: 1.0.6 + + cookie-signature@1.0.6: {} + + cookie@0.4.1: {} + + cookie@0.5.0: {} + + cookies-next@2.1.2: + dependencies: + '@types/cookie': 0.4.1 + '@types/node': 16.18.23 + cookie: 0.4.1 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.2: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.4: + dependencies: + ms: 2.1.2 + + decamelize@1.2.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.4.356: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + esbuild@0.17.15: + optionalDependencies: + '@esbuild/android-arm': 0.17.15 + '@esbuild/android-arm64': 0.17.15 + '@esbuild/android-x64': 0.17.15 + '@esbuild/darwin-arm64': 0.17.15 + '@esbuild/darwin-x64': 0.17.15 + '@esbuild/freebsd-arm64': 0.17.15 + '@esbuild/freebsd-x64': 0.17.15 + '@esbuild/linux-arm': 0.17.15 + '@esbuild/linux-arm64': 0.17.15 + '@esbuild/linux-ia32': 0.17.15 + '@esbuild/linux-loong64': 0.17.15 + '@esbuild/linux-mips64el': 0.17.15 + '@esbuild/linux-ppc64': 0.17.15 + '@esbuild/linux-riscv64': 0.17.15 + '@esbuild/linux-s390x': 0.17.15 + '@esbuild/linux-x64': 0.17.15 + '@esbuild/netbsd-x64': 0.17.15 + '@esbuild/openbsd-x64': 0.17.15 + '@esbuild/sunos-x64': 0.17.15 + '@esbuild/win32-arm64': 0.17.15 + '@esbuild/win32-ia32': 0.17.15 + '@esbuild/win32-x64': 0.17.15 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.1.1: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + etag@1.8.1: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + express@4.18.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + fill-range@7.0.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.2.0: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + foreground-child@3.1.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.0.1 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fsevents@2.3.2: + optional: true + + function-bind@1.1.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.0: + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.3.10: + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.1 + minipass: 5.0.0 + path-scurry: 1.10.1 + + globals@11.12.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.0.3: {} + + has@1.0.3: + dependencies: + function-bind: 1.1.1 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.1: {} + + inherits@2.0.4: {} + + inquirer@7.3.3: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + + ipaddr.js@1.9.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.2.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + jsesc@2.5.2: {} + + json-fixer@1.6.15: + dependencies: + '@babel/runtime': 7.21.5 + chalk: 4.1.2 + pegjs: 0.10.0 + + json5@2.2.3: {} + + lilconfig@3.1.0: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.sortby@4.7.0: {} + + lodash@4.17.21: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@9.1.1: {} + + magic-string@0.27.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 + + media-typer@0.3.0: {} + + merge-descriptors@1.0.1: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.5: + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.1 + + minipass@5.0.0: {} + + ms@2.0.0: {} + + ms@2.1.2: {} + + ms@2.1.3: {} + + mute-stream@0.0.8: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.6: {} + + negotiator@0.6.3: {} + + next@13.4.6(react-dom@18.2.0)(react@18.2.0): + dependencies: + '@next/env': 13.4.6 + '@swc/helpers': 0.5.1 + busboy: 1.6.0 + caniuse-lite: 1.0.30001476 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.1(react@18.2.0) + watchpack: 2.4.0 + zod: 3.21.4 + optionalDependencies: + '@next/swc-darwin-arm64': 13.4.6 + '@next/swc-darwin-x64': 13.4.6 + '@next/swc-linux-arm64-gnu': 13.4.6 + '@next/swc-linux-arm64-musl': 13.4.6 + '@next/swc-linux-x64-gnu': 13.4.6 + '@next/swc-linux-x64-musl': 13.4.6 + '@next/swc-win32-arm64-msvc': 13.4.6 + '@next/swc-win32-ia32-msvc': 13.4.6 + '@next/swc-win32-x64-msvc': 13.4.6 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-fetch@2.6.7: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.10: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + object-inspect@1.12.3: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + os-tmpdir@1.0.2: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.10.1: + dependencies: + lru-cache: 9.1.1 + minipass: 5.0.0 + + path-to-regexp@0.1.7: {} + + path-type@4.0.0: {} + + pegjs@0.10.0: {} + + picocolors@1.0.0: {} + + picomatch@2.3.1: {} + + pify@5.0.0: {} + + pirates@4.0.6: {} + + postcss-load-config@4.0.2(postcss@8.4.23): + dependencies: + lilconfig: 3.1.0 + yaml: 2.3.4 + optionalDependencies: + postcss: 8.4.23 + + postcss@8.4.14: + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + postcss@8.4.23: + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + prettier@2.8.8: + optional: true + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qs@6.11.0: + dependencies: + side-channel: 1.0.4 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@2.5.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-dom@18.2.0(react@18.2.0): + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + + react-refresh@0.14.0: {} + + react-router-dom@6.13.0(react-dom@18.2.0)(react@18.2.0): + dependencies: + '@remix-run/router': 1.6.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-router: 6.13.0(react@18.2.0) + + react-router@6.13.0(react@18.2.0): + dependencies: + '@remix-run/router': 1.6.3 + react: 18.2.0 + + react@18.2.0: + dependencies: + loose-envify: 1.4.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + regenerator-runtime@0.13.11: {} + + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + + resolve-from@5.0.0: {} + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.0.4: {} + + rollup@3.21.2: + optionalDependencies: + fsevents: 2.3.2 + + rollup@4.12.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.12.0 + '@rollup/rollup-android-arm64': 4.12.0 + '@rollup/rollup-darwin-arm64': 4.12.0 + '@rollup/rollup-darwin-x64': 4.12.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.12.0 + '@rollup/rollup-linux-arm64-gnu': 4.12.0 + '@rollup/rollup-linux-arm64-musl': 4.12.0 + '@rollup/rollup-linux-riscv64-gnu': 4.12.0 + '@rollup/rollup-linux-x64-gnu': 4.12.0 + '@rollup/rollup-linux-x64-musl': 4.12.0 + '@rollup/rollup-win32-arm64-msvc': 4.12.0 + '@rollup/rollup-win32-ia32-msvc': 4.12.0 + '@rollup/rollup-win32-x64-msvc': 4.12.0 + fsevents: 2.3.2 + + run-async@2.4.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@6.6.7: + dependencies: + tslib: 1.14.1 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + scheduler@0.23.0: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.0: {} + + send@0.18.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.15.0: + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + + set-blocking@2.0.0: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.4: + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + object-inspect: 1.12.3 + + signal-exit@3.0.7: {} + + signal-exit@4.0.1: {} + + slash@3.0.0: {} + + source-map-js@1.0.2: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + statuses@2.0.1: {} + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-final-newline@2.0.0: {} + + styled-jsx@5.1.1(react@18.2.0): + dependencies: + client-only: 0.0.1 + react: 18.2.0 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 10.3.10 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through@2.3.8: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tslib@1.14.1: {} + + tslib@2.5.0: {} + + tsup@8.1.0(postcss@8.4.23)(typescript@5.5.3): + dependencies: + bundle-require: 4.0.2(esbuild@0.21.5) + cac: 6.7.14 + chokidar: 3.6.0 + debug: 4.3.4 + esbuild: 0.21.5 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.2(postcss@8.4.23) + resolve-from: 5.0.0 + rollup: 4.12.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.4.23 + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + - ts-node + + type-fest@0.21.3: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typescript@5.1.3: {} + + typescript@5.5.3: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.0.10(browserslist@4.21.5): + dependencies: + browserslist: 4.21.5 + escalade: 3.1.1 + picocolors: 1.0.0 + + use-sync-external-store@1.2.0(react@18.2.0): + dependencies: + react: 18.2.0 + + utils-merge@1.0.1: {} + + vary@1.1.2: {} + + vite@4.3.9(@types/node@20.3.1): + dependencies: + '@types/node': 20.3.1 + esbuild: 0.17.15 + postcss: 8.4.23 + rollup: 3.21.2 + optionalDependencies: + fsevents: 2.3.2 + + watchpack@2.4.0: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-module@2.0.1: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + y18n@4.0.3: {} + + yallist@3.1.1: {} + + yaml@2.3.4: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + zod@3.21.4: {} From 0d9d9020b42a2b3ff4cf1e3b506284d3d2737728 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 15 Jul 2024 13:29:47 +0200 Subject: [PATCH 13/18] chore: fix github action --- .github/workflows/release.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1e90a9d..50ebcde 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,10 +11,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + id-token: write steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: @@ -26,7 +25,7 @@ jobs: SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} SPOTIFY_REFRESH_TOKEN: ${{ secrets.SPOTIFY_REFRESH_TOKEN }} - - run: deno publish + - run: npx jsr publish - uses: actions/setup-node@v3 with: From 1a38305e06d849128c2a741c9679dfc3e437d1c5 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 15 Jul 2024 13:32:59 +0200 Subject: [PATCH 14/18] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 150c894..3573e52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.1", + "version": "1.1.2", "description": "🎧 Spotify Web API client for js/ts runtime environments", "type": "module", "main": "dist/mod.cjs", From 51e1b7d7ff11018a8bff8721f3df3dc0027d6749 Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 15 Jul 2024 13:36:20 +0200 Subject: [PATCH 15/18] fix publish issues --- .github/workflows/release.yaml | 2 ++ deno.json | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 50ebcde..03527a9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,6 +14,8 @@ jobs: id-token: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: denoland/setup-deno@v1 with: diff --git a/deno.json b/deno.json index 977c4fc..696c752 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.0", + "version": "1.1.3", "exports": { ".": "./mod.ts", "./pagination": "./pagination.ts", diff --git a/package.json b/package.json index 3573e52..523104b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.2", + "version": "1.1.3", "description": "🎧 Spotify Web API client for js/ts runtime environments", "type": "module", "main": "dist/mod.cjs", From 9a6bba5ce8377c190f90a62df1cc59a94719018a Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 15 Jul 2024 13:39:53 +0200 Subject: [PATCH 16/18] chore: include only needed files to jsr package --- deno.json | 15 ++++++++++++++- package.json | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/deno.json b/deno.json index 696c752..f0e3c42 100644 --- a/deno.json +++ b/deno.json @@ -1,11 +1,24 @@ { "name": "@soundify/web-api", - "version": "1.1.3", + "version": "1.1.4", "exports": { ".": "./mod.ts", "./pagination": "./pagination.ts", "./auth": "./auth.ts" }, + "publish": { + "include": [ + "client.ts", + "auth.ts", + "LICENSE", + "README.md", + "endpoints/**/*.ts", + "mod.ts", + "pagination.ts", + "shared.ts" + ], + "exclude": ["endpoints/**.test.ts", "endpoints/**.schemas.ts"] + }, "imports": { "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", "oauth4webapi": "https://deno.land/x/oauth4webapi@v2.10.3/mod.ts", diff --git a/package.json b/package.json index 523104b..b75af3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.3", + "version": "1.1.4", "description": "🎧 Spotify Web API client for js/ts runtime environments", "type": "module", "main": "dist/mod.cjs", From c1ea9c424848edd7ee16d539932a759c7bc62ebd Mon Sep 17 00:00:00 2001 From: Artem Melnyk Date: Mon, 15 Jul 2024 13:43:26 +0200 Subject: [PATCH 17/18] chore: just fix it, please --- deno.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deno.json b/deno.json index f0e3c42..52c2311 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.4", + "version": "1.1.5", "exports": { ".": "./mod.ts", "./pagination": "./pagination.ts", @@ -17,7 +17,7 @@ "pagination.ts", "shared.ts" ], - "exclude": ["endpoints/**.test.ts", "endpoints/**.schemas.ts"] + "exclude": ["endpoints/**/*.test.ts", "endpoints/**/*.schemas.ts"] }, "imports": { "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", diff --git a/package.json b/package.json index b75af3f..f0cf8c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@soundify/web-api", - "version": "1.1.4", + "version": "1.1.5", "description": "🎧 Spotify Web API client for js/ts runtime environments", "type": "module", "main": "dist/mod.cjs", From a49d8ef2a18644c3e2a9eb3c4f5dda0498e3aca1 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:47:57 +0200 Subject: [PATCH 18/18] Docs: Add braydenbabbitt as a contributor for bug (#50) * Docs: Update README.md * Docs: Update .all-contributorsrc --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3ab5a82..c52ce0a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -36,6 +36,15 @@ "contributions": [ "bug" ] + }, + { + "login": "braydenbabbitt", + "name": "Brayden Babbitt", + "avatar_url": "https://avatars.githubusercontent.com/u/1495342?v=4", + "profile": "https://braydenbabbitt.com", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 08c30c7..f0feb6a 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,7 @@ All contributions are very welcome ❤️ Artem Melnyk
Artem Melnyk

🚧 danluki
danluki

💻 Andrii Zontov
Andrii Zontov

🐛 + Brayden Babbitt
Brayden Babbitt

🐛