This package has been deprecated

Author message:

WARNING: This module has been renamed to @molaux/sequelize-graphql-schema-builder. Please install it instead.

@molaux/graphql-sequelize-r

2.0.0-alpha.4 • Public • Published

Graphql Sequelize R

This project is an experimental work that aims to bring relations and miscellaneous features on top of graphql-sequelize.

The sequelizeToGraphQLSchemaBuilder builds queries, mutations and types needed to build a complete GraphQL API according to your Sequelize models and associations. Generated queries and insertions are able to resolve nested associations, so the GraphQL schema is tight to the Sequelize schema.

Installation

$ npm install --save @molaux/graphql-sequelize-r

graphql-sequelize-r assumes you have graphql and sequelize installed.

Use

const { sequelizeToGraphQLSchemaBuilder } = require('@molaux/graphql-sequelize-r')

const schema = sequelize => {
  const {
    modelsTypes: sequelizeModelsTypes,
    queries: sequelizeModelsQueries,
    // TODO: mutations: sequelizeModelsMutations
  } = sequelizeToGraphQLSchemaBuilder(sequelize)

  return new GraphQLSchema({
    query: new GraphQLObjectType({
      name: 'RootQueryType',
      fields: () => sequelizeModelsTypes,
    }),
    subscription: new GraphQLObjectType({
      name: 'RootSubscriptionType',
      fields: () => {} // Your subscriptions here
    }),
    mutation: new GraphQLObjectType({
      name: 'RootMutationType',
      fields: () => sequelizeModelsMutations
    })
  })
}

GraphQL API

This documentation is based on the GraphQL Sequelize R example.

Introduction

The api will shape like this :

query {
  staffs {
    firstName
    fullName
    store { 
      manager {
      	firstName
      }
      staffs {
        firstName
      }
    }
    asManagerStores { 
      staffs {
        fullName
      }
    }
    address {
      address
    }
  }
}
{
  "data": {
    "staffs": [
      {
        "firstName": "Mike",
        "fullName": "Mike HILLYER",
        "store": null,
        "asManagerStores": [],
        "address": {
          "address": "23 Workhaven Lane"
        }
      },
      {
        "firstName": "Jon",
        "fullName": "Jon STEPHENS",
        "store": {
          "manager": {
            "firstName": "Jon"
          },
          "staffs": [
            {
              "firstName": "Jon"
            }
          ]
        },
        "asManagerStores": [
          {
            "staffs": [
              {
                "fullName": "Jon STEPHENS"
              }
            ]
          }
        ],
        "address": {
          "address": "1411 Lillydale Drive"
        }
      }
    ]
  }
}

Note the nested associations : here Store and Staff are linked by 2 foreign keys.

In the example, Staff.firstName has a getter that transforms raw data into upper case, and Staff.fullName is a Sequelize VIRTUAL field.

The corresponding SQL query is composed of multiple joins, and only needed fields to respond to the GraphQL query are pulled (and those needed by JOINs and VIRTUAL fields)

SELECT 
  `Staff`.`first_name` AS `firstName`,
  `Staff`.`store_id` AS `storeId`,
  `Staff`.`staff_id` AS `staffId`,
  `Staff`.`address_id` AS `addressId`,
  `Staff`.`last_name` AS `lastName`,
  `Store`.`manager_staff_id` AS `Store.managerStaffId`,
  `Store`.`store_id` AS `Store.storeId`,
  `Store->Manager`.`staff_id` AS `Store.Manager.staffId`,
  `Store->Manager`.`first_name` AS `Store.Manager.firstName`,
  `Store->Staffs`.`staff_id` AS `Store.Staffs.staffId`,
  `Store->Staffs`.`first_name` AS `Store.Staffs.firstName`,
  `AsManagerStore`.`store_id` AS `AsManagerStore.storeId`,
  `AsManagerStore->Staffs`.`staff_id` AS `AsManagerStore.Staffs.staffId`,
  `AsManagerStore->Staffs`.`first_name` AS `AsManagerStore.Staffs.firstName`,
  `AsManagerStore->Staffs`.`last_name` AS `AsManagerStore.Staffs.lastName`,
  `Address`.`address_id` AS `Address.addressId`, 
  `Address`.`address` AS `Address.address`

FROM `staff` AS `Staff`

LEFT OUTER JOIN `store` AS `Store`
  ON `Staff`.`store_id` = `Store`.`store_id` 

LEFT OUTER JOIN `staff` AS `Store->Manager`
  ON `Store`.`manager_staff_id` = `Store->Manager`.`staff_id`

LEFT OUTER JOIN `staff` AS `Store->Staffs`
  ON `Store`.`store_id` = `Store->Staffs`.`store_id`

LEFT OUTER JOIN `store` AS `AsManagerStore`
  ON `Staff`.`staff_id` = `AsManagerStore`.`manager_staff_id`
 
LEFT OUTER JOIN `staff` AS `AsManagerStore->Staffs`
  ON `AsManagerStore`.`store_id` = `AsManagerStore->Staffs`.`store_id`

LEFT OUTER JOIN `address` AS `Address`
  ON `Staff`.`address_id` = `Address`.`address_id`
  
ORDER BY `Staff`.`staff_id` ASC

required

query {
  staffs {
    fullName
    store(required: true) { 
      manager {
      	firstName
    	}
    }
  }
}

This time, "Mike HILLYER" is excluded, since he has no Store.

query

where

query {
  films(query: { 
    where: {
      length: {
        _andOp: { 
          _gteOp: 60,
          _lteOp: 70,
        }
      },
      releaseYear: 2006
    }
  }) {
    length
    title
    releaseYear
    filmActors {
      actor {
      	lastName
      }
    }
  }
}

