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

duckql

v1.0.0

Published

🦆 DuckQL, untyped GraphQL server

Downloads

6

Readme

Tests

DuckQL is an untyped GraphQL server that lets you write JS to resolve queries without a schema. It's a useful layer to build custom resolvers or as a way of unifying other GraphQL servers.

Why?

GraphQL is an overloaded concept. It consists of two wholly unrelated parts:

  • a simple query language
  • a complex type system

GraphQL queries don't know or care about the underlying types that they might resolve. For example, take a list query:

query Foo($filter: Filter!) {
  listFoo(filter: $filter) {
    items {
      id
      name
      fooProp
    }
  }
}

This query knows nothing about what a Foo is, does not specify that items must be returned as a list, nor the Filter type we expect as a variable. Yet, using Apollo requires us to specify a whole schema just to handle a request like this.

Usage

DuckQL parses incoming GraphQL queries (via the core graphql package) and parses them into a ResolverContext type:

import { DuckQLServer } from 'duckql';

const gqlServer = new DuckQLServer({
  resolver(context) {
    const out = { data: {} };

    if ('me' in context.selection.sub) {
      out.data['me'] = { firstName: 'Sam', lastName: 'Thor' };
    }

    return out;
  },
});

const out = await gqlServer.handle({
  query: `query { me { firstName lastName }}`,
});

Other Helpers

DuckQL can also process a query synchronously into a ResolverContext:

import { buildContext } from 'duckql';
const context = buildContext({ query: `{ foo }` });

Or it can handle HTTP requests directly (on "/graphql" with method "POST"), using e.g., Polka:

import polka from 'polka';
import { DuckQLServer } from 'duckql';
const gqlServer = new DuckQLServer({
  resolver(context) { /* TODO */ },
});

polka()
  .post('/graphql', gqlServer.httpHandle)
  // or
  .use(gqlServer.buildMiddlware())
  .listen(3000);

Variable Interpolation

DuckQL interpolates any GraphQL variables it finds, like $foo. For example, for a request like:

const request = {
  variables: {
    'x': 'hi!',
  },
  query: `query($x: String, $y: Number = 123) { listFoo(message: $x, size: $y) }`,
}

The processed selection of listFoo will already contain args { message: "hi!", size: 123 }. Missing or unresolved variables are a parse error and will through GraphQLQueryError from this package.

API

The ResolverContext is an object which wraps up the selections of your query in a structured way. Most importantly, it has a property selection, which contains a recursive type SelectionNode:

export type SelectionNode = {
  args?: { [key: string]: GraphQLType };
  directives?: any[];
  sub?: SelectionSet;
  node: FieldNode;
};
export type SelectionSet = { [key: string]: SelectionNode };

For example, if the user made a query for { listBar { bar(x: 123) { zing } } }, then context.selection will look like:

({
  node: ...,
  sub: {
    'listBar': {
      node: ...,
      sub: {
        'bar': {
          node: ...,
          args: { 'x': 123 },
          sub: {
            'zing': {
              node: ...,
            },
          },
        },
      },
    },
  },
})

Importantly, each sub-tree contains a node which can be used to reproduce a sub-tree of the original query. This can be useful to forward these queries to another server without having to care about the schema. For example:

import { print } from 'graphql';
const q = print(context.sub['listBar'].sub['bar'].node);
q === `bar(x: 123) {
  zing
}`;

Other Context Properties

As well as the selection, the context also contains:

  • operation: one of 'query', 'mutation' or 'subscription'
  • operationName: the operation name in e.g., "query Foo {" would be "Foo", or the blank string for default/none
  • maxDepth: the maximum depth of selection (useful to catch abuse via deeply nested queries)
  • node: the original GraphQL AST node, without variable interpolation

Missing Features

DuckQL does not yet support:

  • Fragments: it will treat these as an invalid query
  • Directives: these are silently ignored, but remain in the AST to be forwarded