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

queryfish

v1.0.0

Published

OpenAPI → type-safe React Query Kit factories.

Readme

Hook your API. Typed, straight from the spec.

Turn an OpenAPI document into fully typed React Query hooks powered by react-query-kit — with zero runtime and no hand-written client code.

License: MIT Node Types

Quick start · How it works · Configuration · Example app · Docs


Why QueryFish?

You already describe your API once, in OpenAPI. Writing the fetch functions, the TypeScript types, and the React Query hooks by hand means describing it three more times — and keeping all four in sync forever.

QueryFish generates the other three.

- const { data } = useQuery({
-   queryKey: ['pet', petId],
-   queryFn: () => fetch(`/api/pets/${petId}`).then(r => r.json()),
- });
- // data: any — and the key is whatever you remembered to type

+ const { data } = useGetPet({ variables: { petId } });
+ // data: Pet — petId is required, typos don't compile

What makes it different:

  • 🎯 It never guesses. Where OpenAPI has no standard — pagination, notably — QueryFish requires an explicit opt-in instead of inferring. A wrong guess that compiles is worse than no feature.
  • 🔑 Query keys mirror your URLs, so one call invalidates a whole resource tree instead of a single endpoint.
  • 🪶 Zero runtime. QueryFish is a devDependency. Nothing it publishes ends up in your bundle.
  • ✅ The output is verified to compile, not assumed to — CI type-checks generated code against the real react-query-kit on every run.
  • 🧬 Handles specs that break other generators: recursive schemas, duplicate and missing operationIds, reserved words, allOf/oneOf, Swagger 2.0.

🚀 Quick start

1. Install

npm install queryfish --save-dev

2. Write your client — this is yours, so auth and interceptors stay in your code:

// src/api/client.ts
import axios from 'axios'

export const client = axios.create({ baseURL: '/api' })

An axios instance works as-is. Prefer fetch? See the worked example — about 40 lines, no dependencies.

3. Configure

// queryfish.config.ts
import { defineConfig } from 'queryfish'

export default defineConfig({
    input: './openapi.yaml', // path or URL
    output: './src/api',
    client: './src/api/client.ts',
})

4. Generate

npx queryfish generate
  + types.ts
  + requests.ts
  + queries.ts
  + mutations.ts
  + index.ts
✓ Generated 6 operations → src/api

5. Use it

import { useGetPet } from './api/queries'
import { useCreatePet } from './api/mutations'

function Pet({ petId }: { petId: string }) {
    const { data, isPending } = useGetPet({ variables: { petId } })

    if (isPending) return <Spinner />
    return <h1>{data.name}</h1> // data is `Pet`, fully typed
}

🔍 How it works

Give it a spec:

paths:
    /pets/{petId}:
        get:
            operationId: getPet
            summary: Get a pet by ID
            parameters:
                - name: petId
                  in: path
                  required: true
                  schema: { type: string }
            responses:
                '200':
                    content:
                        application/json:
                            schema: { $ref: '#/components/schemas/Pet' }

Get back four files of ordinary, readable TypeScript:

types.ts

export interface Pet {
    id: string
    name: string
    tag?: string
}

export type GetPetVariables = {
    petId: string
}

export type GetPetResponse = Pet

requests.ts

import { client } from '../client'

export const getPet = (variables: GetPetVariables) =>
    client.request<GetPetResponse>({
        method: 'GET',
        url: `/pets/${variables.petId}`,
    })

queries.ts

/** Get a pet by ID */
export const useGetPet = createQuery<GetPetResponse, GetPetVariables>({
    queryKey: ['pets', '{petId}'],
    fetcher: getPet,
})

mutations.ts

/** Create a pet */
export const useCreatePet = createMutation<CreatePetResponse, CreatePetVariables>({
    mutationFn: createPet,
})

Your spec's summary becomes JSDoc, so the description shows up on hover in your editor.

Query keys mirror your URLs

This is the detail that pays off daily. react-query-kit appends variables automatically, so ['pets', '{petId}'] becomes ['pets', '{petId}', { petId: '123' }] at runtime — cache entries stay separate per pet. But because the prefix is the URL path, one call clears everything about a resource:

// After creating, updating, or deleting a pet:
queryClient.invalidateQueries({ queryKey: ['pets'] })
// ↑ refetches /pets, /pets/{petId}, /pets/{petId}/toys — all of it

With operation-name keys you would have to list every affected hook by hand, and update that list whenever an endpoint is added.

