{"id":15043133,"url":"https://github.com/molaux/sequelize-graphql-schema-builder","last_synced_at":"2025-04-14T20:57:34.417Z","repository":{"id":42665596,"uuid":"197368350","full_name":"molaux/sequelize-graphql-schema-builder","owner":"molaux","description":"Builds a complete GraphQL Schema for CRUD operations from a sequelize instance","archived":false,"fork":false,"pushed_at":"2025-03-14T11:54:38.000Z","size":368,"stargazers_count":3,"open_issues_count":5,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-02T17:49:05.226Z","etag":null,"topics":["graphql-api","graphql-schema","graphql-sequelize","mutations","nested-associations","queries","resolvers","sequelize-models","sequelize-schema","subscriptions"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/molaux.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-17T10:32:32.000Z","updated_at":"2025-03-14T11:54:41.000Z","dependencies_parsed_at":"2024-10-01T04:40:44.168Z","dependency_job_id":"58264bcb-398a-4b14-9259-09377b35597a","html_url":"https://github.com/molaux/sequelize-graphql-schema-builder","commit_stats":{"total_commits":137,"total_committers":4,"mean_commits":34.25,"dds":"0.35036496350364965","last_synced_commit":"965d7060a72a7a20a2313427077ae5222c2ed000"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molaux%2Fsequelize-graphql-schema-builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molaux%2Fsequelize-graphql-schema-builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molaux%2Fsequelize-graphql-schema-builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/molaux%2Fsequelize-graphql-schema-builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/molaux","download_url":"https://codeload.github.com/molaux/sequelize-graphql-schema-builder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248717248,"owners_count":21150389,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql-api","graphql-schema","graphql-sequelize","mutations","nested-associations","queries","resolvers","sequelize-models","sequelize-schema","subscriptions"],"created_at":"2024-09-24T20:48:36.934Z","updated_at":"2025-04-14T20:57:34.393Z","avatar_url":"https://github.com/molaux.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sequelize GraphQL Schema Builder\n\nThis project is an experimental work that aims to build a complete GraphQL schema for CRUD operations, on top of [graphql-sequelize](https://github.com/mickhansen/graphql-sequelize).\n\nThe `sequelizeGraphQLSchemaBuilder` builds queries, mutations, subscriptions and types needed to build a complete GraphQL API according to your Sequelize models and associations. Generated queries, mutations and subscriptions are able to resolve nested associations, so the GraphQL schema is tight to the Sequelize schema.\n\n## Installation\n\n``` bash\n$ npm install --save @molaux/sequelize-graphql-schema-builder\n```\n`sequelize-graphql-schema-builder` assumes you have graphql and sequelize installed.\n\n## Use\n\n```javascript\nconst { schemaBuilder } = require('@molaux/sequelize-graphql-schema-builder')\n\nconst schema = sequelize =\u003e {\n  const {\n    modelsTypes: sequelizeModelsTypes,\n    queries: sequelizeModelsQueries,\n    mutations: sequelizeModelsMutations,\n    subscriptions: sequelizeModelsSubscriptions\n  } = schemaBuilder(sequelize)\n\n  return new GraphQLSchema({\n    query: new GraphQLObjectType({\n      name: 'RootQueryType',\n      fields: () =\u003e sequelizeModelsTypes,\n    }),\n    subscription: new GraphQLObjectType({\n      name: 'RootSubscriptionType',\n      fields: () =\u003e sequelizeModelsSubscriptions\n    }),\n    mutation: new GraphQLObjectType({\n      name: 'RootMutationType',\n      fields: () =\u003e sequelizeModelsMutations\n    })\n  })\n}\n```\n\n## Example\n\nThis documentation is based on the [Sequelize GraphQL Schema Builder example](https://github.com/molaux/sequelize-graphql-schema-builder-example).\n\n## Queries\n\nThe query api will shape like this :\n\n```gql\ntype RootQueryType {\n  ...\n  Staffs(query: JSON): [Staff]\n  Stores(query: JSON): [Store]\n  ...\n}\n```\n\n```gql\nquery {\n  Staffs {\n    firstName\n    fullName\n    Store { \n      ManagerStaff {\n      \tfirstName\n      }\n      Staffs {\n        firstName\n      }\n    }\n    ManagerStaffStores { \n      Staffs {\n        fullName\n      }\n    }\n    Address {\n      address\n    }\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"Staffs\": [\n      {\n        \"firstName\": \"Mike\",\n        \"fullName\": \"Mike HILLYER\",\n        \"Store\": {\n          \"ManagerStaff\": {\n            \"firstName\": \"Mike\"\n          },\n          \"Staffs\": [\n            {\n              \"firstName\": \"Mike\"\n            }\n          ]\n        },\n        \"ManagerStaffStores\": [\n          {\n            \"Staffs\": [\n              {\n                \"fullName\": \"Mike HILLYER\"\n              }\n            ]\n          }\n        ],\n        \"Address\": {\n          \"address\": \"23 Workhaven Lane\"\n        }\n      },\n      {\n        \"firstName\": \"Jon\",\n        \"fullName\": \"Jon STEPHENS\",\n        \"Store\": {\n          \"ManagerStaff\": {\n            \"firstName\": \"Jon\"\n          },\n          \"Staffs\": [\n            {\n              \"firstName\": \"Jon\"\n            }\n          ]\n        },\n        \"ManagerStaffStores\": [\n          {\n            \"Staffs\": [\n              {\n                \"fullName\": \"Jon STEPHENS\"\n              }\n            ]\n          }\n        ],\n        \"Address\": {\n          \"address\": \"1411 Lillydale Drive\"\n        }\n      }\n    ]\n  }\n}\n```\n\nNote the nested associations : here `Store` and `Staff` are linked by 2 foreign keys. \n\n[In the example](https://github.com/molaux/sequelize-graphql-schema-builder-example/tree/master/src/models/sakila/extensions/Staff.cjs), `Staff.firstName` has a getter that transforms raw data into upper case, and `Staff.fullName` is a `Sequelize` `VIRTUAL` field.\n\nThe 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)\n\n```sql\nSELECT \n  \"Staff\".\"first_name\" AS \"firstName\",\n  \"Staff\".\"store_id\" AS \"storeId\",\n  \"Staff\".\"staff_id\" AS \"staffId\",\n  \"Staff\".\"address_id\" AS \"addressId\",\n  \"Staff\".\"last_name\" AS \"lastName\",\n  \"Store\".\"manager_staff_id\" AS \"Store.managerStaffId\",\n  \"Store\".\"store_id\" AS \"Store.storeId\",\n  \"Store-\u003eManagerStaff\".\"staff_id\" AS \"Store.ManagerStaff.staffId\",\n  \"Store-\u003eManagerStaff\".\"first_name\" AS \"Store.ManagerStaff.firstName\",\n  \"Store-\u003eStaffs\".\"staff_id\" AS \"Store.Staffs.staffId\",\n  \"Store-\u003eStaffs\".\"first_name\" AS \"Store.Staffs.firstName\",\n  \"ManagerStaffStores\".\"store_id\" AS \"ManagerStaffStores.storeId\", \"ManagerStaffStores-\u003eStaffs\".\"staff_id\" AS \"ManagerStaffStores.Staffs.staffId\",\n  \"ManagerStaffStores-\u003eStaffs\".\"first_name\" AS \"ManagerStaffStores.Staffs.firstName\",\n  \"ManagerStaffStores-\u003eStaffs\".\"last_name\" AS \"ManagerStaffStores.Staffs.lastName\",\n  \"Address\".\"address_id\" AS \"Address.addressId\", \"Address\".\"address\" AS \"Address.address\"\nFROM \"staff\" AS \"Staff\"\nLEFT OUTER JOIN \"store\" AS \"Store\" ON \"Staff\".\"store_id\" = \"Store\".\"store_id\"\nLEFT OUTER JOIN \"staff\" AS \"Store-\u003eManagerStaff\" ON \"Store\".\"manager_staff_id\" = \"Store-\u003eManagerStaff\".\"staff_id\"\nLEFT OUTER JOIN \"staff\" AS \"Store-\u003eStaffs\" ON \"Store\".\"store_id\" = \"Store-\u003eStaffs\".\"store_id\"\nLEFT OUTER JOIN \"store\" AS \"ManagerStaffStores\" ON \"Staff\".\"staff_id\" = \"ManagerStaffStores\".\"manager_staff_id\"\nLEFT OUTER JOIN \"staff\" AS \"ManagerStaffStores-\u003eStaffs\" ON \"ManagerStaffStores\".\"store_id\" = \"ManagerStaffStores-\u003eStaffs\".\"store_id\"\nLEFT OUTER JOIN \"address\" AS \"Address\" ON \"Staff\".\"address_id\" = \"Address\".\"address_id\"\nORDER BY \"Staff\".\"staff_id\" ASC\n```\n\n### dissociate\nThe previous big join query can be splitted where you want with the optionnal argument `dissociate` (default is `false`) :\n\n```gql\nquery {\n  Staffs {\n    firstName\n    fullName\n    Store(dissociate: true) { \n      ManagerStaff {\n      \tfirstName\n      }\n      Staffs {\n        firstName\n      }\n    }\n    ManagerStaffStores { \n      Staffs {\n        fullName\n      }\n    }\n    Address {\n      address\n    }\n  }\n}\n```\n\nThe resolvers will split association trees so the resulting queries will look like this :\n\n```sql\nSELECT \n  \"Staff\".\"first_name\" AS \"firstName\",\n  \"Staff\".\"staff_id\" AS \"staffId\",\n  ...\nFROM \"staff\" AS \"Staff\"\nLEFT OUTER JOIN \"store\" AS \"ManagerStaffStores\" ON \"Staff\".\"staff_id\" = \"ManagerStaffStores\".\"manager_staff_id\"\nLEFT OUTER JOIN \"staff\" AS \"ManagerStaffStores-\u003eStaffs\" ON \"ManagerStaffStores\".\"store_id\" = \"ManagerStaffStores-\u003eStaffs\".\"store_id\"\nLEFT OUTER JOIN \"address\" AS \"Address\" ON \"Staff\".\"address_id\" = \"Address\".\"address_id\"\nORDER BY \"Staff\".\"staff_id\" ASC;\n\nSELECT \n  \"Store\".\"manager_staff_id\" AS \"managerStaffId\",\n  \"Store\".\"store_id\" AS \"storeId\",\n  \"ManagerStaff\".\"staff_id\" AS \"ManagerStaff.staffId\", \"ManagerStaff\".\"first_name\" AS \"ManagerStaff.firstName\", \"Staffs\".\"staff_id\" AS \"Staffs.staffId\", \"Staffs\".\"first_name\" AS \"Staffs.firstName\"\nFROM \"store\" AS \"Store\"\nLEFT OUTER JOIN \"staff\" AS \"ManagerStaff\" ON \"Store\".\"manager_staff_id\" = \"ManagerStaff\".\"staff_id\"\nLEFT OUTER JOIN \"staff\" AS \"Staffs\" ON \"Store\".\"store_id\" = \"Staffs\".\"store_id\"\nWHERE \"Store\".\"store_id\" = 1;\n\nSELECT \n  \"Store\".\"manager_staff_id\" AS \"managerStaffId\",\n  \"Store\".\"store_id\" AS \"storeId\",\n  \"ManagerStaff\".\"staff_id\" AS \"ManagerStaff.staffId\", \"ManagerStaff\".\"first_name\" AS \"ManagerStaff.firstName\", \"Staffs\".\"staff_id\" AS \"Staffs.staffId\", \"Staffs\".\"first_name\" AS \"Staffs.firstName\"\nFROM \"store\" AS \"Store\"\nLEFT OUTER JOIN \"staff\" AS \"ManagerStaff\" ON \"Store\".\"manager_staff_id\" = \"ManagerStaff\".\"staff_id\"\nLEFT OUTER JOIN \"staff\" AS \"Staffs\" ON \"Store\".\"store_id\" = \"Staffs\".\"store_id\"\nWHERE \"Store\".\"store_id\" = 2;\n```\n\nThe 2 `Staff` instances resulting from the first request resolve their `Store` seperatly.\n\nFor each model, a corresponding type is created :\n```gql\ntype Film {\n  filmId: ID!\n  title: String!\n  description: String\n  releaseYear: Int\n  rentalDuration: Int!\n  rentalRate: String!\n  length: Int\n  replacementCost: String!\n  rating: FilmratingEnumType\n  specialFeatures: String\n  lastUpdate: Date!\n  Inventories(query: JSON, dissociate: Boolean): [Inventory]\n  Language(query: JSON, dissociate: Boolean): Language\n  OriginalLanguage(query: JSON, dissociate: Boolean): Language\n  Actors(query: JSON, dissociate: Boolean): [Actor]\n  Categories(query: JSON, dissociate: Boolean): [Category]\n  FilmText(query: JSON, dissociate: Boolean): FilmText\n}\n```\n\n### query\n\n```gql\nFilms(query: JSON): [Film]\n```\n\n```gql\nquery {\n  Films(query: { \n    where: {\n      length: {\n        _andOp: { \n          _gteOp: 60,\n          _lteOp: 70,\n        }\n      },\n      releaseYear: 2006\n    }\n  }) {\n    length\n    title\n    releaseYear\n    Actors {\n      lastName\n    }\n  }\n}\n```\nHere, we juste want films for witch length is in [60, 70], and released in 2006 (there is no other release date in the database);\n\nOperators should be formatted this way : `_${operator}Op` where `operator` is a key from `Sequelize.Op`.\n\n```gql\nquery {\n  Films(query: { \n    where: {\n      length: {\n        _andOp: { \n          _gteOp: 60,\n          _lteOp: 70,\n        }\n      },\n      releaseYear: 2006\n    }\n  }) {\n    length\n    title\n    releaseYear\n    Actors(query: {\n      required: true\n      where: {\n        lastName: \"DEAN\"\n      }\n    }) {\n      firstName\n      lastName\n    }\n  }\n}\n```\n\nThe same request as above, but this time we filtered movies having actors named \"DEAN\".\n\n#### required\n\nLets say we haved nullized \"Mike HILLYER\" `store_id` in the database.\n\n```gql\nquery {\n  Staffs {\n    fullName\n    Store(query: { required: true }) {\n      storeId\n    }\n  }\n}\n```\nThis time, \"Mike HILLYER\" is excluded, since he has no `Store`.\n\nNote that this is not compatible with the `dissociate`: `true` argument on the same node since the required is passed to the `include` dependencies tree of the root requested element by Sequelize.\n\n#### order, offset, limit\n\n```gql\nquery {\n  Films(query: { \n    where: {\n      length: {\n        _andOp: { \n          _gteOp: 60,\n          _lteOp: 70,\n        }\n      },\n      releaseYear: 2006\n    }\n    limit: 3\n    offset: 0\n    order: [[ \"length\", \"DESC\" ], [ \"title\", \"DESC\" ]]\n  }) {\n    length\n    title\n    releaseYear\n    Inventories(query: {\n      limit: 3\n    \toffset: 0\n    }) { \n      inventoryId\n    }\n  }\n}\n```\n\nThe `limit` and `offset` options are supported in nested elements but be warned that the Sequelize `separate` flag is automatically setted in association. (See [Sequelize documentation](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-findAll)).\n\nNote that the `query` argument is more limited (by default) on nested nodes since its injected in association includes to the root `findAll` Sequelize query. To retrieve the behavior of root query (for example, for being able to use `order`) in nested nodes, you must `dissociate` the node.\n\n#### group\n\nThe group clause uses existing attributes to apply aggregation functions. Note that the node must be dissociated to be applied (`dissociate: true`).\n\n```gql\nquery {\n  Suppliers(query: { where: {id: 5}}) {\n    id\n    Products {\n      id\n      label\n      CustomerOrders(query: {\n        transform: {\n          quantity: {\n            fn: [\n              \"SUM\",\n              { col: [ \"quantity\" ]}\n            ]\n          },\n          price: {\n            fn: [\n              \"AVG\",\n              { col: [ \"price\" ]}\n            ]\n          },\n          date: {\n            fn: [\n              \"concat\",\n              {\n                cast: [\n                  {\n                    fn: [\n                      \"DATEPART\",\n                      { literal: [ \"year\" ]},\n                      { col: [ \"date\" ]}\n                    ]\n                  },\n                  \"varchar\"\n                ]\n              },\n              \"/\",\n              {\n                cast: [\n                  {\n                    fn: [\n                      \"DATEPART\",\n                      { literal: [ \"week\" ]},\n                      { col: [ \"date\" ]}\n                    ]\n                  },\n                  \"varchar\"\n                ]\n              }\n            ]\n          },\n          year: {\n            fn: [\n              \"DATEPART\",\n              { literal: [ \"year\" ]},\n              { col: [ \"date\" ]}\n            ]\n          },\n          week: {\n            fn: [\n              \"DATEPART\",\n              { literal: [ \"week\" ]},\n              { col: [ \"date\" ]}\n            ]\n          }\n        },\n        where: {\n          date: {\n            _gtOp: \"2020-08-31T00:00:00.000Z\"\n          }\n        },\n        group: [\"year\", \"week\"],\n        order: [[\"date\", \"ASC\"]]\n      }, dissociate: true) {\n        quantity\n        date\n        price\n      }\n    }\n  }\n}\n```\n\nThe `transform` object introduce the possibility to transform fields using [Sequelize static methods](https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html). For grouping to work, you need a `transform` with an aggregation function for each requested attribute. New aliases (here `year` and `week`) cannot be requested as it's not part of the schema.\n\n```json\n{\n  \"data\": {\n    \"Suppliers\": [\n      {\n        \"id\": \"5\",\n        \"Products\": [\n          {\n            \"id\": \"3215\",\n            \"label\": \"SALAD\",\n            \"CustomerOrders\": [\n              {\n                \"quantity\": \"18.546\",\n                \"date\": \"2020/50\",\n                \"price\": \"2.5\"\n              },\n              {\n                \"quantity\": \"20.846\",\n                \"date\": \"2020/51\",\n                \"price\": \"2.5\"\n              },\n              {\n                \"quantity\": \"2.284\",\n                \"date\": \"2020/52\",\n                \"price\": \"2.5\"\n              }\n            ]\n          },\n          {\n            \"id\": \"3219\",\n            \"label\": \"APPLE\",\n            \"CustomerOrders\": [\n              {\n                \"quantity\": \"2.184\",\n                \"date\": \"2020/36\",\n                \"price\": \"2.25\"\n              },\n              {\n                \"quantity\": \"12.602\",\n                \"date\": \"2020/39\",\n                \"price\": \"4.1\"\n              },\n              {\n                \"quantity\": \"2.61\",\n                \"date\": \"2020/40\",\n                \"price\": \"4.1\"\n              }\n            ]\n          },\n          ...\n        ]\n      }\n    ]\n  }\n}\n\n```\n\n## Mutations\n\nFor input types, when possible, a fake `union type` is used (For example, here : `InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms`). It can be resolved either by a node embedding only its primary key (`InputLanguageByLanguageId` : refering to an existing one), or any else that will be considered as a creation type (`LanguageCreateInputThroughFilms`). The `LanguageCreateInputThroughFilms` is the `LanguageCreateInput` without the `Films` field : when you add a `Language` through a `Film` entity, you cannot add other `Films` to this new `Language` (at the present time).\n\n```gql\ninput FilmCreateInput {\n  filmId: ID\n  title: String!\n  description: String\n  releaseYear: Int\n  rentalDuration: Int\n  rentalRate: String\n  length: Int\n  replacementCost: String\n  rating: FilmratingEnumType\n  specialFeatures: String\n  lastUpdate: Date\n  Inventories: [InputInventoryByInventoryIdOrInventoryCreateInputThroughFilm]\n  Language: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms!\n  OriginalLanguage: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms\n  Actors: [InputActorByActorIdOrActorCreateInputThroughFilms]\n  Categories: [InputCategoryByCategoryIdOrCategoryCreateInputThroughFilms]\n  FilmText: InputFilmTextByFilmIdOrFilmTextCreateInputThroughFilm\n}\n\ninput InputFilmByFilmId {\n  filmId: ID\n}\n\nscalar InputFilmByFilmIdOrFilmCreateInputThroughLanguage\n...\n\ninput FilmCreateInputThroughLanguage {\n  filmId: ID\n  title: String!\n  description: String\n  releaseYear: Int\n  rentalDuration: Int\n  rentalRate: String\n  length: Int\n  replacementCost: String\n  rating: FilmratingEnumType\n  specialFeatures: String\n  lastUpdate: Date\n  Inventories: [InputInventoryByInventoryIdOrInventoryCreateInputThroughFilm]\n  OriginalLanguage: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms\n  Actors: [InputActorByActorIdOrActorCreateInputThroughFilms]\n  Categories: [InputCategoryByCategoryIdOrCategoryCreateInputThroughFilms]\n  FilmText: InputFilmTextByFilmIdOrFilmTextCreateInputThroughFilm\n}\n...\n\n```\n\nGraphQL union input type is [under discussions](https://github.com/graphql/graphql-spec/pull/825) at the present time. Here it is simulated through a `SCALAR` type. It works well but the unioned types are not exposed in the schema. It can be tricky to debug when having type issues since GraphQL consider the SCALAR as final type. To workaround this problem, I use explicit naming (`InputInventoryByInventoryIdOrInventoryCreateInputThroughFilm`) and add _mock_ queries to expose hidden types :\n\n```gql\nmockFilm(\n    InputInventoryByInventoryId: InputInventoryByInventoryId\n    InputLanguageByLanguageId: InputLanguageByLanguageId\n    InputActorByActorId: InputActorByActorId\n    InputCategoryByCategoryId: InputCategoryByCategoryId\n    InputFilmTextByFilmId: InputFilmTextByFilmId\n    FilmCreateInputThroughInventories: FilmCreateInputThroughInventories\n    FilmCreateInputThroughLanguage: FilmCreateInputThroughLanguage\n    FilmCreateInputThroughOriginalLanguage: FilmCreateInputThroughOriginalLanguage\n    FilmCreateInputThroughActors: FilmCreateInputThroughActors\n    FilmCreateInputThroughCategories: FilmCreateInputThroughCategories\n    FilmCreateInputThroughFilmText: FilmCreateInputThroughFilmText\n  ): Film\n\ninput FilmCreateInputThroughInventories {\n  filmId: ID\n  title: String!\n  description: String\n  releaseYear: Int\n  rentalDuration: Int\n  rentalRate: String\n  length: Int\n  replacementCost: String\n  rating: FilmratingEnumType\n  specialFeatures: String\n  lastUpdate: Date\n  Language: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms!\n  OriginalLanguage: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms\n  Actors: [InputActorByActorIdOrActorCreateInputThroughFilms]\n  Categories: [InputCategoryByCategoryIdOrCategoryCreateInputThroughFilms]\n  FilmText: InputFilmTextByFilmIdOrFilmTextCreateInputThroughFilm\n}\n...\n```\n\nAn `atomic` boolean parameter is available for all mutations to enable/disable the use of transactions (default is `true`). When transactions are enabled (by default), if something goes wrong, transaction for whole mutation is rolled back and pending publications for subscriptions are canceled.\n\n### create\n\n```gql\ntype RootMutationType {\n  ...\n  createFilm(input: FilmCreateInput, atomic: Boolean): Film\n  ...\n}\n```\n\n```gql\nmutation {\n  createFilm(input: {\n    title: \"Interstellar\"\n    # a new language\n    Language: { \n    \tname: \"Breton\" \n  \t}\n    # an existing language\n    OriginalLanguage: { \n      languageId: 2\n    }\n    # new one to one\n    FilmText: {\n      title: \"FilmText title\"\n      description: \"FilmText description\"\n    }\n    Categories: [\n      # a new Category\n      { \n        name: \"New category\"\n      }\n      # an existing Category\n      {\n        categoryId: 14\n      }\n      # a new Category\n      { \n        name: \"Other category\"\n        # we can create other nested creations / associations as well\n      }\n    ]\n  }) {\n    filmId\n    Categories {\n      categoryId\n      name\n    }\n    Language {\n      languageId\n    }\n    OriginalLanguage {\n      languageId\n    }\n  }\n}\n```\n\nThe new ids are pulled in the response :\n\n```json\n{\n  \"data\": {\n    \"createFilm\": {\n      \"filmId\": \"1001\",\n      \"Categories\": [\n        {\n          \"categoryId\": \"14\",\n          \"name\": \"Sci-Fi\"\n        },\n        {\n          \"categoryId\": \"17\",\n          \"name\": \"New category\"\n        },\n        {\n          \"categoryId\": \"18\",\n          \"name\": \"Other category\"\n        }\n      ],\n      \"Language\": {\n        \"languageId\": \"7\"\n      },\n      \"OriginalLanguage\": {\n        \"languageId\": \"2\"\n      }\n    }\n  }\n}\n```\n\n### update\n\nUpdate is a little bit different since we can omit any field to update only those needed, and wee need a query to select entities we want to apply mutation on.\n\nConsequently non nullable fields have to become nullable.\n\n```gql \ninput FilmUpdateInput {\n  filmId: ID\n  title: String\n  description: String\n  releaseYear: Int\n  rentalDuration: Int\n  rentalRate: String\n  length: Int\n  replacementCost: String\n  rating: FilmratingEnumType\n  specialFeatures: String\n  lastUpdate: Date\n  Inventories: [InputInventoryByInventoryIdOrInventoryCreateInputThroughFilm]\n  addInventories: [InputInventoryByInventoryIdOrInventoryCreateInputThroughFilm]\n  Language: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms\n  OriginalLanguage: InputLanguageByLanguageIdOrLanguageCreateInputThroughFilms\n  Actors: [InputActorByActorIdOrActorCreateInputThroughFilms]\n  addActors: [InputActorByActorIdOrActorCreateInputThroughFilms]\n  removeActors: [InputActorByActorId]\n  Categories: [InputCategoryByCategoryIdOrCategoryCreateInputThroughFilms]\n  addCategories: [InputCategoryByCategoryIdOrCategoryCreateInputThroughFilms]\n  removeCategories: [InputCategoryByCategoryId]\n  FilmText: InputFilmTextByFilmIdOrFilmTextCreateInputThroughFilm\n}\n\ntype RootMutationType {\n  ...\n  updateFilm(query: JSON, input: FilmUpdateInput, atomic: Boolean): [Film]\n  ...\n}\n```\n\nWe can see that all associations are union types, so you can create new entities or refer to existing one.\n\nThe only exception is obviously for `belongsToMany` associations removal that can only refer to existing models (here: `removeCategories: [InputCategoryByCategoryId]`).\n\n```gql\nmutation {\n  updateFilm(query: { where: { filmId: 1001 } }, input: {\n    title: \"Other title\"\n    Language: {\n    \tname: \"Portuguese\"\n  \t}\n    OriginalLanguage: {\n      languageId: 2\n    }\n    Categories: [\n      {\n        name: \"New topic\"\n      }\n      {\n        name: \"Other topic\"\n      }\n      {\n        categoryId: 10\n      }\n    ]\n  }) {\n    filmId\n    Categories {\n      categoryId\n      name\n    }\n    Language {\n      languageId\n    }\n    OriginalLanguage {\n      languageId\n    }\n  }\n}\n```\n\n### delete\n\n```gql\ntype RootMutationType {\n  ...\n  deleteFilm(query: JSON, atomic: Boolean): [Film]\n  ...\n}\n```\n\n```gql\nmutation {\n  deleteFilm(query: { where: { filmId: 1001 } }) {\n    filmId\n  }\n}\n```\n\n## Subscriptions\n\nThe builder creates three subscriptions for each model :\n\n```gql\ntype RootSubscriptionType {\n  ...\n  createdCountry: [Country]\n  updatedCountry: [Country]\n  deletedCountry: [CountryID]\n  ...\n}\n\n# Country ID type\ntype CountryID {\n  countryId: ID\n}\n```\n\nThe `CountryID` type is the `Country` type, reduced to the only field we are sure to know after deletion : the primary key.\n\nFor subscriptions to work, you must [provide a `pubSub`](https://github.com/molaux/sequelize-graphql-schema-builder-example/blob/master/src/server.js) entry to the GraphQL context.\n\nWarning : the cascading delete / set null is not handled yet, and will not trigger publications.\n\nSee the [the example](https://github.com/molaux/sequelize-graphql-schema-builder-example) for testing purpose. Subscritions are tested with jest. Example :\n\n```gql\nmutation {\n  createFilm(input: {\n    title: \"initial title\"\n    # a new language\n    Language: { \n    \tname: \"initial language\"\n      Films: [{\n        title: \"New Film\"\n      }]\n  \t}\n    # new one to one\n    FilmText: {\n      title: \"B\"\n      description: \"autre\"\n    }\n    # an existing language\n    OriginalLanguage: { \n      languageId: 1\n    }\n    Categories: [\n      # a new Category\n      { \n        name: \"initial new category\"\n      }\n      # an existing Category\n      {\n        categoryId: 14\n      }\n      # a new Category\n      { \n        name: \"initial other category\"\n        # we can create other nested creations / associations as well\n      }\n    ]\n  }, atomic: true) {\n    filmId\n    title\n    FilmText {\n      filmId\n      title\n    }\n    Categories {\n      categoryId\n      name\n    }\n    Language {\n      languageId\n      name\n    }\n    OriginalLanguage {\n      languageId\n      name\n    }\n  }\n}\n```\n\nThe above mutation will trigger following publications to subscribtors :\n```json\n{\n  \"data\": {\n    \"updatedCategory\": [\n      {\n        \"categoryId\": \"14\",\n        \"name\": \"Sci-Fi\",\n        \"Films\": [\n          {\n            \"filmId\": \"1001\",\n            \"title\": \"initial title\"\n          },\n          {\n            \"filmId\": \"985\",\n            \"title\": \"WONDERLAND CHRISTMAS\"\n          },\n          {\n            \"filmId\": \"972\",\n            \"title\": \"WHISPERER GIANT\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"createdCategory\": [\n      {\n        \"categoryId\": \"17\",\n        \"name\": \"initial new category\",\n        \"Films\": [\n          {\n            \"filmId\": \"1001\",\n            \"title\": \"initial title\"\n          }\n        ]\n      },\n      {\n        \"categoryId\": \"18\",\n        \"name\": \"initial other category\",\n        \"Films\": [\n          {\n            \"filmId\": \"1001\",\n            \"title\": \"initial title\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"createdFilm\": [\n      {\n        \"filmId\": \"1001\",\n        \"title\": \"initial title\",\n        \"Categories\": [\n          {\n            \"categoryId\": \"14\",\n            \"name\": \"Sci-Fi\"\n          },\n          {\n            \"categoryId\": \"17\",\n            \"name\": \"initial new category\"\n          },\n          {\n            \"categoryId\": \"18\",\n            \"name\": \"initial other category\"\n          }\n        ],\n        \"Language\": {\n          \"languageId\": \"7\",\n          \"name\": \"initial language\"\n        }\n      }\n    ]\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"createdFilmText\": [\n      {\n        \"filmId\": \"1001\",\n        \"title\": \"B\",\n        \"Film\": {\n          \"filmId\": \"1001\"\n        }\n      }\n    ]\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"updatedLanguage\": [\n      {\n        \"languageId\": \"1\",\n        \"name\": \"English\",\n        \"Films\": [\n          {\n            \"filmId\": \"1000\",\n            \"title\": \"ZORRO ARK\"\n          },\n          {\n            \"filmId\": \"999\",\n            \"title\": \"ZOOLANDER FICTION\"\n          },\n          {\n            \"filmId\": \"998\",\n            \"title\": \"ZHIVAGO CORE\"\n          }\n        ],\n        \"OriginalLanguageFilms\": [\n          {\n            \"filmId\": \"1001\",\n            \"title\": \"initial title\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"createdLanguage\": [\n      {\n        \"languageId\": \"7\",\n        \"name\": \"initial language\",\n        \"Films\": [\n          {\n            \"title\": \"initial title\"\n          }\n        ],\n        \"OriginalLanguageFilms\": []\n      }\n    ]\n  }\n}\n```\n\n## Meta infos\n\nThe generated schema exposes an other type of queries that aims to provide information about how to validate data on the client side.\n\n```gql\ntype FilmMeta {\n  validators: FilmValidator\n  defaultValues: FilmDefaultValue\n}\n\ntype FilmValidator {\n  filmId: JSON\n  title: JSON\n  description: JSON\n  releaseYear: JSON\n  rentalDuration: JSON\n  rentalRate: JSON\n  length: JSON\n  replacementCost: JSON\n  rating: JSON\n  specialFeatures: JSON\n  lastUpdate: JSON\n  languageId: JSON\n  originalLanguageId: JSON\n}\n\ntype FilmDefaultValue {\n  rentalDuration: Int!\n  rentalRate: String!\n  replacementCost: String!\n  rating: FilmratingEnumType!\n  lastUpdate: Date!\n}\n```\n\nFor each field of `validators`, the provided object is the corresponding Sequelize validator (Regex have to be quoted as strings to be compatible with `JSON`). `defaultValues` is only present if at least one field has default value.\n\n## API\n\n### `schemaBuilder`\n```javascript\nschemaBuilder(sequelize, { \n  namespace: '',\n  extraModelFields: () =\u003e ({}),\n  extraModelQueries: () =\u003e ({}),\n  extraModelTypes: () =\u003e ({}),\n  subscriptionsContextFilter: (emitterContext, context) =\u003e true\n  maxManyAssociations: 3,\n  debug: false\n})\n```\n\n#### `namespace`\n\nA prefix for generated queries and types.\n\n#### `extraModelFields({ modelsTypes, nameFormatter, logger }, model)`\n\nA callback that lets you add custom fields to Sequelize models types. It will be called each time a GraphQL model type is built. The resulting object will be merged with model's GraphQL type fields.\n\n[In the example](https://github.com/molaux/sequelize-graphql-schema-builder-example/blob/master/src/schema/country/index.js), we use this hook to inject rich country infos coming from [restcountries.eu](https://restcountries.eu) to our `Country` GraphQL type. \n\n```gql\ntype Country {\n  countryId: ID!\n  country: String!\n  lastUpdate: Date!\n  infos: JSON\n  Cities(query: JSON, dissociate: Boolean): [City]\n}\n```\n\nIn the example, we only return the field `infos` for `Country` model, but we can inject other common fields to all models if needed.\n\n```gql\nquery {\n  Countries(query: { limit: 3 }) {\n    country\n    infos\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"Countries\": [\n      {\n        \"country\": \"Afghanistan\",\n        \"infos\": [\n          {\n            \"name\": \"Afghanistan\",\n            \"topLevelDomain\": [\n              \".af\"\n            ],\n            \"alpha2Code\": \"AF\",\n            \"alpha3Code\": \"AFG\",\n            \"callingCodes\": [\n              \"93\"\n            ],\n            ...\n          }\n        ]\n      },\n      ...\n    ]\n  }\n}\n```\n\n#### `extraModelQueries({ modelsTypes, nameFormatter, logger }, model, queries)`\n\nA callback that lets you add custom queries depending on generated Sequelize models types.\n\nTo be documented...\n\n#### `extraModelTypes({ modelsTypes, nameFormatter, logger }, model)`\n\nA callback that lets you add custom types depending on generated Sequelize models types.\n\nTo be documented...\n\n#### `subscriptionsContextFilter(emitterContext, context)`\n\nA filter passed to subcriptions `subscribe` and `resolve` methods in order to filter events based on emitter and subscriber context.\n\nExample to not resend events to initiator of mutation (given that te client provides a uuid header and server handles this to context) :\n\n```javascript\n { \n   subscriptionsContextFilter: (emitterContext, context) =\u003e emitterContext.user.uuid !== context.user.uuid\n }\n ```\n#### `maxManyAssociations`\n\nLimits the number of \"parallel\" resulting left joins. Default is 3. \n\n#### `debug`\n\nPrints debug infos. \n\n### `getRequestedAttributes(model, fieldNode, infos, logger, map)`\n\nTo be documented...\n\n### `beforeResolver(model, { nameFormatter, logger })`\n\nTo be documented...\n\n### `findOptionsMerger(fo1, fo2)`\n\nTo be documented...\n\n## Tests\n\nThe [Sequelize GraphQL Schema Builder example](https://github.com/molaux/sequelize-graphql-schema-builder-example) embeds a jest test suite.\n\n## Related projects\n\n * [MUI App Biolerplate](https://github.com/molaux/mui-app-boilerplate) is a complete client / server application that relies on this piece of code.\n * [MUI CRUDF](https://github.com/molaux/mui-crudf) is a Material-UI component able to handle this API to generate CRUD interfaces","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmolaux%2Fsequelize-graphql-schema-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmolaux%2Fsequelize-graphql-schema-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmolaux%2Fsequelize-graphql-schema-builder/lists"}