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

@saschb2b/gql-drift

v0.1.0

Published

Dynamic GraphQL queries and mutations at runtime - from schema introspection to typed field registries, query/mutation builders, and React integration

Readme

gql-drift

CI npm version License: MIT

Dynamic GraphQL queries and mutations at runtime.

When your query shape is determined by user interaction — not by a developer at build time — codegen can't help you. gql-drift can.


Why

You have a table where users pick which columns to display. Or an admin dashboard where each role sees different fields. Or a report builder where filters are chosen at runtime. The GraphQL query doesn't exist until someone clicks.

Traditional codegen requires static .graphql files. String concatenation gives you no type safety. gql-drift sits in between:

Schema → Introspection → Field Registry → Query Builder → Flatten → UI
  • Introspect types, fields, nesting, scalars, enums, and mutations
  • Build queries dynamically from user-selected fields
  • Build mutations with automatic input type discovery
  • Flatten nested responses to table rows (and back)
  • Validate at runtime with auto-generated Zod schemas

Install

pnpm add @saschb2b/gql-drift

Optional peer dependencies — install only what you use:

pnpm add react @tanstack/react-query   # React integration
pnpm add zod                            # Runtime validation
pnpm add graphql                        # Local schema file support (CLI)

Quick Start

1. Generate from your schema

npx gql-drift init       # scaffold config
npx gql-drift generate   # generate field registries

Config file (gql-drift.config.json):

{
  "endpoint": "http://localhost:4000/graphql",
  "types": ["Order", "Customer"],
  "out": "src/generated",
  "depth": 1
}

Or skip the config file:

npx gql-drift generate --endpoint http://localhost:4000/graphql --types Order,Customer
npx gql-drift generate --schema ./schema.graphql --types Order,Customer

2. Use the generated code

Each generated file exports a DriftType, field arrays, and TanStack Query options factories:

// src/generated/order.ts (auto-generated)
import { orderType, ORDER_FIELDS, orderQueryOptions, updateOrderMutation } from "./generated/order";

React + TanStack Query

gql-drift follows the TanStack Query v5 queryOptions pattern. Generated code produces options factories — you spread them into standard TanStack hooks.

Provider Setup

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { DriftProvider } from "@saschb2b/gql-drift/react";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <DriftProvider config={{ endpoint: "/graphql" }}>
        <YourApp />
      </DriftProvider>
    </QueryClientProvider>
  );
}

Querying and Mutating

Spread generated options into useQuery / useMutation:

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useDriftConfig } from "@saschb2b/gql-drift/react";
import { orderQueryOptions, updateOrderMutation, orderQueryKey } from "./generated/order";

function OrderTable() {
  const config = useDriftConfig();
  const queryClient = useQueryClient();

  const { data: rows } = useQuery({
    ...orderQueryOptions({ config }),
  });

  const { mutate } = useMutation({
    ...updateOrderMutation({ config }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: orderQueryKey() }),
  });

  return /* your UI */;
}

This works with useQuery, useSuspenseQuery, queryClient.prefetchQuery, and anything else in TanStack Query.

Dynamic Field Selection

For the full experience — field checkboxes, toggle on/off, auto-rebuilding queries — use useDriftType:

import { useDriftType } from "@saschb2b/gql-drift/react";
import { orderType } from "./generated/order";

