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 🙏

© 2024 – Pkg Stats / Ryan Hefner

graphql-lookahead

v1.1.2

Published

GraphQL Lookahead in Typescript to check if some fields are present in the operation

Downloads

453

Readme

GraphQL Lookahead in Javascript

TypeScript npm version npm downloads License

Use graphql-lookahead to check within the resolver function if particular fields are part of the operation (query or mutation).

❤️ Provided by Accès Impôt's engineering team

| | | :---: | | 🇨🇦 Online tax declaration service 🇨🇦 |

Table of contents

Highlights

  • ⚡️ Performant - Avoid querying nested database relationships if they are not requested.
  • 🎯 Accurate - Check for the field or type name. Check for a specific hierarchy of fields.
  • 🧘 Flexible - Works with any ORM, query builder, GraphQL servers.
  • 💪 Reliable - Covered by integration tests.
  • 🏀 Accessible - Clone this repository and try it out locally using the playground.

Quick Setup

Install the module:

# pnpm
pnpm add graphql-lookahead

# yarn
yarn add graphql-lookahead

# npm
npm i graphql-lookahead

Basic usage

You can add a condition using the until option which will be called for every nested field within the operation starting from the resolver field.

import type { createSchema } from 'graphql-yoga'
import { lookahead } from 'graphql-lookahead'

type Resolver = NonNullable<Parameters<typeof createSchema>[0]['resolvers']>

export const resolvers: Resolver = {
  Query: {
    order: async (_parent, args, _context, info) => {
      //
      // add your condition

      if (lookahead({ info, until: ({ field }) => field === 'product' })) {
        // include product in the query
      }
      // ...
    },
  },
}

Types

import type { GraphQLResolveInfo, SelectionNode } from 'graphql'

function lookahead<TState, RError extends boolean | undefined>(options: {
  depth?: number | null
  info: Pick<
    GraphQLResolveInfo,
    'operation' | 'schema' | 'fragments' | 'returnType' | 'fieldNodes' | 'fieldName'
  >
  next?: (details: NextHandlerDetails<TState>) => TState
  onError?: (err: unknown) => RError
  state?: TState
  until?: (details: UntilHandlerDetails<TState>) => boolean
}): boolean

type HandlerDetails<TState> = {
  field: string
  selection: SelectionNode
  state: TState
  type: string
}

type UntilHandlerDetails<TState> = HandlerDetails<TState> & {
  nextSelectionSet?: SelectionSetNode
}

type NextHandlerDetails<TState> = HandlerDetails<TState> & {
  nextSelectionSet: SelectionSetNode
}

Options

| Name | Description | | ------ | :---------- | | depth | ❔ Optional - Specify how deep it should look in the selectionSet (i.e. depth: 1 is the initial selectionSet, depth: null is no limit). Default: depth: null. | | info | ❗️ Required - GraphQLResolveInfo object which is usually the fourth argument of the resolver function. | | next | ❔ Optional - Handler called for every nested field within the operation. It can return a state that will be passed to each next call of its direct child fields. See Advanced usage. | | onError | ❔ Optional - Hook called from a try..catch when an error is caught. Default: (err: unknown) => { console.error(ERROR_PREFIX, err); return true }. | | state | ❔ Optional - Initial state used in next handler. See Advanced usage.| | until | ❔ Optional - Handler called for every nested field within the operation. Returning true will stop the iteration and make lookahead return true as well. |

Advanced usage

You can pass a state and use the next option that will be called for every nested field within the operation. It is similar to until, but next can mutate the parent state and return the next state that will be passed to its child fields. You will still need the until option if you want to stop the iteration at some point (optional).

If your schema matches your database models, you could build the query filters like this:

Example: Sequelize with nested query filters

📚 Sequelize ORM

import type { createSchema } from 'graphql-yoga'
import { lookahead } from 'graphql-lookahead'

type Resolver = NonNullable<Parameters<typeof createSchema>[0]['resolvers']>

interface QueryFilter {
  model?: string
  include?: (QueryFilter | string)[]
}

export const resolvers: Resolver = {
  Query: {
    order: async (_parent, args, _context, info) => {
      const sequelizeQueryFilters: QueryFilter = {}

      lookahead({
        info,
        state: sequelizeQueryFilters,

        next({ state, type }) {
          const nextState: QueryFilter = { model: type }

          state.include = state.include || []
          state.include.push(nextState)

          return nextState
        },
      })

      /**
       * `sequelizeQueryFilters` now equals to
       * {
       *   include: [
       *     {
       *       model: 'OrderItem',
       *       include: [
       *         { model: 'Product', include: [{ model: 'Inventory' }] },
       *         { model: 'ProductGroup' },
       *       ],
       *     },
       *   ],
       * }
       *
       * or would be without the `ProductGroup` filter if the operation didn't include it
       */

      return await Order.findOne(sequelizeQueryFilters)
    },
  },
}

More examples in integration tests

Playground

You can play around with lookahead and our mock schema by cloning this repository and running the dev script locally (requires pnpm).

pnpm install
pnpm dev

Visit the playground at http://localhost:4455/graphql 🚀

Contribution

# Install dependencies
pnpm install

# Develop using the playground
pnpm dev

# Run ESLint
pnpm lint

# Run Vitest
pnpm test

# Run Vitest in watch mode
pnpm test:watch