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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@docyrus/api-client

v0.0.3

Published

A modern, type-safe API client library for JavaScript/TypeScript applications with support for multiple backend types, streaming, error handling, and token management.

Readme

@docyrus/api-client

A modern, type-safe API client library for JavaScript/TypeScript applications with support for multiple backend types, streaming, error handling, and token management.

Features

  • 🚀 Multiple Client Types: REST, Edge Functions, and Cloudflare Workers
  • 🔐 Built-in Token Management: Memory, Storage, and Async token managers
  • 🌊 Streaming Support: SSE and chunked responses
  • 🔄 Request/Response Interceptors: Transform requests and handle responses
  • Retry Logic: Automatic retry with configurable conditions
  • 🎯 Type-Safe: Full TypeScript support with comprehensive type definitions
  • 🌐 Isomorphic: Works in both browser and Node.js environments
  • 🛡️ Comprehensive Error Handling: Typed error classes for different scenarios
  • ⏱️ Timeout Support: Configurable request timeouts with AbortController
  • 📄 HTML to PDF: Built-in support for HTML to PDF conversion

Installation

npm install @docyrus/api-client
yarn add @docyrus/api-client
pnpm add @docyrus/api-client

Quick Start

import { RestApiClient, MemoryTokenManager } from '@docyrus/api-client';

// Create a client instance
const client = new RestApiClient({
  baseURL: 'https://api.example.com',
  tokenManager: new MemoryTokenManager(),
  timeout: 5000,
  headers: {
    'X-API-Version': '1.0'
  }
});

// Set authentication token
await client.setAccessToken('your-auth-token');

// Make API calls
const response = await client.get('/users', {
  params: { page: 1, limit: 10 }
});

console.log(response.data);

Client Types

RestApiClient

Standard REST API client for traditional HTTP endpoints.

import { RestApiClient } from '@docyrus/api-client';

const client = new RestApiClient({
  baseURL: 'https://api.example.com'
});

// GET request
const users = await client.get('/users');

// POST request with data
const newUser = await client.post('/users', {
  name: 'John Doe',
  email: '[email protected]'
});

// PUT request
const updatedUser = await client.put('/users/123', {
  name: 'Jane Doe'
});

// DELETE request
await client.delete('/users/123');

EdgeFunctionClient

Optimized for edge function deployments (Vercel, Netlify, etc.).

import { EdgeFunctionClient } from '@docyrus/api-client';

const client = new EdgeFunctionClient({
  baseURL: 'https://edge.example.com'
});

// Invoke edge function
const result = await client.invokeFunction('processData', {
  input: 'data'
});

// Stream from edge function
for await (const chunk of client.streamFunction('generateContent', { prompt: 'Hello' })) {
  console.log(chunk);
}

CloudflareWorkerClient

Specialized client for Cloudflare Workers with dynamic function routing.

import { CloudflareWorkerClient } from '@docyrus/api-client';

const client = new CloudflareWorkerClient({
  baseURL: 'https://{{function}}.example.workers.dev'
});

// Automatically replaces {{function}} with the function name
const response = await client.post('/myfunction/endpoint', data);

Token Management

MemoryTokenManager

Stores tokens in memory (default).

import { MemoryTokenManager } from '@docyrus/api-client';

const tokenManager = new MemoryTokenManager();
tokenManager.setToken('token');
const token = tokenManager.getToken();

StorageTokenManager

Persists tokens in browser storage.

import { StorageTokenManager } from '@docyrus/api-client';

// Use localStorage
const localManager = new StorageTokenManager(localStorage, 'auth_token');

// Use sessionStorage
const sessionManager = new StorageTokenManager(sessionStorage, 'auth_token');

AsyncTokenManager

For async token operations (e.g., secure storage, encrypted tokens).

import { AsyncTokenManager } from '@docyrus/api-client';

const tokenManager = new AsyncTokenManager({
  async getToken() {
    return await secureStorage.get('token');
  },
  async setToken(token: string) {
    await secureStorage.set('token', token);
  },
  async clearToken() {
    await secureStorage.remove('token');
  }
});

Interceptors

Transform requests and handle responses globally.

// Request interceptor
client.use({
  async request(config) {
    // Add timestamp to all requests
    config.headers = {
      ...config.headers,
      'X-Request-Time': new Date().toISOString()
    };
    return config;
  }
});

// Response interceptor
client.use({
  async response(response, request) {
    console.log(`API call to ${request.url} took ${Date.now() - request.timestamp}ms`);
    return response;
  }
});

// Error interceptor
client.use({
  async error(error, request, response) {
    if (error.status === 401) {
      // Handle authentication errors
      await refreshToken();
      throw error;
    }
    return { error, request, response };
  }
});

Streaming

Server-Sent Events (SSE)

