npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alejo-dev/xavi-providers

v1.0.2

Published

Un proveedor de API cliente modular y fuertemente tipado para React Native con soporte completo para React Query v5, persistencia automática, detección de conectividad y modo offline.

Downloads

41

Readme

@alejo-dev/xavi-providers

Un proveedor de API cliente modular y fuertemente tipado para React Native con soporte completo para React Query v5, persistencia automática, detección de conectividad y modo offline.

🚀 Características

  • React Query v5 con persistencia AsyncStorage
  • Hooks REST completos (GET, POST, PUT, PATCH, DELETE, UPLOAD)
  • Hooks GraphQL (Query, Mutation, Infinite Query)
  • Detección automática de conectividad y estado del servidor
  • Modo offline manual para debugging
  • Ping/health checks configurables
  • Manejo de errores normalizado
  • TypeScript fuerte con generics
  • Compatible con React Native

📦 Instalación

npm install @alejo-dev/xavi-providers
# o
yarn add @alejo-dev/xavi-providers

Dependencias requeridas

Asegúrate de tener instaladas estas dependencias peer:

{
  "@react-native-async-storage/async-storage": "^2.1.2",
  "@tanstack/react-query": "^5.75.5",
  "@tanstack/react-query-persist-client": "^5.77.1",
  "axios": "^1.9.0",
  "react": ">=19.0.0",
  "react-native": ">=0.79.2"
}

🏗️ Configuración Básica

1. Configura el QueryClient

import { QueryClient } from '@tanstack/react-query';
import { createAsyncStoragePersistor } from '@tanstack/react-query-persist-client';
import AsyncStorage from '@react-native-async-storage/async-storage';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutos
      gcTime: 1000 * 60 * 60, // 1 hora
    },
  },
});

const asyncStoragePersistor = createAsyncStoragePersistor({
  storage: AsyncStorage,
  key: 'REACT_QUERY_CACHE',
});

2. Envuelve tu App con el Provider

import React from 'react';
import { ApiClientProvider } from '@alejo-dev/xavi-providers';

export default function App() {
  return (
    <ApiClientProvider
      queryClient={queryClient}
      config={{
        pingEndpoint: '/api/health',
        enableServerHealthCheck: true,
        enableInitialPing: true,
      }}
    >
      {/* Tu aplicación aquí */}
    </ApiClientProvider>
  );
}

🔧 Configuración Avanzada

const config = {
  // Endpoint para health checks
  pingEndpoint: '/api/health',
  pingMethod: 'GET',
  pingHeaders: { Authorization: 'Bearer token' },
  pingTimeoutMs: 5000,

  // Reintentos y offline
  offlineRetryIntervalMs: 30000,
  maxConsecutiveFailuresBeforeOffline: 3,
  offlineStatusCodes: [502, 503, 504],

  // Validación de respuesta de ping
  pingResponseValidator: (response) => response.status === 200,

  // Modo offline manual
  manualOfflineStrategy: 'block-network', // o 'serve-cache-and-block-network'
  enableDebugOfflineControls: __DEV__,

  // --- Health check mode ---
  // 'always' (default): health check inicial y periódicos
  // 'on-failure': solo hace health check cuando una request falla
  healthCheckMode: 'always', // o 'on-failure'
};

📡 Uso de Hooks REST

GET Query

import { useGetQuery } from '@alejo-dev/xavi-providers';

interface User {
  id: number;
  name: string;
  email: string;
}

function UserProfile({ userId }: { userId: number }) {
  const { data, isLoading, error } = useGetQuery<User>({
    endpoint: `/api/users/${userId}`,
    queryKey: ['user', userId],
    enabled: !!userId,
  });

  if (isLoading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error.message}</Text>;

  return (
    <View>
      <Text>{data?.name}</Text>
      <Text>{data?.email}</Text>
    </View>
  );
}

Infinite Query

import { useInfiniteGetQuery } from '@alejo-dev/xavi-providers';

