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

@fxfn/ipa

v0.3.1

Published

Its an API but backwards.

Readme

@fxfn/ipa

Its an API but backwards.


Generate a typescript API Client from a swagger.json file.

getting started (dev)

generate a API Schema (MyService)

$ git clone https://github.com/fxfn/ipa
$ cd ipa
$ pnpm i
$ pnpm run generate http://localhost:3000/api/swagger.json MyService

import @fxfn/ipa and create a client for MyService

import { createClient } from "@fxfn/ipa"
import { MyService } from "./schema/my-service"

const client = createClient<MyService>({
  // see options
})

async function main() {
  const res = await client['/api/contacts'].get()
  console.log(res)
}

main()

API Reference

createClient Options

The createClient function accepts the following configuration options:

interface ClientConfig {
  baseUrl: string
  headers?: Record<string, string>
  interceptors?: {
    success: (data: any) => any
    error: (error: any) => any
  }
}
  • baseUrl (required): The base URL for all API requests
  • headers (optional): Default headers to include in all requests
  • interceptors (optional): Functions to transform successful responses and errors

Declaration Merging & Interceptors

To customize the response types, you can use TypeScript declaration merging to extend the default wrapper types:

import { createClient } from '@fxfn/ipa'

// Extend the default wrapper types
declare module '@fxfn/ipa' {
  interface SuccessWrapper<T> {
    success: true
    result: T
  }

  interface ErrorWrapper {
    success: false
    result: null
    error: { 
      message: string 
    }
  }
}

// Your API schema type
type APISchema = {
  '/api/users': {
    GET: {
      response: {
        200: {
          users: Array<{ id: string; name: string }>
        }
      }
    }
  }
}

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com',

  // add interceptors to mutate the response to the shape 
  // defined in the above merged wrapper declarations
  interceptors: {
    success: (data) => ({
      success: true,
      result: data,
    }),
    error: (error) => ({
      success: false,
      result: null,
      error: {
        message: error.message,
      }
    })
  }
})

Example Responses

With the above configuration, here's what the response objects look like:

Success Response:

const result = await client['/api/users'].get()
// result will be:
{
  success: true,
  result: {
    users: [
      { id: "1", name: "John Doe" },
      { id: "2", name: "Jane Smith" }
    ]
  }
}

Error Response:

const result = await client['/api/users'].get()
// result will be:
{
  success: false,
  result: null,
  error: {
    message: "Bad Request"
  }
}

Error Handling

The client provides flexible error handling through interceptors. You can either transform errors into structured responses or throw custom errors for exceptional cases.

Transforming Errors (Default Behavior)

By default, error interceptors transform API errors into structured response objects:

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com',
  interceptors: {
    error: (error) => ({
      success: false,
      result: null,
      error: {
        message: error.message || 'An error occurred',
        code: error.status || 500
      }
    })
  }
})

// Usage - errors are returned as structured responses
const result = await client['/api/users'].get()
if (!result.success) {
  console.error(result.error.message) // "Bad Request"
  console.error(result.error.code)    // 400
}

Throwing Custom Errors

For exceptional cases where you want to throw errors instead of returning them, you can throw custom errors from the error interceptor:

class ApiError extends Error {
  constructor(
    message: string,
    public status: number,
    public code?: string
  ) {
    super(message)
    this.name = 'ApiError'
  }
}

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com',
  interceptors: {
    error: (error) => {
      // Throw custom error for specific status codes
      if (error.status === 401) {
        throw new ApiError('Unauthorized', 401, 'UNAUTHORIZED')
      }
      if (error.status === 403) {
        throw new ApiError('Forbidden', 403, 'FORBIDDEN')
      }
      if (error.status >= 500) {
        throw new ApiError('Server Error', error.status, 'SERVER_ERROR')
      }
      
      // For other errors, return structured response
      return {
        success: false,
        result: null,
        error: {
          message: error.message,
          code: error.status
        }
      }
    }
  }
})

