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-db-projection

v1.0.17

Published

Given GraphQL query, creates db fields projection to fetch only fields that are required. <br/>Supports lists, nested queries, fragments and inline fragments.

Downloads

82

Readme

graphql-db-projection

Given GraphQL query, creates db fields projection to fetch only fields that are required. Supports lists, nested queries, fragments and inline fragments.

Installation

Install with yarn:

$ yarn add graphql-db-projection

or npm:

$ npm i -S graphql-db-projection

Setup

Prepare directives you intend to use:

import { ApolloProjector, IncludeAll, IgnoreField } from 'graphql-db-projection';

const typeDefs = gql`
  directive @proj(
    projection: String,
    projections: [String],
    nameInDB: String
  ) on FIELD_DEFINITION
  directive @all on FIELD_DEFINITION
  directive @ignore on FIELD_DEFINITION

  // ... your schemas
`;

// ...

// (you can also call the directives differently)
const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives: {
    proj: ApolloProjector,
    all: IncludeAll,
    ignore: IgnoreField
  }
});

Simple Example

Suppose we have User model:

const typeDefs = gql`
  type User {
    firstName: String
    lastName: String
    username: String
    address: Address
  }

  type Address {
    country: String
    city: String
    street: String
  }
`;

we can call makeProjection on last ASTs param to get projections mapping:

import makeProjection from 'graphql-db-projection';
// ...
const resolvers = {
  Query: {
    users: (obj, args, request, fieldASTs) => {
      const projection = makeProjection(fieldASTs);
      // ...
    },
  },
};

then the following query:

query ($id: String){
  user (id: $id){
    firstName
    address {
      city
      street
    }
  }
}

will produce projection:

{
  firstName: 1,
  address: {
    city: 1,
    street: 1
  }
}

now you can use it to project fields for db, for example for mongoDB:

import { toMongoProjection } from 'graphql-db-projection';

const resolvers = {
  Query: {
    users: (obj, args, request, fieldASTs) => {
      const projection = makeProjection(fieldASTs);
      const mongoProjection = toMongoProjection(projection)
      return db.collection('users').findOne(args.id, mongoProjection);
    }
  }
}

Include automatically all nested fields

To automatically include all nested fields of object use @all directive:

const typeDefs = gql`
  type User {
    username: String
    address: Address @all
  }
  
  type Address {
    city: String
    postalCode: String
  }
`;

now makeProjection result on query

query ($id: String){
  user (id: $id){
    firstName
    address {
      city
      street
    }
  }
}

will be:

{
  firstName: 1,
  address: 1
}

Ignore projection

To remove field from projection use @ignore directive:

const typeDefs = gql`
  type User {
    firstName: String
    lastName: String @ignore
    username: String
    address: Address @ignore
  }
`;

the result when requesting all fields will be just { firstName: 1, username: 1 }

Custom Projections

If resolve function of GraphQL field uses multiple DB fields to calculate the value, use @proj(projection: <field in db>) or @proj(projections: [<field in db>, ...]) to specify absolute paths of fields you need:

const typeDefs = gql`
  type User {

    // will add username to projection
    displayName: String @proj(projection: "username")

    // will add gender, firstName and lastName to projection
    fullName: String @proj(projections: ["gender", "firstName", "lastName"])
    
    address: Address
  }
  
  type Address {
    country: String
    fullAddress: @proj(projections: ["city", "street"])
  }
`;

const resolvers = {
  User: {
    // displayName is calculated from username in DB
    displayName: user => user.username,
    
    // fullName is calculated from gender, firstName, lastName in DB
    fullName: user => `${user.gender ? 'Mr.' : 'Mrs.'} ${user.firstName} ${user.lastName}`,
  },
  
  Address: {
    fullAddress: address => `${address.city} ${address.street}`,
  }
};

requesting all these fields in GraphQL query will result in projection:

{ 
  username: 1,
  gender: 1,
  firstName: 1,
  lastName: 1,
  addess: {
    country: 1,
    city: 1,
    street: 1
  }
}

Name of Field in DB called differently

Custom projections specify absolute paths inside nested project, but don't do recursion on the nested fields like you have by default. If only the name of field is called differently in DB but you want to project the nested fields recursively, use @proj(nameInDB: <field name in db>):

const typeDefs = gql`

  type User {
    username: String

    // stored as 'location' object in DB
    address: Address @proj(nameInDB: "location")
  }
  
  type Address {
    city: String
    postalCode: String
  }
`;

const resolvers = {
  Query: {
    users: (obj, args, request, fieldASTs) => {
      const projection = makeProjection(fieldASTs);

      // ...
    },
  },
  User: {
    address: user => user.location,
  },
};

requesting all these fields in GraphQL query will result in projection:

{
  username: 1,
  location: {
    city: 1,
    postalCode: 1
  }
}

Projection of subquery

If your subquery in GraphQL needs additional fetch from DB to join into parent object, remember that you can call makeProjection on fieldASTs argument inside subquery resolver function. Suppose we have array of Posts inside User:


const typeDefs = gql`
  type User {
    # suppose posts are located in different collection/table,
    # this is why we don't need to project that field in User
    posts: [PostType] @ignore
  }
`;

const resolvers = {
  User: {
    posts: (user, args, ctx, postsFieldASTs) => {

      // you can make new isolated projection only for User's Posts fetch
      // based on Post's GraphQL subquery
      const projectionOfPost = makeProjection(postsFieldASTs);
      const mongoProjection = toMongoProjection(projectionOfPost)
      return db.collection('posts')
          .find({ postedBy: user.id }, mongoProjection).toArray();
    },
  },
};