Infinite queries — opt in, never inferred

OpenAPI has no standard for pagination. Generators that guess produce the worst kind of bug: a hook that type-checks, looks right, and then silently fetches the same page forever.

So QueryFish asks. Either in config:

pagination: {
  '/pets': { param: 'cursor', nextField: 'nextCursor' },
}

or in the spec itself:

x-queryfish-pagination:
    param: cursor
    nextField: nextCursor
export const useListPetsInfinite = createInfiniteQuery<
    ListPetsResponse,
    Omit<ListPetsVariables, 'cursor'>, // ← cursor comes from pageParam
    Error,
    string | undefined
>({
    queryKey: ['pets'],
    fetcher: (variables, { pageParam }) => listPets({ ...variables, cursor: pageParam }),
    getNextPageParam: (lastPage) => lastPage?.nextCursor ?? undefined,
    initialPageParam: undefined,
})

Opt in to nothing and infiniteQueries.ts is never written at all. Full reasoning →


⚙️ Configuration

import { defineConfig } from 'queryfish'

export default defineConfig({
    /** Path or URL to your OpenAPI/Swagger document. Required. */
    input: './openapi.yaml',

    /** Directory for generated files. Required. */
    output: './src/api',

    /** Module exporting your HTTP `client`. Default: './client' */
    client: './src/api/client.ts',

    /** Opt in to infinite queries, keyed by path. Default: none. */
    pagination: {
        '/pets': { param: 'cursor', nextField: 'nextCursor' },
    },

    /** Format output with your Prettier config. Default: true */
    format: true,

    /** Emit a barrel index.ts. Default: true */
    barrel: true,
})

Config files may be .ts, .mts, .js, .mjs, or .json.

CLI

queryfish generate [options]

| Option | Description | | --------------------- | --------------------------------------- | | -c, --config <path> | Path to config file | | -i, --input <spec> | Path or URL to the OpenAPI document | | -o, --output <dir> | Output directory | | --client <module> | Module exporting the HTTP client | | --dry-run | Report what would change, write nothing | | --watch | Regenerate when the spec changes | | --no-format | Skip Prettier formatting | | --silent | Suppress output |

CLI flags override the config file. Errors point at the exact spot in your spec:

error: cannot resolve schema
  at paths./pets.get.responses.200
  in openapi.yaml:42

  Check that the $ref target exists in components.schemas.

📂 What gets generated

src/api/
├── types.ts           # Interfaces, enums, request/response types
├── requests.ts        # Plain async functions — usable without React
├── queries.ts         # createQuery factories (GET, HEAD)
├── mutations.ts       # createMutation factories (POST, PUT, PATCH, DELETE)
├── infiniteQueries.ts # createInfiniteQuery factories — only if opted in
└── index.ts           # Barrel re-export

Output is formatted with your Prettier config, so it matches the rest of your codebase rather than fighting it. Commit it — it's meant to be read and reviewed.


🧬 Specs it handles

Real specs break naive generators. These are covered, with a regression fixture for each:

| | | | --------------------------- | ------------------------------------------------------------------------ | | Recursive schemas | Comment.replies: Comment[] and mutually recursive types emit correctly | | Missing operationId | Derived from method + path (getPetsByPetIdToys) | | Duplicate operationId | Deterministically suffixed, with a warning | | Reserved words | delete → delete_ | | Odd parameter names | filter[status], X-Request-Id quoted correctly | | Composition | allOf → intersection, oneOf/anyOf → union | | Nullability | Both nullable: true and 3.1's type: ['string', 'null'] | | Empty responses | 204 → void | | Swagger 2.0 | definitions, in: body parameters, response schemas |


📖 Documentation

| | | | ---------------------------------------- | -------------------------------------------- | | Example app | A React app using generated hooks end to end | | Architecture | How the generator works, stage by stage | | Decisions (ADRs) | Why things are the way they are | | Contributing | Clone to merged PR | | Conventions | Commits, code style, versioning |

🤝 Contributing

Contributions are genuinely welcome, and the project is set up so a first PR doesn't require asking anyone anything.

git clone https://github.com/ravitejas-tech/queryfish.git
cd queryfish
npm install
npm test

Start with CONTRIBUTING.md, then docs/architecture.md. If something looks wrong in the code, check the ADRs first — it may be deliberate, and the reasoning plus the rejected alternatives are written down.

Good first contributions: add a spec shape to edge-cases.yaml that breaks the generator, or improve an error message.


📄 License

MIT