const eventSource = client.sse('/events', {
  onMessage(data) {
    console.log('Received:', data);
  },
  onError(error) {
    console.error('SSE error:', error);
  },
  onComplete() {
    console.log('Stream completed');
  }
});

// Stop the stream
eventSource.close();

Chunked Streaming

for await (const chunk of client.stream('/stream', {
  method: 'POST',
  body: { query: 'stream data' }
})) {
  console.log('Chunk:', chunk);
}

Error Handling

Comprehensive error types for different scenarios:

import {
  ApiError,
  NetworkError,
  TimeoutError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  RateLimitError,
  ValidationError
} from '@docyrus/api-client';

try {
  const data = await client.get('/protected-resource');
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Handle authentication error (401)
    console.log('Please login');
  } else if (error instanceof AuthorizationError) {
    // Handle authorization error (403)
    console.log('Access denied');
  } else if (error instanceof NotFoundError) {
    // Handle not found error (404)
    console.log('Resource not found');
  } else if (error instanceof RateLimitError) {
    // Handle rate limit error (429)
    console.log(`Rate limited. Retry after: ${error.retryAfter}`);
  } else if (error instanceof NetworkError) {
    // Handle network errors
    console.log('Network issue');
  } else if (error instanceof TimeoutError) {
    // Handle timeout errors
    console.log('Request timed out');
  }
}

Advanced Features

Retry Logic

import { withRetry } from '@docyrus/api-client';

const response = await withRetry(
  () => client.get('/flaky-endpoint'),
  {
    retries: 3,
    retryDelay: 1000,
    retryCondition: (error) => error.status >= 500
  }
);

File Upload

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('description', 'File description');

const response = await client.post('/upload', formData, {
  headers: {
    // Content-Type will be set automatically for FormData
  }
});

File Download

const response = await client.get('/download/file.pdf', {
  responseType: 'blob'
});

// Save the file
const url = URL.createObjectURL(response.data);
const link = document.createElement('a');
link.href = url;
link.download = 'file.pdf';
link.click();

HTML to PDF

const pdfResult = await client.html2pdf({
  url: 'https://example.com',
  // or
  html: '<html><body>Content</body></html>',
  options: {
    format: 'A4',
    margin: { top: 10, bottom: 10, left: 10, right: 10 },
    landscape: false
  }
});

Filter Queries

import { prepareFilterQueryForApi } from '@docyrus/api-client';

const filter = {
  operator: 'and',
  rules: [
    { field: 'status', operator: 'eq', value: 'active' },
    { field: 'age', operator: 'gte', value: 18 }
  ]
};

const queryString = prepareFilterQueryForApi(filter);
const response = await client.get(`/users?${queryString}`);

Configuration Options

interface ApiClientConfig {
  // Base URL for all requests
  baseURL?: string;
  
  // Token manager instance
  tokenManager?: TokenManager;
  
  // Default headers
  headers?: Record<string, string>;
  
  // Request timeout in milliseconds
  timeout?: number;
  
  // Custom fetch implementation
  fetch?: typeof fetch;
  
  // Custom FormData implementation
  FormData?: typeof FormData;
  
  // Custom AbortController implementation
  AbortController?: typeof AbortController;
  
  // Storage for persistence (browser only)
  storage?: Storage;
}

Utility Functions

import {
  buildUrl,
  isAbortError,
  parseContentDisposition,
  isFormData,
  isBlob,
  isStream,
  createAbortSignal,
  jsonToQueryString
} from '@docyrus/api-client';

// Build URL with query parameters
const url = buildUrl('/api/users', { page: 1, limit: 10 });
// Result: /api/users?page=1&limit=10

// Check if error is abort error
if (isAbortError(error)) {
  console.log('Request was aborted');
}

// Parse content disposition header
const { filename, type } = parseContentDisposition(
  'attachment; filename="document.pdf"'
);

// Create abort signal with timeout
const signal = createAbortSignal(5000); // 5 second timeout

TypeScript Support

Full TypeScript support with generic types:

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

interface ApiResponse<T> {
  data: T;
  meta: {
    page: number;
    total: number;
  };
}

// Typed responses
const response = await client.get<ApiResponse<User[]>>('/users');
const users: User[] = response.data.data;

// Typed request bodies
const newUser = await client.post<User>('/users', {
  name: 'John Doe',
  email: '[email protected]'
} as Omit<User, 'id'>);

Browser Support

  • Chrome/Edge: Latest 2 versions
  • Firefox: Latest 2 versions
  • Safari: Latest 2 versions
  • Node.js: 18+

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

License

MIT © Docyrus

Support

For issues and feature requests, please open an issue on GitHub.

Development

# Install dependencies
pnpm install

# Development mode with watch
pnpm dev

# Build the package
pnpm build

# Run linting
pnpm lint

# Type checking
pnpm typecheck

# Publish package
pnpm publish