// Usage - handle both thrown errors and structured responses
try {
  const result = await client['/api/users'].get()
  if (result.success) {
    console.log(result.result)
  } else {
    console.error(result.error.message)
  }
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`${error.name}: ${error.message} (${error.status})`)
    // Handle specific error types
    if (error.status === 401) {
      // Redirect to login
    }
  } else {
    console.error('Unexpected error:', error)
  }
}

Mixed Error Handling Strategy

You can implement a hybrid approach that throws errors for critical failures but returns structured responses for recoverable errors:

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com',
  interceptors: {
    error: (error) => {
      // Throw for network errors and server errors
      if (error.status >= 500 || !error.status) {
        throw new Error(`Server error: ${error.message}`)
      }
      
      // Return structured response for client errors (4xx)
      return {
        success: false,
        result: null,
        error: {
          message: error.message,
          status: error.status,
          retryable: false
        }
      }
    }
  }
})

// Usage
try {
  const result = await client['/api/users'].get()
  if (result.success) {
    console.log(result.result)
  } else {
    // Handle client errors gracefully
    console.error(`Client error: ${result.error.message}`)
  }
} catch (error) {
  // Handle server errors and network issues
  console.error('Critical error:', error.message)
  // Maybe retry or show fallback UI
}

Header Merging

The client supports flexible header management with automatic merging:

Global Headers

Set default headers that apply to all requests:

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com',
  headers: {
    'authorization': 'bearer token123',
    'x-api-version': 'v1'
  }
})

Request-Specific Headers

Add headers for individual requests:

await client['/api/users'].get({
  headers: {
    'x-request-id': 'unique-id',
    'cache-control': 'no-cache'
  }
})

Header Merging Behavior

Request-specific headers are merged with global headers, with request headers taking precedence:

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com',
  headers: {
    'authorization': 'bearer token123'
  }
})

// This request will include both headers
await client['/api/users'].get({
  headers: {
    'x-api-key': 'api-key-456'
  }
})
// Final headers: { authorization: 'bearer token123', x-api-key: 'api-key-456' }

Request Methods

The client supports all HTTP methods with type-safe parameters:

GET Requests

// With query parameters
await client['/api/users'].get({
  query: { page: 1, limit: 10 }
})

// With path parameters
await client['/api/users/:id'].get({
  params: { id: '123' }
})

POST Requests

// With request body
await client['/api/users'].post({
  body: { name: 'John Doe', email: '[email protected]' }
})

Path Parameters

Replace path parameters in URLs using the params option:

type APISchema = {
  '/api/users/:id/posts': {
    GET: {
      params: {
        id: number
      }
    }
  }
}

// This will make a request to /api/users/123/posts
await client['/api/users/:id/posts'].get({
  params: { id: 123 }
})

Query Parameters

Add query parameters to GET requests:

await client['/api/users'].get({
  query: {
    page: 1,
    limit: 10,
    search: 'john'
  }
})
// Results in: /api/users?page=1&limit=10&search=john

Request Bodies

For POST, PUT, PATCH requests, include a request body:

await client['/api/users'].post({
  body: {
    name: 'Jane Doe',
    email: '[email protected]'
  }
})

The client automatically:

  • Sets Content-Type: application/json header
  • Serializes the body to JSON

Response Handling

Success Responses

const result = await client['/api/users'].get()
if (result.success) {
  console.log(result.result) // Typed response data
}

Error Responses

const result = await client['/api/users'].get()
if (!result.success) {
  console.error(result.error.message) // Error message
}

Type Safety

The client provides full TypeScript support with your API schema:

type APISchema = {
  '/api/users': {
    GET: {
      query: {
        page: number
        limit: number
      }
      response: {
        200: {
          users: Array<{ id: string; name: string }>
          total: number
        }
      }
    }
    POST: {
      body: {
        name: string
        email: string
      }
      response: {
        200: {
          id: string
          name: string
          email: string
        }
      }
    }
  }
}

const client = createClient<APISchema>({
  baseUrl: 'https://api.example.com'
})

// Fully typed - TypeScript will enforce correct parameters and response types
const users = await client['/api/users'].get({ query: { page: 1, limit: 10 } })
const newUser = await client['/api/users'].post({ 
  body: { name: 'John', email: '[email protected]' } 
})