diff --git a/dist/index.d.mts b/dist/index.d.mts index e797dcf..6c69cdc 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -1,10 +1,13 @@ +/** + * Represents the search options for querying. + */ interface XCQueryOption { /** * Group * * Use the grp tag to narrow down your search to a specific group. This tag is particularly useful in combination with one of the other tags. Valid group values are birds, grasshoppers and bats. You can also use their respective ids (1 to 3), so grp:2 will restrict your search to grasshoppers. Soundscapes are a special case, as these recordings may include multiple groups. Use grp:soundscape or grp:0 to search these. */ - grp?: "soundscape" | "birds" | "grasshoppers" | "bats" | 0 | 1 | 2 | 3; + grp?: "soundscape" | "birds" | "grasshoppers" | "bats" | 0 | 1 | 2 | 3 | (string & {}); /** * Genus/Subspecies * @@ -40,13 +43,13 @@ interface XCQueryOption { * * Two tags (seen and playback respectively) that previously were stored as part of Recordist remarks, but now can be used independently. Both only accept yes and no as input. For example, use seen:yes playback:no to search for recordings where the animal was seen, but not lured by playback. */ - seen?: "yes" | "no"; + seen?: "yes" | "no" | (string & {}); /** * Animal seen/Playback used * * Two tags (seen and playback respectively) that previously were stored as part of Recordist remarks, but now can be used independently. Both only accept yes and no as input. For example, use seen:yes playback:no to search for recordings where the animal was seen, but not lured by playback. */ - playback?: "yes" | "no"; + playback?: "yes" | "no" | (string & {}); /** * Geographic coordinates * @@ -84,7 +87,7 @@ interface XCQueryOption { * * Up until 2022, the 'type' tag used to search a free text field. We have retained the option to search for non-standardized sound types by using the othertype tag. This tag also accepts a 'matches' operator, e.g. othertype:"=wing flapping". */ - type?: "aberrant" | "alarm call" | "begging call" | "call" | "calling song" | "courtship song" | "dawn song" | "distress call" | "disturbance song" | "drumming" | "duet" | "echolocation" | "female song" | "flight call" | "flight song" | "imitation" | "nocturnal flight call" | "rivalry song" | "searching song" | "social call" | "song" | "subsong"; + type?: "aberrant" | "alarm call" | "begging call" | "call" | "calling song" | "courtship song" | "dawn song" | "distress call" | "disturbance song" | "drumming" | "duet" | "echolocation" | "female song" | "flight call" | "flight song" | "imitation" | "nocturnal flight call" | "rivalry song" | "searching song" | "social call" | "song" | "subsong" | (string & {}); /** * Sound type * @@ -98,19 +101,19 @@ interface XCQueryOption { * * Formerly included under 'sound types', the sex tag can now be used independently. Valid values for this tag are: female, male. This tag always uses a 'matches' operator. */ - sex?: "female" | "male"; + sex?: "female" | "male" | (string & {}); /** * Life stage * * Values of the stage tag were previously included under 'sound types' as well. Valid values are: adult, juvenile, nestling, nymph, subadult. This tag always uses a 'matches' operator. */ - state?: "adult" | "juvenile" | "nestling" | "nymph" | "subadult"; + state?: "adult" | "juvenile" | "nestling" | "nymph" | "subadult" | (string & {}); /** * Recording method * * The method tag accepts the following, group-dependent values: emerging from roost, field recording, fluorescent light tag, hand-release, in enclosure, in net, in the hand, roosting, roped, studio recording. Do not forget to enclose the term between double quotes! This tag always uses a 'matches' operator. */ - method?: "emerging from roost" | "field recording" | "fluorescent light tag" | "hand-release" | "in enclosure" | "in net" | "in the hand" | "roosting" | "roped" | "studio recording"; + method?: "emerging from roost" | "field recording" | "fluorescent light tag" | "hand-release" | "in enclosure" | "in net" | "in the hand" | "roosting" | "roped" | "studio recording" | (string & {}); /** * XC number * @@ -152,7 +155,7 @@ interface XCQueryOption { * * The area tag allows you to search by world area. Valid values for this tag are africa, america, asia, australia, europe. */ - area?: "africa" | "america" | "asia" | "australia" | "europe"; + area?: "africa" | "america" | "asia" | "australia" | "europe" | (string & {}); /** * Additional search tags * @@ -200,7 +203,7 @@ interface XCQueryOption { * * The auto tag searches for automatic (non-supervised) recordings. This tag accepts yes and no. */ - auto?: "yes" | "no"; + auto?: "yes" | "no" | (string & {}); /** * Additional search tags * @@ -220,126 +223,135 @@ interface XCQueryOption { */ smp?: number | string; /** - * Any other custom tags not in the API for compatibility + * Any other custom tags not in the API for compatibility. */ [rest: string]: number | string | undefined; } +/** + * Represents additional options for the wrapper. + */ interface AdditionalWrapperOption { /** - * Override the default BASE_URL + * Override the default BASE_URL. */ baseUrl?: string; } +/** + * Represents the response object returned by the Xeno-Canto API. + */ interface XCResponse { /** - * the total number of recordings found for this query + * The total number of recordings found for this query. */ numRecordings: number; /** - * the total number of species found for this query + * The total number of species found for this query. */ numSpecies: number; /** - * the page number of the results page that is being displayed + * The page number of the results page that is being displayed. */ page: number; /** - * the total number of pages available for this query + * The total number of pages available for this query. */ numPages: number; /** - * an array of recording objects + * An array of recording objects. */ recordings: XCRecording[]; /** - * error type + * Error type, if any. */ error?: string; /** - * error message + * Error message, if any. */ message?: string; } +/** + * Represents a recording entity from the Xeno-Canto API response's "recordings" array. + */ interface XCRecording { /** - * the catalogue number of the recording on xeno-canto + * The catalogue number of the recording on xeno-canto. */ id: string; /** - * the generic name of the species + * The generic name of the species. */ gen: string; /** - * the specific name (epithet) of the species + * The specific name (epithet) of the species. */ sp: string; /** - * the subspecies name (subspecific epithet) + * The subspecies name (subspecific epithet). */ ssp: string; /** - * the group to which the species belongs (birds, grasshoppers, bats) + * The group to which the species belongs (birds, grasshoppers, bats). */ group: string; /** - * the English name of the species + * The English name of the species. */ en: string; /** - * the name of the recordist + * The name of the recordist. */ rec: string; /** - * the country where the recording was made + * The country where the recording was made. */ cnt: string; /** - * the name of the locality + * The name of the locality. */ loc: string; /** - * the latitude of the recording in decimal coordinates + * The latitude of the recording in decimal coordinates. */ lat: string; /** - * the longitude of the recording in decimal coordinates + * The longitude of the recording in decimal coordinates. */ lng: string; /** - * TODO: unknown usage + * Elevation (in meter). */ alt: string; /** - * the sound type of the recording (combining both predefined terms such as 'call' or 'song' and additional free text options) + * The sound type of the recording (combining both predefined terms such as 'call' or 'song' and additional free text options). */ type: string; /** - * the sex of the animal + * The sex of the animal. */ sex: string; /** - * the life stage of the animal (adult, juvenile, etc.) + * The life stage of the animal (adult, juvenile, etc.). */ stage: string; /** - * the recording method (field recording, in the hand, etc.) + * The recording method (field recording, in the hand, etc.). */ method: string; /** - * the URL specifying the details of this recording + * The URL specifying the details of this recording. */ url: string; /** - * the URL to the audio file + * The URL to the audio file. */ file: string; /** - * the original file name of the audio file + * The original file name of the audio file. */ fileName: string; /** - * an object with the URLs to the four versions of sonograms + * An object with the URLs to the four versions of sonograms. */ sono: { small: string; @@ -348,7 +360,7 @@ interface XCRecording { full: string; }; /** - * an object with the URLs to the three versions of oscillograms + * An object with the URLs to the three versions of oscillograms. */ osci: { small: string; @@ -356,93 +368,92 @@ interface XCRecording { large: string; }; /** - * the URL describing the license of this recording + * The URL describing the license of this recording. */ lic: string; /** - * the current quality rating for the recording + * The current quality rating for the recording. */ q: string; /** - * the length of the recording in minutes + * The length of the recording in minutes. */ length: string; /** - * the time of day that the recording was made + * The time of day that the recording was made. */ time: string; /** - * the date that the recording was made + * The date that the recording was made. */ date: string; /** - * the date that the recording was uploaded to xeno-canto + * The date that the recording was uploaded to xeno-canto. */ uploaded: string; /** - * an array with the identified background species in the recording + * An array with the identified background species in the recording. */ also: string[]; /** - * additional remarks by the recordist + * Additional remarks by the recordist. */ rmk: string; /** - * indicates whether the recorded animal was seen + * Indicates whether the recorded animal was seen. */ birdSeen: string; /** - * was the recorded animal seen + * Was the recorded animal seen. */ animalSeen: string; /** - * was playback used to lure the animal + * Was playback used to lure the animal. */ playbackUsed: string; /** - * temperature during recording (applicable to specific groups only) + * Temperature during recording (applicable to specific groups only). */ temp: string; /** - * registration number of specimen (when collected) + * Registration number of specimen (when collected). */ regnr: string; /** - * automatic (non-supervised) recording + * Automatic (non-supervised) recording. */ auto: string; /** - * recording device used + * Recording device used. */ dvc: string; /** - * microphone used + * Microphone used. */ mic: string; /** - * sample rate + * Sample rate. */ smp: number; } /** - * Constructs a query URL by appending the provided query string to the base URL and - * optionally including additional query options. + * Constructs a query URL by appending the provided query string to the base URL and optionally including additional query options. * * @param {string} baseUrl - The base URL to which the query string will be appended. * @param {string} query - The query string to be appended to the base URL. * @param {XCQueryOption} [options] - Optional additional query options. * @param {number} [page] - Optional page number. - * @return {string} The constructed query URL. + * @return {URL} The constructed query URL. */ -declare function constructQueryUrl(baseUrl: string, query: string, options?: XCQueryOption, page?: number): string; +declare function constructQueryUrl(baseUrl: string, query: string, options?: XCQueryOption, page?: number): URL; /** - * Converts an XCQueryOption object to a URL-encoded string. + * Converts an XCQueryOption object to a required URL string parameter format. For example: "grp:"birds" cnt:"United States" method:"field recording"" * * @param {XCQueryOption} option - The XCQueryOption object to convert. - * @return {string} The URL-encoded string representation of the XCQueryOption object. + * @return {URLSearchParams} The URLSearchParams object representing the XCQueryOption object. */ -declare function convertXCQueryOptionToString(option: XCQueryOption): string; +declare function convertXCQueryOptionToSearchParams(option: XCQueryOption): URLSearchParams; /** * Takes a JSON object and converts it into an XCResponse object. * @@ -451,20 +462,20 @@ declare function convertXCQueryOptionToString(option: XCQueryOption): string; */ declare function convertJsonToXCResponse(json: any): XCResponse; -declare const BASE_URL = "https://www.xeno-canto.org/api/2/recordings?query="; +declare const BASE_URL = "https://www.xeno-canto.org/api/2/recordings"; /** - * Searches for a query and returns the response and XC response. + * Searches for a query via Fetch API and returns the response and XC response. * * @param {string} query - The query to search for. * @param {XCQueryOption} [options] - Options for the search query. * @param {number} [page] - The page parameter is optional and is only needed if the results from a given search don't fit in a single page. If specified, page must be an integer between 1 and XCResponse.numPages. * @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper. - * @return {Promise<{ response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the response from fetch and a XCResponse object. + * @return {Promise<{ url: URL, response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the query URL, the response from fetch and a XCResponse object. */ declare function search(query: string, options?: XCQueryOption, page?: number, additionalOptions?: AdditionalWrapperOption): Promise<{ - url: string; + url: URL; rawResponse: Response; xcResponse: XCResponse; }>; -export { type AdditionalWrapperOption, BASE_URL, type XCQueryOption, type XCRecording, type XCResponse, constructQueryUrl, convertJsonToXCResponse, convertXCQueryOptionToString, search }; +export { type AdditionalWrapperOption, BASE_URL, type XCQueryOption, type XCRecording, type XCResponse, constructQueryUrl, convertJsonToXCResponse, convertXCQueryOptionToSearchParams, search }; diff --git a/dist/index.d.ts b/dist/index.d.ts index e797dcf..6c69cdc 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,10 +1,13 @@ +/** + * Represents the search options for querying. + */ interface XCQueryOption { /** * Group * * Use the grp tag to narrow down your search to a specific group. This tag is particularly useful in combination with one of the other tags. Valid group values are birds, grasshoppers and bats. You can also use their respective ids (1 to 3), so grp:2 will restrict your search to grasshoppers. Soundscapes are a special case, as these recordings may include multiple groups. Use grp:soundscape or grp:0 to search these. */ - grp?: "soundscape" | "birds" | "grasshoppers" | "bats" | 0 | 1 | 2 | 3; + grp?: "soundscape" | "birds" | "grasshoppers" | "bats" | 0 | 1 | 2 | 3 | (string & {}); /** * Genus/Subspecies * @@ -40,13 +43,13 @@ interface XCQueryOption { * * Two tags (seen and playback respectively) that previously were stored as part of Recordist remarks, but now can be used independently. Both only accept yes and no as input. For example, use seen:yes playback:no to search for recordings where the animal was seen, but not lured by playback. */ - seen?: "yes" | "no"; + seen?: "yes" | "no" | (string & {}); /** * Animal seen/Playback used * * Two tags (seen and playback respectively) that previously were stored as part of Recordist remarks, but now can be used independently. Both only accept yes and no as input. For example, use seen:yes playback:no to search for recordings where the animal was seen, but not lured by playback. */ - playback?: "yes" | "no"; + playback?: "yes" | "no" | (string & {}); /** * Geographic coordinates * @@ -84,7 +87,7 @@ interface XCQueryOption { * * Up until 2022, the 'type' tag used to search a free text field. We have retained the option to search for non-standardized sound types by using the othertype tag. This tag also accepts a 'matches' operator, e.g. othertype:"=wing flapping". */ - type?: "aberrant" | "alarm call" | "begging call" | "call" | "calling song" | "courtship song" | "dawn song" | "distress call" | "disturbance song" | "drumming" | "duet" | "echolocation" | "female song" | "flight call" | "flight song" | "imitation" | "nocturnal flight call" | "rivalry song" | "searching song" | "social call" | "song" | "subsong"; + type?: "aberrant" | "alarm call" | "begging call" | "call" | "calling song" | "courtship song" | "dawn song" | "distress call" | "disturbance song" | "drumming" | "duet" | "echolocation" | "female song" | "flight call" | "flight song" | "imitation" | "nocturnal flight call" | "rivalry song" | "searching song" | "social call" | "song" | "subsong" | (string & {}); /** * Sound type * @@ -98,19 +101,19 @@ interface XCQueryOption { * * Formerly included under 'sound types', the sex tag can now be used independently. Valid values for this tag are: female, male. This tag always uses a 'matches' operator. */ - sex?: "female" | "male"; + sex?: "female" | "male" | (string & {}); /** * Life stage * * Values of the stage tag were previously included under 'sound types' as well. Valid values are: adult, juvenile, nestling, nymph, subadult. This tag always uses a 'matches' operator. */ - state?: "adult" | "juvenile" | "nestling" | "nymph" | "subadult"; + state?: "adult" | "juvenile" | "nestling" | "nymph" | "subadult" | (string & {}); /** * Recording method * * The method tag accepts the following, group-dependent values: emerging from roost, field recording, fluorescent light tag, hand-release, in enclosure, in net, in the hand, roosting, roped, studio recording. Do not forget to enclose the term between double quotes! This tag always uses a 'matches' operator. */ - method?: "emerging from roost" | "field recording" | "fluorescent light tag" | "hand-release" | "in enclosure" | "in net" | "in the hand" | "roosting" | "roped" | "studio recording"; + method?: "emerging from roost" | "field recording" | "fluorescent light tag" | "hand-release" | "in enclosure" | "in net" | "in the hand" | "roosting" | "roped" | "studio recording" | (string & {}); /** * XC number * @@ -152,7 +155,7 @@ interface XCQueryOption { * * The area tag allows you to search by world area. Valid values for this tag are africa, america, asia, australia, europe. */ - area?: "africa" | "america" | "asia" | "australia" | "europe"; + area?: "africa" | "america" | "asia" | "australia" | "europe" | (string & {}); /** * Additional search tags * @@ -200,7 +203,7 @@ interface XCQueryOption { * * The auto tag searches for automatic (non-supervised) recordings. This tag accepts yes and no. */ - auto?: "yes" | "no"; + auto?: "yes" | "no" | (string & {}); /** * Additional search tags * @@ -220,126 +223,135 @@ interface XCQueryOption { */ smp?: number | string; /** - * Any other custom tags not in the API for compatibility + * Any other custom tags not in the API for compatibility. */ [rest: string]: number | string | undefined; } +/** + * Represents additional options for the wrapper. + */ interface AdditionalWrapperOption { /** - * Override the default BASE_URL + * Override the default BASE_URL. */ baseUrl?: string; } +/** + * Represents the response object returned by the Xeno-Canto API. + */ interface XCResponse { /** - * the total number of recordings found for this query + * The total number of recordings found for this query. */ numRecordings: number; /** - * the total number of species found for this query + * The total number of species found for this query. */ numSpecies: number; /** - * the page number of the results page that is being displayed + * The page number of the results page that is being displayed. */ page: number; /** - * the total number of pages available for this query + * The total number of pages available for this query. */ numPages: number; /** - * an array of recording objects + * An array of recording objects. */ recordings: XCRecording[]; /** - * error type + * Error type, if any. */ error?: string; /** - * error message + * Error message, if any. */ message?: string; } +/** + * Represents a recording entity from the Xeno-Canto API response's "recordings" array. + */ interface XCRecording { /** - * the catalogue number of the recording on xeno-canto + * The catalogue number of the recording on xeno-canto. */ id: string; /** - * the generic name of the species + * The generic name of the species. */ gen: string; /** - * the specific name (epithet) of the species + * The specific name (epithet) of the species. */ sp: string; /** - * the subspecies name (subspecific epithet) + * The subspecies name (subspecific epithet). */ ssp: string; /** - * the group to which the species belongs (birds, grasshoppers, bats) + * The group to which the species belongs (birds, grasshoppers, bats). */ group: string; /** - * the English name of the species + * The English name of the species. */ en: string; /** - * the name of the recordist + * The name of the recordist. */ rec: string; /** - * the country where the recording was made + * The country where the recording was made. */ cnt: string; /** - * the name of the locality + * The name of the locality. */ loc: string; /** - * the latitude of the recording in decimal coordinates + * The latitude of the recording in decimal coordinates. */ lat: string; /** - * the longitude of the recording in decimal coordinates + * The longitude of the recording in decimal coordinates. */ lng: string; /** - * TODO: unknown usage + * Elevation (in meter). */ alt: string; /** - * the sound type of the recording (combining both predefined terms such as 'call' or 'song' and additional free text options) + * The sound type of the recording (combining both predefined terms such as 'call' or 'song' and additional free text options). */ type: string; /** - * the sex of the animal + * The sex of the animal. */ sex: string; /** - * the life stage of the animal (adult, juvenile, etc.) + * The life stage of the animal (adult, juvenile, etc.). */ stage: string; /** - * the recording method (field recording, in the hand, etc.) + * The recording method (field recording, in the hand, etc.). */ method: string; /** - * the URL specifying the details of this recording + * The URL specifying the details of this recording. */ url: string; /** - * the URL to the audio file + * The URL to the audio file. */ file: string; /** - * the original file name of the audio file + * The original file name of the audio file. */ fileName: string; /** - * an object with the URLs to the four versions of sonograms + * An object with the URLs to the four versions of sonograms. */ sono: { small: string; @@ -348,7 +360,7 @@ interface XCRecording { full: string; }; /** - * an object with the URLs to the three versions of oscillograms + * An object with the URLs to the three versions of oscillograms. */ osci: { small: string; @@ -356,93 +368,92 @@ interface XCRecording { large: string; }; /** - * the URL describing the license of this recording + * The URL describing the license of this recording. */ lic: string; /** - * the current quality rating for the recording + * The current quality rating for the recording. */ q: string; /** - * the length of the recording in minutes + * The length of the recording in minutes. */ length: string; /** - * the time of day that the recording was made + * The time of day that the recording was made. */ time: string; /** - * the date that the recording was made + * The date that the recording was made. */ date: string; /** - * the date that the recording was uploaded to xeno-canto + * The date that the recording was uploaded to xeno-canto. */ uploaded: string; /** - * an array with the identified background species in the recording + * An array with the identified background species in the recording. */ also: string[]; /** - * additional remarks by the recordist + * Additional remarks by the recordist. */ rmk: string; /** - * indicates whether the recorded animal was seen + * Indicates whether the recorded animal was seen. */ birdSeen: string; /** - * was the recorded animal seen + * Was the recorded animal seen. */ animalSeen: string; /** - * was playback used to lure the animal + * Was playback used to lure the animal. */ playbackUsed: string; /** - * temperature during recording (applicable to specific groups only) + * Temperature during recording (applicable to specific groups only). */ temp: string; /** - * registration number of specimen (when collected) + * Registration number of specimen (when collected). */ regnr: string; /** - * automatic (non-supervised) recording + * Automatic (non-supervised) recording. */ auto: string; /** - * recording device used + * Recording device used. */ dvc: string; /** - * microphone used + * Microphone used. */ mic: string; /** - * sample rate + * Sample rate. */ smp: number; } /** - * Constructs a query URL by appending the provided query string to the base URL and - * optionally including additional query options. + * Constructs a query URL by appending the provided query string to the base URL and optionally including additional query options. * * @param {string} baseUrl - The base URL to which the query string will be appended. * @param {string} query - The query string to be appended to the base URL. * @param {XCQueryOption} [options] - Optional additional query options. * @param {number} [page] - Optional page number. - * @return {string} The constructed query URL. + * @return {URL} The constructed query URL. */ -declare function constructQueryUrl(baseUrl: string, query: string, options?: XCQueryOption, page?: number): string; +declare function constructQueryUrl(baseUrl: string, query: string, options?: XCQueryOption, page?: number): URL; /** - * Converts an XCQueryOption object to a URL-encoded string. + * Converts an XCQueryOption object to a required URL string parameter format. For example: "grp:"birds" cnt:"United States" method:"field recording"" * * @param {XCQueryOption} option - The XCQueryOption object to convert. - * @return {string} The URL-encoded string representation of the XCQueryOption object. + * @return {URLSearchParams} The URLSearchParams object representing the XCQueryOption object. */ -declare function convertXCQueryOptionToString(option: XCQueryOption): string; +declare function convertXCQueryOptionToSearchParams(option: XCQueryOption): URLSearchParams; /** * Takes a JSON object and converts it into an XCResponse object. * @@ -451,20 +462,20 @@ declare function convertXCQueryOptionToString(option: XCQueryOption): string; */ declare function convertJsonToXCResponse(json: any): XCResponse; -declare const BASE_URL = "https://www.xeno-canto.org/api/2/recordings?query="; +declare const BASE_URL = "https://www.xeno-canto.org/api/2/recordings"; /** - * Searches for a query and returns the response and XC response. + * Searches for a query via Fetch API and returns the response and XC response. * * @param {string} query - The query to search for. * @param {XCQueryOption} [options] - Options for the search query. * @param {number} [page] - The page parameter is optional and is only needed if the results from a given search don't fit in a single page. If specified, page must be an integer between 1 and XCResponse.numPages. * @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper. - * @return {Promise<{ response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the response from fetch and a XCResponse object. + * @return {Promise<{ url: URL, response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the query URL, the response from fetch and a XCResponse object. */ declare function search(query: string, options?: XCQueryOption, page?: number, additionalOptions?: AdditionalWrapperOption): Promise<{ - url: string; + url: URL; rawResponse: Response; xcResponse: XCResponse; }>; -export { type AdditionalWrapperOption, BASE_URL, type XCQueryOption, type XCRecording, type XCResponse, constructQueryUrl, convertJsonToXCResponse, convertXCQueryOptionToString, search }; +export { type AdditionalWrapperOption, BASE_URL, type XCQueryOption, type XCRecording, type XCResponse, constructQueryUrl, convertJsonToXCResponse, convertXCQueryOptionToSearchParams, search }; diff --git a/dist/index.js b/dist/index.js index a162e8a..bc1d5a4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -43,34 +43,42 @@ __export(src_exports, { BASE_URL: () => BASE_URL, constructQueryUrl: () => constructQueryUrl, convertJsonToXCResponse: () => convertJsonToXCResponse, - convertXCQueryOptionToString: () => convertXCQueryOptionToString, + convertXCQueryOptionToSearchParams: () => convertXCQueryOptionToSearchParams, search: () => search }); module.exports = __toCommonJS(src_exports); // src/utils/utils.ts function constructQueryUrl(baseUrl, query, options, page) { - let url = baseUrl; - if (query.trim()) { - url += query.trim(); - if (options) { - url += " "; - } + let url = new URL(baseUrl); + let parms = new URLSearchParams(); + const processedQuery = query.trim(); + if (processedQuery) { + parms.append("query", processedQuery); + } else { + parms.append("query", `""`); } if (options) { - url += convertXCQueryOptionToString(options); + const optionParms = convertXCQueryOptionToSearchParams(options); + for (let [key, val] of optionParms.entries()) { + parms.append(key, `"${val}"`); + } } if (page) { - url += `&page=${page}`; + parms.append("page", String(page)); } - url = url.replace(/\s+/g, "+"); + url.search = parms.toString(); return url; } -function convertXCQueryOptionToString(option) { +function convertXCQueryOptionToSearchParams(option) { + const params = new URLSearchParams(); if (!option) { - return ""; + return params; } - return Object.entries(option).map(([key, value]) => `${key}:"${value != null ? value : ""}"`).join(" "); + Object.entries(option).forEach(([key, value]) => { + params.append(key, String(value != null ? value : "")); + }); + return params; } function convertJsonToXCResponse(json) { var _a; @@ -136,7 +144,7 @@ function convertJsonToXCResponse(json) { } // src/index.ts -var BASE_URL = "https://www.xeno-canto.org/api/2/recordings?query="; +var BASE_URL = "https://www.xeno-canto.org/api/2/recordings"; function search(query, options, page, additionalOptions) { return __async(this, null, function* () { var _a; @@ -160,7 +168,7 @@ function search(query, options, page, additionalOptions) { if (xcResponse.error) { Promise.reject( new Error( - `API returned error '${xcResponse.error}': ${xcResponse.message}` + `Xeno-Canto API returned error '${xcResponse.error}': ${xcResponse.message}` ) ); } @@ -178,7 +186,7 @@ function search(query, options, page, additionalOptions) { BASE_URL, constructQueryUrl, convertJsonToXCResponse, - convertXCQueryOptionToString, + convertXCQueryOptionToSearchParams, search }); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map index 0f85b46..a61dda2 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/utils/utils.ts"],"sourcesContent":["// Import the types and utility functions\r\nimport { AdditionalWrapperOption, XCQueryOption, XCResponse } from \"./types\";\r\nimport { convertJsonToXCResponse, constructQueryUrl } from \"./utils/utils\";\r\n\r\n// Define the base URL for the API\r\nexport const BASE_URL = \"https://www.xeno-canto.org/api/2/recordings?query=\";\r\n\r\n/**\r\n * Searches for a query and returns the response and XC response.\r\n *\r\n * @param {string} query - The query to search for.\r\n * @param {XCQueryOption} [options] - Options for the search query.\r\n * @param {number} [page] - The page parameter is optional and is only needed if the results from a given search don't fit in a single page. If specified, page must be an integer between 1 and XCResponse.numPages.\r\n * @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper.\r\n * @return {Promise<{ response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the response from fetch and a XCResponse object.\r\n */\r\nasync function search(\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n additionalOptions?: AdditionalWrapperOption,\r\n): Promise<{ url: string; rawResponse: Response; xcResponse: XCResponse }> {\r\n // If query is empty and options is not provided, throw an error instantly instead of trying to fetch\r\n if (!query.trim() && !options) {\r\n return Promise.reject(\r\n new Error(\r\n \"Please ensure that the 'query' parameter is not empty or that the 'options' parameter is provided\",\r\n ),\r\n );\r\n }\r\n\r\n // Create the query URL\r\n const url = constructQueryUrl(\r\n additionalOptions?.baseUrl ?? BASE_URL,\r\n query,\r\n options,\r\n page,\r\n );\r\n\r\n // Fetch the response and parse the JSON\r\n try {\r\n const rawResponse = await fetch(url);\r\n const json = await rawResponse.json();\r\n const xcResponse = convertJsonToXCResponse(json);\r\n\r\n // If the API returned an error, throw an error\r\n if (xcResponse.error) {\r\n Promise.reject(\r\n new Error(\r\n `API returned error '${xcResponse.error}': ${xcResponse.message}`,\r\n ),\r\n );\r\n }\r\n\r\n return Promise.resolve({ url, rawResponse, xcResponse });\r\n } catch (error: any) {\r\n // Error handling\r\n console.error(error);\r\n return Promise.reject(\r\n new Error(`Failed to perform search: ${error.message}`),\r\n );\r\n }\r\n}\r\n\r\nexport { search };\r\nexport * from \"./types\";\r\nexport * from \"./utils\";\r\n","import { XCQueryOption } from \"../types/query\";\r\nimport { XCRecording, XCResponse } from \"../types/response\";\r\n\r\n/**\r\n * Constructs a query URL by appending the provided query string to the base URL and\r\n * optionally including additional query options.\r\n *\r\n * @param {string} baseUrl - The base URL to which the query string will be appended.\r\n * @param {string} query - The query string to be appended to the base URL.\r\n * @param {XCQueryOption} [options] - Optional additional query options.\r\n * @param {number} [page] - Optional page number.\r\n * @return {string} The constructed query URL.\r\n */\r\nexport function constructQueryUrl(\r\n baseUrl: string,\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n): string {\r\n let url = baseUrl;\r\n\r\n if (query.trim()) {\r\n url += query.trim();\r\n\r\n if (options) {\r\n url += \" \";\r\n }\r\n }\r\n\r\n if (options) {\r\n url += convertXCQueryOptionToString(options);\r\n }\r\n\r\n if (page) {\r\n url += `&page=${page}`;\r\n }\r\n\r\n url = url.replace(/\\s+/g, \"+\"); // Replace spaces with +\r\n\r\n return url;\r\n}\r\n\r\n/**\r\n * Converts an XCQueryOption object to a URL-encoded string.\r\n *\r\n * @param {XCQueryOption} option - The XCQueryOption object to convert.\r\n * @return {string} The URL-encoded string representation of the XCQueryOption object.\r\n */\r\nexport function convertXCQueryOptionToString(option: XCQueryOption): string {\r\n if (!option) {\r\n return \"\";\r\n }\r\n\r\n return Object.entries(option)\r\n .map(([key, value]) => `${key}:\"${value ?? \"\"}\"`)\r\n .join(\" \");\r\n}\r\n\r\n/**\r\n * Takes a JSON object and converts it into an XCResponse object.\r\n *\r\n * @param {any} json - The JSON object to be converted.\r\n * @return {XCResponse} The converted XCResponse object.\r\n */\r\nexport function convertJsonToXCResponse(json: any): XCResponse {\r\n return {\r\n numRecordings: Number(json[\"numRecordings\"]),\r\n numSpecies: Number(json[\"numSpecies\"]),\r\n page: Number(json[\"page\"]),\r\n numPages: Number(json[\"numPages\"]),\r\n recordings:\r\n json[\"recordings\"]?.map((recording: any): XCRecording => {\r\n return {\r\n id: recording[\"id\"],\r\n gen: recording[\"gen\"],\r\n sp: recording[\"sp\"],\r\n ssp: recording[\"ssp\"],\r\n group: recording[\"group\"],\r\n en: recording[\"en\"],\r\n rec: recording[\"rec\"],\r\n cnt: recording[\"cnt\"],\r\n loc: recording[\"loc\"],\r\n lat: recording[\"lat\"],\r\n lng: recording[\"lng\"],\r\n alt: recording[\"alt\"],\r\n type: recording[\"type\"],\r\n sex: recording[\"sex\"],\r\n stage: recording[\"stage\"],\r\n method: recording[\"method\"],\r\n url: recording[\"url\"],\r\n file: recording[\"file\"],\r\n fileName: recording[\"file-name\"],\r\n sono: {\r\n small: recording[\"sono\"][\"small\"],\r\n med: recording[\"sono\"][\"med\"],\r\n large: recording[\"sono\"][\"large\"],\r\n full: recording[\"sono\"][\"full\"],\r\n },\r\n osci: {\r\n small: recording[\"osci\"][\"small\"],\r\n med: recording[\"osci\"][\"med\"],\r\n large: recording[\"osci\"][\"large\"],\r\n },\r\n lic: recording[\"lic\"],\r\n q: recording[\"q\"],\r\n length: recording[\"length\"],\r\n time: recording[\"time\"],\r\n date: recording[\"date\"],\r\n uploaded: recording[\"uploaded\"],\r\n also: recording[\"also\"],\r\n rmk: recording[\"rmk\"],\r\n birdSeen: recording[\"bird-seen\"],\r\n animalSeen: recording[\"animal-seen\"],\r\n playbackUsed: recording[\"playback-used\"],\r\n temp: recording[\"temp\"],\r\n regnr: recording[\"regnr\"],\r\n auto: recording[\"auto\"],\r\n dvc: recording[\"dvc\"],\r\n mic: recording[\"mic\"],\r\n smp: Number(recording[\"smp\"]),\r\n };\r\n }) || [],\r\n error: json[\"error\"],\r\n message: json[\"message\"],\r\n };\r\n}\r\n"],"mappings":"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","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/utils/utils.ts"],"sourcesContent":["// Import the types and utility functions\r\nimport { AdditionalWrapperOption, XCQueryOption, XCResponse } from \"./types\";\r\nimport { convertJsonToXCResponse, constructQueryUrl } from \"./utils/utils\";\r\n\r\n// Define the base URL for the API\r\nexport const BASE_URL = \"https://www.xeno-canto.org/api/2/recordings\";\r\n\r\n/**\r\n * Searches for a query via Fetch API and returns the response and XC response.\r\n *\r\n * @param {string} query - The query to search for.\r\n * @param {XCQueryOption} [options] - Options for the search query.\r\n * @param {number} [page] - The page parameter is optional and is only needed if the results from a given search don't fit in a single page. If specified, page must be an integer between 1 and XCResponse.numPages.\r\n * @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper.\r\n * @return {Promise<{ url: URL, response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the query URL, the response from fetch and a XCResponse object.\r\n */\r\nasync function search(\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n additionalOptions?: AdditionalWrapperOption,\r\n): Promise<{ url: URL; rawResponse: Response; xcResponse: XCResponse }> {\r\n // If query is empty and options is not provided, throw an error instantly instead of trying to fetch\r\n if (!query.trim() && !options) {\r\n return Promise.reject(\r\n new Error(\r\n \"Please ensure that the 'query' parameter is not empty or that the 'options' parameter is provided\",\r\n ),\r\n );\r\n }\r\n\r\n // Create the query URL\r\n const url = constructQueryUrl(\r\n additionalOptions?.baseUrl ?? BASE_URL,\r\n query,\r\n options,\r\n page,\r\n );\r\n\r\n // Fetch the response and parse the JSON\r\n try {\r\n const rawResponse = await fetch(url);\r\n const json = await rawResponse.json();\r\n const xcResponse = convertJsonToXCResponse(json);\r\n\r\n // If the API returned an error, throw an error\r\n if (xcResponse.error) {\r\n Promise.reject(\r\n new Error(\r\n `Xeno-Canto API returned error '${xcResponse.error}': ${xcResponse.message}`,\r\n ),\r\n );\r\n }\r\n\r\n return Promise.resolve({ url, rawResponse, xcResponse });\r\n } catch (error: any) {\r\n // Error handling\r\n console.error(error);\r\n return Promise.reject(\r\n new Error(`Failed to perform search: ${error.message}`),\r\n );\r\n }\r\n}\r\n\r\nexport { search };\r\nexport * from \"./types\";\r\nexport * from \"./utils\";\r\n","import { XCQueryOption } from \"../types/query\";\r\nimport { XCRecording, XCResponse } from \"../types/response\";\r\n\r\n/**\r\n * Constructs a query URL by appending the provided query string to the base URL and optionally including additional query options.\r\n *\r\n * @param {string} baseUrl - The base URL to which the query string will be appended.\r\n * @param {string} query - The query string to be appended to the base URL.\r\n * @param {XCQueryOption} [options] - Optional additional query options.\r\n * @param {number} [page] - Optional page number.\r\n * @return {URL} The constructed query URL.\r\n */\r\nexport function constructQueryUrl(\r\n baseUrl: string,\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n): URL {\r\n let url = new URL(baseUrl);\r\n let parms = new URLSearchParams();\r\n\r\n // Append query to search parameters\r\n const processedQuery = query.trim();\r\n if (processedQuery) {\r\n parms.append(\"query\", processedQuery);\r\n } else {\r\n parms.append(\"query\", `\"\"`); // As an empty query is not allowed\r\n }\r\n\r\n // Append options to search parameters\r\n if (options) {\r\n const optionParms = convertXCQueryOptionToSearchParams(options);\r\n for (let [key, val] of optionParms.entries()) {\r\n parms.append(key, `\"${val}\"`);\r\n }\r\n }\r\n\r\n // Append page to search parameters\r\n if (page) {\r\n parms.append(\"page\", String(page));\r\n }\r\n\r\n // Set search parameters\r\n url.search = parms.toString();\r\n\r\n return url;\r\n}\r\n\r\n/**\r\n * Converts an XCQueryOption object to a required URL string parameter format. For example: \"grp:\"birds\" cnt:\"United States\" method:\"field recording\"\"\r\n *\r\n * @param {XCQueryOption} option - The XCQueryOption object to convert.\r\n * @return {URLSearchParams} The URLSearchParams object representing the XCQueryOption object.\r\n */\r\nexport function convertXCQueryOptionToSearchParams(\r\n option: XCQueryOption,\r\n): URLSearchParams {\r\n const params = new URLSearchParams();\r\n\r\n if (!option) {\r\n return params;\r\n }\r\n\r\n Object.entries(option).forEach(([key, value]) => {\r\n params.append(key, String(value ?? \"\"));\r\n });\r\n\r\n return params;\r\n}\r\n\r\n/**\r\n * Takes a JSON object and converts it into an XCResponse object.\r\n *\r\n * @param {any} json - The JSON object to be converted.\r\n * @return {XCResponse} The converted XCResponse object.\r\n */\r\nexport function convertJsonToXCResponse(json: any): XCResponse {\r\n return {\r\n numRecordings: Number(json[\"numRecordings\"]),\r\n numSpecies: Number(json[\"numSpecies\"]),\r\n page: Number(json[\"page\"]),\r\n numPages: Number(json[\"numPages\"]),\r\n recordings:\r\n json[\"recordings\"]?.map((recording: any): XCRecording => {\r\n return {\r\n id: recording[\"id\"],\r\n gen: recording[\"gen\"],\r\n sp: recording[\"sp\"],\r\n ssp: recording[\"ssp\"],\r\n group: recording[\"group\"],\r\n en: recording[\"en\"],\r\n rec: recording[\"rec\"],\r\n cnt: recording[\"cnt\"],\r\n loc: recording[\"loc\"],\r\n lat: recording[\"lat\"],\r\n lng: recording[\"lng\"],\r\n alt: recording[\"alt\"],\r\n type: recording[\"type\"],\r\n sex: recording[\"sex\"],\r\n stage: recording[\"stage\"],\r\n method: recording[\"method\"],\r\n url: recording[\"url\"],\r\n file: recording[\"file\"],\r\n fileName: recording[\"file-name\"],\r\n sono: {\r\n small: recording[\"sono\"][\"small\"],\r\n med: recording[\"sono\"][\"med\"],\r\n large: recording[\"sono\"][\"large\"],\r\n full: recording[\"sono\"][\"full\"],\r\n },\r\n osci: {\r\n small: recording[\"osci\"][\"small\"],\r\n med: recording[\"osci\"][\"med\"],\r\n large: recording[\"osci\"][\"large\"],\r\n },\r\n lic: recording[\"lic\"],\r\n q: recording[\"q\"],\r\n length: recording[\"length\"],\r\n time: recording[\"time\"],\r\n date: recording[\"date\"],\r\n uploaded: recording[\"uploaded\"],\r\n also: recording[\"also\"],\r\n rmk: recording[\"rmk\"],\r\n birdSeen: recording[\"bird-seen\"],\r\n animalSeen: recording[\"animal-seen\"],\r\n playbackUsed: recording[\"playback-used\"],\r\n temp: recording[\"temp\"],\r\n regnr: recording[\"regnr\"],\r\n auto: recording[\"auto\"],\r\n dvc: recording[\"dvc\"],\r\n mic: recording[\"mic\"],\r\n smp: Number(recording[\"smp\"]),\r\n };\r\n }) || [],\r\n error: json[\"error\"],\r\n message: json[\"message\"],\r\n };\r\n}\r\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs index 9a87591..098a95f 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -21,27 +21,35 @@ var __async = (__this, __arguments, generator) => { // src/utils/utils.ts function constructQueryUrl(baseUrl, query, options, page) { - let url = baseUrl; - if (query.trim()) { - url += query.trim(); - if (options) { - url += " "; - } + let url = new URL(baseUrl); + let parms = new URLSearchParams(); + const processedQuery = query.trim(); + if (processedQuery) { + parms.append("query", processedQuery); + } else { + parms.append("query", `""`); } if (options) { - url += convertXCQueryOptionToString(options); + const optionParms = convertXCQueryOptionToSearchParams(options); + for (let [key, val] of optionParms.entries()) { + parms.append(key, `"${val}"`); + } } if (page) { - url += `&page=${page}`; + parms.append("page", String(page)); } - url = url.replace(/\s+/g, "+"); + url.search = parms.toString(); return url; } -function convertXCQueryOptionToString(option) { +function convertXCQueryOptionToSearchParams(option) { + const params = new URLSearchParams(); if (!option) { - return ""; + return params; } - return Object.entries(option).map(([key, value]) => `${key}:"${value != null ? value : ""}"`).join(" "); + Object.entries(option).forEach(([key, value]) => { + params.append(key, String(value != null ? value : "")); + }); + return params; } function convertJsonToXCResponse(json) { var _a; @@ -107,7 +115,7 @@ function convertJsonToXCResponse(json) { } // src/index.ts -var BASE_URL = "https://www.xeno-canto.org/api/2/recordings?query="; +var BASE_URL = "https://www.xeno-canto.org/api/2/recordings"; function search(query, options, page, additionalOptions) { return __async(this, null, function* () { var _a; @@ -131,7 +139,7 @@ function search(query, options, page, additionalOptions) { if (xcResponse.error) { Promise.reject( new Error( - `API returned error '${xcResponse.error}': ${xcResponse.message}` + `Xeno-Canto API returned error '${xcResponse.error}': ${xcResponse.message}` ) ); } @@ -148,7 +156,7 @@ export { BASE_URL, constructQueryUrl, convertJsonToXCResponse, - convertXCQueryOptionToString, + convertXCQueryOptionToSearchParams, search }; //# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/index.mjs.map b/dist/index.mjs.map index cb426d3..7820386 100644 --- a/dist/index.mjs.map +++ b/dist/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/utils/utils.ts","../src/index.ts"],"sourcesContent":["import { XCQueryOption } from \"../types/query\";\r\nimport { XCRecording, XCResponse } from \"../types/response\";\r\n\r\n/**\r\n * Constructs a query URL by appending the provided query string to the base URL and\r\n * optionally including additional query options.\r\n *\r\n * @param {string} baseUrl - The base URL to which the query string will be appended.\r\n * @param {string} query - The query string to be appended to the base URL.\r\n * @param {XCQueryOption} [options] - Optional additional query options.\r\n * @param {number} [page] - Optional page number.\r\n * @return {string} The constructed query URL.\r\n */\r\nexport function constructQueryUrl(\r\n baseUrl: string,\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n): string {\r\n let url = baseUrl;\r\n\r\n if (query.trim()) {\r\n url += query.trim();\r\n\r\n if (options) {\r\n url += \" \";\r\n }\r\n }\r\n\r\n if (options) {\r\n url += convertXCQueryOptionToString(options);\r\n }\r\n\r\n if (page) {\r\n url += `&page=${page}`;\r\n }\r\n\r\n url = url.replace(/\\s+/g, \"+\"); // Replace spaces with +\r\n\r\n return url;\r\n}\r\n\r\n/**\r\n * Converts an XCQueryOption object to a URL-encoded string.\r\n *\r\n * @param {XCQueryOption} option - The XCQueryOption object to convert.\r\n * @return {string} The URL-encoded string representation of the XCQueryOption object.\r\n */\r\nexport function convertXCQueryOptionToString(option: XCQueryOption): string {\r\n if (!option) {\r\n return \"\";\r\n }\r\n\r\n return Object.entries(option)\r\n .map(([key, value]) => `${key}:\"${value ?? \"\"}\"`)\r\n .join(\" \");\r\n}\r\n\r\n/**\r\n * Takes a JSON object and converts it into an XCResponse object.\r\n *\r\n * @param {any} json - The JSON object to be converted.\r\n * @return {XCResponse} The converted XCResponse object.\r\n */\r\nexport function convertJsonToXCResponse(json: any): XCResponse {\r\n return {\r\n numRecordings: Number(json[\"numRecordings\"]),\r\n numSpecies: Number(json[\"numSpecies\"]),\r\n page: Number(json[\"page\"]),\r\n numPages: Number(json[\"numPages\"]),\r\n recordings:\r\n json[\"recordings\"]?.map((recording: any): XCRecording => {\r\n return {\r\n id: recording[\"id\"],\r\n gen: recording[\"gen\"],\r\n sp: recording[\"sp\"],\r\n ssp: recording[\"ssp\"],\r\n group: recording[\"group\"],\r\n en: recording[\"en\"],\r\n rec: recording[\"rec\"],\r\n cnt: recording[\"cnt\"],\r\n loc: recording[\"loc\"],\r\n lat: recording[\"lat\"],\r\n lng: recording[\"lng\"],\r\n alt: recording[\"alt\"],\r\n type: recording[\"type\"],\r\n sex: recording[\"sex\"],\r\n stage: recording[\"stage\"],\r\n method: recording[\"method\"],\r\n url: recording[\"url\"],\r\n file: recording[\"file\"],\r\n fileName: recording[\"file-name\"],\r\n sono: {\r\n small: recording[\"sono\"][\"small\"],\r\n med: recording[\"sono\"][\"med\"],\r\n large: recording[\"sono\"][\"large\"],\r\n full: recording[\"sono\"][\"full\"],\r\n },\r\n osci: {\r\n small: recording[\"osci\"][\"small\"],\r\n med: recording[\"osci\"][\"med\"],\r\n large: recording[\"osci\"][\"large\"],\r\n },\r\n lic: recording[\"lic\"],\r\n q: recording[\"q\"],\r\n length: recording[\"length\"],\r\n time: recording[\"time\"],\r\n date: recording[\"date\"],\r\n uploaded: recording[\"uploaded\"],\r\n also: recording[\"also\"],\r\n rmk: recording[\"rmk\"],\r\n birdSeen: recording[\"bird-seen\"],\r\n animalSeen: recording[\"animal-seen\"],\r\n playbackUsed: recording[\"playback-used\"],\r\n temp: recording[\"temp\"],\r\n regnr: recording[\"regnr\"],\r\n auto: recording[\"auto\"],\r\n dvc: recording[\"dvc\"],\r\n mic: recording[\"mic\"],\r\n smp: Number(recording[\"smp\"]),\r\n };\r\n }) || [],\r\n error: json[\"error\"],\r\n message: json[\"message\"],\r\n };\r\n}\r\n","// Import the types and utility functions\r\nimport { AdditionalWrapperOption, XCQueryOption, XCResponse } from \"./types\";\r\nimport { convertJsonToXCResponse, constructQueryUrl } from \"./utils/utils\";\r\n\r\n// Define the base URL for the API\r\nexport const BASE_URL = \"https://www.xeno-canto.org/api/2/recordings?query=\";\r\n\r\n/**\r\n * Searches for a query and returns the response and XC response.\r\n *\r\n * @param {string} query - The query to search for.\r\n * @param {XCQueryOption} [options] - Options for the search query.\r\n * @param {number} [page] - The page parameter is optional and is only needed if the results from a given search don't fit in a single page. If specified, page must be an integer between 1 and XCResponse.numPages.\r\n * @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper.\r\n * @return {Promise<{ response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the response from fetch and a XCResponse object.\r\n */\r\nasync function search(\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n additionalOptions?: AdditionalWrapperOption,\r\n): Promise<{ url: string; rawResponse: Response; xcResponse: XCResponse }> {\r\n // If query is empty and options is not provided, throw an error instantly instead of trying to fetch\r\n if (!query.trim() && !options) {\r\n return Promise.reject(\r\n new Error(\r\n \"Please ensure that the 'query' parameter is not empty or that the 'options' parameter is provided\",\r\n ),\r\n );\r\n }\r\n\r\n // Create the query URL\r\n const url = constructQueryUrl(\r\n additionalOptions?.baseUrl ?? BASE_URL,\r\n query,\r\n options,\r\n page,\r\n );\r\n\r\n // Fetch the response and parse the JSON\r\n try {\r\n const rawResponse = await fetch(url);\r\n const json = await rawResponse.json();\r\n const xcResponse = convertJsonToXCResponse(json);\r\n\r\n // If the API returned an error, throw an error\r\n if (xcResponse.error) {\r\n Promise.reject(\r\n new Error(\r\n `API returned error '${xcResponse.error}': ${xcResponse.message}`,\r\n ),\r\n );\r\n }\r\n\r\n return Promise.resolve({ url, rawResponse, xcResponse });\r\n } catch (error: any) {\r\n // Error handling\r\n console.error(error);\r\n return Promise.reject(\r\n new Error(`Failed to perform search: ${error.message}`),\r\n );\r\n }\r\n}\r\n\r\nexport { search };\r\nexport * from \"./types\";\r\nexport * from \"./utils\";\r\n"],"mappings":"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","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/utils/utils.ts","../src/index.ts"],"sourcesContent":["import { XCQueryOption } from \"../types/query\";\r\nimport { XCRecording, XCResponse } from \"../types/response\";\r\n\r\n/**\r\n * Constructs a query URL by appending the provided query string to the base URL and optionally including additional query options.\r\n *\r\n * @param {string} baseUrl - The base URL to which the query string will be appended.\r\n * @param {string} query - The query string to be appended to the base URL.\r\n * @param {XCQueryOption} [options] - Optional additional query options.\r\n * @param {number} [page] - Optional page number.\r\n * @return {URL} The constructed query URL.\r\n */\r\nexport function constructQueryUrl(\r\n baseUrl: string,\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n): URL {\r\n let url = new URL(baseUrl);\r\n let parms = new URLSearchParams();\r\n\r\n // Append query to search parameters\r\n const processedQuery = query.trim();\r\n if (processedQuery) {\r\n parms.append(\"query\", processedQuery);\r\n } else {\r\n parms.append(\"query\", `\"\"`); // As an empty query is not allowed\r\n }\r\n\r\n // Append options to search parameters\r\n if (options) {\r\n const optionParms = convertXCQueryOptionToSearchParams(options);\r\n for (let [key, val] of optionParms.entries()) {\r\n parms.append(key, `\"${val}\"`);\r\n }\r\n }\r\n\r\n // Append page to search parameters\r\n if (page) {\r\n parms.append(\"page\", String(page));\r\n }\r\n\r\n // Set search parameters\r\n url.search = parms.toString();\r\n\r\n return url;\r\n}\r\n\r\n/**\r\n * Converts an XCQueryOption object to a required URL string parameter format. For example: \"grp:\"birds\" cnt:\"United States\" method:\"field recording\"\"\r\n *\r\n * @param {XCQueryOption} option - The XCQueryOption object to convert.\r\n * @return {URLSearchParams} The URLSearchParams object representing the XCQueryOption object.\r\n */\r\nexport function convertXCQueryOptionToSearchParams(\r\n option: XCQueryOption,\r\n): URLSearchParams {\r\n const params = new URLSearchParams();\r\n\r\n if (!option) {\r\n return params;\r\n }\r\n\r\n Object.entries(option).forEach(([key, value]) => {\r\n params.append(key, String(value ?? \"\"));\r\n });\r\n\r\n return params;\r\n}\r\n\r\n/**\r\n * Takes a JSON object and converts it into an XCResponse object.\r\n *\r\n * @param {any} json - The JSON object to be converted.\r\n * @return {XCResponse} The converted XCResponse object.\r\n */\r\nexport function convertJsonToXCResponse(json: any): XCResponse {\r\n return {\r\n numRecordings: Number(json[\"numRecordings\"]),\r\n numSpecies: Number(json[\"numSpecies\"]),\r\n page: Number(json[\"page\"]),\r\n numPages: Number(json[\"numPages\"]),\r\n recordings:\r\n json[\"recordings\"]?.map((recording: any): XCRecording => {\r\n return {\r\n id: recording[\"id\"],\r\n gen: recording[\"gen\"],\r\n sp: recording[\"sp\"],\r\n ssp: recording[\"ssp\"],\r\n group: recording[\"group\"],\r\n en: recording[\"en\"],\r\n rec: recording[\"rec\"],\r\n cnt: recording[\"cnt\"],\r\n loc: recording[\"loc\"],\r\n lat: recording[\"lat\"],\r\n lng: recording[\"lng\"],\r\n alt: recording[\"alt\"],\r\n type: recording[\"type\"],\r\n sex: recording[\"sex\"],\r\n stage: recording[\"stage\"],\r\n method: recording[\"method\"],\r\n url: recording[\"url\"],\r\n file: recording[\"file\"],\r\n fileName: recording[\"file-name\"],\r\n sono: {\r\n small: recording[\"sono\"][\"small\"],\r\n med: recording[\"sono\"][\"med\"],\r\n large: recording[\"sono\"][\"large\"],\r\n full: recording[\"sono\"][\"full\"],\r\n },\r\n osci: {\r\n small: recording[\"osci\"][\"small\"],\r\n med: recording[\"osci\"][\"med\"],\r\n large: recording[\"osci\"][\"large\"],\r\n },\r\n lic: recording[\"lic\"],\r\n q: recording[\"q\"],\r\n length: recording[\"length\"],\r\n time: recording[\"time\"],\r\n date: recording[\"date\"],\r\n uploaded: recording[\"uploaded\"],\r\n also: recording[\"also\"],\r\n rmk: recording[\"rmk\"],\r\n birdSeen: recording[\"bird-seen\"],\r\n animalSeen: recording[\"animal-seen\"],\r\n playbackUsed: recording[\"playback-used\"],\r\n temp: recording[\"temp\"],\r\n regnr: recording[\"regnr\"],\r\n auto: recording[\"auto\"],\r\n dvc: recording[\"dvc\"],\r\n mic: recording[\"mic\"],\r\n smp: Number(recording[\"smp\"]),\r\n };\r\n }) || [],\r\n error: json[\"error\"],\r\n message: json[\"message\"],\r\n };\r\n}\r\n","// Import the types and utility functions\r\nimport { AdditionalWrapperOption, XCQueryOption, XCResponse } from \"./types\";\r\nimport { convertJsonToXCResponse, constructQueryUrl } from \"./utils/utils\";\r\n\r\n// Define the base URL for the API\r\nexport const BASE_URL = \"https://www.xeno-canto.org/api/2/recordings\";\r\n\r\n/**\r\n * Searches for a query via Fetch API and returns the response and XC response.\r\n *\r\n * @param {string} query - The query to search for.\r\n * @param {XCQueryOption} [options] - Options for the search query.\r\n * @param {number} [page] - The page parameter is optional and is only needed if the results from a given search don't fit in a single page. If specified, page must be an integer between 1 and XCResponse.numPages.\r\n * @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper.\r\n * @return {Promise<{ url: URL, response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the query URL, the response from fetch and a XCResponse object.\r\n */\r\nasync function search(\r\n query: string,\r\n options?: XCQueryOption,\r\n page?: number,\r\n additionalOptions?: AdditionalWrapperOption,\r\n): Promise<{ url: URL; rawResponse: Response; xcResponse: XCResponse }> {\r\n // If query is empty and options is not provided, throw an error instantly instead of trying to fetch\r\n if (!query.trim() && !options) {\r\n return Promise.reject(\r\n new Error(\r\n \"Please ensure that the 'query' parameter is not empty or that the 'options' parameter is provided\",\r\n ),\r\n );\r\n }\r\n\r\n // Create the query URL\r\n const url = constructQueryUrl(\r\n additionalOptions?.baseUrl ?? BASE_URL,\r\n query,\r\n options,\r\n page,\r\n );\r\n\r\n // Fetch the response and parse the JSON\r\n try {\r\n const rawResponse = await fetch(url);\r\n const json = await rawResponse.json();\r\n const xcResponse = convertJsonToXCResponse(json);\r\n\r\n // If the API returned an error, throw an error\r\n if (xcResponse.error) {\r\n Promise.reject(\r\n new Error(\r\n `Xeno-Canto API returned error '${xcResponse.error}': ${xcResponse.message}`,\r\n ),\r\n );\r\n }\r\n\r\n return Promise.resolve({ url, rawResponse, xcResponse });\r\n } catch (error: any) {\r\n // Error handling\r\n console.error(error);\r\n return Promise.reject(\r\n new Error(`Failed to perform search: ${error.message}`),\r\n );\r\n }\r\n}\r\n\r\nexport { search };\r\nexport * from \"./types\";\r\nexport * from \"./utils\";\r\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/package.json b/package.json index 6ec8904..1db7250 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "dev": "tsup --watch", "test": "vitest", "prepare": "husky install", - "release": "bumpp --commit --tag --push", + "release": "npm run build && bumpp --commit --tag --push", "docs": "typedoc --options typedoc.json" }, "author": {