function PostsList() {
  const { data, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = useInfiniteGetQuery({
    endpoint: '/api/posts',
    queryKey: ['posts'],
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });

  const posts = data?.pages.flatMap((page) => page.posts) ?? [];

  return (
    <FlatList
      data={posts}
      onEndReached={() => hasNextPage && fetchNextPage()}
      // ... otros props
    />
  );
}

Mutations (POST, PUT, PATCH, DELETE)

import { usePostMutation, usePutMutation, useDeleteMutation } from '@alejo-dev/xavi-providers';

function UserForm() {
  const createUser = usePostMutation({
    endpoint: '/api/users',
    onSuccess: (data) => {
      console.log('Usuario creado:', data);
      // Invalidar queries relacionadas
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });

  const updateUser = usePutMutation<UpdateInput>({
    // El endpoint puede ser una función de las variables para URLs dinámicas
    endpoint: (variables) => `/api/users/${variables.id}`,
    onSuccess: () => {
      // Actualizar cache optimísticamente
      queryClient.setQueryData(['user', userId], updatedUser);
    },
  });

  const deleteUser = useDeleteMutation<DeleteInput>({
    endpoint: (variables) => `/api/users/${variables.id}`,
    onSuccess: () => {
      queryClient.removeQueries({ queryKey: ['user', userId] });
    },
  });

  const handleSubmit = (userData) => {
    createUser.mutate(userData);
  };

  return (
    <Button
      title="Crear Usuario"
      onPress={() => handleSubmit(formData)}
      disabled={createUser.isPending}
    />
  );
}

File Upload

import { useUploadMutation } from '@alejo-dev/xavi-providers';

function ImageUploader() {
  const uploadImage = useUploadMutation({
    endpoint: '/api/upload',
    onSuccess: (data) => {
      console.log('Imagen subida:', data.url);
    },
  });

  const handleUpload = async (imageUri) => {
    const formData = new FormData();
    formData.append('image', {
      uri: imageUri,
      type: 'image/jpeg',
      name: 'photo.jpg',
    });

    uploadImage.mutate(formData);
  };

  return <Button title="Subir Imagen" onPress={() => handleUpload(imageUri)} />;
}

🌐 Uso de Hooks GraphQL

GraphQL Query

import { useGQLQuery } from '@alejo-dev/xavi-providers';

const GET_USER = `
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading } = useGQLQuery({
    document: GET_USER,
    variables: { id: userId },
    queryKey: ['user', userId],
  });

  return <Text>{data?.user?.name}</Text>;
}

GraphQL Mutation

import { useGQLMutation } from '@alejo-dev/xavi-providers';

const CREATE_USER = `
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

function CreateUserForm() {
  const createUser = useGQLMutation({
    document: CREATE_USER,
    onSuccess: (data) => {
      console.log('Usuario creado:', data.createUser);
    },
  });

  return <Button title="Crear Usuario" onPress={() => createUser.mutate({ input: formData })} />;
}

📶 Gestión de Estado Offline

Estado del Servidor

import { useApiServerStatus } from '@alejo-dev/xavi-providers';

function ConnectionStatus() {
  const {
    status,
    isServerReachable,
    isCheckingConnection,
    offlineReason,
    consecutiveFailures,
    lastOnlineAt,
    lastOfflineAt,
  } = useApiServerStatus();

  return (
    <View>
      <Text>Estado: {status}</Text>
      <Text>Conectado: {isServerReachable ? 'Sí' : 'No'}</Text>
      <Text>Razón offline: {offlineReason}</Text>
      <Text>Fallos consecutivos: {consecutiveFailures}</Text>
    </View>
  );
}

Modo Offline Manual (Debugging)

import { useApiOfflineMode } from '@alejo-dev/xavi-providers';

function DebugControls() {
  const {
    isManualOfflineEnabled,
    isEffectiveOfflineMode,
    enableManualOfflineMode,
    disableManualOfflineMode,
    toggleManualOfflineMode,
  } = useApiOfflineMode();

  return (
    <View>
      <Text>Offline Manual: {isManualOfflineEnabled ? 'Activado' : 'Desactivado'}</Text>
      <Text>Offline Efectivo: {isEffectiveOfflineMode ? 'Sí' : 'No'}</Text>

      <Button
        title={isManualOfflineEnabled ? 'Desactivar Offline' : 'Activar Offline'}
        onPress={toggleManualOfflineMode}
      />
    </View>
  );
}

Acciones del Servidor

import { useApiServerStatus } from '@alejo-dev/xavi-providers';

function ServerActions() {
  const { checkServerConnection, markOnline, markOffline } = useApiServerStatus();

  return (
    <View>
      <Button title="Verificar Conexión" onPress={checkServerConnection} />
      <Button title="Marcar Online" onPress={() => markOnline()} />
      <Button title="Marcar Offline" onPress={() => markOffline('manual-debug')} />
    </View>
  );
}

🛠️ Utilidades

Crear Query Keys

import { createQueryKeys } from '@alejo-dev/xavi-providers';

export const userKeys = createQueryKeys('users');
// {
//   all: () => ['users'],
//   lists: () => ['users', 'list'],
//   list: (filters) => ['users', 'list', 'serialized-filters'],
//   details: () => ['users', 'detail'],
//   detail: (id) => ['users', 'detail', id],
//   infinite: () => ['users', 'infinite'],
//   infiniteList: (filters) => ['users', 'infinite', 'serialized-filters'],
// }

// Los filtros se serializan automáticamente para mantener consistencia
const filters = { status: 'active', sort: 'name' };
userKeys.list(filters); // ['users', 'list', '{"sort":"name","status":"active"}']

Detección de Errores de Conectividad

import { isConnectivityError, normalizeRestError } from '@alejo-dev/xavi-providers';

try {
  // alguna petición
} catch (error) {
  if (isConnectivityError(error)) {
    console.log('Error de red - activando modo offline');
  } else {
    const normalizedError = normalizeRestError(error);
    console.log('Error de aplicación:', normalizedError.message);
  }
}

Error Offline Manual

import { ManualOfflineError, isManualOfflineError } from '@alejo-dev/xavi-providers';

try {
  // petición que falla en modo offline
} catch (error) {
  if (isManualOfflineError(error)) {
    console.log('Modo offline manual activado');
  }
}

📋 Estados del Servidor

| Estado | Descripción | | ---------- | -------------------------------- | | unknown | Estado inicial, sin verificación | | online | Servidor reachable | | checking | Verificando conectividad | | offline | Servidor no reachable |

Razones de Offline

  • none - Conectado
  • manual-debug - Activado manualmente
  • network-error - Error de red
  • ping-failed - Ping falló
  • server-unreachable - Servidor no responde

🔄 Ciclo de Vida de Peticiones

Todas las peticiones automáticamente:

  1. En éxito: Llaman handleRequestSuccess() → resetean contador de fallos
  2. En fallo: Verifican si es error de conectividad → incrementan contador
  3. Después de N fallos: Disparan verificación de ping
  4. Ping falla: Marcan como offline
  5. Ping reintenta: Cada intervalo configurado

⚡ Mejores Prácticas

1. Query Keys Consistentes

// ✅ Bueno
const userKeys = createQueryKeys('users');
const { data } = useGetQuery({ queryKey: userKeys.detail(userId) });

// ❌ Evitar
const { data } = useGetQuery({ queryKey: ['user', userId] });

2. Invalidación Inteligente

const createUser = usePostMutation({
  endpoint: '/api/users',
  onSuccess: () => {
    // Invalidar lista de usuarios
    queryClient.invalidateQueries({ queryKey: userKeys.lists() });
  },
});

3. Cache Optimista

const updateUser = usePutMutation({
  endpoint: `/api/users/${userId}`,
  onMutate: async (newUser) => {
    // Cancelar queries salientes
    await queryClient.cancelQueries({ queryKey: userKeys.detail(userId) });

    // Snapshot del valor anterior
    const previousUser = queryClient.getQueryData(userKeys.detail(userId));

    // Actualizar cache optimísticamente
    queryClient.setQueryData(userKeys.detail(userId), newUser);

    return { previousUser };
  },
  onError: (err, newUser, context) => {
    // Revertir en caso de error
    if (context?.previousUser) {
      queryClient.setQueryData(userKeys.detail(userId), context.previousUser);
    }
  },
});

4. Manejo de Errores

const { data, error, isError } = useGetQuery({
  // ...
});

if (isError) {
  if (isConnectivityError(error)) {
    return <OfflineMessage />;
  } else {
    return <ErrorMessage error={normalizeRestError(error)} />;
  }
}

📊 Tipos TypeScript

// Configuración
type ApiClientConfig = {
  pingEndpoint?: string;
  pingMethod?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  pingHeaders?: Record<string, string>;
  pingTimeoutMs?: number;
  offlineRetryIntervalMs?: number;
  enableServerHealthCheck?: boolean;
  enableInitialPing?: boolean;
  maxConsecutiveFailuresBeforeOffline?: number;
  offlineStatusCodes?: number[];
  pingResponseValidator?: (response: any) => boolean;
  manualOfflineStrategy?: 'block-network' | 'serve-cache-and-block-network';
  enableDebugOfflineControls?: boolean;
  healthCheckMode?: 'always' | 'on-failure';
};

// Estado del servidor
type ServerStatus = 'unknown' | 'online' | 'checking' | 'offline';
type OfflineReason =
  | 'none'
  | 'manual-debug'
  | 'network-error'
  | 'ping-failed'
  | 'server-unreachable';

// Errores
class ManualOfflineError extends Error {
  constructor(message?: string);
}
function isManualOfflineError(error: any): error is ManualOfflineError;

🐛 Debugging

Estado de React Query DevTools

Instala @tanstack/react-query-devtools para inspeccionar el estado del cache:

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

export default function App() {
  return (
    <ApiClientProvider>
      {/* Tu app */}
      <ReactQueryDevtools initialIsOpen={false} />
    </ApiClientProvider>
  );
}

📝 Notas de Migración

Si actualizas desde una versión anterior:

  1. Provider: El ApiClientProvider ahora acepta un prop config opcional
  2. Persistencia: La configuración de persistencia se mantiene igual
  3. Hooks: Todos los hooks existentes siguen funcionando
  4. Nuevos hooks: Los nuevos hooks están disponibles pero son opcionales

🤝 Contribuir

  1. Fork el repositorio
  2. Crea una branch para tu feature (git checkout -b feature/amazing-feature)
  3. Commit tus cambios (git commit -m 'Add amazing feature')
  4. Push a la branch (git push origin feature/amazing-feature)
  5. Abre un Pull Request

📄 Licencia

ISC