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

@reharik/graphql-codegen-smart-enum-type-policies

v0.1.3

Published

GraphQL Code Generator plugin for SmartEnum type policies generation from schema enums

Readme

@reharik/graphql-codegen-smart-enum-type-policies

A GraphQL Code Generator plugin that generates Apollo Client typePolicies for automatic smart-enum rehydration. It walks your schema, finds every field on every object type that returns an enum, and emits read functions that convert raw cache strings back into live @reharik/smart-enum instances.

The problem

When Apollo Client reads a query result from its normalized cache, enum fields come back as plain strings. Your components receive 'ACTIVE' instead of Status.active, which means no .display, no .key, no custom fields — just a raw string you have to look up manually everywhere you use it.

What this plugin does

It generates a typePolicies config object you spread into your InMemoryCache. Every enum field on every object type gets a read function that calls EnumName.fromValue(existing), so cache reads return smart-enum instances automatically.

What it generates

Given this schema:

enum PaymentStatus {
  PENDING
  PAID
  VOIDED
}
enum SortDirection {
  ASC
  DESC
}

type Order {
  id: ID!
  status: PaymentStatus!
  direction: SortDirection
  total: Float!
}

type Customer {
  id: ID!
  name: String!
  preferredSort: SortDirection!
}

And config { enumImportPath: './graphql-smart-enums' }, the plugin emits:

import { PaymentStatus, SortDirection } from './graphql-smart-enums';

export const smartEnumTypePolicies = {
  Customer: {
    fields: {
      preferredSort: {
        read(existing: string) {
          return existing ? SortDirection.fromValue(existing) : existing;
        },
      },
    },
  },
  Order: {
    fields: {
      direction: {
        read(existing: string) {
          return existing ? SortDirection.fromValue(existing) : existing;
        },
      },
      status: {
        read(existing: string) {
          return existing ? PaymentStatus.fromValue(existing) : existing;
        },
      },
    },
  },
};

The generated read functions call .fromValue() on smart-enum objects, which means those objects need to exist somewhere your generated file can import them. That's what enumImportPath points to — the file where your smart-enum definitions live. If you use @reharik/graphql-codegen-smart-enum to generate them, point enumImportPath at that output file. If your enums are hand-authored, point it wherever they're exported from.

Non-enum fields (id, total, name) are not included. Enum types that don't appear on any object type field are not imported. Object types, fields, and imports are all sorted alphabetically for stable output.

Install

npm install @reharik/smart-enum
npm install -D @reharik/graphql-codegen-smart-enum-type-policies @graphql-codegen/cli graphql

Configuration

codegen.ts

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: './schema.graphql',
  generates: {
    './src/generated/graphql-smart-enum-type-policies.ts': {
      plugins: ['@reharik/graphql-codegen-smart-enum-type-policies'],
      config: {
        enumImportPath: './graphql-smart-enums',
      },
    },
  },
};

export default config;

The enumImportPath is the import path that will appear in the generated file's import statement. It should be the relative path from the generated type-policies file to wherever your smart-enum definitions are exported. If both generated files go in the same directory, it's just the filename without the extension.

Using with the enum-definition plugin

If you also use @reharik/graphql-codegen-smart-enum to generate your enum definitions, a typical codegen config looks like:

const config: CodegenConfig = {
  schema: './schema.graphql',
  generates: {
    './src/generated/graphql-smart-enums.ts': {
      plugins: ['@reharik/graphql-codegen-smart-enum'],
      config: {
        emitDescriptionsAsDisplay: true,
      },
    },
    './src/generated/graphql-smart-enum-type-policies.ts': {
      plugins: ['@reharik/graphql-codegen-smart-enum-type-policies'],
      config: {
        enumImportPath: './graphql-smart-enums',
      },
    },
  },
};

The two plugins are independent — they can be used together or separately. This plugin doesn't care how your smart-enum objects were created, only that they exist at the import path you specify and have a .fromValue() method.

Config options

| Option | Type | Default | Required | Description | | ----------------- | ---------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | | enumImportPath | string | — | Yes | Import path written into the generated file's import statement. Usually a relative path to your smart-enum definitions file. | | enumClassSuffix | string | '' | No | Suffix appended to enum names in imports and fromValue calls. If your enums are named PaymentStatusEnum, set this to 'Enum'. | | skipEnums | string[] | — | No | GraphQL enum type names to exclude. Fields of skipped enum types are omitted from the output. |

Using the generated type policies

Here's where the generated output fits into a typical Apollo Client setup. This is usually in a file like src/apolloClient.ts or wherever you configure your client:

// src/apolloClient.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { smartEnumTypePolicies } from './generated/graphql-smart-enum-type-policies';

const httpLink = new HttpLink({
  uri: 'https://your-api.com/graphql',
});

const cache = new InMemoryCache({
  typePolicies: {
    ...smartEnumTypePolicies,
    // any other type policies you have go here too
  },
});

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache,
});

Then in your app's entry point, wrap your component tree with the provider as usual:

// src/App.tsx
import { ApolloProvider } from '@apollo/client';
import { apolloClient } from './apolloClient';

function App() {
  return (
    <ApolloProvider client={apolloClient}>
      <YourRoutes />
    </ApolloProvider>
  );
}

That's it. Now every component that reads from the cache gets smart-enum instances instead of raw strings:

// src/components/OrderStatus.tsx
import { useQuery, gql } from '@apollo/client';

const GET_ORDER = gql`
  query GetOrder($id: ID!) {
    order(id: $id) {
      id
      status
      direction
    }
  }
`;

function OrderStatus({ orderId }: { orderId: string }) {
  const { data } = useQuery(GET_ORDER, { variables: { id: orderId } });

  if (!data) return null;

  // Without type policies: data.order.status === 'PAID' (just a string)
  // With type policies:    data.order.status === PaymentStatus.paid (smart-enum instance)

  return (
    <div>
      <span>{data.order.status.display}</span>   {/* 'Paid' */}
      <span>{data.order.status.key}</span>        {/* 'paid' */}
      <span>{data.order.status.value}</span>      {/* 'PAID' */}
    </div>
  );
}

## How `read` functions handle edge cases

The generated `read` functions use a truthiness check: `existing ? Enum.fromValue(existing) : existing`. This means:

- `null` (field is nullable and explicitly null) → passes through as `null`
- `undefined` (field not yet loaded / cache miss) → passes through as `undefined`
- `'ACTIVE'` → `Status.active` (the smart-enum instance)

If the string doesn't match any enum member, `fromValue` throws — same behavior as calling it directly. If you need silent fallback, you'd customize the type policy yourself.

## Scope

The plugin only inspects `GraphQLObjectType` — not input types, not interfaces. It unwraps `NonNull` and `List` wrappers to find the underlying type, so `PaymentStatus!`, `[PaymentStatus]`, and `[PaymentStatus!]!` all resolve correctly.

Introspection types (`__Schema`, `__Type`, etc.) are always skipped.

## Related packages

| Package | Purpose |
|---|---|
| [`@reharik/smart-enum`](https://www.npmjs.com/package/@reharik/smart-enum) | Core smart-enum library (runtime dependency) |
| [`@reharik/graphql-codegen-smart-enum`](https://www.npmjs.com/package/@reharik/graphql-codegen-smart-enum) | Generate smart-enum definitions from GraphQL schema enums |
| [`@reharik/smart-enum-knex`](https://www.npmjs.com/package/@reharik/smart-enum-knex) | Knex query-level enum revival |

## License

MIT