function OrderTable() {
  const {
    registry, // all available fields
    selectedFields, // currently active fields
    toggleField, // toggle a field by key
    rows, // flattened query results
    isLoading,
    format, // format a cell value for display
    updateRow, // (id, values) => Promise
    createRow, // (values) => Promise
  } = useDriftType({ type: orderType });

  return (
    <div>
      {registry.map((field) => (
        <label key={field.key}>
          <input
            type="checkbox"
            checked={selectedFields.some((s) => s.key === field.key)}
            onChange={() => toggleField(field.key)}
          />
          {field.label}
        </label>
      ))}

      <table>
        <thead>
          <tr>
            {selectedFields.map((f) => (
              <th key={f.key}>{f.label}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <tr key={row.id as string}>
              {selectedFields.map((f) => (
                <td key={f.key}>{format(f, row[f.key])}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Config comes from DriftProvider. Query name defaults from the type name ("orders").


Custom GraphQL Client

By default gql-drift uses fetch. Pass a fetcher to use your own client:

import { GraphQLClient } from "graphql-request";

const client = new GraphQLClient("/graphql", {
  headers: { Authorization: `Bearer ${token}` },
});

<DriftProvider
  config={{
    endpoint: "/graphql",
    fetcher: ({ query, variables }) => client.request(query, variables),
  }}
/>;

The fetcher receives { query, variables } and returns the data portion of the response. When provided, endpoint and headers are ignored — your client owns the transport.

urql

import { client } from "./urql-client";

fetcher: async ({ query, variables }) => {
  const result = await client.query(query, variables).toPromise();
  if (result.error) throw result.error;
  return result.data;
};

Apollo Client

import { client } from "./apollo-client";
import { gql } from "@apollo/client";

fetcher: async ({ query, variables }) => {
  const { data } = await client.query({ query: gql(query), variables });
  return data;
};

Vanilla TypeScript

No React required. Use the core directly:

import { createDrift } from "@saschb2b/gql-drift";

const drift = createDrift({ endpoint: "/graphql" });

const order = await drift.type("Order");
const query = drift.buildQuery("orders", order.fields);
const { rows } = await drift.fetch("orders", order);

await drift.update(order, { id: "1", values: { status: "SHIPPED" } });

With static generation (no network introspection):

import { createDriftFromRegistry } from "@saschb2b/gql-drift";
import { orderType } from "./generated/order";

const drift = createDriftFromRegistry({ endpoint: "/graphql" }, orderType);
const { rows } = await drift.fetch("orders", await drift.type("Order"));

Zod Validation

Auto-generate Zod schemas from your field definitions:

import { buildResultSchema, buildInputSchema } from "@saschb2b/gql-drift/zod";
import { orderType } from "./generated/order";

const resultSchema = buildResultSchema(orderType.fields);
const inputSchema = buildInputSchema(orderType.editableFields);

inputSchema.parse(userInput); // throws ZodError on invalid data

In useDriftType, pass validate: true to auto-validate before every mutation:

const { updateRow } = useDriftType({ type: orderType, validate: true });

Core Concepts

FieldDefinition

The single unit that flows through the entire pipeline:

interface FieldDefinition {
  key: string; // Flat key: "shippingAddressCity"
  label: string; // Human label: "Shipping Address City"
  graphqlPath: string; // Nested path: "shippingAddress.city"
  type: FieldType; // "string" | "number" | "date" | "boolean" | "enum"
  enumValues?: string[]; // ["PENDING", "SHIPPED", "DELIVERED"]
}

Nested Fields

gql-drift flattens nested GraphQL objects into dot-free keys:

GraphQL:  order { shippingAddress { city, zip } }
Registry: { key: "shippingAddressCity", graphqlPath: "shippingAddress.city" }

buildQuery reconstructs the nesting. flatten / unflatten convert between nested responses and flat rows.

Rendering Helpers

import { formatValue, inputType, parseInput } from "@saschb2b/gql-drift";

formatValue(field, value); // "99.99" | "true" | "Jan 1, 2024"
inputType(field); // "text" | "number" | "date" | "checkbox" | "select"
parseInput(field, rawValue); // string → number, "true" → boolean, etc.

CLI Reference

gql-drift init                  Create gql-drift.config.json
gql-drift generate [options]    Generate field registries

Options:
  --endpoint <url>     GraphQL endpoint URL
  --schema <path>      Local .graphql SDL file
  --types <names>      Comma-separated type names
  --out <path>         Output directory (default: src/generated)
  --depth <n>          Max nesting depth (default: 1)
  --header <value>     HTTP header as "Key: Value" (repeatable)

Config file values are defaults. CLI flags override them.


Entry Points

| Import | Contents | | --------------------------- | ----------------------------------------------------------------------------------------- | | @saschb2b/gql-drift | Core: types, introspection, registry, query/mutation builders, flatten, rendering helpers | | @saschb2b/gql-drift/react | DriftProvider, useDriftType, options factories | | @saschb2b/gql-drift/zod | buildResultSchema, buildInputSchema | | @saschb2b/gql-drift/cli | CLI entry point (npx gql-drift) |

All entry points are tree-shakeable. ESM and CJS.

Peer Dependencies

| Package | Used by | Required | | ----------------------- | --------------------------- | -------- | | react | @saschb2b/gql-drift/react | No | | @tanstack/react-query | @saschb2b/gql-drift/react | No | | zod | @saschb2b/gql-drift/zod | No | | graphql | --schema flag | No |

The core package has zero dependencies.

License

MIT