client.ts•7.55 kB
/**
* PokeAPI REST Client
* Official REST API client for https://pokeapi.co/api/v2/
*/
const BASE_URL = 'https://pokeapi.co/api/v2';
// Type definitions based on PokeAPI documentation
export interface NamedAPIResource {
name: string;
url: string;
}
export interface APIResource {
url: string;
}
export interface PaginatedResponse<T> {
count: number;
next: string | null;
previous: string | null;
results: T[];
}
export interface Pokemon {
id: number;
name: string;
base_experience: number;
height: number;
weight: number;
order: number;
is_default: boolean;
location_area_encounters: string;
sprites: {
back_default: string | null;
back_female: string | null;
back_shiny: string | null;
back_shiny_female: string | null;
front_default: string | null;
front_female: string | null;
front_shiny: string | null;
front_shiny_female: string | null;
other?: {
'official-artwork'?: {
front_default?: string;
front_shiny?: string;
};
};
};
species: NamedAPIResource;
stats: {
base_stat: number;
effort: number;
stat: NamedAPIResource;
}[];
types: {
slot: number;
type: NamedAPIResource;
}[];
abilities: {
is_hidden: boolean;
slot: number;
ability: NamedAPIResource;
}[];
forms: NamedAPIResource[];
game_indices: {
game_index: number;
version: NamedAPIResource;
}[];
held_items: {
item: NamedAPIResource;
version_details: {
rarity: number;
version: NamedAPIResource;
}[];
}[];
moves: {
move: NamedAPIResource;
version_group_details: {
level_learned_at: number;
move_learn_method: NamedAPIResource;
version_group: NamedAPIResource;
}[];
}[];
}
export interface Type {
id: number;
name: string;
damage_relations: {
no_damage_to: NamedAPIResource[];
half_damage_to: NamedAPIResource[];
double_damage_to: NamedAPIResource[];
no_damage_from: NamedAPIResource[];
half_damage_from: NamedAPIResource[];
double_damage_from: NamedAPIResource[];
};
game_indices: {
game_index: number;
generation: NamedAPIResource;
}[];
generation: NamedAPIResource;
move_damage_class: NamedAPIResource | null;
names: {
name: string;
language: NamedAPIResource;
}[];
pokemon: {
slot: number;
pokemon: NamedAPIResource;
}[];
moves: NamedAPIResource[];
}
export interface Stat {
id: number;
name: string;
game_index: number;
is_battle_only: boolean;
affecting_moves: {
increase: {
change: number;
move: NamedAPIResource;
}[];
decrease: {
change: number;
move: NamedAPIResource;
}[];
};
affecting_natures: {
increase: NamedAPIResource[];
decrease: NamedAPIResource[];
};
characteristics: APIResource[];
move_damage_class: NamedAPIResource | null;
names: {
name: string;
language: NamedAPIResource;
}[];
}
export interface LocationAreaEncounter {
location_area: NamedAPIResource;
version_details: {
max_chance: number;
encounter_details: {
min_level: number;
max_level: number;
condition_values: NamedAPIResource[];
chance: number;
method: NamedAPIResource;
}[];
version: NamedAPIResource;
}[];
}
/**
* PokeAPI REST Client Class
*/
export class PokeAPIClient {
private baseUrl: string;
constructor(baseUrl: string = BASE_URL) {
this.baseUrl = baseUrl;
}
private async fetchAPI<T>(endpoint: string): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch ${url}: ${error.message}`);
}
throw new Error(`Failed to fetch ${url}: Unknown error`);
}
}
// Pokemon endpoints
async getPokemonList(limit: number = 20, offset: number = 0): Promise<PaginatedResponse<NamedAPIResource>> {
return this.fetchAPI<PaginatedResponse<NamedAPIResource>>(`/pokemon?limit=${limit}&offset=${offset}`);
}
async getPokemon(idOrName: string | number): Promise<Pokemon> {
return this.fetchAPI<Pokemon>(`/pokemon/${idOrName}`);
}
async getPokemonEncounters(idOrName: string | number): Promise<LocationAreaEncounter[]> {
return this.fetchAPI<LocationAreaEncounter[]>(`/pokemon/${idOrName}/encounters`);
}
// Type endpoints
async getTypeList(): Promise<PaginatedResponse<NamedAPIResource>> {
return this.fetchAPI<PaginatedResponse<NamedAPIResource>>('/type');
}
async getType(idOrName: string | number): Promise<Type> {
return this.fetchAPI<Type>(`/type/${idOrName}`);
}
// Stat endpoints
async getStatList(): Promise<PaginatedResponse<NamedAPIResource>> {
return this.fetchAPI<PaginatedResponse<NamedAPIResource>>('/stat');
}
async getStat(idOrName: string | number): Promise<Stat> {
return this.fetchAPI<Stat>(`/stat/${idOrName}`);
}
// Utility methods
async searchPokemon(query: string): Promise<Pokemon | null> {
try {
return await this.getPokemon(query.toLowerCase());
} catch (error) {
// If not found by name, try to search in the list
const list = await this.getPokemonList(2000); // Get larger list for search
const match = list.results.find(p =>
p.name.toLowerCase().includes(query.toLowerCase())
);
if (match) {
return await this.getPokemon(match.name);
}
return null;
}
}
async getPokemonWithEncounters(idOrName: string | number): Promise<{
pokemon: Pokemon;
encounters: LocationAreaEncounter[];
}> {
const [pokemon, encounters] = await Promise.all([
this.getPokemon(idOrName),
this.getPokemonEncounters(idOrName)
]);
return { pokemon, encounters };
}
async getTypeEffectiveness(typeName: string): Promise<{
type: Type;
strongAgainst: string[];
weakAgainst: string[];
immuneTo: string[];
resistantTo: string[];
vulnerableTo: string[];
}> {
const type = await this.getType(typeName);
return {
type,
strongAgainst: type.damage_relations.double_damage_to.map(t => t.name),
weakAgainst: type.damage_relations.half_damage_to.map(t => t.name),
immuneTo: type.damage_relations.no_damage_to.map(t => t.name),
resistantTo: type.damage_relations.half_damage_from.map(t => t.name),
vulnerableTo: type.damage_relations.double_damage_from.map(t => t.name),
};
}
}
// Export a default instance
export const pokeAPI = new PokeAPIClient();
// Export individual functions for convenience
export const getPokemon = (idOrName: string | number) => pokeAPI.getPokemon(idOrName);
export const getPokemonList = (limit?: number, offset?: number) => pokeAPI.getPokemonList(limit, offset);
export const getPokemonEncounters = (idOrName: string | number) => pokeAPI.getPokemonEncounters(idOrName);
export const getType = (idOrName: string | number) => pokeAPI.getType(idOrName);
export const getTypeList = () => pokeAPI.getTypeList();
export const getStat = (idOrName: string | number) => pokeAPI.getStat(idOrName);
export const getStatList = () => pokeAPI.getStatList();
export const searchPokemon = (query: string) => pokeAPI.searchPokemon(query);
export const getTypeEffectiveness = (typeName: string) => pokeAPI.getTypeEffectiveness(typeName);