0.5.1 • Published 4 years ago

fauna-fields-list-compile v0.5.1

Weekly downloads
2
License
MIT
Repository
github
Last release
4 years ago

Fauna Fields List Compile

npm

A POC for a GraphQL to FaunaDB FQL compiler and general utility to produce FaunaDB queries from a fields list.

Motivation

FaunaDB's query language is, FQL, is particularly well suited for getting a bunch of relational data and returning a denormalized result, with just the data you want, all from only one hit to the DB. GraphQL is great for just getting the data you want, but a typical naive setup will often have to make many round trips to a database before it completes.

By compiling the GraphQL query directly into a single FQL query, we can make the database do all the work for us in one trip. All we need is an understanding of the schema (database and GraphQL) and the query AST. We can get the AST from the now demystified info argument!

This project started off specifically as a way to compile GraphQL queries down to a single FQL query. I hope this project can be a proof of concept for compiling GraphQL queries as well as just building FQL queries rapidly.

TODO List

  • resolve all scalar properties in the data model
  • resolve properties containing a Ref and get their properties
  • resolve embedded objects
  • compile GraphQL query to single FQL query
  • Typescript!
  • resolve _id and _ts fields
  • resolve reverse refs (index on other collection)
  • synchronize data model with Fauna gql meta objects.
  • authorization helpers.
    • login, logout
    • RBAC
  • converter for GraphQL Schema AST to data model
  • pluggable way to do resolvers/compilers with directives
  • interfaces & unions

Standalone Usage

The library provides functions that convert the data model into a recursive query builder. They take the data model and a starting FaunaDB class. From there they can take a selection set (or a "fields list") and a starting Ref.

See the full example.

const { Client } = require('faunadb');
const {
  FaunaDBCompiler,
  SelectionBuilder
} = require('fauna-fields-list-compile');
const client = new Client({ secret: process.env.FAUNADB_SECRET });

// define the data model and create the compiler
const faunadbTypeDefs = {
  /*...*/
};
const faunaDBCompiler = new FaunaDBCompiler({ typeDefs: faunadbTypeDefs });

// *****************************************************************************
// query a single object
// *****************************************************************************
// 1) Build compilers
const memberRefBaseQuery = q.Ref(q.Collection('Member'), '238074476388942340');
const memberCompiler = faunaDBCompiler.getCollectionCompiler(
  memberRefBaseQuery,
  'Member'
);

// 2) Create Selections
const memberSelections = [
  /*...*/
];

// 3) Run Query

client
  .query(memberCompiler(memberSelections))
  .then(results => console.log(results))
  .catch(e => console.error(e));

// *****************************************************************************
// Query Many
// *****************************************************************************
// 1) Build compilers
const booksListBaseQuery = q.Paginate(q.Match(q.Index('books')));
const booksCompiler = faunaDBCompiler.getCollectionListCompiler(
  booksListBaseQuery,
  'Book'
);

// 2) Create Selections
const booksSelections = [
  /*...*/
];

// 3) Run Query
client
  .query(booksCompiler(booksSelections))
  .then(results => console.log(results))
  .catch(e => console.error(e));

GraphQL Usage

A full example with Apollo Server (V2.9) shows how to use with GraphQL.

The standalone usage can be applied to GraphQL resolver anywhere you like, but there is an additional helper to build root queries.

const { Client } = require('faunadb');
const { FaunaGraphQLClient } = require('fauna-fields-list-compile');
const client = new Client({ secret: process.env.FAUNADB_SECRET });

// define the data model
const faunadbTypeDefs = {
  /*...*/
};

// Create the FaunaDB client and put it in the library wrapper
const faunaGraphQLClient = new FaunaGraphQLClient({
  client,
  typeDefs: faunadbTypeDefs
});

/*...*/

// Define the graphQL resolvers
const resolvers = {
  Query: {
    books: faunaGraphQLClient.createRootResolver('Book', 'books'),
    members: faunaGraphQLClient.createRootResolver('Member', 'members')
  }
};

/*...*/

Data Models

The data model is a stripped down version of the database schema. The following data model is used in the examples:

Version 0.4.0 included breaking changes in the data model. Helper functions are provided to build up the new format. These changes are making it easier to extend the types of relationships possible between types.

Type Definitions

The data model is made up of a list of type definitions. These will be

  • Collection Types
  • Embedded Types
  • Interfaces
  • Unions

A SchemaBuilder helper object is available to help build up types.

const { SchemaBuilder } = require('fauna-fields-list-compile');
const {
  collectionType,
  embeddedType,
  listType,
  namedType,
  NumberField,
  StringField
} = SchemaBuilder;

const MemberTypeDef = collectionType(/*...*/);
const BookTypeDef = collectionType(/*...*/);
const HasRelationshipTypeDef = collectionType(/*...*/);
const AddressTypeDef = embeddedType(/*...*/);

const faunadbTypeDefs = [
  AddressTypeDef,
  BookTypeDef,
  MemberTypeDef,
  HasRelationshipTypeDef
];

Collection References

The default compiler for a collection type expands a Ref or a list of Refs. Nothing more is needed than something like the following:

const BookTypeDef = collectionType('Book', [
  { name: 'title', type: StringField },
  { name: 'author', type: namedType('Member') }
]);

Match Index Relationship

a compiler override can be specified for collection type fields.

define an index like:

q.CreateIndex({
  name: 'relationships_out',
  source: q.Class('HasRelationship'),
  terms: [{ field: ['data', 'from'] }]
});

Then the following will work to create a link from the Member type to the HasRelationship type.

const MemberTypeDef = collectionType('Member', [
  /*...*/
  {
    name: 'relationships_out',
    type: listType(namedType('HasRelationship')),
    resolver: {
      kind: 'matchRefResolver',
      index: 'relationships_out'
    }
  }
]);

const HasRelationshipTypeDef = {
  kind: 'CollectionTypeDefinition',
  name: 'HasRelationship',
  fields: [
    { name: 'from', type: namedType('Member') },
    { name: 'to', type: namedType('Member') },
    { name: 'relationship', type: StringField }
  ]
};

this corresponds to the following GraphQL

type Member {
  _id: ID!
  _ts: Long!
  # ...
  relationships_out: [HasRelationship]
}

type HasRelationship {
  from: Member
  to: Member
  relationship: String
}

Selections

The selection set is a kind of map of the fields that you want queried.

Version 0.4.0 included breaking changes to Selections (formerly Fields List). Helper functions are provided to build up the new format. The new format will allow the addition of type conditions, i.e. interfaces and unions!

The following are used in the examples

const memberSelections = [
  field('_id'),
  field('_ts'),
  field('name'),
  field('age'),
  field('address', [field('street'), field('city'), field('zip')]),
  field('tags'),
  field('favorites', [
    field('title'),
    field('author', [field('_id'), field('_ts'), field('name')])
  ]),
  field('relationships_out', [
    field('relationship'),
    field('to', [(field('_id'), field('_ts'), field('name'))])
  ])
];

const booksSelections = [
  field('_id'),
  field('_ts'),
  field('title'),
  field('author', [field('_id'), field('_ts'), field('name')])
];

The format is the nolonger the same as the output from node package graphql-fields. However, a helper method is provided to convert from graphql-fields. This is what is used internally in FaunaGraphQLClient.

0.5.0

4 years ago

0.5.1

4 years ago

0.4.1

5 years ago

0.4.0

5 years ago

0.3.0

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago