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

@grafserv/persisted

v1.0.0

Published

Persisted operations support for Grafserv

Readme

@grafserv/persisted

GitHub Sponsors Discord chat room Package on npm MIT license Follow Follow

Persisted operations (aka "persisted queries", "query allowlist", "persisted documents") support for Grafserv. Applies to both standard GET and POST requests and to websocket connections. Works for all operation types (queries, mutations and subscriptions).

We recommend that all GraphQL servers (PostGraphile or otherwise) that only intend first party clients to use the GraphQL schema should use persisted operations to mitigate attacks against the GraphQL API and to help track the fields that have been used. This package is our solution for Grafserv, for other servers you will need different software.

Crowd-funded open-source software

To help us develop this software sustainably, we ask all individuals and businesses that use it to help support its ongoing maintenance and development via sponsorship.

Click here to find out more about sponsors and sponsorship.

And please give some love to our featured sponsors 🤩:

* Sponsors the entire Graphile suite

Installation

yarn add @grafserv/persisted
# or: npm install --save @grafserv/persisted

Usage

Import PersistedPlugin from @grafserv/persisted and then add it to the plugins list in your graphile.config.ts (or equivalent) file:

import "graphile-config";
import PersistedPlugin from "@grafserv/persisted";

const preset: GraphileConfig.Preset = {
  plugins: [PersistedPlugin],
  grafserv: {
    /* add configuration options here, e.g. */
    persistedOperationsDirectory: `${process.cwd()}/.persisted_operations`,
  },
};

export default preset;

Options

This plugin adds the following options to the Grafserv options:

/**
 * This function will be passed a GraphQL request object (normally
 * `{query: string, variables?: any, operationName?: string, extensions?: any}`,
 * but in the case of persisted operations it likely won't have a `query`
 * property), and must extract the hash to use to identify the persisted
 * operation. For Apollo Client, this might be something like:
 * `request?.extensions?.persistedQuery?.sha256Hash`; for Relay something
 * like: `request?.documentId`.
 */
hashFromPayload?(request: ParsedGraphQLBody): string | undefined;

/**
 * We can read persisted operations from a folder (they must be named
 * `<hash>.graphql`). When used in this way, the first request for a hash
 * will read the file, and then the result will be cached such that the
 * **filesystem read** will only impact the first use of that hash. We
 * periodically scan the folder for new files, requests for hashes that
 * were not present in our last scan of the folder will be rejected to
 * mitigate denial of service attacks asking for non-existent hashes.
 */
persistedOperationsDirectory?: string;

/**
 * An optional string-string key-value object defining the persisted
 * operations, where the keys are the hashes, and the values are the
 * operation document strings to use.
 */
persistedOperations?: { [hash: string]: string };

/**
 * If your known persisted operations may change over time, or you'd rather
 * load them on demand, you may supply this function. Note this function is
 * **performance critical** so you should use caching to improve
 * performance of any follow-up requests for the same hash.
 */
persistedOperationsGetter?: PersistedOperationGetter;

/**
 * There are situations where you may want to allow arbitrary operations
 * (for example using GraphiQL in development, or allowing an admin to
 * make arbitrary requests in production) whilst enforcing Persisted
 * Operations for the application and non-admin users. This function
 * allows you to determine under which circumstances persisted operations
 * may be bypassed.
 *
 * IMPORTANT: this function must not throw!
 *
 * @example
 *
 * ```
 * app.use(postgraphile(DATABASE_URL, SCHEMAS, {
 *   allowUnpersistedOperation(event) {
 *     return process.env.NODE_ENV === "development" && event.request?.getHeader('referer')?.endsWith("/graphiql");
 *   }
 * });
 * ```
 */
allowUnpersistedOperation?:
  | boolean
  | ((event: ProcessGraphQLRequestBodyEvent) => boolean);

All these options are optional; but you should specify exactly one of persistedOperationsDirectory, persistedOperations or persistedOperationsGetter for this plugin to be useful.

Generating Persisted Operations

Relay

Relay has built-in support for persisted operations:

https://relay.dev/docs/guides/persisted-queries/

Pass --persist-output ./path/to/server.json to relay-compiler along with your normal options to have it generate the persisted operations file. You can then use the addToPersistedOperations.js script below to split this operations JSON file into one file per query that can be passed to --persisted-operations-directory.

Then in your network config (fetchQuery function) you need to change the query: operation.text to documentId: operation.id as shown in the relay docs.

GraphQL-Code-Generator

For everything except Relay, we recommend that you generate persisted operations when you build your clients using the awesome graphql-code-generator project and the graphql-codegen-persisted-query-ids (tested with v0.1.2) plugin. A config might look like:

schema: "schema.graphql"
documents: "src/**/*.graphql"
hooks:
  afterAllFileWrite:
    - node addToPersistedOperations.js
generates:
  client.json:
    plugins:
      - graphql-codegen-persisted-query-ids:
          output: client
          algorithm: sha256
  server.json:
    plugins:
      - graphql-codegen-persisted-query-ids:
          output: server
          algorithm: sha256

We also recommend using the file addToPersistedOperations.js to write the server.json file contents out to separate GraphQL files every time the code is built for easier version control:

// addToPersistedOperations.js
const map = require("./server.json");
const { promises: fsp } = require("fs");

async function main() {
  await Promise.all(
    Object.entries(map).map(([hash, query]) =>
      fsp.writeFile(
        `${__dirname}/.persisted_operations/${hash}.graphql`,
        query,
      ),
    ),
  );
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});

Then you pass the .persisted_operations folder via the persistedOperationsDirectory option as shown in the usage example.

Apollo Client

You can configure Apollo Client to send the persisted operations generated by graphql-codegen with apollo-link-persisted-queries:

import { createPersistedQueryLink } from "apollo-link-persisted-queries";
import { usePregeneratedHashes as withPregeneratedHashes } from "graphql-codegen-persisted-query-ids/lib/apollo";
import { hashes } from "./path/to/client.json";

const persistedLink = createPersistedQueryLink({
  useGETForHashedQueries: false,
  generateHash: withPregeneratedHashes(hashes),
  disable: () => false,
});

// ...

const client = new ApolloClient({
  link: ApolloLink.from([persistedLink, httpLink]),
  // ...
});