Here, we juste want films for witch length is in [60, 70], and released in 2006 (there is no other release date in the database);

Operators should formatted in this way : _${operator}Op where operator is a key from Sequelize.Op.

query {
  films(query: { 
    where: {
      length: {
        _andOp: { 
          _gteOp: 60,
          _lteOp: 70,
        }
      },
      releaseYear: 2006
    }
  }) {
    length
    title
    releaseYear
    filmActors(required: true) {
      actor (required: true, query: {
        where: {
          lastName: "DEAN"
        }
      }) {
      	firstName
        lastName
      }
    }
  }
}

The same request as above, but this time we filter movies having actors named "DEAN".

order

To be documented

group

To be documented

Mutations

insert

Multiple insertions at one time :

mutation {
  insertFilm(input: {
    title: "Interstellar"
    language: {
    	name: "Spanish"
  	}
    originalLanguageId: 1
    filmCategories: [
      {
        categoryId: 14
      }
      {
        category: {
          name: "Adventure"
        }
      }
    ]
  }) {
    filmId
    filmCategories {
      category {
        categoryId
      }
    }
    language {
      languageId
    }
    original {
      languageId
    }
  }
}

Here we used an existing Language (english with id 1) and associated it as the original language. For the movie language, we introduced a new "spanish" language. The same is true for categories : we use existing "Sci-fi" (id 14) category and introduced a new "Adventure" Category.

The new ids are pulled in the response :

{
  "data": {
    "insertFilm": {
      "filmId": 3,
      "filmCategories": [
        {
          "category": {
            "categoryId": 5
          }
        },
        {
          "category": {
            "categoryId": 6
          }
        }
      ],
      "language": {
        "languageId": 5
      },
      "original": {
        "languageId": 6
      }
    }
  }
}

Check :

query {
  films {
    title
    filmCategories(required: true) {
      category(query: { where: { name: "Adventure" } }, required: true) {
        categoryId
      }
    }
  }
}

Result :

{
  "data": {
    "films": [
      {
        "title": "Interstellar",
        "filmCategories": [
          {
            "category": {
              "categoryId": 17
            }
          }
        ]
      }
    ]
  }
}

update

mutation {
  updateFilm(query: { where: { filmId: 1001 } }, input: {
		releaseYear: 2014
  })
}

The resolver returns the amount of modified items :

{
  "data": {
    "updateFilm": 1
  }
}

delete

mutation {
  deleteFilmCategory(query: { where: { filmId: 1001 } })
}

The resolver returns the amount of deleted items :

"data": {
  "deleteFilmCategory": 2
}

Types

For each model, several types are created :

Model type

This type is used for queries.

type Address {
  addressId: Int!
  address: String!
  address2: String
  district: String!
  postalCode: String
  phone: String!
  lastUpdate: Date!
  cityId: Int!
  customers(query: JSON, required: Boolean): [Customer]
  staffs(query: JSON, required: Boolean): [Staff]
  stores(query: JSON, required: Boolean): [Store]
  city(query: JSON, required: Boolean): City
}

Insert input type

This type is used for insert mutations. Autoincremented fields becomes nullable, as for timestamp fields.

Note that in Address model, cityId cannot be null, but here it is nullable bexause you can either pass an existing cityId or a nested new city to be created at the same time. A GraphQLUnionInput should be pertinent here, but it is not yet implemented.

input AddressInsertInput {
  addressId: Int
  address: String!
  address2: String
  district: String!
  postalCode: String
  phone: String!
  lastUpdate: Date
  cityId: Int
  customers: [CustomerInsertInput]
  staffs: [StaffInsertInput]
  stores: [StoreInsertInput]
  city: CityInsertInput
}

Update input type

Here, there is no more association and non nullable fields becomes nullable, so each field is optionnal (we lose the type information here : if we pass null to cityId, it will result in a constraint error).

input AddressUpdateInput {
  addressId: Int
  address: String
  address2: String
  district: String
  postalCode: String
  phone: String
  lastUpdate: Date
  cityId: Int
}

API

sequelizeToGraphQLSchemaBuilder

sequelizeToGraphQLSchemaBuilder(sequelize, { 
  namespace: '',
  extraModelFields: () => ({}),
  extraModelQueries: () => ({}),
  extraModelTypes: () => ({}),
  maxManyAssociations: 3,
  debug: false
})

namespace

A prefix for generated queries and types.

extraModelFields({ modelsTypes, nameFormatter, logger }, model)

A callback that lets you add custom fields to Sequelize models types.

To be documented...

extraModelQueries({ modelsTypes, nameFormatter, logger }, model, queries)

A callback that lets you add custom queries depending on generated Sequelize models types.

To be documented...

extraModelTypes({ modelsTypes, nameFormatter, logger }, model)

A callback that lets you add custom types depending on generated Sequelize models types.

To be documented...

maxManyAssociations

Limits the number of "parallel" resulting left joins. Default is 3.

debug

Prints debug infos.

getRequestedAttributes(model, fieldNode, logger, map)

To be documented...

beforeResolver(model, { nameFormatter, logger })

To be documented...

findOptionsMerger(fo1, fo2)

To be documented...

Related project

Readme

Keywords

Package Sidebar

Install

npm i @molaux/graphql-sequelize-r

Weekly Downloads

0

Version

2.0.0-alpha.4

License

MIT

Unpacked Size

45.7 kB

Total Files

5

Last publish

Collaborators

  • molaux