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

join-monster-graphql-tools-adapter

v0.1.1

Published

Fetch data for your graph-tools schema using join-monster.

Downloads

7,563

Readme

Join Monster GraphQL Tools Adapter

Use Join Monster's SQL generation and query batching powers with the Apollo graphql-tools server package.

What's this package for?

Suppose you have a GraphQL schema for a forum website, defined with the Schema Language like so:

const typeDefs = `
type Comment {
  id: Int!,
  body: String!,
  postId: Int,
  authorId: Int,
  archived: Boolean
}

type Post {
  id: Int!,
  body: String!,
  authorId: Int,
  numComments: Int!,
  comments: [Comment]
}

type User {
  id: Int!,
  email: String!,
  fullName: String!,
  favNums: [Int],
  posts: [Post]
}

type Query {
  user(id: Int!): User
}
`

module.exports = typeDefs

When using graphql-js, the reference implementation, you tag the Type constructors with extra metadata to configure Join Monster. The schema language does not allow adding arbitrary properties to the type definitions.

This package let's you add those tags without messing with the internals of the built schema object. Once you familiarize yourself with Join Monster's API, you can use all the same properties by passing it to this function.

const joinMonsterAdapt = require('join-monster-graphql-tools-adapter')
const typeDefs = require('../path/to/types')

const joinMonster = require('join-monster').default
// node drivers for talking to SQLite
const db = require('sqlite')
const { makeExecutableSchema } = require('graphql-tools')

const resolvers = {
  Query: {
    // call joinMonster in the "user" resolver, and all child fields that are tagged with "sqlTable" are handled!
    user(parent, args, ctx, resolveInfo) {
      return joinMonster(resolveInfo, ctx, sql => {
        return db.all(sql)
      }, { dialect: 'sqlite3' })
    }
  },
  User: {
    // the only field that needs a resolver, joinMonster hydrates the rest!
    fullName(user) {
      return user.first_name + ' ' + user.last_name
    }
  }
}

const schema = makeExecutableSchema({
  typeDefs,
  resolvers
})

// tag the schema types with the extra join monster metadata
// Note the change in JoinMonster API (v3) - extensions: {} block
joinMonsterAdapt(schema, {
  Query: {
    fields: {
      // add a function to generate the "where condition"
      user: {
        extensions: {
          joinMonster: {
            where: (table, args) => `${table}.id = ${args.id}`
          }
        }
      }
    }
  },
  User: {
    // map the User object type to its SQL table
    extensions: {
      joinMonster: {
        sqlTable: 'accounts',
        uniqueKey: 'id',
      }
    },
    // tag the User's fields
    fields: {
      email: {
        extensions: {
          joinMonster: {
            sqlColumn: 'email_address'
          }
        }
      },
      fullName: {
        extensions: {
          joinMonster: {
            sqlDeps: [ 'first_name', 'last_name' ]
          }
        }
      },
      posts: {
        extensions: {
          joinMonster: {
            sqlJoin: (userTable, postTable) => `${userTable}.id = ${postTable}.author_id`,
          }
        }
      }
    }
  },
  Post: {
    extensions: {
      joinMonster: {
        sqlTable: 'posts',
        uniqueKey: 'id',
      }
    },
    fields: {
      numComments: {
        extensions: {
          joinMonster: {
        // count with a correlated subquery
            sqlExpr: table => `(SELECT count(*) FROM comments where ${table}.id = comments.post_id)`
          }
        }
      },
      comments: {
        // fetch the comments in another batch request instead of joining
        extensions: {
          joinMonster: {
            sqlBatch: {
              thisKey: 'post_id',
              parentKey: 'id'
            }
          }
        }
      }
    }
  },
  Comment: {
    extensions: {
      joinMonster: {
        sqlTable: 'comments',
        uniqueKey: 'id',
      }
    },
    fields: {
      postId: {
        extensions: {
          joinMonster: {
            sqlColumn: 'post_id'
            }
          }
      },
      authorId: {
        extensions: {
          joinMonster: {
            sqlColumn: 'author_id'
          }
        }
      }
    }
  }
})

Now that our schema is Join-monsterized, we are ready to start executing some queries!

const { graphql } = require('graphql')

const query = `{
  user(id: 1) {
    id
    fullName
    email
    posts {
      id
      body
      numComments
      comments {
        id
        body
        authorId
        archived
      }
    }
  }
}`
graphql(schema, query).then(doSomethingCrazy)

Advisory

There is a known issue (see #4) that passing the logger in graphql-tools makeExecutableSchema breaks automatic fetching of default column values. For the time being, it is suggested to remove the logger or add sqlColumn to every field.