Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
+
+
Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut.
+
Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra.
+
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum.
+
Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis.
\ No newline at end of file
diff --git a/blog/2017/04/10/blog-post-two.html b/blog/2017/04/10/blog-post-two.html
new file mode 100644
index 00000000..a0a918f0
--- /dev/null
+++ b/blog/2017/04/10/blog-post-two.html
@@ -0,0 +1,112 @@
+New Blog Post · graphql-compose
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
+
+
Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut.
+
Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra.
+
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum.
+
Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis.
\ No newline at end of file
diff --git a/blog/2017/09/25/testing-rss.html b/blog/2017/09/25/testing-rss.html
new file mode 100644
index 00000000..5e3d1259
--- /dev/null
+++ b/blog/2017/09/25/testing-rss.html
@@ -0,0 +1,110 @@
+Adding RSS Support - RSS Truncation Test · graphql-compose
\ No newline at end of file
diff --git a/blog/2017/09/26/adding-rss.html b/blog/2017/09/26/adding-rss.html
new file mode 100644
index 00000000..4b876254
--- /dev/null
+++ b/blog/2017/09/26/adding-rss.html
@@ -0,0 +1,108 @@
+Adding RSS Support · graphql-compose
\ No newline at end of file
diff --git a/blog/2017/10/24/new-version-1.0.0.html b/blog/2017/10/24/new-version-1.0.0.html
new file mode 100644
index 00000000..6148956c
--- /dev/null
+++ b/blog/2017/10/24/new-version-1.0.0.html
@@ -0,0 +1,107 @@
+New Version 1.0.0 · graphql-compose
\ No newline at end of file
diff --git a/blog/atom.xml b/blog/atom.xml
new file mode 100644
index 00000000..9895cc89
--- /dev/null
+++ b/blog/atom.xml
@@ -0,0 +1,74 @@
+
+
+ https://graphql-compose.github.io/blog
+ graphql-compose Blog
+ 2017-10-24T06:00:00Z
+ Feed for Node.js
+
+ The best place to stay up-to-date with the latest graphql-compose news and events.
+ https://graphql-compose.github.io/img/logo.png
+
+
+ https://graphql-compose.github.io/blog/2017/10/24/new-version-1.0.0.html
+
+
+ 2017-10-24T06:00:00Z
+ This blog post will test file name parsing issues when periods are present. ]]>
+
+ Eric Nakagawa
+ http://twitter.com/ericnakagawa
+
+
+
+
+ https://graphql-compose.github.io/blog/2017/09/26/adding-rss.html
+
+
+ 2017-09-26T06:00:00Z
+ This is a test post.
+]]>
+
+ Eric Nakagawa
+ http://twitter.com/ericnakagawa
+
+
+
+
+ https://graphql-compose.github.io/blog/2017/04/10/blog-post-two.html
+
+
+ 2017-04-10T06:00:00Z
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
+]]>
+
+ Blog Author
+ http://twitter.com/
+
+
+
+
+ https://graphql-compose.github.io/blog/2016/03/11/blog-post.html
+
+
+ 2016-03-11T06:00:00Z
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
+]]>
+
+ Blog Author
+ http://twitter.com/
+
+
+
\ No newline at end of file
diff --git a/blog/feed.xml b/blog/feed.xml
new file mode 100644
index 00000000..767c626d
--- /dev/null
+++ b/blog/feed.xml
@@ -0,0 +1,55 @@
+
+
+
+ graphql-compose Blog
+ https://graphql-compose.github.io/blog
+ The best place to stay up-to-date with the latest graphql-compose news and events.
+ Tue, 24 Oct 2017 06:00:00 GMT
+ http://blogs.law.harvard.edu/tech/rss
+ Feed for Node.js
+
+ graphql-compose Blog
+ https://graphql-compose.github.io/img/logo.png
+ https://graphql-compose.github.io/blog
+
+
+
+ https://graphql-compose.github.io/blog/2017/10/24/new-version-1.0.0.html
+ https://graphql-compose.github.io/blog/2017/10/24/new-version-1.0.0.html
+ Tue, 24 Oct 2017 06:00:00 GMT
+ This blog post will test file name parsing issues when periods are present. ]]>
+
+
+
+ https://graphql-compose.github.io/blog/2017/09/26/adding-rss.html
+ https://graphql-compose.github.io/blog/2017/09/26/adding-rss.html
+ Tue, 26 Sep 2017 06:00:00 GMT
+ This is a test post.
+]]>
+
+
+
+ https://graphql-compose.github.io/blog/2017/04/10/blog-post-two.html
+ https://graphql-compose.github.io/blog/2017/04/10/blog-post-two.html
+ Mon, 10 Apr 2017 06:00:00 GMT
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
+]]>
+
+
+
+ https://graphql-compose.github.io/blog/2016/03/11/blog-post.html
+ https://graphql-compose.github.io/blog/2016/03/11/blog-post.html
+ Fri, 11 Mar 2016 06:00:00 GMT
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
+]]>
+
+
+
\ No newline at end of file
diff --git a/blog/index.html b/blog/index.html
new file mode 100644
index 00000000..1321989c
--- /dev/null
+++ b/blog/index.html
@@ -0,0 +1,113 @@
+Blog · graphql-compose
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien.
\ No newline at end of file
diff --git a/docs/5.12.0/api/InputTypeComposer.html b/docs/5.12.0/api/InputTypeComposer.html
new file mode 100644
index 00000000..570166f4
--- /dev/null
+++ b/docs/5.12.0/api/InputTypeComposer.html
@@ -0,0 +1,320 @@
+InputTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/api/InterfaceTypeComposer.html b/docs/5.12.0/api/InterfaceTypeComposer.html
new file mode 100644
index 00000000..89b4fe85
--- /dev/null
+++ b/docs/5.12.0/api/InterfaceTypeComposer.html
@@ -0,0 +1,372 @@
+InterfaceTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/api/ObjectTypeComposer.html b/docs/5.12.0/api/ObjectTypeComposer.html
new file mode 100644
index 00000000..22884d92
--- /dev/null
+++ b/docs/5.12.0/api/ObjectTypeComposer.html
@@ -0,0 +1,59 @@
+ObjectTypeComposer · graphql-compose
For version 5.x.x and below please see TypeComposer class.
+
\ No newline at end of file
diff --git a/docs/5.12.0/api/Resolver.html b/docs/5.12.0/api/Resolver.html
new file mode 100644
index 00000000..326d6627
--- /dev/null
+++ b/docs/5.12.0/api/Resolver.html
@@ -0,0 +1,473 @@
+Resolver · graphql-compose
The most interesting class in graphql-compose. The main goal of Resolver is to keep available resolve methods for Type and use them for building relation with other types.
+
Properties
+
schemaComposer
+
Current SchemaComposer instance which is used for storing types created by Resolver.
type ResolverSortArgConfig<TSource, TContext> = {
+ name: string,
+ sortTypeNameFallback?: string,
+ // value also can be an `Object`, but flow does not understande union with object and function
+ // see https://github.com/facebook/flow/issues/1948
+ value:
+ | { [key: string]: any }
+ | ResolverSortArgFn<TSource, TContext>
+ | string
+ | number
+ | boolean
+ | Array<any>,
+ deprecationReason?: ?string,
+ description?: ?string,
+};
+
\ No newline at end of file
diff --git a/docs/5.12.0/api/ScalarTypeComposer.html b/docs/5.12.0/api/ScalarTypeComposer.html
new file mode 100644
index 00000000..4f02fd62
--- /dev/null
+++ b/docs/5.12.0/api/ScalarTypeComposer.html
@@ -0,0 +1,186 @@
+ScalarTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/api/SchemaComposer.html b/docs/5.12.0/api/SchemaComposer.html
new file mode 100644
index 00000000..7344c480
--- /dev/null
+++ b/docs/5.12.0/api/SchemaComposer.html
@@ -0,0 +1,434 @@
+SchemaComposer · graphql-compose
When using Interfaces you may have such Types which are hidden under Interface.resolveType method. In such cases you should add these types explicitly. Cause buildSchema() will take only real used types and types which added via addSchemaMustHaveType() method.
\ No newline at end of file
diff --git a/docs/5.12.0/api/TypeComposer.html b/docs/5.12.0/api/TypeComposer.html
new file mode 100644
index 00000000..c7c111e3
--- /dev/null
+++ b/docs/5.12.0/api/TypeComposer.html
@@ -0,0 +1,599 @@
+TypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/api/TypeMapper.html b/docs/5.12.0/api/TypeMapper.html
new file mode 100644
index 00000000..5415bb2d
--- /dev/null
+++ b/docs/5.12.0/api/TypeMapper.html
@@ -0,0 +1,210 @@
+TypeMapper · graphql-compose
Type storage and type generator from Schema Definition Language (SDL). This is slightly rewritten buildASTSchema utility from graphql-js that allows to create type from a string (SDL).
+
Properties
+
schemaComposer
+
Current SchemaComposer instance which is used for getting types by name for type creation via SDL.
\ No newline at end of file
diff --git a/docs/5.12.0/api/UnionTypeComposer.html b/docs/5.12.0/api/UnionTypeComposer.html
new file mode 100644
index 00000000..46642d98
--- /dev/null
+++ b/docs/5.12.0/api/UnionTypeComposer.html
@@ -0,0 +1,278 @@
+UnionTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/api/misc-api-methods.html b/docs/5.12.0/api/misc-api-methods.html
new file mode 100644
index 00000000..5df6ec7b
--- /dev/null
+++ b/docs/5.12.0/api/misc-api-methods.html
@@ -0,0 +1,447 @@
+API misc · graphql-compose
The same as getProjectionFromAST except that all nested fields will not be extracted. Complex address will be just true, not { city: true, street: true },
graphql-compose re-exports GraphQL.js package for its plugins. It helps to avoid the hell with maintaining versions of graphql and graphql-compose in plugins' package.json files.
+
If you want to write a plugin for graphql-compose and publish it to npm, just add graphql-compose in dependencies of its package.json. And if you will need GraphQL.js objects and methods you may import them in such way:
+
// My awesome Plugin for graphql-compose
+import { graphql } from'graphql-compose';
+
+const { GraphQLNonNull, GraphQLObjectType } = graphql;
+
+
graphqlVersion
+
Sometimes it need to know which version of GraphQL.js is installed in the project.
+It may be used in graphql-compose plugins, cause different versions of GraphQL.js may have breaking changes and your plugins may have workarounds for different behavior.
+
const graphqlVersion: number;
+
+
import { graphqlVersion } from'graphql-compose';
+
+if (graphqlVersion < 13) {
+ throwError(`This plugin does not work with GraphQL.js v${graphqlVersion}`);
+}
+
+
Scalar Types
+
GraphQLDate
+
GraphQL scalar type that converts javascript Date object to string YYYY-MM-DDTHH:MM:SS.SSSZ and back.
+
import { GraphQLDate } from'graphql-compose';
+
+
GraphQLJSON
+
GraphQL scalar type that represents JSON. Field with this type may have arbitrary structure. Copied from @taion's graphql-type-json for reducing dependencies tree.
+
import { GraphQLJSON } from'graphql-compose';
+
+
TypeStorage
+
You may need some isolated storage for keeping types in your plugins. So TypeStorage is the easy way to obtain such storage.
\ No newline at end of file
diff --git a/docs/5.12.0/basics/generating-schema.html b/docs/5.12.0/basics/generating-schema.html
new file mode 100644
index 00000000..862b1eb1
--- /dev/null
+++ b/docs/5.12.0/basics/generating-schema.html
@@ -0,0 +1,214 @@
+Generating Schema · graphql-compose
SchemaComposer is a builder of GraphQLSchema object. Obtained Schema via buildSchema() method may be used in express-graphql, apollo-server and other libs which uses GraphQL.js under the hood for query execution at runtime.
+
Create Schema
+
SchemaComposer provides basic root types Query, Mutation, Subscription. You must add fields at least to one of these types, otherwise Schema will not have sense and cannot be build.
+
import { schemaComposer } from'graphql-compose';
+import { AuthorTC } from'./author';
+
+schemaComposer.Query.addFields({
+ // add field with regular FieldConfig
+ currentTime: {
+ type: 'Date',
+ resolve: () =>Date.now(),
+ },
+ // Assume that `AuthorTC` build with `graphql-compose-mongoose` which has CRUD resolvers
+ // in such case we can use pre-generated Resolvers as a FieldConfig
+ authorById: AuthorTC.getResolver('findById'),
+ authorMany: AuthorTC.getResolver('findMany'),
+ // ...
+});
+
+schemaComposer.Mutation.addNestedFields({
+ // also it may be very useful define nested fields
+ // Mutation will have `author` field, `author` will have `create` and `update` fields inside
+ 'author.create': AuthorTC.getResolver('createOne'),
+ 'author.update': AuthorTC.getResolver('updateById'),
+ // ...
+});
+
+exportdefault schemaComposer.buildSchema(); // exports GraphQLSchema
+
+
Restrict access
+
GraphQL.js does not provide any access rights checks. You should it do manually in resolve methods. With graphql-compose you may do it via wrapping Resolvers:
+
// rootMutation.js
+import { schemaComposer } from'graphql-compose';
+
+import { CommentTC } from'./comment';
+import { UserTC } from'./user';
+
+schemaComposer.Mutation.addNestedFields({
+ commentCreate: CommentTC.getResolver('createOne'), // may anybody
+
+ ...adminAccess({
+ // only for admins
+ 'user.create': UserTC.getResolver('createOne'),
+ 'user.update': UserTC.getResolver('updateById'),
+ 'user.remove': UserTC.getResolver('removeById'),
+ }),
+});
+
+functionadminAccess(resolvers) {
+ Object.keys(resolvers).forEach(k => {
+ resolvers[k] = resolvers[k].wrapResolve(next => rp => {
+ if (!rp.context.isAdmin) {
+ thrownewError('You should be admin, to have access to this action.');
+ }
+ return next(rp);
+ });
+ });
+ return resolvers;
+}
+
+
For getting isAdmin property from context you must define it in express-graphql or apollo-server:
In some complex scenarios you may need to have several GraphQL Schemas in one app. Graphql-compose by default exports following classes/instances for single schema mode:
\ No newline at end of file
diff --git a/docs/5.12.0/basics/type-modification.html b/docs/5.12.0/basics/type-modification.html
new file mode 100644
index 00000000..9eed222f
--- /dev/null
+++ b/docs/5.12.0/basics/type-modification.html
@@ -0,0 +1,209 @@
+Type modification · graphql-compose
This is the most important part of graphql-compose and the main difference in Schema creation with GraphQL.js. In GraphQL.js you have strict abilities in type definition and its further modification. But graphql-compose allows to you modify types after creation in very convenient ways.
+
+
Note: With graphql-compose you may modify types before GraphQLSchema object creation. When schema was created you cannot change types.
+
+
Fields modification
+
Available methods in TypeComposer, InputTypeComposer and EnumTypeComposer instances:
+
+
getFields()
+
setFields()
+
getFieldNames()
+
hasField(name)
+
setField(name, fieldConfig)
+
addFields(newFieldsConfig)
+
getField(name)
+
removeField(nameOrArray)
+
removeOtherFields(nameOrArray)
+
extendField(name, partialFieldConfig)
+
reorderFields(names)
+
deprecateFields(nameOrMap)
+
+
Additional methods in TypeComposer, InputTypeComposer instances:
+
+
getFieldType(name)
+
getFieldTC(name)
+
getFieldConfig(name)
+
makeFieldNonNull(nameOrArray)
+
makeFieldNullable(nameOrArray)
+
addNestedFields(newFields)
+
+
// add description to `firstName`
+AuthorTC.extendField('firstName', {
+ description: "This field returns Author's first name",
+});
+
+// Add new field `status` with Enum type
+AuthorTC.addField('status', `enum AuthorStatus { ACTIVE INACTIVE }`);
+
+// Change order of fields in type
+// unlisted fields will be added to the end of field list with old order
+AuthorTC.reorderFields(['status', 'firstName']);
+
+// Mark fields as deprecated with some message
+AuthorTC.deprecateFields({
+ rating: 'This field will be removed in June 2018',
+ dob: 'Use `age` field instead. This field will be removed in June 2018',
+});
+
+// Add new field with `address` name and for type
+// create a new object type with `city` and `country` fields
+AuthorTC.addNestedFields({
+ 'address.city': 'String',
+ 'address.country': 'String',
+});
+
+
Type modification
+
Available methods in TypeComposer, InputTypeComposer and EnumTypeComposer instances:
+
+
getType()
+
getTypePlural()
+
getTypeNonNull()
+
getTypeName()
+
setTypeName(newName)
+
getDescription()
+
setDescription()
+
clone(newTypeName)
+
+
Additional methods in TypeComposer
+
+
getInterfaces()
+
setInterfaces(interfaces)
+
hasInterface(interfaceObj)
+
addInterface(interfaceObj)
+
removeInterface(interfaceObj)
+
getInputType()
+
getITC()
+
+
Create your custom modification function
+
With this set of methods, you may write your own type modification functions. It may greatly reduce repetitive code across your schema definition.
+
As an example, lets write a function which will add rawData field with full record data from database. Also check isAdmin = true in context and if so return data, otherwise return null.
+
functionaddRawData(tc: TypeComposer) {
+ if (!tc.hasField('rawData')) {
+ tc.addField('rawData', {
+ type: 'JSON',
+ resolve: (source, args, context) => {
+ if (context.isAdmin) {
+ return source;
+ }
+ returnnull;
+ },
+ // add magic property `projection`
+ // which request all fields from database
+ // when requested this `rawData` field in the query
+ projection: { '*': 1 },
+ });
+ }
+}
+addRawData(AuthorTC);
+addRawData(PostTC);
+
+
Or even more
+
You may write your own plugins which will generate types from some models or non-graphql schemas. Take a look on avaliable list of plugins build on top of graphql-compose.
\ No newline at end of file
diff --git a/docs/5.12.0/basics/understanding-relations.html b/docs/5.12.0/basics/understanding-relations.html
new file mode 100644
index 00000000..e54ce87b
--- /dev/null
+++ b/docs/5.12.0/basics/understanding-relations.html
@@ -0,0 +1,310 @@
+Relations between Types · graphql-compose
GraphQL allows to create additional fields in your types which may provide data from other type. For example, you may add field posts to the Author type and write resolve function in such way that this field will return array of posts only for current Author.
Hm, it's became quite long. But what if you have other Types wich have relations with Posts (eg Reviewer, Reader)? I don't think that copy/paste of resolve method will be a good idea. Cause in the future you may want to add a new filter property and should scan all your code and put additional logic in all FieldConfigs. So if you meet with such problem the next section is for you.
+
Relation via Resolver
+
If you need to use the same FieldConfigs in different Types for such cases graphql-compose provides Resolver class. You may create a Resolver which will define type, args and resolve and reuse in all places of your Schema where you need it.
+
Anyway if you put posts resolver in separate file, you will meet with another problems
+
+
in Author type you will use criteria = { authorId: source.id } for resolve method;
+
in Reviewer - criteria = { reviewers: { $has: source.id } } and so on.
+
+
For such case better to improve args.filter by allowing to set authorId and reviewerId via arguments:
Should be an arrow function which returns Resolver. Wrapping resolver in arrow function helps to solve hoisting problem (when two types imports each other).
+
prepareArgs
+
At runtime we should have ability to prepare somehow args which will be passed to Resolver.
+
For example our Resolver has following arguments filter, limit, skip and sort.
+prepareArgs provides instruction how to setup them:
+
+
limit: 10 - hide limit arg from schema and set it equal to 10
+
filter: (source) => value - hide filter arg form schema and at runtime evaluate its value
+
sort: null - disable argument (hide from schema and do not pass it to resolver)
+
all undescribed args (like skip) will be avaliable in the schema and will be avaliable in query
+
+
projection
+
Is very useful option for extending requested fields in your query. It very good practice to request from database only that fields which were requested in the query. But sometimes we need to request additional fields for fullfilling findById resolver with authorId value in arguments. For this purpose you need to use projection.
Without projection when we will request author field its resolver may get args.authorId equals to undefined. In this situation will not provide any data for Author. It happens if fetching only that fields which listed in the query from database. So when client requests author field in GraphQL Query he also must request authorId explicitly. But why client should care it? So required additional fields should be requested via projection option.
\ No newline at end of file
diff --git a/docs/5.12.0/basics/understanding-types.html b/docs/5.12.0/basics/understanding-types.html
new file mode 100644
index 00000000..9395e0f6
--- /dev/null
+++ b/docs/5.12.0/basics/understanding-types.html
@@ -0,0 +1,393 @@
+Type creation · graphql-compose
If you need to create some complex type with several properties, you will need to use TypeComposer. It's a builder for GraphQLObjectType object.
+
TypeComposer has very convenient ways of type creation via create method.
+
via config
+
Most recommended way to define your Output type. Such definition provides hoisting problems solution via wrapping types by arrow function. Better developer experience with jumping to the type declarations.
Also this way of definition provides a lot of syntax sugar for field definition:
+
const AuthorTC = TypeComposer.create({
+ posts: {
+ // wrapping Type with arrow function helps to solve a hoisting problem
+ // also using type instances provides better DX
+ // (ctrl+click allows to jump to PostTC type declaration in your IDE)
+ type: () => PostTC,
+ description: 'Posts written by Author',
+ resolve: (source, args, context, info) => { ... },
+ },
+ // using standard GraphQL Type
+ ucFirstName: {
+ type: GraphQLString,
+ resolve: (source) => source.firstName.toUpperCase(),
+ // also request `firstName` field which must be loaded from database
+ projection: { firstName: true },
+ },
+ // fast way if you need to define only type
+ counter: 'Int',
+ // using SDL for definition new ObjectType
+ complex: `type ComplexType {
+ subField1: String
+ subField2: Float
+ subField3: Boolean
+ subField4: ID
+ subField5: JSON
+ subField6: Date
+ }`,
+ // SDL for defining array of strings, which is NonNull
+ list0: {
+ type: '[String]!',
+ description: 'Array of strings',
+ },
+ list1: '[String]',
+ list2: ['String'],
+ list3: [GraphQLString],
+ list4: [`type Complex2Type { f1: Float, f2: Int }`],
+});
+
+
via SDL
+
May have hoisting problems. Be aware that all used complex types must be already defined.
GraphQL allows to pass arguments for fields. You may freely use Scalars, Enums when describing input args. But what you should do in the case of mutations, where you might want to pass in a whole object to be created? For such cases for complex types instead of GraphQLObjectType you should use GraphQLInputObjectType. They they have small differences in its fields declaration:
+
+
input object type has defaultValue
+
input object type does not have args
+
input object type does not have resolve method
+
+
If you need to create some complex type with several properties, you will need to use InputTypeComposer. It's a builder for GraphQLInputObjectType object.
+
InputTypeComposer has very convenient ways of type creation via create method.
+
via config
+
Most recommended way to define your Input type. Such definition provides hoisting problems solution via wrapping types by arrow function. Better developer experience with jumping to the type declarations.
+
InputTypeComposer has the same type definition capabilities for describing fields as TypeComposer - as string, as arrow function, as SDL.
If you want indicate that field or argument return an array of some type, you may do the following:
+
import { GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field1: [AuthorTC], // RECOMMENDED just wrap in the regular js array
+ field2: AuthorTC.getTypePlural(), // call specific TypeComposer method
+ field3: '[Author]', // use SDL format
+ field4: new GraphQLList(AuthorTC.getType()) // use standard GraphQLList
+});
+
+
Non-Null
+
If you want indicate that field is not empty or argument is required:
+
import { GraphQLNonNull } from'graphql';
+
+SomeTypeComposer.addFields({
+ // field1: ???, // doesn't exists any regular object in js for expressing NonNull value
+ field2: AuthorTC.getTypeNonNull(), // call specific TypeComposer method
+ field3: 'Author!', // use SDL format
+ field4: new GraphQLNonNull(AuthorTC.getType()) // use standard GraphQLNonNull
+});
+
+
Non-Null List of Non-Null values may be expressed in following way:
+
import { GraphQLNonNull, GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field3: '[Author!]!', // use SDL format
+ field4: new GraphQLNonNull( // use standard GraphQLNonNull & GraphQLList
+ new GraphQLList(
+ new GraphQLNonNull(AuthorTC.getType())
+ )
+ )
+});
+
+
Union types
+
Graphql-compose does not provide any helper for Union types. You should use standard GraphQLUnionType.
+
import { GraphQLUnionType } from'graphql';
+
+// Get GraphQLObjectType from TypeComposer instance
+const DogType = DogTC.getType();
+const CatType = CatTC.getType();
+
+const PetType = new GraphQLUnionType({
+ name: 'Pet',
+ types: [ DogType, CatType ],
+ resolveType(value) {
+ if (value instanceof Dog) {
+ return DogType;
+ }
+ if (value instanceof Cat) {
+ return CatType;
+ }
+ }
+});
+
+// You may use GraphQLUnionType for field definition in TypeComposer
+AuthorTC.addFields({
+ favoritePet: PetType,
+});
+
import { schemaComposer, GraphQLJSON, InterfaceTypeComposer } from'graphql-compose';
+
+const TimestampInterface = InterfaceTypeComposer.create({
+ name: 'Timestampable',
+ description: 'An object with createdAt and updatedAt fields',
+ fields: {
+ createdAt: 'Date',
+ updatedAt: 'Date',
+ },
+});
+
+// When you create Interface, you need to provide instructions how to determine exact ObjectType from `value`.
+// So if `value` is instance of UserDoc, then use `UserTC` as exact type.
+TimestampInterface.addTypeResolver(UserTC, value => (value instanceof UserDoc));
+TimestampInterface.addTypeResolver(ArticleTC, value => (value instanceof UserDoc));
+
\ No newline at end of file
diff --git a/docs/5.12.0/basics/what-is-resolver.html b/docs/5.12.0/basics/what-is-resolver.html
new file mode 100644
index 00000000..7b4975e2
--- /dev/null
+++ b/docs/5.12.0/basics/what-is-resolver.html
@@ -0,0 +1,320 @@
+Resolvers · graphql-compose
Shortly, Resolver is an object which knows how to process data and what to return. It's like a function definition in static language where you give it name, describe types for input arguments and output result.
+
GraphQL.js describes such functions in complex output types via GraphQLFieldConfig:
GraphQLFieldConfig has information about returned type, available args, implementation of resolve logic and some other properties. In terms of graphql-compose this field config is called as Resolver.
+
The main aim of Resolver is to keep available resolve methods for Type and use them for building relation with other types. Resolver provide following abilities:
+
+
add, remove, get, make optional/required arguments
+
clone Resolver for further logic extension
+
wrap args, type, resolve (get resolver and create new one with extended/modified functionality)
+
provide helper methods addFilterArg and addSortArg which wrap resolver by adding argument and additional resolve logic
+
+
Resolver has following properties:
+
+
type output complex or scalar type (resolver returns data of this type)
+
args list of fields of input or scalar types (resolver accept input arguments for resolve method)
+
resolve method which contains your bussiness logic, for fetching, processing and returning data. BE AWARE: that all arguments (source, args, context, info) are passed inside one argument called as resolveParams (rp for brevity in the code).
+
description public description which will be passed to graphql schema and will be available via introspection
+
deprecationReason if you want to hide field from schema, but leave it working for old clients
+
name any name for resolver that allow to you identify what it does, eg findById, updateMany, removeOne
+
kind type of resolver query (resolver just fetch data) or mutation (resolver change data)
+
parent you may wrap existed Resolver for adding additional checks, modifying result, adding arguments. This property keeps reference to existed unwrapped Resolver
+
+
Why do we need the Resolver?
+
Graphql-compose allows creating such "functions" or "FieldConfigs" via giving it names and keep in your TypeComposer. You may create any number of Resolvers and store them in your type.
+
Assume you have an Author type. And you have different standard CRUD operations for fetching and modifying this type:
+
+
findById
+
findMany
+
updateById
+
removeById
+
etc
+
+
When you will construct your Schema, you may need several times the same logic from standard Resolvers. For example
+
+
in the Query type may be added fields
+
+
authorById for finding Author by id arg via findById resolver
+
authorMany for finding list of Author with some filter criteria via findMany resolver
+
+
in the Post type may be added
+
+
author field which request Author by id from current post.authorId value via findById resolver
+
reviewers field which request Authors via findMany resolver with custom filtering
+
+
+
Resolvers helps to describe CRUD operations logic only once and then reuse them in different scenarios. For Query.authorById provides its full functionality from findById resolver. For Post.author you wrap findById resolver where should be hidden id arg and its value automatically will be set from post.authorId. For wrapping Resolvers graphql-compose provides a bunch of methods.
+
Creating Resolver
+
via TC.addResolver()
+
Mostly Resolvers are created according to the specific Type. So it's better to create them and store in some TypeComposer instance.
+
Lets's take AuthorTC and describe how it can be found by id:
+
AuthorTC.addResolver({
+ name: 'findById',
+ args: { id: 'Int' },
+ type: AuthorTC,
+ resolve: async ({ source, args }) => {
+ const res = await fetch(`/endpoint/${args.id}`); // or some fetch from any database
+ const data = await res.json();
+ // here you may clean up `data` response from API or Database,
+ // it should has same shape like AuthorTC fields
+ // eg. { firstName: 'Peter', nickname: 'peet', views: 20 }
+ // if some fields in `data`:
+ // are undefined or missing - graphql returns `null` for that fields
+ // are not described in output `type` - graphql will remove them from responce
+ return data;
+ },
+});
+
+
And in any place of your schema you will able to use this Resolver in such way:
You may create instance of Resolver without attaching it to some TypeComposer. It can be done in following way:
+
import { Resolver } from'graphql-compose';
+
+const findCityLocationByIdResolver = new Resolver({
+ name: 'findCityLocationById',
+ type: `type CityLocation { lon: Float, lat: Float }`,
+ args: {
+ id: 'Int!',
+ },
+ // BE AWARE! `resolve` method in `Resolver` accept only one argument `resolveParams`
+ // which contains
+ // standard properties from `GraphQLFieldResolveFn`: source, args, context, info
+ // and additional properties: projection
+ resolve: async ({ source, args, context, info }) => {
+ const city = await DB.city.findById(args.id);
+ if (!city) returnnull;
+ return {
+ lon: city.longitude,
+ lat: city.latitude,
+ };
+ }
+});
+
+// And add this resolver to your Schema
+schemaComposer.Query.addFields({
+ cityLocation: findCityLocationByIdResolver,
+});
+
+
Wrapping Resolver
+
In many cases, it is very convenient to create a Resolver which just fetch data providing rich filter and sort arguments (also it may modify data).
+But what if we need to restrict access or set up some arguments of Resolver from source (parent) object or context?
+
Yep, you need to wrap the Resolver! Wrap just resolve method via Resolver.wrapResolve(). Or Resolver.wrap() if we want to change simultaneously output type, args and resolve method.
+
via Resolver.wrapResolve()
+
The most commonly used method for wrapping is Resolver.wrapResolve(). Let take a look how can be it used in your Schema:
+
schemaComposer.Query.addFields({
+ // add endpoint which returns only visible posts
+ publicPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `visibility` argument
+ // so forcibly set this arg to true
+ rp.args.visibility = true;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns posts only for current authenticated user
+ ownerPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `authorId` argument
+ // so forcibly set this arg to current user id
+ rp.args.authorId = rp.context.currentUserId;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns all authors only for admin
+ allAuthorsForAdmin: AuthorTC.getResolver('findMany').wrapResolve(next => rp => {
+ // check `isAdmin` property in context, which was somehow setted
+ // on express-graphql or apollo-server level
+ // for regular user return null
+ if (!rp.context.isAdmin) returnnull;
+ // for admin delegate execution to the basic resolver
+ return next(rp);
+ });
+});
+
+
via Resolver.wrap()
+
This is a less-used method. But it's more powerfull. It allows to change simultaneously output type, args and resolve method.
+
What if admin should have all avaliable filter params and add new one for searching but regular user just limited set of arguments?
+
Resolver wrapping creates a new Resolver. So for admin you create a new resolver findManyForAdmin by wrapping a basic resolver, eg. findMany add additional args and logic. For user you create findManyReduced by wrapping existed findMany resolver and removing some filter args.
+
Let write reduced resolver findManyReduced, where we remove some args
+
const findManyReduced = AuthorTC.getResolver('findMany').wrap(newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeFields(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
via TC.wrapResolverAs()
+
Also you may want to modify already existed Resolver in some TypeComposer, like it did Resolver.wrap() method.
+
For simplifying this process you may use TypeComposer.wrapResolverAs() method.
+Let take AuthorTCs findMany resolver and create a new one with name findManyReduced.
+
AuthorTC.wrapResolverAs('findManyReduced', 'findMany', newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeField(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
Advanced
+
How Resolver.wrapResolve() work internally
+
+
capturing phase, when you may change resolveParams (rp in the code) before it will pass to next resolve
+
bubbling phase, when you may change response from underlying resolve
+
+
Resolver.wrapResolve(next => rp => {
+ // [CAPTURING PHASE]:
+ // `rp` consist from { source, args, context, info, projection }
+ // you may change `source`, `args`, `context`, `info`, `projection` before it will pass to `next` underlying resolve function.
+
+ // ...some code which modify `rp` (resolveParams)
+
+ // ... or just stop propagation
+ // throw new Error();
+ // or
+ // return Promise.resolve(null);
+
+ // pass request to underlying middleware and get result promise from it
+ const resultPromise = next(rp);
+
+ // [BUBBLING PHASE]: here you may change payload of underlying resolve method, via promise syntax
+ // ...some code, which may add `then()` or `catch()` to result promise
+ // resultPromise.then(payload => { console.log(payload); return payload; })
+
+ return resultPromise; // return payload promise to upper wrapper
+});
+
\ No newline at end of file
diff --git a/docs/5.12.0/guide/elasticsearch-with-mongoose.html b/docs/5.12.0/guide/elasticsearch-with-mongoose.html
new file mode 100644
index 00000000..478cf8d9
--- /dev/null
+++ b/docs/5.12.0/guide/elasticsearch-with-mongoose.html
@@ -0,0 +1,435 @@
+[WIP] Use ElasticSearch with Mongoose · graphql-compose
Connect MongoDB with ElasticSearch and GraphQL quite complex and long task and consist of a bunch of steps. Every step can be tuned for your needs.
+
1. Extending Mongoose ORM with elasticsearch data
+
For working with MongoDB collections and documents is good practice to use some ORM. For nodejs better solution is mongoose. Also exists cool mongoose-elasticsearch-xp (by @jbdemonte) package (plugin for mongoose) which provides useful methods and hooks which ridiculously simplify data syncing with MongoDB and ElasticSearch.
+
1.1. Defining Mongoose schema with settings for elasticsearch-xp [SCHEMA DEFINITION]
1.2 Plug mongoose-elasticsearch-xp to your Mongoose Schema with data filtering [SYNC MONGO & ES DATA]
+
/* elastic */
+JobSchema.plugin(mongooseElasticsearch, {
+ client: elasticClient, // <------ see `graphql-elasticsearch-xp` for details
+ filter: doc => {
+ if (doc.visibility !== 'published') {
+ // add to index new record with visibility='published'
+ // or remove existed record from index if `visibility` changed and not 'published' anymore
+ returnfalse;
+ }
+ returntrue;
+ },
+});
+
+
By default mongoose-elasticsearch-xp will track add/remove operations and update your data in elasticsearch. In this case I provide filter option, now it will track more clever model's inserts/updates and send proper changes to your elasticsearch server.
+
Already existed data can be synced via esSynchronize method.
+
1.3 Connection with elasticsearch server elasticClient [ES CLIENT]
+
You should provide elasticClient in step 1.2 (for mongoose plugin [UPDATING DATA]) and 1.4 (for graphql resolvers [SEARCH]). It holds connection of your nodejs server with elasticsearch server.
import { composeWithElastic } from'graphql-compose-elasticsearch';
+import { generate } from'mongoose-elasticsearch-xp/lib/mapping';
+
+exportconst JobEsTC = composeWithElastic({
+ graphqlTypeName: 'JobES',
+ elasticIndex: 'job',
+ elasticType: 'job',
+ elasticMapping: {
+ properties: generate(JobSchema),
+ },
+ elasticClient,
+ // elastic mapping does not contain information about is fields are arrays or not
+ // so provide this information explicitly for obtaining correct types in GraphQL
+ pluralFields: ['employment'],
+});
+
fragment on Query {
+ jobEsConnection(first: $first, query: $query, sort: $sort, aggs: $aggs) {
+ count
+ aggregations
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ cursor
+ node {
+ _score# meta-data from ES
+ _id# meta-data from ES
+
+ _source {
+ employment # record data from ES
+ position# record data from ES
+ }
+
+ fromMongo { # data from Mongo
+ _id
+ onlyMongooseData
+ visibility
+ salary { fromto currency}
+ position
+ }
+ }
+ }
+ }
+}
+
+
See https://github.com/nodkz/graphql-compose
+Sorry bad docs in graphql-compose. Really do not have time to write it. So try to see issues they contain a lot of info.
+
1.7 Add needed resolvers to schema [BUILD GRAPHQL SCHEMA]
import { GQC } from 'graphql-compose';
+import { elasticApiFieldConfig } from 'graphql-compose-elasticsearch';
+import elasticClient from 'schema/elasticClient';
+
+export const ElasticTC = GQC.get('ELASTIC');
+
+ElasticTC.addResolver({
+ name: 'onlyForAdmins',
+ type: ElasticTC,
+ resolve: ({ context }) => {
+ if (!isAdmin({ context })) { // <--- somehow check that you are admin
+ throw new Error('You should be admin, to have access to this area.');
+ }
+ return {};
+ },
+});
+
+# expose all elastic api via graphql
+ElasticTC.addFields({
+ api: elasticApiFieldConfig(elasticClient),
+});
+
+// DONT FORGET TO add elastic to your schema (eg. to ROOT query)
+GQC.rootQuery().addFields({
+ elastic: ElasticTC.getResolver('onlyForAdmins'),
+});
+
\ No newline at end of file
diff --git a/docs/5.12.0/guide/file-uploads.html b/docs/5.12.0/guide/file-uploads.html
new file mode 100644
index 00000000..cc49c668
--- /dev/null
+++ b/docs/5.12.0/guide/file-uploads.html
@@ -0,0 +1,256 @@
+File uploads · graphql-compose
If you decide how to upload files via some REST endpoint or GraphQL. So I recommend to upload via some REST API and then provide a path of the uploaded file to your mutation request. GraphQL designed to provide typed data according to client request shape. With files (binary data) it works too, but better to do it via well-recommended REST calls. In such case, you separate highly costed upload logic from data manipulation logic. In the future, this will help you diagnose problems with the load more easily.
+
Anyway products have different scenarios and you may be forced to upload files via GraphQL. For uploading files via GraphQL you will need:
apollo-upload-server - for parsing multipart/form-data POST requests via busboy and providing Files data to resolve function as argument.
+
+
Tutorial
+
1. Preparing express-graphql server
+
This is most important part of enabling file uploads on server-side. You need to parse body data via bodyParser.json() and multipart form data via apolloUploadExpress(/* Options */).
This is a most problematic part and it's out of scope of graphql-compose (it's client-side problem). You must correctly send HTTP request from the client. But if you very carefully read graphql-multipart-request-spec, then you should not have any questions.
+
Here's an example of proper multipart/form-data POST request with
+
+
operations key for GraphQL request with query and variables
+
map key with mapping some multipart-data to exact GraphQL variable
+
and other keys for multipart-data which contains binary data of files
\ No newline at end of file
diff --git a/docs/5.12.0/guide/mongoose.html b/docs/5.12.0/guide/mongoose.html
new file mode 100644
index 00000000..6673519f
--- /dev/null
+++ b/docs/5.12.0/guide/mongoose.html
@@ -0,0 +1,183 @@
+[WIP] Generate types from Mongoose Models · graphql-compose
Well TypeComposers generated by graphql-compose-mongoose ships with resolvers for create, update and remove.
+Looking like this:
+
UserTC.getResolver('createOne').getFieldConfig();
+UserTC.getResolver('updateById').getFieldConfig();
+// or for shorthand
+UserTC.get('$removeMany').getFieldConfig();
+// and buch of other resolvers
+
+
Lets add a working example from the preview UserTC we have created
\ No newline at end of file
diff --git a/docs/5.12.0/guide/relay.html b/docs/5.12.0/guide/relay.html
new file mode 100644
index 00000000..d16e07a7
--- /dev/null
+++ b/docs/5.12.0/guide/relay.html
@@ -0,0 +1,123 @@
+[WIP] Relay Schema · graphql-compose
Adding support for Relay is done via plugin graphql-compose-relay For more detailed descriptions on how to use and reporting issues please use the link.
\ No newline at end of file
diff --git a/docs/5.12.0/guide/wrapping-rest-api.html b/docs/5.12.0/guide/wrapping-rest-api.html
new file mode 100644
index 00000000..08b341cb
--- /dev/null
+++ b/docs/5.12.0/guide/wrapping-rest-api.html
@@ -0,0 +1,220 @@
+Wrapping REST API · graphql-compose
Many developers are attracted by GraphQL’s benefits over REST. The reason for that is its query language enabling to stick to the data that the client needs at the moment and not to restructure the client to fit API structure. Single endpoint, but flexible data shape.
+
Let’s imagine you already have an existing RESTful API, but your task requires using GraphQL either you just want to try it out of curiosity. If that's the case, you would need to wrap your REST in GraphQL Schema and hardcoding all the GraphQL Types is a real pain.
+
That's why we came up with a RESTful API wrapper for GraphQL featuring automatic GraphQL Type generation.
+
Installation
+
npm install graphql-compose-json
+
+
Demo
+
We've wrapped SWAPI RESTful API in to show capabilities of graphq-compose-json
Using graphql-compose is easy — it's just one, but helpful function:
+
import composeWithJson from'graphql-compose-json';
+
+const restApiResponse = {
+ name: 'Anakin Skywalker',
+ birth_year: '41.9BBY',
+ starships: [
+ 'https://swapi.co/api/starships/59/',
+ 'https://swapi.co/api/starships/65/',
+ 'https://swapi.co/api/starships/39/',
+ ],
+ mass: () =>'Int!', // by default JSON numbers are coerced to Float, here we've set it to Integer
+ starships_count: () => ({ // granular inline field config with resolve function
+ type: 'Int',
+ resolve: source => source.starships.length,
+ }),
+};
+
+exportconst CustomPersonTC = composeWithJson('CustomPerson', restApiResponse);
+
+
That's it! The Type is ready to be used and have its resolvers defined. CustomPersonTC contains all things you need to compose Resolvers and Schema.
+
Specifying data fetching method
+
What we're trying to do is to wrap an existing RESTful API in GraphQL Schema, but it is not yet aware of where the data is stored, it knows only the possible data shape; thus we need to specify how to fetch the API data.
+
Valid GraphQL data request requires three pieces: resolve(data fetching method), args(list of acceptable input arguments) and type(data representation form, which we already have thanks to graphql-compose-json). GraphQL terms label these three a Field Config (or Resolver).
It's unlikely that the Schema will have only one Type, hence we've got to link our scattered types. Imagine we want Person Type to return the list of movies they starred in. Assuming that Person has links to them, all we need is to add a resolver to FilmTC.
Defining Resolvers within TypeComposers they belong to helps to keep your code DRY, as further on you'll be able to reuse them with just one line of code:
+
Planet.getResolver('findMany');
+
+
Composing the Schema
+
Now with Types and Resolvers created it's time to put them into Schema.
\ No newline at end of file
diff --git a/docs/5.12.0/intro/installation.html b/docs/5.12.0/intro/installation.html
new file mode 100644
index 00000000..db494fd3
--- /dev/null
+++ b/docs/5.12.0/intro/installation.html
@@ -0,0 +1,114 @@
+Installation · graphql-compose
Module graphql is declared in peerDependencies, so it should be installed explicitly in your project. This helps to solve a common problem when some of your other dependencies (like Relay, GraphiQL, graphql-compose) can leave your node_modules directory with duplicate installs of GraphQL.js. In such case graphql-js may throw errors stating that some classes are not instances of duplicate module.
+
Also you may need to install some graphql-compose plugins. Each plugin has own Install section with instructions.
\ No newline at end of file
diff --git a/docs/5.12.0/intro/live-demos.html b/docs/5.12.0/intro/live-demos.html
new file mode 100644
index 00000000..79367cf3
--- /dev/null
+++ b/docs/5.12.0/intro/live-demos.html
@@ -0,0 +1,120 @@
+Live Demos · graphql-compose
graphql-compose-boilerplate - ready to run a skeleton app for GraphQL server. It contains the example from Quick Start. This boilerplate includes Babel (ES6, babel-preset-env), ESLint, Flowtype, express, express-graphql, graphql, graphql-compose, nodemon.
+
+
Other demos
+
+
nodkz.github.io/relay-northwind - live demo of Relay Client App working with GraphQL Northwind Schema (8 crazy pages, 47 files, ~3000 LOC)
\ No newline at end of file
diff --git a/docs/5.12.0/intro/prerequisites.html b/docs/5.12.0/intro/prerequisites.html
new file mode 100644
index 00000000..79e96d85
--- /dev/null
+++ b/docs/5.12.0/intro/prerequisites.html
@@ -0,0 +1,116 @@
+Prerequisites · graphql-compose
To use this package it would be a good idea to know the basics of GraphQL, and how the Type System works. Since you are going to generate and edit its types you should start out there first.
+
Node.js
+
This package generates GraphQL Schema on the server side. And it will be great if you have experience with Node.js and ES6 syntax.
+
For serving requests to your generated Schema you should use one of the following packages express-graphql or apollo-server.
+
Flowtype/TypeScript
+
This is optional but quite recommended feature which covers your javascript code with static type-checking. It will help you with autosuggestion and method call validation in your IDE. This package contains built-in type definitions for Flowtype and TypeScript.
+
Internally source code of this package is written with Flowtype and has deep static type-checking with graphq-js which is also written with Flow.
\ No newline at end of file
diff --git a/docs/5.12.0/intro/quick-start.html b/docs/5.12.0/intro/quick-start.html
new file mode 100644
index 00000000..daa84454
--- /dev/null
+++ b/docs/5.12.0/intro/quick-start.html
@@ -0,0 +1,273 @@
+Quick Start Guide · graphql-compose
For simplicity, this example works with arrays, but in future, it will not be a problem to change data-source to any your favorite DB or a mix of them.
+
Creating Types
+
Building a GraphQL Schema starts with complex Types declaration. In order to create a Type, you have to give it a unique name and specify it’s fields list. So let's create Types which will describe our data. For this purpose need to take TypeComposer helper from graphql-compose package.
Now as we can declare Types, request them, it’s time to link these Types with each other. This is the exact stage where GraphQL enormously simplifies work for clients that request data. A typical scenario of a query to RESTful API: client requests a piece of data, receives it and request other resources according to the first server response it got, while GraphQL implements the same logic on the server’s side and sends back nested data of any depth.
+
To make such nesting possible you’ve got to link Author and Post Types with each other. For that you need to create author field in your Post Type, it will resolve author's data for every post. And for Author Type create posts field which will resolve for each author its posts.
+
PostTC.addFields({
+ author: {
+ // you may provide type name as string 'Author',
+ // but for better developer experience use Type instance `AuthorTC`
+ // it allows to jump to type declaration via Ctrl+Click in your IDE
+ type: AuthorTC,
+ // resolve method as first argument will receive data for some Post
+ // from this data you should somehow fetch Author's data
+ // let's take lodash `find` method, for searching by `authorId`
+ // PS. `resolve` method may be async for fetching data from DB
+ // resolve: async (source, args, context, info) => { return DB.find(); }
+ resolve: post => find(authors, { id: post.authorId }),
+ },
+});
+
+AuthorTC.addFields({
+ posts: {
+ // Array of posts may be described as string in SDL in such way '[Post]'
+ // But graphql-compose allow to use Type instance wrapped in array
+ type: [PostTC],
+ // for obtaining list of post we get current author.id
+ // and scan and filter all Posts with desired authorId
+ resolve: author => filter(posts, { authorId: author.id }),
+ },
+ postCount: {
+ type: 'Int',
+ description: 'Number of Posts written by Author',
+ resolve: author => filter(posts, { authorId: author.id }).length,
+ },
+});
+
+
Building Schema
+
Now that you’ve got your Types created, linked and taught how to fetch data, it’s time to create your Schema. For this purpose, you will need to use schemaComposer. It has three Root Types (entry points): Query, Mutation and Subscription and at least one of them must have defined fields.
+
import { schemaComposer } from'graphql-compose';
+
+// Requests which read data put into Query
+schemaComposer.Query.addFields({
+ posts: {
+ type: '[Post]',
+ resolve: () => posts,
+ },
+ author: {
+ type: 'Author',
+ args: { id: 'Int!' },
+ resolve: (_, { id }) => find(authors, { id }),
+ },
+});
+
+// Requests which modify data put into Mutation
+schemaComposer.Mutation.addFields({
+ upvotePost: {
+ type: 'Post',
+ args: {
+ postId: 'Int!',
+ },
+ resolve: (_, { postId }) => {
+ const post = find(posts, { id: postId });
+ if (!post) {
+ thrownewError(`Couldn't find post with id ${postId}`);
+ }
+ post.votes += 1;
+ return post;
+ },
+ },
+});
+
+// After Root type definition, you are ready to build Schema
+// which should be passed to `express-graphql` or `apollo-server`
+exportconst schema = schemaComposer.buildSchema();
+
+
Creating HTTP server
+
When your Schema is constructed, it needs to implement a server. It will serve client requests, execute them and send responses back. Let's construct a simple express app which will accept POST requests at http://localhost:4000/graphql endpoint for serving graphql queries. And GET requests with same address for providing GraphiQL an in-browser IDE for exploring GraphQL.
Graphql-compose has following built-in scalar types: String, Float, Int, Boolean, ID, Date, JSON. If you need to create some complex type, you will need to use TypeComposer.
+
Let demonstrate another way of type creation in SDL format via TypeComposer.create() method:
+
const AddressTC = TypeComposer.create(`
+ type Address {
+ city: String
+ country: String
+ street: String
+ }
+`);
+
+// and now we can extend existed Author Type with a new field with complex type
+AuthorTC.addFields({
+ address: {
+ type: AddressTC, // or 'Address'
+ description: "Author's address",
+ },
+})
+
+
More useful information about type creation can be found here.
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/list-of-plugins.html b/docs/5.12.0/plugins/list-of-plugins.html
new file mode 100644
index 00000000..0647fe78
--- /dev/null
+++ b/docs/5.12.0/plugins/list-of-plugins.html
@@ -0,0 +1,128 @@
+Plugins list · graphql-compose
graphql-compose – the imperative tool which worked on top of graphql-js. It provides useful methods for creating GraphQL Types and GraphQL Models (type with a list of
+resolvers) for further building of complex relations in your Schema. With graphql-compose you may fastly write own functions/generators for common tasks.
+
graphql-compose-[plugin] – is a declarative generator/plugin that build on top of graphql-compose, which take some ORMs, schema definitions and creates GraphQL Models from them or modify existed GraphQL Types.
+
Type generator plugins
+
+
graphql-compose-json - generates GraphQL type from JSON (a good helper for wrapping REST APIs)
+
graphql-compose-mongoose - generates GraphQL types from mongoose (MongoDB models) with Resolvers.
+
graphql-compose-elasticsearch - generates GraphQL types from elastic mappings; ElasticSearch REST API proxy via GraphQL.
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-aws.html b/docs/5.12.0/plugins/plugin-aws.html
new file mode 100644
index 00000000..c7e2ae50
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-aws.html
@@ -0,0 +1,153 @@
+graphql-compose-aws · graphql-compose
Generated Schema Introspection in SDL format can be found here (more than 10k types, ~2MB).
+
AWS SDK GraphQL
+
Supported all AWS SDK versions via official aws-sdk js client. Internally it generates Types and FieldConfigs from AWS SDK configs. You may put this generated types to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import awsSDK from'aws-sdk';
+import { AwsApiParser } from'graphql-compose-aws';
+
+const awsApiParser = new AwsApiParser({
+ awsSDK,
+});
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ // Full API
+ aws: awsApiParser.getFieldConfig(),
+
+ // Partial API with desired services
+ s3: awsApiParser.getService('s3').getFieldConfig(),
+ ec2: awsApiParser.getService('ec2').getFieldConfig(),
+ },
+ }),
+});
+
+exportdefault schema;
+
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-connection.html b/docs/5.12.0/plugins/plugin-connection.html
new file mode 100644
index 00000000..31729aa7
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-connection.html
@@ -0,0 +1,207 @@
+graphql-compose-connection · graphql-compose
Besides standard connection arguments first, last, before and after, also added significant arguments:
+
+
filter arg - for filtering records
+
sort arg - for sorting records. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
+
Example
+
import composeWithConnection from'graphql-compose-connection';
+import userTypeComposer from'./user.js';
+
+composeWithConnection(userTypeComposer, {
+ findResolverName: 'findMany',
+ countResolverName: 'count',
+ sort: {
+ // Sorting key, visible for users in GraphQL Schema
+ _ID_ASC: {
+ // Sorting value for ORM/Driver
+ value: { _id: 1 },
+
+ // Field names in record, which data will be packed in `cursor`
+ // edges {
+ // cursor <- base64(cursorData), for this example `cursorData` = { _id: 334ae453 }
+ // node <- record from DB
+ // }
+ // By this fields MUST be created UNIQUE index in database!
+ cursorFields: ['_id'],
+
+ // If for connection query provided `before` argument with above `cursor`.
+ // We should construct (`rawQuery`) which will be point to dataset before cursor.
+ // Unpacked data from `cursor` will be available in (`cursorData`) argument.
+ // PS. All other filter options provided via GraphQL query will be added automatically.
+ // ----- [record] ----- sorted dataset, according to above option with `value` name
+ // ^^^^^ `rawQuery` should filter this set
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+
+ // Constructing `rawQuery` for connection `after` argument.
+ // ----- [record] ----- sorted dataset
+ // ^^^^^ `rawQuery` should filter this set
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ },
+
+ _ID_DESC: {
+ value: { _id: -1 },
+ cursorFields: ['_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+ },
+
+ // More complex sorting parameter with 2 fields
+ AGE_ID_ASC: {
+ value: { age: 1, _id: -1 },
+ // By these fields MUST be created COMPOUND UNIQUE index in database!
+ cursorFields: ['age', '_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$lt = cursorData.age;
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$gt = cursorData.age;
+ rawQuery._id.$lt = cursorData._id;
+ },
+ }
+ },
+});
+
+
+
Requirements
+
Types should have following resolvers:
+
+
count - for counting records
+
findMany - for filtering records. Also required that this resolver supports search with operators (lt, gt), which used in directionFilter option. Resolver findMany should have filter argument, which will be copied to connection. Also should have limit and skip args.
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-elasticsearch.html b/docs/5.12.0/plugins/plugin-elasticsearch.html
new file mode 100644
index 00000000..c352ed40
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-elasticsearch.html
@@ -0,0 +1,234 @@
+graphql-compose-elasticsearch · graphql-compose
This module expose Elastic Search REST API via GraphQL.
+
Elastic Search REST API proxy
+
Supported all elastic versions that support official elasticsearch-js client. Internally it parses its source code annotations and generates all available methods with params and descriptions to GraphQL Field Config Map. You may put this config map to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import elasticsearch from'elasticsearch';
+import { elasticApiFieldConfig } from'graphql-compose-elasticsearch';
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ elastic50: elasticApiFieldConfig(
+ // you may provide existed Elastic Client instance
+ new elasticsearch.Client({
+ host: 'http://localhost:9200',
+ apiVersion: '5.0',
+ })
+ ),
+
+ // or may provide just config
+ elastic24: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '2.4',
+ }),
+
+ elastic17: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '1.7',
+ }),
+ },
+ }),
+});
+
In other side this module is a plugin for graphql-compose, which derives GraphQLType from your elastic mapping generates tons of types, provides all available methods in QueryDSL, Aggregations, Sorting with field autocompletion according to types in your mapping (like Dev Tools Console in Kibana).
+
Generated TypeComposer model has several awesome resolvers:
+
+
search - greatly simplified elastic search method. According to GraphQL adaptation and its projection bunch of params setup automatically due your graphql query (eg _source, explain, version, trackScores), other rare fine tuning params moved to opts input field.
+
searchConnection - elastic search method that implements Relay Cursor Connection spec for infinite lists. Internally it uses cheap search_after API. One downside, Elastic does not support backward scrolling, so before argument will not work.
+
more resolvers will be later after my vacation: suggest, getById, updateById and others
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-json.html b/docs/5.12.0/plugins/plugin-json.html
new file mode 100644
index 00000000..b3f248ea
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-json.html
@@ -0,0 +1,311 @@
+graphql-compose-json · graphql-compose
This is a plugin for graphql-compose, which generates GraphQLTypes from REST response or any JSON. It takes fields from object, determines their types and construct GraphQLObjectType with same shape.
+
Demo
+
We have a Live demo (source code repo) which shows how to build an API upon SWAPI using graphql-compose-json.
Modules graphql, graphql-compose, are located in peerDependencies, so they should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
You have a sample response object restApiResponse which you can pass to graphql-compose-json along with desired type name as your first argument and it will automatically generate a composed GraphQL type PersonTC.
graphql-compose provides a vast variety of methods for fields and resolvers (aka field configs in vanilla GraphQL) management of GraphQL types. To learn more visit graphql-compose repo.
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-mongoose.html b/docs/5.12.0/plugins/plugin-mongoose.html
new file mode 100644
index 00000000..2ed3b509
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-mongoose.html
@@ -0,0 +1,639 @@
+graphql-compose-mongoose · graphql-compose
This is a plugin for graphql-compose, which derives GraphQLType from your mongoose model. Also derives bunch of internal GraphQL Types. Provide all CRUD resolvers, including graphql connection, also provided basic search via operators ($lt, $gt and so on).
Modules graphql, graphql-compose, mongoose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
If you want to add additional resolvers connection and/or pagination - just install following packages and graphql-compose-mongoose will add them automatically.
UserTC - this is a TypeComposer instance for User. TypeComposer has GraphQLObjectType inside, avaliable via method UserTC.getType().
+
Here and in all other places of code variables suffix ...TC means that this is TypeComposer instance, ...ITC - InputTypeComposer, ...ETC - EnumTypeComposer.
+
+
import mongoose from'mongoose';
+import { composeWithMongoose } from'graphql-compose-mongoose';
+import { schemaComposer } from'graphql-compose';
+
+// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
+const LanguagesSchema = new mongoose.Schema({
+ language: String,
+ skill: {
+ type: String,
+ enum: [ 'basic', 'fluent', 'native' ],
+ },
+});
+
+const UserSchema = new mongoose.Schema({
+ name: String, // standard types
+ age: {
+ type: Number,
+ index: true,
+ },
+ languages: {
+ type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
+ default: [],
+ },
+ contacts: { // another mongoose way for providing embedded documents
+ email: String,
+ phones: [String], // array of strings
+ },
+ gender: { // enum field with values
+ type: String,
+ enum: ['male', 'female', 'ladyboy'],
+ },
+ someMixed: {
+ type: mongoose.Schema.Types.Mixed,
+ description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
+ },
+});
+const User = mongoose.model('User', UserSchema);
+
+
+
+// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
+const customizationOptions = {}; // left it empty for simplicity, described below
+const UserTC = composeWithMongoose(User, customizationOptions);
+
+// STEP 3: Add needed CRUD User operations to the GraphQL Schema
+// via graphql-compose it will be much much easier, with less typing
+schemaComposer.Query.addFields({
+ userById: UserTC.getResolver('findById'),
+ userByIds: UserTC.getResolver('findByIds'),
+ userOne: UserTC.getResolver('findOne'),
+ userMany: UserTC.getResolver('findMany'),
+ userCount: UserTC.getResolver('count'),
+ userConnection: UserTC.getResolver('connection'),
+ userPagination: UserTC.getResolver('pagination'),
+});
+
+schemaComposer.Mutation.addFields({
+ userCreateOne: UserTC.getResolver('createOne'),
+ userCreateMany: UserTC.getResolver('createMany'),
+ userUpdateById: UserTC.getResolver('updateById'),
+ userUpdateOne: UserTC.getResolver('updateOne'),
+ userUpdateMany: UserTC.getResolver('updateMany'),
+ userRemoveById: UserTC.getResolver('removeById'),
+ userRemoveOne: UserTC.getResolver('removeOne'),
+ userRemoveMany: UserTC.getResolver('removeMany'),
+});
+
+const graphqlSchema = schemaComposer.buildSchema();
+exportdefault graphqlSchema;
+
+
That's all!
+You think that is to much code?
+I don't think so, because by default internally was created about 55 graphql types (for input, sorting, filtering). So you will need much much more lines of code to implement all these CRUD operations by hands.
+
Working with Mongoose Collection Level Discriminators
+
Variable Namings
+
+
...DTC - Suffix for a DiscriminatorTypeComposer instance, which is also an instance of TypeComposer. All fields and Relations manipulations on this instance affects all registered discriminators and the Discriminator Interface.
const UserTC = composeWithMongoose(User);
+UserTC.getType(); // returns GraphQLObjectType
+UserTC.getInputType(); // returns GraphQLInputObjectType, eg. for args
+UserTC.get('languages').getType(); // get GraphQLObjectType for nested field
+UserTC.get('fieldWithNesting.subNesting').getType(); // get GraphQL type of deep nested field
+
Suppose you User model has friendsIds field with array of user ids. So let build some relations:
+
UserTC.addRelation(
+ 'friends',
+ {
+ resolver: () => UserTC.getResolver('findByIds'),
+ prepareArgs: { // resolver `findByIds` has `_ids` arg, let provide value to it
+ _ids: (source) => source.friendsIds,
+ },
+ projection: { friendsIds: 1 }, // point fields in source object, which should be fetched from DB
+ }
+);
+UserTC.addRelation(
+ 'adultFriendsWithSameGender',
+ {
+ resolver: () => UserTC.get('$findMany'), // shorthand for `UserTC.getResolver('findMany')`
+ prepareArgs: { // resolver `findMany` has `filter` arg, we may provide mongoose query to it
+ filter: (source) => ({
+ _operators : { // Applying criteria on fields which have
+ // operators enabled for them (by default, indexed fields only)
+ _id : { in: source.friendsIds },
+ age: { gt: 21 }
+ },
+ gender: source.gender,
+ }),
+ limit: 10,
+ },
+ projection: { friendsIds: 1, gender: 1 }, // required fields from source object
+ }
+);
+
+
Reusing the same mongoose Schema in embedded object fields
+
Suppose you have a common structure you use as embedded object in multiple Schemas.
+Also suppose you want the structure to have the same GraphQL type across all parent types.
+(For instance, to allow reuse of fragments for this type)
+Here are Schemas to demonstrate:
If you want the ImageDataStructure to use the same GraphQL type in both Article and UserProfile you will need create it as a mongoose schema (not a standard javascript object) and to explicitly tell graphql-compose-mongoose the name you want it to have. Otherwise, without the name, it would generate the name according to the first parent this type was embedded in.
+
Do the following:
+
import { schemaComposer } from'graphql-compose'; // get the default schemaComposer or your created schemaComposer
+import { convertSchemaToGraphQL } from'graphql-compose-mongoose';
+
+convertSchemaToGraphQL(ImageDataStructure, 'EmbeddedImage', schemaComposer); // Force this type on this mongoose schema
+
+
Before continuing to convert your models to TypeComposers:
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can create your own resolver of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts before save hook which can be provided in resolver params as param named beforeRecordMutate. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
This is opts.resolvers level of options.
+If you set the option to false it will disable resolver or some of its input args.
+Every resolver's arg has it own options. They described below.
This is opts.resolvers.[resolverName].[filter|sort|record|limit] level of options.
+You may tune every resolver's args independently as you wish.
+Here you may setup every argument and override some fields from the default input object type, described above in opts.inputType.
+
export type filterHelperArgsOpts = {
+ filterTypeName?: string, // type name for `filter`
+ isRequired?: boolean, // set `filter` arg as required (wraps in GraphQLNonNull)
+ onlyIndexed?: boolean, // leave only that fields, which is indexed in mongodb
+ requiredFields?: string | string[], // provide fieldNames, that should be required
+ operators?: filterOperatorsOpts | false, // provide filtering fields by operators, eg. $lt, $gt
+ // if left empty - provides all operators on indexed fields
+};
+
+// supported operators names in filter `arg`
+export type filterOperatorNames = 'gt' | 'gte' | 'lt' | 'lte' | 'ne' | 'in[]' | 'nin[]';
+export type filterOperatorsOpts = { [fieldName: string]: filterOperatorNames[] | false };
+
+export type sortHelperArgsOpts = {
+ sortTypeName?: string, // type name for `sort`
+};
+
+export type recordHelperArgsOpts = {
+ recordTypeName?: string, // type name for `record`
+ isRequired?: boolean, // set `record` arg as required (wraps in GraphQLNonNull)
+ removeFields?: string[], // provide fieldNames, that should be removed
+ requiredFields?: string[], // provide fieldNames, that should be required
+};
+
+export type limitHelperArgsOpts = {
+ defaultValue?: number, // set your default limit, if it not provided in query (default: 1000)
+};
+
This plugin adds connection resolver. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
+
Besides standard connection arguments first, last, before and after, also added great arguments:
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-pagination.html b/docs/5.12.0/plugins/plugin-pagination.html
new file mode 100644
index 00000000..db97c5fd
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-pagination.html
@@ -0,0 +1,135 @@
+graphql-compose-pagination · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-relay.html b/docs/5.12.0/plugins/plugin-relay.html
new file mode 100644
index 00000000..fa5bb836
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-relay.html
@@ -0,0 +1,148 @@
+graphql-compose-relay · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
TypeComposer is a graphql-compose utility, that wraps GraphQL types and provide bunch of useful methods for type manipulation.
+
import composeWithRelay from'graphql-compose-relay';
+import { TypeComposer } from'graphql-compose';
+import { RootQueryType, UserType } from'./my-graphq-object-types';
+
+const rootQueryTypeComposer = new TypeComposer(RootQueryType);
+const userTypeComposer = new TypeComposer(UserType);
+
+// If passed RootQuery, then will be added only `node` field to this type.
+// Via RootQuery.node you may find objects by globally unique ID among all types.
+composeWithRelay(rootQueryTypeComposer);
+
+// Other types, like User, will be wrapped with middlewares that:
+// - add relay's id field. Field will be added or wrapped to return Relay's globally unique ID.
+// - for mutations will be added clientMutationId to input and output objects types
+// - this type will be added to NodeInterface for resolving via RootQuery.node
+composeWithRelay(userTypeComposer);
+
+
That's all!
+
All mutations resolvers' arguments will be placed into input field, and added clientMutationId. If input fields already exists in resolver, then clientMutationId will be added to it, rest argument stays untouched. Accepted value via args.input.clientMutationId will be transfer to payload.clientMutationId, as Relay required it.
+
To all wrapped Types with Relay, will be added id field or wrapped, if it exist already. This field will return globally unique ID among all types in the following format base64(TypeName + ':' + recordId).
+
For RootQuery will be added node field, that will resolve by globalId only that types, which you wrap with composeWithRelay.
+
All this annoying operations is too fatigue to do by hands. So this middleware done all Relay magic implicitly for you.
+
Requirements
+
Method composeWithRelay accept TypeComposer as input argument. So TypeComposer should meet following requirements:
+
+
has defined recordIdFn (function that from object of this type, returns you id for the globalId construction)
+
should have findById resolver (that will be used by RootQuery.node)
+
+
If something is missing composeWithRelay throws error.
\ No newline at end of file
diff --git a/docs/5.12.0/plugins/plugin-writing-custom-plugin.html b/docs/5.12.0/plugins/plugin-writing-custom-plugin.html
new file mode 100644
index 00000000..136e9ace
--- /dev/null
+++ b/docs/5.12.0/plugins/plugin-writing-custom-plugin.html
@@ -0,0 +1,109 @@
+[WIP] How to write a custom plugin · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/recipes/authorization.html b/docs/5.12.0/recipes/authorization.html
new file mode 100644
index 00000000..fe49f92a
--- /dev/null
+++ b/docs/5.12.0/recipes/authorization.html
@@ -0,0 +1,59 @@
+[WIP] Authorization · graphql-compose
\ No newline at end of file
diff --git a/docs/5.12.0/recipes/writing-tests.html b/docs/5.12.0/recipes/writing-tests.html
new file mode 100644
index 00000000..2d529ec3
--- /dev/null
+++ b/docs/5.12.0/recipes/writing-tests.html
@@ -0,0 +1,59 @@
+[WIP] Writing tests · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/EnumTypeComposer.html b/docs/6.x.x/api/EnumTypeComposer.html
new file mode 100644
index 00000000..8f2a80c8
--- /dev/null
+++ b/docs/6.x.x/api/EnumTypeComposer.html
@@ -0,0 +1,287 @@
+EnumTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/InputTypeComposer.html b/docs/6.x.x/api/InputTypeComposer.html
new file mode 100644
index 00000000..581d844d
--- /dev/null
+++ b/docs/6.x.x/api/InputTypeComposer.html
@@ -0,0 +1,422 @@
+InputTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/InterfaceTypeComposer.html b/docs/6.x.x/api/InterfaceTypeComposer.html
new file mode 100644
index 00000000..622dd196
--- /dev/null
+++ b/docs/6.x.x/api/InterfaceTypeComposer.html
@@ -0,0 +1,482 @@
+InterfaceTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/ObjectTypeComposer.html b/docs/6.x.x/api/ObjectTypeComposer.html
new file mode 100644
index 00000000..876b17ba
--- /dev/null
+++ b/docs/6.x.x/api/ObjectTypeComposer.html
@@ -0,0 +1,726 @@
+ObjectTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/Resolver.html b/docs/6.x.x/api/Resolver.html
new file mode 100644
index 00000000..2a92805a
--- /dev/null
+++ b/docs/6.x.x/api/Resolver.html
@@ -0,0 +1,523 @@
+Resolver · graphql-compose
The most interesting class in graphql-compose. The main goal of Resolver is to keep available resolve methods for Type and use them for building relation with other types.
\ No newline at end of file
diff --git a/docs/6.x.x/api/ScalarTypeComposer.html b/docs/6.x.x/api/ScalarTypeComposer.html
new file mode 100644
index 00000000..864b18da
--- /dev/null
+++ b/docs/6.x.x/api/ScalarTypeComposer.html
@@ -0,0 +1,245 @@
+ScalarTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/SchemaComposer.html b/docs/6.x.x/api/SchemaComposer.html
new file mode 100644
index 00000000..b1827570
--- /dev/null
+++ b/docs/6.x.x/api/SchemaComposer.html
@@ -0,0 +1,460 @@
+SchemaComposer · graphql-compose
Create GraphQLSchema instance from defined types.
+This instance can be provided to express-graphql, apollo-server, graphql-yoga etc.
+
addSchemaMustHaveType()
+
addSchemaMustHaveType(
+ type: AnyType<TContext>
+): this
+
+
When using Interfaces you may have such Types which are hidden under Interface.resolveType method. In such cases you should add these types explicitly. Cause buildSchema() will take only real used types and types which added via addSchemaMustHaveType() method.
\ No newline at end of file
diff --git a/docs/6.x.x/api/TypeComposer.html b/docs/6.x.x/api/TypeComposer.html
new file mode 100644
index 00000000..f7f357f4
--- /dev/null
+++ b/docs/6.x.x/api/TypeComposer.html
@@ -0,0 +1,549 @@
+TypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/TypeMapper.html b/docs/6.x.x/api/TypeMapper.html
new file mode 100644
index 00000000..388d5919
--- /dev/null
+++ b/docs/6.x.x/api/TypeMapper.html
@@ -0,0 +1,236 @@
+TypeMapper · graphql-compose
Type storage and type generator from Schema Definition Language (SDL).
+This is slightly rewritten buildASTSchema
+utility from graphql-js that allows to create type from a string (SDL).
\ No newline at end of file
diff --git a/docs/6.x.x/api/UnionTypeComposer.html b/docs/6.x.x/api/UnionTypeComposer.html
new file mode 100644
index 00000000..6b11ce2c
--- /dev/null
+++ b/docs/6.x.x/api/UnionTypeComposer.html
@@ -0,0 +1,317 @@
+UnionTypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/api/misc-api-methods.html b/docs/6.x.x/api/misc-api-methods.html
new file mode 100644
index 00000000..02de046b
--- /dev/null
+++ b/docs/6.x.x/api/misc-api-methods.html
@@ -0,0 +1,427 @@
+API misc · graphql-compose
The same as getProjectionFromAST except that all nested fields will not be extracted. Complex address will be just true, not { city: true, street: true },
graphql-compose re-exports GraphQL.js package for its plugins. It helps to avoid the hell with maintaining versions of graphql and graphql-compose in plugins' package.json files.
+
If you want to write a plugin for graphql-compose and publish it to npm, just add graphql-compose in dependencies of its package.json. And if you will need GraphQL.js objects and methods you may import them in such way:
+
// My awesome Plugin for graphql-compose
+import { graphql } from'graphql-compose';
+
+const { GraphQLNonNull, GraphQLObjectType } = graphql;
+
+
graphqlVersion
+
Sometimes it need to know which version of GraphQL.js is installed in the project.
+It may be used in graphql-compose plugins, cause different versions of GraphQL.js may have breaking changes and your plugins may have workarounds for different behavior.
+
const graphqlVersion: number;
+
+
import { graphqlVersion } from'graphql-compose';
+
+if (graphqlVersion < 13) {
+ throwError(`This plugin does not work with GraphQL.js v${graphqlVersion}`);
+}
+
+
Scalar Types
+
GraphQLDate
+
GraphQL scalar type that converts javascript Date object to string YYYY-MM-DDTHH:MM:SS.SSSZ and back.
+
import { GraphQLDate } from'graphql-compose';
+
+
GraphQLJSON
+
GraphQL scalar type that represents JSON. Field with this type may have arbitrary structure. Copied from @taion's graphql-type-json for reducing dependencies tree.
+
import { GraphQLJSON } from'graphql-compose';
+
+
TypeStorage
+
You may need some isolated storage for keeping types in your plugins. So TypeStorage is the easy way to obtain such storage.
\ No newline at end of file
diff --git a/docs/6.x.x/basics/generating-schema.html b/docs/6.x.x/basics/generating-schema.html
new file mode 100644
index 00000000..94007375
--- /dev/null
+++ b/docs/6.x.x/basics/generating-schema.html
@@ -0,0 +1,214 @@
+Generating Schema · graphql-compose
SchemaComposer is a builder of GraphQLSchema object. Obtained Schema via buildSchema() method may be used in express-graphql, apollo-server and other libs which uses GraphQL.js under the hood for query execution at runtime.
+
Create Schema
+
SchemaComposer provides basic root types Query, Mutation, Subscription. You must add fields at least to one of these types, otherwise Schema will not have sense and cannot be build.
+
import { schemaComposer } from'graphql-compose';
+import { AuthorTC } from'./author';
+
+schemaComposer.Query.addFields({
+ // add field with regular FieldConfig
+ currentTime: {
+ type: 'Date',
+ resolve: () =>Date.now(),
+ },
+ // Assume that `AuthorTC` build with `graphql-compose-mongoose` which has CRUD resolvers
+ // in such case we can use pre-generated Resolvers as a FieldConfig
+ authorById: AuthorTC.getResolver('findById'),
+ authorMany: AuthorTC.getResolver('findMany'),
+ // ...
+});
+
+schemaComposer.Mutation.addNestedFields({
+ // also it may be very useful define nested fields
+ // Mutation will have `author` field, `author` will have `create` and `update` fields inside
+ 'author.create': AuthorTC.getResolver('createOne'),
+ 'author.update': AuthorTC.getResolver('updateById'),
+ // ...
+});
+
+exportdefault schemaComposer.buildSchema(); // exports GraphQLSchema
+
+
Restrict access
+
GraphQL.js does not provide any access rights checks. You should it do manually in resolve methods. With graphql-compose you may do it via wrapping Resolvers:
+
// rootMutation.js
+import { schemaComposer } from'graphql-compose';
+
+import { CommentTC } from'./comment';
+import { UserTC } from'./user';
+
+schemaComposer.Mutation.addNestedFields({
+ commentCreate: CommentTC.getResolver('createOne'), // may anybody
+
+ ...adminAccess({
+ // only for admins
+ 'user.create': UserTC.getResolver('createOne'),
+ 'user.update': UserTC.getResolver('updateById'),
+ 'user.remove': UserTC.getResolver('removeById'),
+ }),
+});
+
+functionadminAccess(resolvers) {
+ Object.keys(resolvers).forEach(k => {
+ resolvers[k] = resolvers[k].wrapResolve(next => rp => {
+ if (!rp.context.isAdmin) {
+ thrownewError('You should be admin, to have access to this action.');
+ }
+ return next(rp);
+ });
+ });
+ return resolvers;
+}
+
+
For getting isAdmin property from context you must define it in express-graphql or apollo-server:
In some complex scenarios you may need to have several GraphQL Schemas in one app. Graphql-compose by default exports following classes/instances for single schema mode:
Types created via ObjectTypeComposer1 and ObjectTypeComposer2 will not be visible to each other. So may have different definitions for types with the same name.
\ No newline at end of file
diff --git a/docs/6.x.x/basics/type-modification.html b/docs/6.x.x/basics/type-modification.html
new file mode 100644
index 00000000..1a59375a
--- /dev/null
+++ b/docs/6.x.x/basics/type-modification.html
@@ -0,0 +1,209 @@
+Type modification · graphql-compose
This is the most important part of graphql-compose and the main difference in Schema creation with GraphQL.js. In GraphQL.js you have strict abilities in type definition and its further modification. But graphql-compose allows to you modify types after creation in very convenient ways.
+
+
Note: With graphql-compose you may modify types before GraphQLSchema object creation. When schema was created you cannot change types.
+
+
Fields modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer instances:
+
+
getFields()
+
setFields()
+
getFieldNames()
+
hasField(name)
+
setField(name, fieldConfig)
+
addFields(newFieldsConfig)
+
getField(name)
+
removeField(nameOrArray)
+
removeOtherFields(nameOrArray)
+
extendField(name, partialFieldConfig)
+
reorderFields(names)
+
deprecateFields(nameOrMap)
+
+
Additional methods in ObjectTypeComposer, InputTypeComposer, InterfaceTypeComposer instances:
+
+
getFieldType(name)
+
getFieldTC(name)
+
getFieldConfig(name)
+
makeFieldNonNull(nameOrArray)
+
makeFieldNullable(nameOrArray)
+
addNestedFields(newFields)
+
+
// add description to `firstName`
+AuthorTC.extendField('firstName', {
+ description: "This field returns Author's first name",
+});
+
+// Add new field `status` with Enum type
+AuthorTC.addField('status', `enum AuthorStatus { ACTIVE INACTIVE }`);
+
+// Change order of fields in type
+// unlisted fields will be added to the end of field list with old order
+AuthorTC.reorderFields(['status', 'firstName']);
+
+// Mark fields as deprecated with some message
+AuthorTC.deprecateFields({
+ rating: 'This field will be removed in June 2018',
+ dob: 'Use `age` field instead. This field will be removed in June 2018',
+});
+
+// Add new field with `address` name and for type
+// create a new object type with `city` and `country` fields
+AuthorTC.addNestedFields({
+ 'address.city': 'String',
+ 'address.country': 'String',
+});
+
+
Type modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer, UnionTypeComposer instances:
+
+
getType()
+
getTypePlural()
+
getTypeNonNull()
+
getTypeName()
+
setTypeName(newName)
+
getDescription()
+
setDescription()
+
clone(newTypeName)
+
+
Additional methods in ObjectTypeComposer
+
+
getInterfaces()
+
setInterfaces(interfaces)
+
hasInterface(interfaceObj)
+
addInterface(interfaceObj)
+
removeInterface(interfaceObj)
+
getInputType()
+
getITC()
+
+
Create your custom modification function
+
With this set of methods, you may write your own type modification functions. It may greatly reduce repetitive code across your schema definition.
+
As an example, lets write a function which will add rawData field with full record data from database. Also check isAdmin = true in context and if so return data, otherwise return null.
+
functionaddRawData(tc: ObjectTypeComposer<any, any>) {
+ if (!tc.hasField('rawData')) {
+ tc.addField('rawData', {
+ type: 'JSON',
+ resolve: (source, args, context) => {
+ if (context.isAdmin) {
+ return source;
+ }
+ returnnull;
+ },
+ // add magic property `projection`
+ // which request all fields from database
+ // when requested this `rawData` field in the query
+ projection: { '*': 1 },
+ });
+ }
+}
+addRawData(AuthorTC);
+addRawData(PostTC);
+
+
Or even more
+
You may write your own plugins which will generate types from some models or non-graphql schemas. Take a look on avaliable list of plugins build on top of graphql-compose.
\ No newline at end of file
diff --git a/docs/6.x.x/basics/understanding-relations.html b/docs/6.x.x/basics/understanding-relations.html
new file mode 100644
index 00000000..602de29c
--- /dev/null
+++ b/docs/6.x.x/basics/understanding-relations.html
@@ -0,0 +1,310 @@
+Relations between Types · graphql-compose
GraphQL allows to create additional fields in your types which may provide data from other type. For example, you may add field posts to the Author type and write resolve function in such way that this field will return array of posts only for current Author.
Hm, it's became quite long. But what if you have other Types wich have relations with Posts (eg Reviewer, Reader)? I don't think that copy/paste of resolve method will be a good idea. Cause in the future you may want to add a new filter property and should scan all your code and put additional logic in all FieldConfigs. So if you meet with such problem the next section is for you.
+
Relation via Resolver
+
If you need to use the same FieldConfigs in different Types for such cases graphql-compose provides Resolver class. You may create a Resolver which will define type, args and resolve and reuse in all places of your Schema where you need it.
+
Anyway if you put posts resolver in separate file, you will meet with another problems
+
+
in Author type you will use criteria = { authorId: source.id } for resolve method;
+
in Reviewer - criteria = { reviewers: { $has: source.id } } and so on.
+
+
For such case better to improve args.filter by allowing to set authorId and reviewerId via arguments:
Should be an arrow function which returns Resolver. Wrapping resolver in arrow function helps to solve hoisting problem (when two types imports each other).
+
prepareArgs
+
At runtime we should have ability to prepare somehow args which will be passed to Resolver.
+
For example our Resolver has following arguments filter, limit, skip and sort.
+prepareArgs provides instruction how to setup them:
+
+
limit: 10 - hide limit arg from schema and set it equal to 10
+
filter: (source) => value - hide filter arg form schema and at runtime evaluate its value
+
sort: null - disable argument (hide from schema and do not pass it to resolver)
+
all undescribed args (like skip) will be avaliable in the schema and will be avaliable in query
+
+
projection
+
Is very useful option for extending requested fields in your query. It very good practice to request from database only that fields which were requested in the query. But sometimes we need to request additional fields for fullfilling findById resolver with authorId value in arguments. For this purpose you need to use projection.
Without projection when we will request author field its resolver may get args.authorId equals to undefined. In this situation will not provide any data for Author. It happens if fetching only that fields which listed in the query from database. So when client requests author field in GraphQL Query he also must request authorId explicitly. But why client should care it? So required additional fields should be requested via projection option.
\ No newline at end of file
diff --git a/docs/6.x.x/basics/understanding-types.html b/docs/6.x.x/basics/understanding-types.html
new file mode 100644
index 00000000..94fb5232
--- /dev/null
+++ b/docs/6.x.x/basics/understanding-types.html
@@ -0,0 +1,401 @@
+Type creation · graphql-compose
With graphql-compose you need to create types under some schemaComposer instance. By default graphql-compose has a global schemaComposer instance which can be obtained in the following manner:
But if you need to create several GrasphQL schemas in your app, you may import SchemaComposer class and create schemaComposer instances as much as you need:
+
import { SchemaComposer } from'graphql-compose';
+
+const schemaComposer1 = new SchemaComposer();
+const schemaComposer2 = new SchemaComposer();
+
+
Take a note that schemaComposer1 and schemaComposer2 will have different type storages. And types in schemaComposer1 will not be avaliable in schemaComposer2 and vice versa.
+
Scalar types
+
Graphql-compose has following built-in scalar types:
+
+
String
+
Float
+
Int
+
Boolean
+
ID
+
Date
+
JSON
+
+
via config
+
You may create scalar types via config, like with GraphQLScalarType:
If you need to create some complex type with several properties (fields), you will need to use ObjectTypeComposer. It's a builder for GraphQLObjectType object.
+
ObjectTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Output type. Such definition provides better developer experience with jumping to the type declarations.
+
const AuthorTC = schemaComposer.createObjectTC({
+ name: 'Author',
+ fields: {
+ id: 'Int!',
+ firstName: 'String',
+ lastName: 'String',
+ posts: {
+ type: () => [PostTC], // arrow function fot `type` helps to solve hoisting problems and keep ability to list all fields
+ args: {
+ limit: { type: 'Int', defaultValue: 20 },
+ skip: 'Int', // shortand to `{ type: 'Int' }`
+ sort: `enum AuthorPostsSortEnum { ASC DESC }`, // type creation via SDL
+ },
+ resolve: () => { ... },
+ }
+ },
+});
+
+
Also this way of definition provides a lot of syntax sugar for field definition:
+
const AuthorTC = schemaComposer.createObjectTC({
+ posts: {
+ // wrapping Type with arrow function helps to solve a hoisting problem
+ // also using type instances provides better DX
+ // (ctrl+click allows to jump to PostTC type declaration in your IDE)
+ type: () => PostTC,
+ description: 'Posts written by Author',
+ resolve: (source, args, context, info) => {},
+ },
+ // using standard GraphQL Type
+ ucFirstName: {
+ type: GraphQLString,
+ resolve: (source) => { return source.firstName.toUpperCase(); },
+ // also request `firstName` field which must be loaded from database
+ projection: { firstName: true },
+ },
+ // fast way if you need to define only type
+ counter: 'Int',
+ // using SDL for definition new ObjectType
+ complex: `type ComplexType {
+ subField1: String
+ subField2: Float
+ subField3: Boolean
+ subField4: ID
+ subField5: JSON
+ subField6: Date
+ }`,
+ // SDL for defining array of strings, which is NonNull
+ list0: {
+ type: '[String]!',
+ description: 'Array of strings',
+ },
+ list1: '[String]',
+ list2: ['String'],
+ list3: [GraphQLString],
+ list4: [`type Complex2Type { f1: Float, f2: Int }`],
+});
+
+
via SDL
+
May have hoisting problems. Be aware that all used complex types must be already defined.
GraphQL allows to pass arguments for fields. You may freely use Scalars, Enums when describing input args. But what you should do in the case of mutations, where you might want to pass in a whole object to be created? For such cases for complex types instead of GraphQLObjectType you should use GraphQLInputObjectType. They they have small differences in its fields declaration:
+
+
input object type has defaultValue
+
input object type does not have args
+
input object type does not have resolve method
+
+
If you need to create some complex type with several properties, you will need to use InputTypeComposer. It's a builder for GraphQLInputObjectType object.
+
InputTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Input type. Such definition provides hoisting problems solution via wrapping types by arrow function. Better developer experience with jumping to the type declarations.
+
InputTypeComposer has the same type definition capabilities for describing fields as ObjectTypeComposer - as string, as arrow function, as SDL.
Useful when you write your own type generators. Enum has values (not fields), but for similar method naming with ObjectTypeComposer and InputTypeComposer in graphql-compose methods for value modification have field keyword.
Graphql-compose provides the following helper for Interfaces - InterfaceTypeComposer.
+
import { schemaComposer } from'graphql-compose';
+
+const TimestampInterface = schemaComposer.createInterfaceTC({
+ name: 'Timestampable',
+ description: 'An object with createdAt and updatedAt fields',
+ fields: {
+ createdAt: 'Date',
+ updatedAt: 'Date',
+ },
+});
+
+// When you create Interface, you need to provide instructions how to determine exact ObjectType from `value`.
+// So if `value` is instance of UserDoc, then use `UserTC` as exact type.
+TimestampInterface.addTypeResolver(UserTC, value => (value instanceof UserDoc));
+TimestampInterface.addTypeResolver(ArticleTC, value => (value instanceof UserDoc));
+
+
Lists
+
If you want indicate that field or argument return an array of some type, you may do the following:
+
import { GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field1: [AuthorTC], // RECOMMENDED just wrap in the regular js array
+ field2: AuthorTC.getTypePlural(), // call specific ObjectTypeComposer method
+ field3: '[Author]', // use SDL format
+ field4: new GraphQLList(AuthorTC.getType()) // use standard GraphQLList
+});
+
+
Non-Null
+
If you want indicate that field is not empty or argument is required:
+
import { GraphQLNonNull } from'graphql';
+
+SomeTypeComposer.addFields({
+ // field1: ???, // doesn't exists any regular object in js for expressing NonNull value
+ field2: AuthorTC.getTypeNonNull(), // call specific ObjectTypeComposer method
+ field3: 'Author!', // use SDL format
+ field4: new GraphQLNonNull(AuthorTC.getType()) // use standard GraphQLNonNull
+});
+
+
Non-Null List of Non-Null values may be expressed in following way:
+
import { GraphQLNonNull, GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field3: '[Author!]!', // use SDL format
+ field4: new GraphQLNonNull( // use standard GraphQLNonNull & GraphQLList
+ new GraphQLList(
+ new GraphQLNonNull(AuthorTC.getType())
+ )
+ )
+});
+
\ No newline at end of file
diff --git a/docs/6.x.x/basics/what-is-resolver.html b/docs/6.x.x/basics/what-is-resolver.html
new file mode 100644
index 00000000..437a49fe
--- /dev/null
+++ b/docs/6.x.x/basics/what-is-resolver.html
@@ -0,0 +1,320 @@
+Resolvers · graphql-compose
Shortly, Resolver is an object which knows how to process data and what to return. It's like a function definition in static language where you give it name, describe types for input arguments and output result.
+
GraphQL.js describes such functions in complex output types via GraphQLFieldConfig:
GraphQLFieldConfig has information about returned type, available args, implementation of resolve logic and some other properties. In terms of graphql-compose this field config is called as Resolver.
+
The main aim of Resolver is to keep available resolve methods for Type and use them for building relation with other types. Resolver provide following abilities:
+
+
add, remove, get, make optional/required arguments
+
clone Resolver for further logic extension
+
wrap args, type, resolve (get resolver and create new one with extended/modified functionality)
+
provide helper methods addFilterArg and addSortArg which wrap resolver by adding argument and additional resolve logic
+
+
Resolver has following properties:
+
+
type output complex or scalar type (resolver returns data of this type)
+
args list of fields of input or scalar types (resolver accept input arguments for resolve method)
+
resolve method which contains your bussiness logic, for fetching, processing and returning data. BE AWARE: that all arguments (source, args, context, info) are passed inside one argument called as resolveParams (rp for brevity in the code).
+
description public description which will be passed to graphql schema and will be available via introspection
+
deprecationReason if you want to hide field from schema, but leave it working for old clients
+
name any name for resolver that allow to you identify what it does, eg findById, updateMany, removeOne
+
kind type of resolver query (resolver just fetch data) or mutation (resolver change data)
+
parent you may wrap existed Resolver for adding additional checks, modifying result, adding arguments. This property keeps reference to existed unwrapped Resolver
+
+
Why do we need the Resolver?
+
Graphql-compose allows creating such "functions" or "FieldConfigs" via giving it names and keep in your ObjectTypeComposer. You may create any number of Resolvers and store them in your type.
+
Assume you have an Author type. And you have different standard CRUD operations for fetching and modifying this type:
+
+
findById
+
findMany
+
updateById
+
removeById
+
etc
+
+
When you will construct your Schema, you may need several times the same logic from standard Resolvers. For example
+
+
in the Query type may be added fields
+
+
authorById for finding Author by id arg via findById resolver
+
authorMany for finding list of Author with some filter criteria via findMany resolver
+
+
in the Post type may be added
+
+
author field which request Author by id from current post.authorId value via findById resolver
+
reviewers field which request Authors via findMany resolver with custom filtering
+
+
+
Resolvers helps to describe CRUD operations logic only once and then reuse them in different scenarios. For Query.authorById provides its full functionality from findById resolver. For Post.author you wrap findById resolver where should be hidden id arg and its value automatically will be set from post.authorId. For wrapping Resolvers graphql-compose provides a bunch of methods.
+
Creating Resolver
+
via TC.addResolver()
+
Mostly Resolvers are created according to the specific Type. So it's better to create them and store in some ObjectTypeComposer instance.
+
Lets's take AuthorTC and describe how it can be found by id:
+
AuthorTC.addResolver({
+ name: 'findById',
+ args: { id: 'Int' },
+ type: AuthorTC,
+ resolve: async ({ source, args }) => {
+ const res = await fetch(`/endpoint/${args.id}`); // or some fetch from any database
+ const data = await res.json();
+ // here you may clean up `data` response from API or Database,
+ // it should has same shape like AuthorTC fields
+ // eg. { firstName: 'Peter', nickname: 'peet', views: 20 }
+ // if some fields in `data`:
+ // are undefined or missing - graphql returns `null` for that fields
+ // are not described in output `type` - graphql will remove them from responce
+ return data;
+ },
+});
+
+
And in any place of your schema you will able to use this Resolver in such way:
You may create instance of Resolver without attaching it to some ObjectTypeComposer. It can be done in following way:
+
import { schemaComposer } from'graphql-compose';
+
+const findCityLocationByIdResolver = schemaComposer.createResolver({
+ name: 'findCityLocationById',
+ type: `type CityLocation { lon: Float, lat: Float }`,
+ args: {
+ id: 'Int!',
+ },
+ // BE AWARE! `resolve` method in `Resolver` accept only one argument `resolveParams`
+ // which contains
+ // standard properties from `GraphQLFieldResolveFn`: source, args, context, info
+ // and additional properties: projection
+ resolve: async ({ source, args, context, info }) => {
+ const city = await DB.city.findById(args.id);
+ if (!city) returnnull;
+ return {
+ lon: city.longitude,
+ lat: city.latitude,
+ };
+ }
+});
+
+// And add this resolver to your Schema
+schemaComposer.Query.addFields({
+ cityLocation: findCityLocationByIdResolver,
+});
+
+
Wrapping Resolver
+
In many cases, it is very convenient to create a Resolver which just fetch data providing rich filter and sort arguments (also it may modify data).
+But what if we need to restrict access or set up some arguments of Resolver from source (parent) object or context?
+
Yep, you need to wrap the Resolver! Wrap just resolve method via Resolver.wrapResolve(). Or Resolver.wrap() if we want to change simultaneously output type, args and resolve method.
+
via Resolver.wrapResolve()
+
The most commonly used method for wrapping is Resolver.wrapResolve(). Let take a look how can be it used in your Schema:
+
schemaComposer.Query.addFields({
+ // add endpoint which returns only visible posts
+ publicPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `visibility` argument
+ // so forcibly set this arg to true
+ rp.args.visibility = true;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns posts only for current authenticated user
+ ownerPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `authorId` argument
+ // so forcibly set this arg to current user id
+ rp.args.authorId = rp.context.currentUserId;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns all authors only for admin
+ allAuthorsForAdmin: AuthorTC.getResolver('findMany').wrapResolve(next => rp => {
+ // check `isAdmin` property in context, which was somehow setted
+ // on express-graphql or apollo-server level
+ // for regular user return null
+ if (!rp.context.isAdmin) returnnull;
+ // for admin delegate execution to the basic resolver
+ return next(rp);
+ });
+});
+
+
via Resolver.wrap()
+
This is a less-used method. But it's more powerfull. It allows to change simultaneously output type, args and resolve method.
+
What if admin should have all avaliable filter params and add new one for searching but regular user just limited set of arguments?
+
Resolver wrapping creates a new Resolver. So for admin you create a new resolver findManyForAdmin by wrapping a basic resolver, eg. findMany add additional args and logic. For user you create findManyReduced by wrapping existed findMany resolver and removing some filter args.
+
Let write reduced resolver findManyReduced, where we remove some args
+
const findManyReduced = AuthorTC.getResolver('findMany').wrap(newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeFields(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
via TC.wrapResolverAs()
+
Also you may want to modify already existed Resolver in some ObjectTypeComposer, like it did Resolver.wrap() method.
+
For simplifying this process you may use ObjectTypeComposer.wrapResolverAs() method.
+Let take AuthorTCs findMany resolver and create a new one with name findManyReduced.
+
AuthorTC.wrapResolverAs('findManyReduced', 'findMany', newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeField(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
Advanced
+
How Resolver.wrapResolve() work internally
+
+
capturing phase, when you may change resolveParams (rp in the code) before it will pass to next resolve
+
bubbling phase, when you may change response from underlying resolve
+
+
Resolver.wrapResolve(next => rp => {
+ // [CAPTURING PHASE]:
+ // `rp` consist from { source, args, context, info, projection }
+ // you may change `source`, `args`, `context`, `info`, `projection` before it will pass to `next` underlying resolve function.
+
+ // ...some code which modify `rp` (resolveParams)
+
+ // ... or just stop propagation
+ // throw new Error();
+ // or
+ // return Promise.resolve(null);
+
+ // pass request to underlying middleware and get result promise from it
+ const resultPromise = next(rp);
+
+ // [BUBBLING PHASE]: here you may change payload of underlying resolve method, via promise syntax
+ // ...some code, which may add `then()` or `catch()` to result promise
+ // resultPromise.then(payload => { console.log(payload); return payload; })
+
+ return resultPromise; // return payload promise to upper wrapper
+});
+
\ No newline at end of file
diff --git a/docs/6.x.x/guide/elasticsearch-with-mongoose.html b/docs/6.x.x/guide/elasticsearch-with-mongoose.html
new file mode 100644
index 00000000..6a5cd6f3
--- /dev/null
+++ b/docs/6.x.x/guide/elasticsearch-with-mongoose.html
@@ -0,0 +1,385 @@
+[WIP] Use ElasticSearch with Mongoose · graphql-compose
Connect MongoDB with ElasticSearch and GraphQL quite complex and long task and consist of a bunch of steps. Every step can be tuned for your needs.
+
1. Extending Mongoose ORM with elasticsearch data
+
For working with MongoDB collections and documents is good practice to use some ORM. For nodejs better solution is mongoose. Also exists cool mongoose-elasticsearch-xp (by @jbdemonte) package (plugin for mongoose) which provides useful methods and hooks which ridiculously simplify data syncing with MongoDB and ElasticSearch.
+
1.1. Defining Mongoose schema with settings for elasticsearch-xp [SCHEMA DEFINITION]
1.2 Plug mongoose-elasticsearch-xp to your Mongoose Schema with data filtering [SYNC MONGO & ES DATA]
+
/* elastic */
+JobSchema.plugin(mongooseElasticsearch, {
+ client: elasticClient, // <------ see `graphql-elasticsearch-xp` for details
+ filter: doc => {
+ if (doc.visibility !== 'published') {
+ // add to index new record with visibility='published'
+ // or remove existed record from index if `visibility` changed and not 'published' anymore
+ returnfalse;
+ }
+ returntrue;
+ },
+});
+
+
By default mongoose-elasticsearch-xp will track add/remove operations and update your data in elasticsearch. In this case I provide filter option, now it will track more clever model's inserts/updates and send proper changes to your elasticsearch server.
+
Already existed data can be synced via esSynchronize method.
+
1.3 Connection with elasticsearch server elasticClient [ES CLIENT]
+
You should provide elasticClient in step 1.2 (for mongoose plugin [UPDATING DATA]) and 1.4 (for graphql resolvers [SEARCH]). It holds connection of your nodejs server with elasticsearch server.
import { composeWithElastic } from'graphql-compose-elasticsearch';
+import { generate } from'mongoose-elasticsearch-xp/lib/mapping';
+
+exportconst JobEsTC = composeWithElastic({
+ graphqlTypeName: 'JobES',
+ elasticIndex: 'job',
+ elasticType: 'job',
+ elasticMapping: {
+ properties: generate(JobSchema),
+ },
+ elasticClient,
+ // elastic mapping does not contain information about is fields are arrays or not
+ // so provide this information explicitly for obtaining correct types in GraphQL
+ pluralFields: ['employment'],
+});
+
fragment on Query {
+ jobEsConnection(first: $first, query: $query, sort: $sort, aggs: $aggs) {
+ count
+ aggregations
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ cursor
+ node {
+ _score# meta-data from ES
+ _id# meta-data from ES
+
+ _source {
+ employment # record data from ES
+ position# record data from ES
+ }
+
+ fromMongo { # data from Mongo
+ _id
+ onlyMongooseData
+ visibility
+ salary { fromto currency}
+ position
+ }
+ }
+ }
+ }
+}
+
+
See https://github.com/nodkz/graphql-compose
+Sorry bad docs in graphql-compose. Really do not have time to write it. So try to see issues they contain a lot of info.
+
1.7 Add needed resolvers to schema [BUILD GRAPHQL SCHEMA]
import { GQC } from 'graphql-compose';
+import { elasticApiFieldConfig } from 'graphql-compose-elasticsearch';
+import elasticClient from 'schema/elasticClient';
+
+export const ElasticTC = GQC.get('ELASTIC');
+
+ElasticTC.addResolver({
+ name: 'onlyForAdmins',
+ type: ElasticTC,
+ resolve: ({ context }) => {
+ if (!isAdmin({ context })) { // <--- somehow check that you are admin
+ throw new Error('You should be admin, to have access to this area.');
+ }
+ return {};
+ },
+});
+
+# expose all elastic api via graphql
+ElasticTC.addFields({
+ api: elasticApiFieldConfig(elasticClient),
+});
+
+// DONT FORGET TO add elastic to your schema (eg. to ROOT query)
+GQC.rootQuery().addFields({
+ elastic: ElasticTC.getResolver('onlyForAdmins'),
+});
+
Now you may call reindexing all your data in elasticsearch via following graphql query:
+
query {
+ elastic {
+ reindexJob
+ }
+}
+
+
\ No newline at end of file
diff --git a/docs/6.x.x/guide/file-uploads.html b/docs/6.x.x/guide/file-uploads.html
new file mode 100644
index 00000000..f121eb69
--- /dev/null
+++ b/docs/6.x.x/guide/file-uploads.html
@@ -0,0 +1,256 @@
+File uploads · graphql-compose
If you decide how to upload files via some REST endpoint or GraphQL. So I recommend to upload via some REST API and then provide a path of the uploaded file to your mutation request. GraphQL designed to provide typed data according to client request shape. With files (binary data) it works too, but better to do it via well-recommended REST calls. In such case, you separate highly costed upload logic from data manipulation logic. In the future, this will help you diagnose problems with the load more easily.
+
Anyway products have different scenarios and you may be forced to upload files via GraphQL. For uploading files via GraphQL you will need:
apollo-upload-server - for parsing multipart/form-data POST requests via busboy and providing Files data to resolve function as argument.
+
+
Tutorial
+
1. Preparing express-graphql server
+
This is most important part of enabling file uploads on server-side. You need to parse body data via bodyParser.json() and multipart form data via apolloUploadExpress(/* Options */).
This is a most problematic part and it's out of scope of graphql-compose (it's client-side problem). You must correctly send HTTP request from the client. But if you very carefully read graphql-multipart-request-spec, then you should not have any questions.
+
Here's an example of proper multipart/form-data POST request with
+
+
operations key for GraphQL request with query and variables
+
map key with mapping some multipart-data to exact GraphQL variable
+
and other keys for multipart-data which contains binary data of files
\ No newline at end of file
diff --git a/docs/6.x.x/guide/mongoose.html b/docs/6.x.x/guide/mongoose.html
new file mode 100644
index 00000000..91039824
--- /dev/null
+++ b/docs/6.x.x/guide/mongoose.html
@@ -0,0 +1,133 @@
+[WIP] Generate types from Mongoose Models · graphql-compose
Well TypeComposers generated by graphql-compose-mongoose ships with resolvers for create, update and remove.
+Looking like this:
+
UserTC.getResolver('createOne').getFieldConfig();
+UserTC.getResolver('updateById').getFieldConfig();
+// or for shorthand
+UserTC.get('$removeMany').getFieldConfig();
+// and buch of other resolvers
+
+
Lets add a working example from the preview UserTC we have created
// user.js
+
+UserTC.addResolver({
+ name: 'myCustomUpdate',
+ kind: 'mutation',
+ args: {
+ id: 'String',
+ firstName: 'String',
+ lastName: 'String',
+ complexArg: `input SomeComplexInput {
+ min: Int
+ max: Int
+ }`,
+ },
+ type: UserTC,
+ resolve: ({ _, args, context, info }) => {
+ //edit and do what you need..
+ return user;
+ },
+});
+
+// so now you may add you custom mutation to schema
+GQC.rootMutation().addFields({
+ customUserUpdate: UserTC.get('$myCustomUpdate'),
+});
+
+
\ No newline at end of file
diff --git a/docs/6.x.x/guide/relay.html b/docs/6.x.x/guide/relay.html
new file mode 100644
index 00000000..4feab140
--- /dev/null
+++ b/docs/6.x.x/guide/relay.html
@@ -0,0 +1,73 @@
+[WIP] Relay Schema · graphql-compose
Adding support for Relay is done via plugin graphql-compose-relay For more detailed descriptions on how to use and reporting issues please use the link.
\ No newline at end of file
diff --git a/docs/6.x.x/guide/wrapping-rest-api.html b/docs/6.x.x/guide/wrapping-rest-api.html
new file mode 100644
index 00000000..42c095d4
--- /dev/null
+++ b/docs/6.x.x/guide/wrapping-rest-api.html
@@ -0,0 +1,220 @@
+Wrapping REST API · graphql-compose
Many developers are attracted by GraphQL’s benefits over REST. The reason for that is its query language enabling to stick to the data that the client needs at the moment and not to restructure the client to fit API structure. Single endpoint, but flexible data shape.
+
Let’s imagine you already have an existing RESTful API, but your task requires using GraphQL either you just want to try it out of curiosity. If that's the case, you would need to wrap your REST in GraphQL Schema and hardcoding all the GraphQL Types is a real pain.
+
That's why we came up with a RESTful API wrapper for GraphQL featuring automatic GraphQL Type generation.
+
Installation
+
npm install graphql-compose-json
+
+
Demo
+
We've wrapped SWAPI RESTful API in to show capabilities of graphq-compose-json
Using graphql-compose is easy — it's just one, but helpful function:
+
import composeWithJson from'graphql-compose-json';
+
+const restApiResponse = {
+ name: 'Anakin Skywalker',
+ birth_year: '41.9BBY',
+ starships: [
+ 'https://swapi.co/api/starships/59/',
+ 'https://swapi.co/api/starships/65/',
+ 'https://swapi.co/api/starships/39/',
+ ],
+ mass: () =>'Int!', // by default JSON numbers are coerced to Float, here we've set it to Integer
+ starships_count: () => ({ // granular inline field config with resolve function
+ type: 'Int',
+ resolve: source => source.starships.length,
+ }),
+};
+
+exportconst CustomPersonTC = composeWithJson('CustomPerson', restApiResponse);
+
+
That's it! The Type is ready to be used and have its resolvers defined. CustomPersonTC contains all things you need to compose Resolvers and Schema.
+
Specifying data fetching method
+
What we're trying to do is to wrap an existing RESTful API in GraphQL Schema, but it is not yet aware of where the data is stored, it knows only the possible data shape; thus we need to specify how to fetch the API data.
+
Valid GraphQL data request requires three pieces: resolve(data fetching method), args(list of acceptable input arguments) and type(data representation form, which we already have thanks to graphql-compose-json). GraphQL terms label these three a Field Config (or Resolver).
It's unlikely that the Schema will have only one Type, hence we've got to link our scattered types. Imagine we want Person Type to return the list of movies they starred in. Assuming that Person has links to them, all we need is to add a resolver to FilmTC.
Defining Resolvers within ObjectTypeComposers they belong to helps to keep your code DRY, as further on you'll be able to reuse them with just one line of code:
+
Planet.getResolver('findMany');
+
+
Composing the Schema
+
Now with Types and Resolvers created it's time to put them into Schema.
\ No newline at end of file
diff --git a/docs/6.x.x/intro/installation.html b/docs/6.x.x/intro/installation.html
new file mode 100644
index 00000000..3478ef25
--- /dev/null
+++ b/docs/6.x.x/intro/installation.html
@@ -0,0 +1,114 @@
+Installation · graphql-compose
Module graphql is declared in peerDependencies, so it should be installed explicitly in your project. This helps to solve a common problem when some of your other dependencies (like Relay, GraphiQL, graphql-compose) can leave your node_modules directory with duplicate installs of GraphQL.js. In such case graphql-js may throw errors stating that some classes are not instances of duplicate module.
+
Also you may need to install some graphql-compose plugins. Each plugin has own Install section with instructions.
\ No newline at end of file
diff --git a/docs/6.x.x/intro/live-demos.html b/docs/6.x.x/intro/live-demos.html
new file mode 100644
index 00000000..67fa0937
--- /dev/null
+++ b/docs/6.x.x/intro/live-demos.html
@@ -0,0 +1,120 @@
+Live Demos · graphql-compose
graphql-compose-boilerplate - ready to run a skeleton app for GraphQL server. It contains the example from Quick Start. This boilerplate includes Babel (ES6, babel-preset-env), ESLint, Flowtype, express, express-graphql, graphql, graphql-compose, nodemon.
+
+
Other demos
+
+
nodkz.github.io/relay-northwind - live demo of Relay Client App working with GraphQL Northwind Schema (8 crazy pages, 47 files, ~3000 LOC)
\ No newline at end of file
diff --git a/docs/6.x.x/intro/prerequisites.html b/docs/6.x.x/intro/prerequisites.html
new file mode 100644
index 00000000..67ea8ff1
--- /dev/null
+++ b/docs/6.x.x/intro/prerequisites.html
@@ -0,0 +1,116 @@
+Prerequisites · graphql-compose
To use this package it would be a good idea to know the basics of GraphQL, and how the Type System works. Since you are going to generate and edit its types you should start out there first.
+
Node.js
+
This package generates GraphQL Schema on the server side. And it will be great if you have experience with Node.js and ES6 syntax.
+
For serving requests to your generated Schema you should use one of the following packages express-graphql or apollo-server.
+
Flowtype/TypeScript
+
This is optional but quite recommended feature which covers your javascript code with static type-checking. It will help you with autosuggestion and method call validation in your IDE. This package contains built-in type definitions for Flowtype and TypeScript.
+
Internally source code of this package is written with Flowtype and has deep static type-checking with graphq-js which is also written with Flow.
\ No newline at end of file
diff --git a/docs/6.x.x/intro/quick-start.html b/docs/6.x.x/intro/quick-start.html
new file mode 100644
index 00000000..bf3266be
--- /dev/null
+++ b/docs/6.x.x/intro/quick-start.html
@@ -0,0 +1,273 @@
+Quick Start Guide · graphql-compose
For simplicity, this example works with arrays, but in future, it will not be a problem to change data-source to any your favorite DB or a mix of them.
+
Creating Types
+
Building a GraphQL Schema starts with complex Types declaration. In order to create a Type, you have to give it a unique name and specify it’s fields list. So let's create Types which will describe our data. For this purpose need to take ObjectTypeComposer helper from graphql-compose package.
Now as we can declare Types, request them, it’s time to link these Types with each other. This is the exact stage where GraphQL enormously simplifies work for clients that request data. A typical scenario of a query to RESTful API: client requests a piece of data, receives it and request other resources according to the first server response it got, while GraphQL implements the same logic on the server’s side and sends back nested data of any depth.
+
To make such nesting possible you’ve got to link Author and Post Types with each other. For that you need to create author field in your Post Type, it will resolve author's data for every post. And for Author Type create posts field which will resolve for each author its posts.
+
PostTC.addFields({
+ author: {
+ // you may provide type name as string 'Author',
+ // but for better developer experience use Type instance `AuthorTC`
+ // it allows to jump to type declaration via Ctrl+Click in your IDE
+ type: AuthorTC,
+ // resolve method as first argument will receive data for some Post
+ // from this data you should somehow fetch Author's data
+ // let's take lodash `find` method, for searching by `authorId`
+ // PS. `resolve` method may be async for fetching data from DB
+ // resolve: async (source, args, context, info) => { return DB.find(); }
+ resolve: post => find(authors, { id: post.authorId }),
+ },
+});
+
+AuthorTC.addFields({
+ posts: {
+ // Array of posts may be described as string in SDL in such way '[Post]'
+ // But graphql-compose allow to use Type instance wrapped in array
+ type: [PostTC],
+ // for obtaining list of post we get current author.id
+ // and scan and filter all Posts with desired authorId
+ resolve: author => filter(posts, { authorId: author.id }),
+ },
+ postCount: {
+ type: 'Int',
+ description: 'Number of Posts written by Author',
+ resolve: author => filter(posts, { authorId: author.id }).length,
+ },
+});
+
+
Building Schema
+
Now that you’ve got your Types created, linked and taught how to fetch data, it’s time to create your Schema. For this purpose, you will need to use schemaComposer. It has three Root Types (entry points): Query, Mutation and Subscription and at least one of them must have defined fields.
+
import { schemaComposer } from'graphql-compose';
+
+// Requests which read data put into Query
+schemaComposer.Query.addFields({
+ posts: {
+ type: '[Post]',
+ resolve: () => posts,
+ },
+ author: {
+ type: 'Author',
+ args: { id: 'Int!' },
+ resolve: (_, { id }) => find(authors, { id }),
+ },
+});
+
+// Requests which modify data put into Mutation
+schemaComposer.Mutation.addFields({
+ upvotePost: {
+ type: 'Post',
+ args: {
+ postId: 'Int!',
+ },
+ resolve: (_, { postId }) => {
+ const post = find(posts, { id: postId });
+ if (!post) {
+ thrownewError(`Couldn't find post with id ${postId}`);
+ }
+ post.votes += 1;
+ return post;
+ },
+ },
+});
+
+// After Root type definition, you are ready to build Schema
+// which should be passed to `express-graphql` or `apollo-server`
+exportconst schema = schemaComposer.buildSchema();
+
+
Creating HTTP server
+
When your Schema is constructed, it needs to implement a server. It will serve client requests, execute them and send responses back. Let's construct a simple express app which will accept POST requests at http://localhost:4000/graphql endpoint for serving graphql queries. And GET requests with same address for providing GraphiQL an in-browser IDE for exploring GraphQL.
Graphql-compose has following built-in scalar types: String, Float, Int, Boolean, ID, Date, JSON. If you need to create some complex type, you will need to use schemaComposer.createObjectTC().
+
Let demonstrate another way of type creation via SDL:
+
const AddressTC = schemaComposer.createObjectTC(`
+ type Address {
+ city: String
+ country: String
+ street: String
+ }
+`);
+
+// and now we can extend existed Author Type with a new field with complex type
+AuthorTC.addFields({
+ address: {
+ type: AddressTC, // or 'Address'
+ description: "Author's address",
+ },
+})
+
+
More useful information about type creation can be found here.
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/list-of-plugins.html b/docs/6.x.x/plugins/list-of-plugins.html
new file mode 100644
index 00000000..24e93532
--- /dev/null
+++ b/docs/6.x.x/plugins/list-of-plugins.html
@@ -0,0 +1,128 @@
+Plugins list · graphql-compose
graphql-compose – the imperative tool which worked on top of graphql-js. It provides useful methods for creating GraphQL Types and GraphQL Models (type with a list of
+resolvers) for further building of complex relations in your Schema. With graphql-compose you may fastly write own functions/generators for common tasks.
+
graphql-compose-[plugin] – is a declarative generator/plugin that build on top of graphql-compose, which take some ORMs, schema definitions and creates GraphQL Models from them or modify existed GraphQL Types.
+
Type generator plugins
+
+
graphql-compose-json - generates GraphQL type from JSON (a good helper for wrapping REST APIs)
+
graphql-compose-mongoose - generates GraphQL types from mongoose (MongoDB models) with Resolvers.
+
graphql-compose-elasticsearch - generates GraphQL types from elastic mappings; ElasticSearch REST API proxy via GraphQL.
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-aws.html b/docs/6.x.x/plugins/plugin-aws.html
new file mode 100644
index 00000000..acb6c06b
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-aws.html
@@ -0,0 +1,153 @@
+graphql-compose-aws · graphql-compose
Generated Schema Introspection in SDL format can be found here (more than 10k types, ~2MB).
+
AWS SDK GraphQL
+
Supported all AWS SDK versions via official aws-sdk js client. Internally it generates Types and FieldConfigs from AWS SDK configs. You may put this generated types to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import awsSDK from'aws-sdk';
+import { AwsApiParser } from'graphql-compose-aws';
+
+const awsApiParser = new AwsApiParser({
+ awsSDK,
+});
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ // Full API
+ aws: awsApiParser.getFieldConfig(),
+
+ // Partial API with desired services
+ s3: awsApiParser.getService('s3').getFieldConfig(),
+ ec2: awsApiParser.getService('ec2').getFieldConfig(),
+ },
+ }),
+});
+
+exportdefault schema;
+
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-connection.html b/docs/6.x.x/plugins/plugin-connection.html
new file mode 100644
index 00000000..d64db3c2
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-connection.html
@@ -0,0 +1,207 @@
+graphql-compose-connection · graphql-compose
Besides standard connection arguments first, last, before and after, also added significant arguments:
+
+
filter arg - for filtering records
+
sort arg - for sorting records. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
+
Example
+
import composeWithConnection from'graphql-compose-connection';
+import userTypeComposer from'./user.js';
+
+composeWithConnection(userTypeComposer, {
+ findResolverName: 'findMany',
+ countResolverName: 'count',
+ sort: {
+ // Sorting key, visible for users in GraphQL Schema
+ _ID_ASC: {
+ // Sorting value for ORM/Driver
+ value: { _id: 1 },
+
+ // Field names in record, which data will be packed in `cursor`
+ // edges {
+ // cursor <- base64(cursorData), for this example `cursorData` = { _id: 334ae453 }
+ // node <- record from DB
+ // }
+ // By this fields MUST be created UNIQUE index in database!
+ cursorFields: ['_id'],
+
+ // If for connection query provided `before` argument with above `cursor`.
+ // We should construct (`rawQuery`) which will be point to dataset before cursor.
+ // Unpacked data from `cursor` will be available in (`cursorData`) argument.
+ // PS. All other filter options provided via GraphQL query will be added automatically.
+ // ----- [record] ----- sorted dataset, according to above option with `value` name
+ // ^^^^^ `rawQuery` should filter this set
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+
+ // Constructing `rawQuery` for connection `after` argument.
+ // ----- [record] ----- sorted dataset
+ // ^^^^^ `rawQuery` should filter this set
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ },
+
+ _ID_DESC: {
+ value: { _id: -1 },
+ cursorFields: ['_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+ },
+
+ // More complex sorting parameter with 2 fields
+ AGE_ID_ASC: {
+ value: { age: 1, _id: -1 },
+ // By these fields MUST be created COMPOUND UNIQUE index in database!
+ cursorFields: ['age', '_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$lt = cursorData.age;
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$gt = cursorData.age;
+ rawQuery._id.$lt = cursorData._id;
+ },
+ }
+ },
+});
+
+
+
Requirements
+
Types should have following resolvers:
+
+
count - for counting records
+
findMany - for filtering records. Also required that this resolver supports search with operators (lt, gt), which used in directionFilter option. Resolver findMany should have filter argument, which will be copied to connection. Also should have limit and skip args.
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-elasticsearch.html b/docs/6.x.x/plugins/plugin-elasticsearch.html
new file mode 100644
index 00000000..d383a980
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-elasticsearch.html
@@ -0,0 +1,234 @@
+graphql-compose-elasticsearch · graphql-compose
This module expose Elastic Search REST API via GraphQL.
+
Elastic Search REST API proxy
+
Supported all elastic versions that support official elasticsearch-js client. Internally it parses its source code annotations and generates all available methods with params and descriptions to GraphQL Field Config Map. You may put this config map to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import elasticsearch from'elasticsearch';
+import { elasticApiFieldConfig } from'graphql-compose-elasticsearch';
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ elastic50: elasticApiFieldConfig(
+ // you may provide existed Elastic Client instance
+ new elasticsearch.Client({
+ host: 'http://localhost:9200',
+ apiVersion: '5.0',
+ })
+ ),
+
+ // or may provide just config
+ elastic24: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '2.4',
+ }),
+
+ elastic17: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '1.7',
+ }),
+ },
+ }),
+});
+
In other side this module is a plugin for graphql-compose, which derives GraphQLType from your elastic mapping generates tons of types, provides all available methods in QueryDSL, Aggregations, Sorting with field autocompletion according to types in your mapping (like Dev Tools Console in Kibana).
+
Generated ObjectTypeComposer model has several awesome resolvers:
+
+
search - greatly simplified elastic search method. According to GraphQL adaptation and its projection bunch of params setup automatically due your graphql query (eg _source, explain, version, trackScores), other rare fine tuning params moved to opts input field.
+
searchConnection - elastic search method that implements Relay Cursor Connection spec for infinite lists. Internally it uses cheap search_after API. One downside, Elastic does not support backward scrolling, so before argument will not work.
+
more resolvers will be later after my vacation: suggest, getById, updateById and others
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-json.html b/docs/6.x.x/plugins/plugin-json.html
new file mode 100644
index 00000000..e27b7c7c
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-json.html
@@ -0,0 +1,311 @@
+graphql-compose-json · graphql-compose
This is a plugin for graphql-compose, which generates GraphQLTypes from REST response or any JSON. It takes fields from object, determines their types and construct GraphQLObjectType with same shape.
+
Demo
+
We have a Live demo (source code repo) which shows how to build an API upon SWAPI using graphql-compose-json.
Modules graphql, graphql-compose, are located in peerDependencies, so they should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
You have a sample response object restApiResponse which you can pass to graphql-compose-json along with desired type name as your first argument and it will automatically generate a composed GraphQL type PersonTC.
graphql-compose provides a vast variety of methods for fields and resolvers (aka field configs in vanilla GraphQL) management of GraphQL types. To learn more visit graphql-compose repo.
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-mongoose.html b/docs/6.x.x/plugins/plugin-mongoose.html
new file mode 100644
index 00000000..3b551080
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-mongoose.html
@@ -0,0 +1,639 @@
+graphql-compose-mongoose · graphql-compose
This is a plugin for graphql-compose, which derives GraphQLType from your mongoose model. Also derives bunch of internal GraphQL Types. Provide all CRUD resolvers, including graphql connection, also provided basic search via operators ($lt, $gt and so on).
Modules graphql, graphql-compose, mongoose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
If you want to add additional resolvers connection and/or pagination - just install following packages and graphql-compose-mongoose will add them automatically.
UserTC - this is a ObjectTypeComposer instance for User. ObjectTypeComposer has GraphQLObjectType inside, avaliable via method UserTC.getType().
+
Here and in all other places of code variables suffix ...TC means that this is ObjectTypeComposer instance, ...ITC - InputTypeComposer, ...ETC - EnumTypeComposer.
+
+
import mongoose from'mongoose';
+import { composeWithMongoose } from'graphql-compose-mongoose';
+import { schemaComposer } from'graphql-compose';
+
+// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
+const LanguagesSchema = new mongoose.Schema({
+ language: String,
+ skill: {
+ type: String,
+ enum: [ 'basic', 'fluent', 'native' ],
+ },
+});
+
+const UserSchema = new mongoose.Schema({
+ name: String, // standard types
+ age: {
+ type: Number,
+ index: true,
+ },
+ languages: {
+ type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
+ default: [],
+ },
+ contacts: { // another mongoose way for providing embedded documents
+ email: String,
+ phones: [String], // array of strings
+ },
+ gender: { // enum field with values
+ type: String,
+ enum: ['male', 'female', 'ladyboy'],
+ },
+ someMixed: {
+ type: mongoose.Schema.Types.Mixed,
+ description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
+ },
+});
+const User = mongoose.model('User', UserSchema);
+
+
+
+// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
+const customizationOptions = {}; // left it empty for simplicity, described below
+const UserTC = composeWithMongoose(User, customizationOptions);
+
+// STEP 3: Add needed CRUD User operations to the GraphQL Schema
+// via graphql-compose it will be much much easier, with less typing
+schemaComposer.Query.addFields({
+ userById: UserTC.getResolver('findById'),
+ userByIds: UserTC.getResolver('findByIds'),
+ userOne: UserTC.getResolver('findOne'),
+ userMany: UserTC.getResolver('findMany'),
+ userCount: UserTC.getResolver('count'),
+ userConnection: UserTC.getResolver('connection'),
+ userPagination: UserTC.getResolver('pagination'),
+});
+
+schemaComposer.Mutation.addFields({
+ userCreateOne: UserTC.getResolver('createOne'),
+ userCreateMany: UserTC.getResolver('createMany'),
+ userUpdateById: UserTC.getResolver('updateById'),
+ userUpdateOne: UserTC.getResolver('updateOne'),
+ userUpdateMany: UserTC.getResolver('updateMany'),
+ userRemoveById: UserTC.getResolver('removeById'),
+ userRemoveOne: UserTC.getResolver('removeOne'),
+ userRemoveMany: UserTC.getResolver('removeMany'),
+});
+
+const graphqlSchema = schemaComposer.buildSchema();
+exportdefault graphqlSchema;
+
+
That's all!
+You think that is to much code?
+I don't think so, because by default internally was created about 55 graphql types (for input, sorting, filtering). So you will need much much more lines of code to implement all these CRUD operations by hands.
+
Working with Mongoose Collection Level Discriminators
+
Variable Namings
+
+
...DTC - Suffix for a DiscriminatorTypeComposer instance, which is also an instance of ObjectTypeComposer. All fields and Relations manipulations on this instance affects all registered discriminators and the Discriminator Interface.
const UserTC = composeWithMongoose(User);
+UserTC.getType(); // returns GraphQLObjectType
+UserTC.getInputType(); // returns GraphQLInputObjectType, eg. for args
+UserTC.get('languages').getType(); // get GraphQLObjectType for nested field
+UserTC.get('fieldWithNesting.subNesting').getType(); // get GraphQL type of deep nested field
+
Suppose you User model has friendsIds field with array of user ids. So let build some relations:
+
UserTC.addRelation(
+ 'friends',
+ {
+ resolver: () => UserTC.getResolver('findByIds'),
+ prepareArgs: { // resolver `findByIds` has `_ids` arg, let provide value to it
+ _ids: (source) => source.friendsIds,
+ },
+ projection: { friendsIds: 1 }, // point fields in source object, which should be fetched from DB
+ }
+);
+UserTC.addRelation(
+ 'adultFriendsWithSameGender',
+ {
+ resolver: () => UserTC.get('$findMany'), // shorthand for `UserTC.getResolver('findMany')`
+ prepareArgs: { // resolver `findMany` has `filter` arg, we may provide mongoose query to it
+ filter: (source) => ({
+ _operators : { // Applying criteria on fields which have
+ // operators enabled for them (by default, indexed fields only)
+ _id : { in: source.friendsIds },
+ age: { gt: 21 }
+ },
+ gender: source.gender,
+ }),
+ limit: 10,
+ },
+ projection: { friendsIds: 1, gender: 1 }, // required fields from source object
+ }
+);
+
+
Reusing the same mongoose Schema in embedded object fields
+
Suppose you have a common structure you use as embedded object in multiple Schemas.
+Also suppose you want the structure to have the same GraphQL type across all parent types.
+(For instance, to allow reuse of fragments for this type)
+Here are Schemas to demonstrate:
If you want the ImageDataStructure to use the same GraphQL type in both Article and UserProfile you will need create it as a mongoose schema (not a standard javascript object) and to explicitly tell graphql-compose-mongoose the name you want it to have. Otherwise, without the name, it would generate the name according to the first parent this type was embedded in.
+
Do the following:
+
import { schemaComposer } from'graphql-compose'; // get the default schemaComposer or your created schemaComposer
+import { convertSchemaToGraphQL } from'graphql-compose-mongoose';
+
+convertSchemaToGraphQL(ImageDataStructure, 'EmbeddedImage', schemaComposer); // Force this type on this mongoose schema
+
+
Before continuing to convert your models to TypeComposers:
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can create your own resolver of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts before save hook which can be provided in resolver params as param named beforeRecordMutate. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
This is opts.resolvers level of options.
+If you set the option to false it will disable resolver or some of its input args.
+Every resolver's arg has it own options. They described below.
This is opts.resolvers.[resolverName].[filter|sort|record|limit] level of options.
+You may tune every resolver's args independently as you wish.
+Here you may setup every argument and override some fields from the default input object type, described above in opts.inputType.
+
export type filterHelperArgsOpts = {
+ filterTypeName?: string, // type name for `filter`
+ isRequired?: boolean, // set `filter` arg as required (wraps in GraphQLNonNull)
+ onlyIndexed?: boolean, // leave only that fields, which is indexed in mongodb
+ requiredFields?: string | string[], // provide fieldNames, that should be required
+ operators?: filterOperatorsOpts | false, // provide filtering fields by operators, eg. $lt, $gt
+ // if left empty - provides all operators on indexed fields
+};
+
+// supported operators names in filter `arg`
+export type filterOperatorNames = 'gt' | 'gte' | 'lt' | 'lte' | 'ne' | 'in[]' | 'nin[]';
+export type filterOperatorsOpts = { [fieldName: string]: filterOperatorNames[] | false };
+
+export type sortHelperArgsOpts = {
+ sortTypeName?: string, // type name for `sort`
+};
+
+export type recordHelperArgsOpts = {
+ recordTypeName?: string, // type name for `record`
+ isRequired?: boolean, // set `record` arg as required (wraps in GraphQLNonNull)
+ removeFields?: string[], // provide fieldNames, that should be removed
+ requiredFields?: string[], // provide fieldNames, that should be required
+};
+
+export type limitHelperArgsOpts = {
+ defaultValue?: number, // set your default limit, if it not provided in query (default: 1000)
+};
+
This plugin adds connection resolver. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
+
Besides standard connection arguments first, last, before and after, also added great arguments:
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-pagination.html b/docs/6.x.x/plugins/plugin-pagination.html
new file mode 100644
index 00000000..41565c20
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-pagination.html
@@ -0,0 +1,135 @@
+graphql-compose-pagination · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-relay.html b/docs/6.x.x/plugins/plugin-relay.html
new file mode 100644
index 00000000..fdc54d60
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-relay.html
@@ -0,0 +1,148 @@
+graphql-compose-relay · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
ObjectTypeComposer is a graphql-compose utility, that wraps GraphQL types and provide bunch of useful methods for type manipulation.
+
import composeWithRelay from'graphql-compose-relay';
+import { ObjectTypeComposer } from'graphql-compose';
+import { RootQueryType, UserType } from'./my-graphq-object-types';
+
+const rootQueryTypeComposer = new ObjectTypeComposer(RootQueryType);
+const userTypeComposer = new ObjectTypeComposer(UserType);
+
+// If passed RootQuery, then will be added only `node` field to this type.
+// Via RootQuery.node you may find objects by globally unique ID among all types.
+composeWithRelay(rootQueryTypeComposer);
+
+// Other types, like User, will be wrapped with middlewares that:
+// - add relay's id field. Field will be added or wrapped to return Relay's globally unique ID.
+// - for mutations will be added clientMutationId to input and output objects types
+// - this type will be added to NodeInterface for resolving via RootQuery.node
+composeWithRelay(userTypeComposer);
+
+
That's all!
+
All mutations resolvers' arguments will be placed into input field, and added clientMutationId. If input fields already exists in resolver, then clientMutationId will be added to it, rest argument stays untouched. Accepted value via args.input.clientMutationId will be transfer to payload.clientMutationId, as Relay required it.
+
To all wrapped Types with Relay, will be added id field or wrapped, if it exist already. This field will return globally unique ID among all types in the following format base64(TypeName + ':' + recordId).
+
For RootQuery will be added node field, that will resolve by globalId only that types, which you wrap with composeWithRelay.
+
All this annoying operations is too fatigue to do by hands. So this middleware done all Relay magic implicitly for you.
+
Requirements
+
Method composeWithRelay accept ObjectTypeComposer as input argument. So ObjectTypeComposer should meet following requirements:
+
+
has defined recordIdFn (function that from object of this type, returns you id for the globalId construction)
+
should have findById resolver (that will be used by RootQuery.node)
+
+
If something is missing composeWithRelay throws error.
\ No newline at end of file
diff --git a/docs/6.x.x/plugins/plugin-writing-custom-plugin.html b/docs/6.x.x/plugins/plugin-writing-custom-plugin.html
new file mode 100644
index 00000000..ad12e844
--- /dev/null
+++ b/docs/6.x.x/plugins/plugin-writing-custom-plugin.html
@@ -0,0 +1,59 @@
+[WIP] How to write a custom plugin · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/recipes/authorization.html b/docs/6.x.x/recipes/authorization.html
new file mode 100644
index 00000000..f1bb9b64
--- /dev/null
+++ b/docs/6.x.x/recipes/authorization.html
@@ -0,0 +1,59 @@
+[WIP] Authorization · graphql-compose
\ No newline at end of file
diff --git a/docs/6.x.x/recipes/writing-tests.html b/docs/6.x.x/recipes/writing-tests.html
new file mode 100644
index 00000000..0c4d5c3e
--- /dev/null
+++ b/docs/6.x.x/recipes/writing-tests.html
@@ -0,0 +1,59 @@
+[WIP] Writing tests · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/api/EnumTypeComposer.html b/docs/7.x.x/api/EnumTypeComposer.html
new file mode 100644
index 00000000..f1d1b02b
--- /dev/null
+++ b/docs/7.x.x/api/EnumTypeComposer.html
@@ -0,0 +1,406 @@
+EnumTypeComposer · graphql-compose
You may clone this type with a new provided name as string.
+Or you may provide a new TypeComposer which will get all clonned
+settings from this type.
+
merge()
+
merge(
+ type: GraphQLEnumType | EnumTypeComposer<any>
+): this
+
+
Extensions methods
+
getExtensions()
+
getExtensions(): Extensions
+
+
setExtensions()
+
setExtensions(
+ extensions: Extensions
+): this
+
+
extendExtensions()
+
extendExtensions(
+ extensions: Extensions
+): this
+
\ No newline at end of file
diff --git a/docs/7.x.x/api/InputTypeComposer.html b/docs/7.x.x/api/InputTypeComposer.html
new file mode 100644
index 00000000..236ca01f
--- /dev/null
+++ b/docs/7.x.x/api/InputTypeComposer.html
@@ -0,0 +1,504 @@
+InputTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
You may clone this type with a new provided name as string.
+Or you may provide a new TypeComposer which will get all clonned
+settings from this type.
+
merge()
+
merge(
+ type: GraphQLInputObjectType | InputTypeComposer<any>
+): this
+
+
Extensions methods
+
getExtensions()
+
getExtensions(): Extensions
+
+
setExtensions()
+
setExtensions(
+ extensions: Extensions
+): this
+
+
extendExtensions()
+
extendExtensions(
+ extensions: Extensions
+): this
+
setFieldExtension(
+ fieldName: string,
+ extensionName: string,
+ value: any
+): this
+
+
removeFieldExtension()
+
removeFieldExtension(
+ fieldName: string,
+ extensionName: string
+): this
+
+
Directive methods
+
Directive methods are usefull if you declare your schemas via SDL.
+Users who actively use graphql-tools can open new abilities for writing
+your own directive handlers.
+
If you create your schemas via config objects, then probably you
+no need in directives. Instead directives better to use extensions.
\ No newline at end of file
diff --git a/docs/7.x.x/api/InterfaceTypeComposer.html b/docs/7.x.x/api/InterfaceTypeComposer.html
new file mode 100644
index 00000000..54f0b5db
--- /dev/null
+++ b/docs/7.x.x/api/InterfaceTypeComposer.html
@@ -0,0 +1,713 @@
+InterfaceTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify args types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+isFieldArgPlural() – checks is arg wrapped in ListComposer or not
+makeFieldArgPlural() – for arg wrapping in ListComposer
+makeFieldArgNonPlural() – for arg unwrapping from ListComposer
+isFieldArgNonNull() – checks is arg wrapped in NonNullComposer or not
+makeFieldArgNonNull() – for arg wrapping in NonNullComposer
+makeFieldArgNullable() – for arg unwrapping from NonNullComposer
clone(
+ newTypeNameOrTC: string | InterfaceTypeComposer<any, any>
+): this
+
+
You may clone this type with a new provided name as string.
+Or you may provide a new TypeComposer which will get all clonned
+settings from this type.
\ No newline at end of file
diff --git a/docs/7.x.x/api/ListComposer.html b/docs/7.x.x/api/ListComposer.html
new file mode 100644
index 00000000..becf03c4
--- /dev/null
+++ b/docs/7.x.x/api/ListComposer.html
@@ -0,0 +1,84 @@
+ListComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/api/NonNullComposer.html b/docs/7.x.x/api/NonNullComposer.html
new file mode 100644
index 00000000..37dcd61f
--- /dev/null
+++ b/docs/7.x.x/api/NonNullComposer.html
@@ -0,0 +1,84 @@
+NonNullComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/api/ObjectTypeComposer.html b/docs/7.x.x/api/ObjectTypeComposer.html
new file mode 100644
index 00000000..54a4b26e
--- /dev/null
+++ b/docs/7.x.x/api/ObjectTypeComposer.html
@@ -0,0 +1,949 @@
+ObjectTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify args types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+isFieldArgPlural() – checks is arg wrapped in ListComposer or not
+makeFieldArgPlural() – for arg wrapping in ListComposer
+makeFieldArgNonPlural() – for arg unwrapping from ListComposer
+isFieldArgNonNull() – checks is arg wrapped in NonNullComposer or not
+makeFieldArgNonNull() – for arg wrapping in NonNullComposer
+makeFieldArgNullable() – for arg unwrapping from NonNullComposer
You may clone this type with a new provided name as string.
+Or you may provide a new TypeComposer which will get all clonned
+settings from this type.
Merge fields and interfaces from provided GraphQLObjectType, or ObjectTypeComposer.
+Also you may provide GraphQLInterfaceType or InterfaceTypeComposer for adding fields.
+
InputType methods
+
getInputType()
+
getInputType(): GraphQLInputObjectType
+
+
hasInputTypeComposer()
+
hasInputTypeComposer(): boolean
+
+
setInputTypeComposer()
+
setInputTypeComposer(
+ itc: InputTypeComposer<TContext>
+): this
+
Directive methods are usefull if you declare your schemas via SDL.
+Users who actively use graphql-tools can open new abilities for writing
+your own directive handlers.
+
If you create your schemas via config objects, then probably you
+no need in directives. Instead directives better to use extensions.
\ No newline at end of file
diff --git a/docs/7.x.x/api/Resolver.html b/docs/7.x.x/api/Resolver.html
new file mode 100644
index 00000000..7fb8b8bb
--- /dev/null
+++ b/docs/7.x.x/api/Resolver.html
@@ -0,0 +1,563 @@
+Resolver · graphql-compose
The most interesting class in graphql-compose. The main goal of Resolver is to keep available resolve methods for Type and use them for building relation with other types.
export type ResolverSortArgConfig<TSource, TContext, TArgs = ArgsMap> = {
+ name: string;
+ sortTypeNameFallback?: string;
+ // value also can be an `Object`, but flow does not understande union with object and function
+ // see https://github.com/facebook/flow/issues/1948
+ value:
+ | { [key: string]: any }
+ | ResolverSortArgFn<TSource, TContext, TArgs>
+ | string
+ | number
+ | boolean
+ | any[];
+ deprecationReason?: string | null;
+ description?: string | null;
+};
+
\ No newline at end of file
diff --git a/docs/7.x.x/api/ScalarTypeComposer.html b/docs/7.x.x/api/ScalarTypeComposer.html
new file mode 100644
index 00000000..8d3e1f6a
--- /dev/null
+++ b/docs/7.x.x/api/ScalarTypeComposer.html
@@ -0,0 +1,265 @@
+ScalarTypeComposer · graphql-compose
You may clone this type with a new provided name as string.
+Or you may provide a new TypeComposer which will get all clonned
+settings from this type.
+
merge()
+
merge(
+ type: GraphQLScalarType | ScalarTypeComposer<any>
+): this
+
+
Extensions methods
+
getExtensions()
+
getExtensions(): Extensions
+
+
setExtensions()
+
setExtensions(
+ extensions: Extensions
+): this
+
+
extendExtensions()
+
extendExtensions(
+ extensions: Extensions
+): this
+
\ No newline at end of file
diff --git a/docs/7.x.x/api/SchemaComposer.html b/docs/7.x.x/api/SchemaComposer.html
new file mode 100644
index 00000000..e658de0e
--- /dev/null
+++ b/docs/7.x.x/api/SchemaComposer.html
@@ -0,0 +1,448 @@
+SchemaComposer · graphql-compose
Create GraphQLSchema instance from defined types.
+This instance can be provided to express-graphql, apollo-server, graphql-yoga etc.
+
addSchemaMustHaveType()
+
addSchemaMustHaveType(
+ type: AnyType<TContext>
+): this
+
+
When using Interfaces you may have such Types which are hidden under Interface.resolveType method. In such cases you should add these types explicitly. Cause buildSchema() will take only real used types and types which added via addSchemaMustHaveType() method.
createTC(
+ typeOrSDL: any
+): NamedTypeComposer<TContext>
+
+
Creates or return existed TypeComposer from SDL or object.
+If you call this method again with same params should be returned the same TypeComposer instance.
+
createTempTC()
+
createTempTC(
+ typeOrSDL: any
+): NamedTypeComposer<TContext>
+
+
Creates TypeComposer from SDL or object without adding it to the type storage.
\ No newline at end of file
diff --git a/docs/7.x.x/api/ThunkComposer.html b/docs/7.x.x/api/ThunkComposer.html
new file mode 100644
index 00000000..2ed2f7a3
--- /dev/null
+++ b/docs/7.x.x/api/ThunkComposer.html
@@ -0,0 +1,84 @@
+ThunkComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/api/TypeComposer.html b/docs/7.x.x/api/TypeComposer.html
new file mode 100644
index 00000000..fabe35c2
--- /dev/null
+++ b/docs/7.x.x/api/TypeComposer.html
@@ -0,0 +1,549 @@
+TypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/api/TypeMapper.html b/docs/7.x.x/api/TypeMapper.html
new file mode 100644
index 00000000..169e5aac
--- /dev/null
+++ b/docs/7.x.x/api/TypeMapper.html
@@ -0,0 +1,288 @@
+TypeMapper · graphql-compose
Type storage and type generator from Schema Definition Language (SDL).
+This is slightly rewritten buildASTSchema
+utility from graphql-js that allows to create type from a string (SDL).
+
Static methods
+
static isOutputType()
+
static isOutputType(
+ type: any
+): type is ComposeOutputType<any>
+
+
Check that provided TypeComposer is OutputType (Object, Scalar, Enum, Interface, Union).
+It may be wrapped in NonNull or List.
+
static isInputType()
+
static isInputType(
+ type: any
+): type is ComposeInputType
+
+
Check that provided TypeComposer is InputType (InputObject, Scalar, Enum).
+It may be wrapped in NonNull or List.
\ No newline at end of file
diff --git a/docs/7.x.x/api/UnionTypeComposer.html b/docs/7.x.x/api/UnionTypeComposer.html
new file mode 100644
index 00000000..22441b90
--- /dev/null
+++ b/docs/7.x.x/api/UnionTypeComposer.html
@@ -0,0 +1,355 @@
+UnionTypeComposer · graphql-compose
You may clone this type with a new provided name as string.
+Or you may provide a new TypeComposer which will get all clonned
+settings from this type.
+
merge()
+
merge(
+ type: GraphQLUnionType | UnionTypeComposer<any, any>
+): this
+
\ No newline at end of file
diff --git a/docs/7.x.x/api/misc-api-methods.html b/docs/7.x.x/api/misc-api-methods.html
new file mode 100644
index 00000000..d4532eae
--- /dev/null
+++ b/docs/7.x.x/api/misc-api-methods.html
@@ -0,0 +1,427 @@
+API misc · graphql-compose
The same as getProjectionFromAST except that all nested fields will not be extracted. Complex address will be just true, not { city: true, street: true },
graphql-compose re-exports GraphQL.js package for its plugins. It helps to avoid the hell with maintaining versions of graphql and graphql-compose in plugins' package.json files.
+
If you want to write a plugin for graphql-compose and publish it to npm, just add graphql-compose in dependencies of its package.json. And if you will need GraphQL.js objects and methods you may import them in such way:
+
// My awesome Plugin for graphql-compose
+import { graphql } from'graphql-compose';
+
+const { GraphQLNonNull, GraphQLObjectType } = graphql;
+
+
graphqlVersion
+
Sometimes it need to know which version of GraphQL.js is installed in the project.
+It may be used in graphql-compose plugins, cause different versions of GraphQL.js may have breaking changes and your plugins may have workarounds for different behavior.
+
const graphqlVersion: number;
+
+
import { graphqlVersion } from'graphql-compose';
+
+if (graphqlVersion < 13) {
+ throwError(`This plugin does not work with GraphQL.js v${graphqlVersion}`);
+}
+
+
Scalar Types
+
GraphQLDate
+
GraphQL scalar type that converts javascript Date object to string YYYY-MM-DDTHH:MM:SS.SSSZ and back.
+
import { GraphQLDate } from'graphql-compose';
+
+
GraphQLJSON
+
GraphQL scalar type that represents JSON. Field with this type may have arbitrary structure. Copied from @taion's graphql-type-json for reducing dependencies tree.
+
import { GraphQLJSON } from'graphql-compose';
+
+
TypeStorage
+
You may need some isolated storage for keeping types in your plugins. So TypeStorage is the easy way to obtain such storage.
\ No newline at end of file
diff --git a/docs/7.x.x/basics/generating-schema.html b/docs/7.x.x/basics/generating-schema.html
new file mode 100644
index 00000000..a9822e29
--- /dev/null
+++ b/docs/7.x.x/basics/generating-schema.html
@@ -0,0 +1,214 @@
+Generating Schema · graphql-compose
SchemaComposer is a builder of GraphQLSchema object. Obtained Schema via buildSchema() method may be used in express-graphql, apollo-server and other libs which uses GraphQL.js under the hood for query execution at runtime.
+
Create Schema
+
SchemaComposer provides basic root types Query, Mutation, Subscription. You must add fields at least to one of these types, otherwise Schema will not have sense and cannot be build.
+
import { schemaComposer } from'graphql-compose';
+import { AuthorTC } from'./author';
+
+schemaComposer.Query.addFields({
+ // add field with regular FieldConfig
+ currentTime: {
+ type: 'Date',
+ resolve: () =>Date.now(),
+ },
+ // Assume that `AuthorTC` build with `graphql-compose-mongoose` which has CRUD resolvers
+ // in such case we can use pre-generated Resolvers as a FieldConfig
+ authorById: AuthorTC.getResolver('findById'),
+ authorMany: AuthorTC.getResolver('findMany'),
+ // ...
+});
+
+schemaComposer.Mutation.addNestedFields({
+ // also it may be very useful define nested fields
+ // Mutation will have `author` field, `author` will have `create` and `update` fields inside
+ 'author.create': AuthorTC.getResolver('createOne'),
+ 'author.update': AuthorTC.getResolver('updateById'),
+ // ...
+});
+
+exportdefault schemaComposer.buildSchema(); // exports GraphQLSchema
+
+
Restrict access
+
GraphQL.js does not provide any access rights checks. You should it do manually in resolve methods. With graphql-compose you may do it via wrapping Resolvers:
+
// rootMutation.js
+import { schemaComposer } from'graphql-compose';
+
+import { CommentTC } from'./comment';
+import { UserTC } from'./user';
+
+schemaComposer.Mutation.addNestedFields({
+ commentCreate: CommentTC.getResolver('createOne'), // may anybody
+
+ ...adminAccess({
+ // only for admins
+ 'user.create': UserTC.getResolver('createOne'),
+ 'user.update': UserTC.getResolver('updateById'),
+ 'user.remove': UserTC.getResolver('removeById'),
+ }),
+});
+
+functionadminAccess(resolvers) {
+ Object.keys(resolvers).forEach(k => {
+ resolvers[k] = resolvers[k].wrapResolve(next => rp => {
+ if (!rp.context.isAdmin) {
+ thrownewError('You should be admin, to have access to this action.');
+ }
+ return next(rp);
+ });
+ });
+ return resolvers;
+}
+
+
For getting isAdmin property from context you must define it in express-graphql or apollo-server:
In some complex scenarios you may need to have several GraphQL Schemas in one app. Graphql-compose by default exports following classes/instances for single schema mode:
Types created via ObjectTypeComposer1 and ObjectTypeComposer2 will not be visible to each other. So may have different definitions for types with the same name.
\ No newline at end of file
diff --git a/docs/7.x.x/basics/type-modification.html b/docs/7.x.x/basics/type-modification.html
new file mode 100644
index 00000000..43b15645
--- /dev/null
+++ b/docs/7.x.x/basics/type-modification.html
@@ -0,0 +1,209 @@
+Type modification · graphql-compose
This is the most important part of graphql-compose and the main difference in Schema creation with GraphQL.js. In GraphQL.js you have strict abilities in type definition and its further modification. But graphql-compose allows to you modify types after creation in very convenient ways.
+
+
Note: With graphql-compose you may modify types before GraphQLSchema object creation. When schema was created you cannot change types.
+
+
Fields modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer instances:
+
+
getFields()
+
setFields()
+
getFieldNames()
+
hasField(name)
+
setField(name, fieldConfig)
+
addFields(newFieldsConfig)
+
getField(name)
+
removeField(nameOrArray)
+
removeOtherFields(nameOrArray)
+
extendField(name, partialFieldConfig)
+
reorderFields(names)
+
deprecateFields(nameOrMap)
+
+
Additional methods in ObjectTypeComposer, InputTypeComposer, InterfaceTypeComposer instances:
+
+
getFieldType(name)
+
getFieldTC(name)
+
getFieldConfig(name)
+
makeFieldNonNull(nameOrArray)
+
makeFieldNullable(nameOrArray)
+
addNestedFields(newFields)
+
+
// add description to `firstName`
+AuthorTC.extendField('firstName', {
+ description: "This field returns Author's first name",
+});
+
+// Add new field `status` with Enum type
+AuthorTC.addField('status', `enum AuthorStatus { ACTIVE INACTIVE }`);
+
+// Change order of fields in type
+// unlisted fields will be added to the end of field list with old order
+AuthorTC.reorderFields(['status', 'firstName']);
+
+// Mark fields as deprecated with some message
+AuthorTC.deprecateFields({
+ rating: 'This field will be removed in June 2018',
+ dob: 'Use `age` field instead. This field will be removed in June 2018',
+});
+
+// Add new field with `address` name and for type
+// create a new object type with `city` and `country` fields
+AuthorTC.addNestedFields({
+ 'address.city': 'String',
+ 'address.country': 'String',
+});
+
+
Type modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer, UnionTypeComposer instances:
+
+
getType()
+
getTypePlural()
+
getTypeNonNull()
+
getTypeName()
+
setTypeName(newName)
+
getDescription()
+
setDescription()
+
clone(newTypeName)
+
+
Additional methods in ObjectTypeComposer
+
+
getInterfaces()
+
setInterfaces(interfaces)
+
hasInterface(interfaceObj)
+
addInterface(interfaceObj)
+
removeInterface(interfaceObj)
+
getInputType()
+
getITC()
+
+
Create your custom modification function
+
With this set of methods, you may write your own type modification functions. It may greatly reduce repetitive code across your schema definition.
+
As an example, lets write a function which will add rawData field with full record data from database. Also check isAdmin = true in context and if so return data, otherwise return null.
+
functionaddRawData(tc: ObjectTypeComposer<any, any>) {
+ if (!tc.hasField('rawData')) {
+ tc.addField('rawData', {
+ type: 'JSON',
+ resolve: (source, args, context) => {
+ if (context.isAdmin) {
+ return source;
+ }
+ returnnull;
+ },
+ // add magic property `projection`
+ // which request all fields from database
+ // when requested this `rawData` field in the query
+ projection: { '*': 1 },
+ });
+ }
+}
+addRawData(AuthorTC);
+addRawData(PostTC);
+
+
Or even more
+
You may write your own plugins which will generate types from some models or non-graphql schemas. Take a look on avaliable list of plugins build on top of graphql-compose.
\ No newline at end of file
diff --git a/docs/7.x.x/basics/understanding-relations.html b/docs/7.x.x/basics/understanding-relations.html
new file mode 100644
index 00000000..c315fd8a
--- /dev/null
+++ b/docs/7.x.x/basics/understanding-relations.html
@@ -0,0 +1,311 @@
+Relations between Types · graphql-compose
GraphQL allows to create additional fields in your types which may provide data from another type. For example, you may add a field posts to the Author type and write a resolve function, so that this field will return an array of posts only for the current Author.
What if we want provide a filter argument, which adds the ability to filter by creation date, and min number of votes?
+That would be achieved by the following code:
Hm, it has become quite long. And what if you have other Types which have relations with Posts (eg. Reviewer, Reader)? Copy/pasting our resolve method probably is not a good idea. That's because in the future you may want to add a new filter property, and that would mean scanning all your code and adding additional logic in all FieldConfigs. So if you're met with such a problem, the next section is for you.
+
Relation via Resolver
+
If you need to use the same FieldConfigs in different Types graphql-compose provides the Resolver class. You may create a Resolver which will define type, args and resolve and reuse it everywhere you need in your Schema.
+
However if you put posts resolver in a separate file, you will face another problem
+
+
in Author type you will use criteria = { authorId: source.id } for the resolve method;
+
in Reviewer - criteria = { reviewers: { $has: source.id } } and so on.
+
+
In this case it's better to improve args.filter by allowing to set authorId and reviewerId via arguments:
Should be an arrow function that returns Resolver. Wrapping resolver in an arrow function helps solving the hoisting problem (when two types import each other).
+
prepareArgs
+
At runtime we should have the ability to prepare (ie. assign a value to) the args that will be passed to Resolver.
+
For example our Resolver has the arguments filter, limit, skip and sort.
+prepareArgs provides a way to set them up:
+
+
limit: 10 - hides limit arg from schema and set it equal to 10
+
filter: (source) => value - hides filter arg form schema and at runtime evaluate its value
+
sort: null - disables argument (hides it from schema and do not pass it to resolver)
+
all undescribed args (like skip) will be avaliable in the schema and will be avaliable in query
+
+
projection
+
Is a very useful option for extending requested fields in your query. It's very good practice to request from database only the fields included in our query. But sometimes we need additional fields, for example to provide the findById resolver with an authorId. For this purpose you need to use projection.
Without projection the resolver would try to populate the author field, but args.authorId would be undefined. It would therefore be impossible for the query filter to find matching authors and populate the author field. Normally when a client wants to retrieve the author field in a GraphQL Query, it would also need to provide the authorId explicitly. By using a projection we lift that responsility from the client, making querying easier and less cluttered.
\ No newline at end of file
diff --git a/docs/7.x.x/basics/understanding-types.html b/docs/7.x.x/basics/understanding-types.html
new file mode 100644
index 00000000..dcbe1dc8
--- /dev/null
+++ b/docs/7.x.x/basics/understanding-types.html
@@ -0,0 +1,401 @@
+Type creation · graphql-compose
With graphql-compose you need to create types under some schemaComposer instance. By default graphql-compose has a global schemaComposer instance which can be obtained in the following manner:
But if you need to create several GrasphQL schemas in your app, you may import SchemaComposer class and create schemaComposer instances as much as you need:
+
import { SchemaComposer } from'graphql-compose';
+
+const schemaComposer1 = new SchemaComposer();
+const schemaComposer2 = new SchemaComposer();
+
+
Take a note that schemaComposer1 and schemaComposer2 will have different type storages. And types in schemaComposer1 will not be avaliable in schemaComposer2 and vice versa.
+
Scalar types
+
Graphql-compose has following built-in scalar types:
+
+
String
+
Float
+
Int
+
Boolean
+
ID
+
Date
+
JSON
+
+
via config
+
You may create scalar types via config, like with GraphQLScalarType:
If you need to create some complex type with several properties (fields), you will need to use ObjectTypeComposer. It's a builder for GraphQLObjectType object.
+
ObjectTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Output type. Such definition provides better developer experience with jumping to the type declarations.
+
const AuthorTC = schemaComposer.createObjectTC({
+ name: 'Author',
+ fields: {
+ id: 'Int!',
+ firstName: 'String',
+ lastName: 'String',
+ posts: {
+ type: () => [PostTC], // arrow function fot `type` helps to solve hoisting problems and keep ability to list all fields
+ args: {
+ limit: { type: 'Int', defaultValue: 20 },
+ skip: 'Int', // shortand to `{ type: 'Int' }`
+ sort: `enum AuthorPostsSortEnum { ASC DESC }`, // type creation via SDL
+ },
+ resolve: () => { ... },
+ }
+ },
+});
+
+
Also this way of definition provides a lot of syntax sugar for field definition:
+
const AuthorTC = schemaComposer.createObjectTC({
+ posts: {
+ // wrapping Type with arrow function helps to solve a hoisting problem
+ // also using type instances provides better DX
+ // (ctrl+click allows to jump to PostTC type declaration in your IDE)
+ type: () => PostTC,
+ description: 'Posts written by Author',
+ resolve: (source, args, context, info) => {},
+ },
+ // using standard GraphQL Type
+ ucFirstName: {
+ type: GraphQLString,
+ resolve: (source) => { return source.firstName.toUpperCase(); },
+ // also request `firstName` field which must be loaded from database
+ projection: { firstName: true },
+ },
+ // fast way if you need to define only type
+ counter: 'Int',
+ // using SDL for definition new ObjectType
+ complex: `type ComplexType {
+ subField1: String
+ subField2: Float
+ subField3: Boolean
+ subField4: ID
+ subField5: JSON
+ subField6: Date
+ }`,
+ // SDL for defining array of strings, which is NonNull
+ list0: {
+ type: '[String]!',
+ description: 'Array of strings',
+ },
+ list1: '[String]',
+ list2: ['String'],
+ list3: [GraphQLString],
+ list4: [`type Complex2Type { f1: Float, f2: Int }`],
+});
+
+
via SDL
+
May have hoisting problems. Be aware that all used complex types must be already defined.
GraphQL allows to pass arguments for fields. You may freely use Scalars, Enums when describing input args. But what you should do in the case of mutations, where you might want to pass in a whole object to be created? For such cases for complex types instead of GraphQLObjectType you should use GraphQLInputObjectType. They they have small differences in its fields declaration:
+
+
input object type has defaultValue
+
input object type does not have args
+
input object type does not have resolve method
+
+
If you need to create some complex type with several properties, you will need to use InputTypeComposer. It's a builder for GraphQLInputObjectType object.
+
InputTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Input type. Such definition provides hoisting problems solution via wrapping types by arrow function. Better developer experience with jumping to the type declarations.
+
InputTypeComposer has the same type definition capabilities for describing fields as ObjectTypeComposer - as string, as arrow function, as SDL.
Useful when you write your own type generators. Enum has values (not fields), but for similar method naming with ObjectTypeComposer and InputTypeComposer in graphql-compose methods for value modification have field keyword.
Graphql-compose provides the following helper for Interfaces - InterfaceTypeComposer.
+
import { schemaComposer } from'graphql-compose';
+
+const TimestampInterface = schemaComposer.createInterfaceTC({
+ name: 'Timestampable',
+ description: 'An object with createdAt and updatedAt fields',
+ fields: {
+ createdAt: 'Date',
+ updatedAt: 'Date',
+ },
+});
+
+// When you create Interface, you need to provide instructions how to determine exact ObjectType from `value`.
+// So if `value` is instance of UserDoc, then use `UserTC` as exact type.
+TimestampInterface.addTypeResolver(UserTC, value => (value instanceof UserDoc));
+TimestampInterface.addTypeResolver(ArticleTC, value => (value instanceof UserDoc));
+
+
Lists
+
If you want indicate that field or argument return an array of some type, you may do the following:
+
import { GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field1: [AuthorTC], // RECOMMENDED just wrap in the regular js array
+ field2: AuthorTC.getTypePlural(), // call specific ObjectTypeComposer method
+ field3: '[Author]', // use SDL format
+ field4: new GraphQLList(AuthorTC.getType()) // use standard GraphQLList
+});
+
+
Non-Null
+
If you want indicate that field is not empty or argument is required:
+
import { GraphQLNonNull } from'graphql';
+
+SomeTypeComposer.addFields({
+ // field1: ???, // doesn't exists any regular object in js for expressing NonNull value
+ field2: AuthorTC.getTypeNonNull(), // call specific ObjectTypeComposer method
+ field3: 'Author!', // use SDL format
+ field4: new GraphQLNonNull(AuthorTC.getType()) // use standard GraphQLNonNull
+});
+
+
Non-Null List of Non-Null values may be expressed in following way:
+
import { GraphQLNonNull, GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field3: '[Author!]!', // use SDL format
+ field4: new GraphQLNonNull( // use standard GraphQLNonNull & GraphQLList
+ new GraphQLList(
+ new GraphQLNonNull(AuthorTC.getType())
+ )
+ )
+});
+
\ No newline at end of file
diff --git a/docs/7.x.x/basics/what-is-resolver.html b/docs/7.x.x/basics/what-is-resolver.html
new file mode 100644
index 00000000..a7d58e46
--- /dev/null
+++ b/docs/7.x.x/basics/what-is-resolver.html
@@ -0,0 +1,320 @@
+Resolvers · graphql-compose
Shortly, Resolver is an object which knows how to process data and what to return. It's like a function definition in static language where you give it name, describe types for input arguments and output result.
+
GraphQL.js describes such functions in complex output types via GraphQLFieldConfig:
GraphQLFieldConfig has information about returned type, available args, implementation of resolve logic and some other properties. In terms of graphql-compose this field config is called as Resolver.
+
The main aim of Resolver is to keep available resolve methods for Type and use them for building relation with other types. Resolver provide following abilities:
+
+
add, remove, get, make optional/required arguments
+
clone Resolver for further logic extension
+
wrap args, type, resolve (get resolver and create new one with extended/modified functionality)
+
provide helper methods addFilterArg and addSortArg which wrap resolver by adding argument and additional resolve logic
+
+
Resolver has following properties:
+
+
type output complex or scalar type (resolver returns data of this type)
+
args list of fields of input or scalar types (resolver accept input arguments for resolve method)
+
resolve method which contains your bussiness logic, for fetching, processing and returning data. BE AWARE: that all arguments (source, args, context, info) are passed inside one argument called as resolveParams (rp for brevity in the code).
+
description public description which will be passed to graphql schema and will be available via introspection
+
deprecationReason if you want to hide field from schema, but leave it working for old clients
+
name any name for resolver that allow to you identify what it does, eg findById, updateMany, removeOne
+
kind type of resolver query (resolver just fetch data) or mutation (resolver change data)
+
parent you may wrap existed Resolver for adding additional checks, modifying result, adding arguments. This property keeps reference to existed unwrapped Resolver
+
+
Why do we need the Resolver?
+
Graphql-compose allows creating such "functions" or "FieldConfigs" via giving it names and keep in your ObjectTypeComposer. You may create any number of Resolvers and store them in your type.
+
Assume you have an Author type. And you have different standard CRUD operations for fetching and modifying this type:
+
+
findById
+
findMany
+
updateById
+
removeById
+
etc
+
+
When you will construct your Schema, you may need several times the same logic from standard Resolvers. For example
+
+
in the Query type may be added fields
+
+
authorById for finding Author by id arg via findById resolver
+
authorMany for finding list of Author with some filter criteria via findMany resolver
+
+
in the Post type may be added
+
+
author field which request Author by id from current post.authorId value via findById resolver
+
reviewers field which request Authors via findMany resolver with custom filtering
+
+
+
Resolvers helps to describe CRUD operations logic only once and then reuse them in different scenarios. For Query.authorById provides its full functionality from findById resolver. For Post.author you wrap findById resolver where should be hidden id arg and its value automatically will be set from post.authorId. For wrapping Resolvers graphql-compose provides a bunch of methods.
+
Creating Resolver
+
via TC.addResolver()
+
Mostly Resolvers are created according to the specific Type. So it's better to create them and store in some ObjectTypeComposer instance.
+
Lets's take AuthorTC and describe how it can be found by id:
+
AuthorTC.addResolver({
+ name: 'findById',
+ args: { id: 'Int' },
+ type: AuthorTC,
+ resolve: async ({ source, args }) => {
+ const res = await fetch(`/endpoint/${args.id}`); // or some fetch from any database
+ const data = await res.json();
+ // here you may clean up `data` response from API or Database,
+ // it should has same shape like AuthorTC fields
+ // eg. { firstName: 'Peter', nickname: 'peet', views: 20 }
+ // if some fields in `data`:
+ // are undefined or missing - graphql returns `null` for that fields
+ // are not described in output `type` - graphql will remove them from responce
+ return data;
+ },
+});
+
+
And in any place of your schema you will able to use this Resolver in such way:
You may create instance of Resolver without attaching it to some ObjectTypeComposer. It can be done in following way:
+
import { schemaComposer } from'graphql-compose';
+
+const findCityLocationByIdResolver = schemaComposer.createResolver({
+ name: 'findCityLocationById',
+ type: `type CityLocation { lon: Float, lat: Float }`,
+ args: {
+ id: 'Int!',
+ },
+ // BE AWARE! `resolve` method in `Resolver` accept only one argument `resolveParams`
+ // which contains
+ // standard properties from `GraphQLFieldResolveFn`: source, args, context, info
+ // and additional properties: projection
+ resolve: async ({ source, args, context, info }) => {
+ const city = await DB.city.findById(args.id);
+ if (!city) returnnull;
+ return {
+ lon: city.longitude,
+ lat: city.latitude,
+ };
+ }
+});
+
+// And add this resolver to your Schema
+schemaComposer.Query.addFields({
+ cityLocation: findCityLocationByIdResolver,
+});
+
+
Wrapping Resolver
+
In many cases, it is very convenient to create a Resolver which just fetch data providing rich filter and sort arguments (also it may modify data).
+But what if we need to restrict access or set up some arguments of Resolver from source (parent) object or context?
+
Yep, you need to wrap the Resolver! Wrap just resolve method via Resolver.wrapResolve(). Or Resolver.wrap() if we want to change simultaneously output type, args and resolve method.
+
via Resolver.wrapResolve()
+
The most commonly used method for wrapping is Resolver.wrapResolve(). Let take a look how can be it used in your Schema:
+
schemaComposer.Query.addFields({
+ // add endpoint which returns only visible posts
+ publicPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `visibility` argument
+ // so forcibly set this arg to true
+ rp.args.visibility = true;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns posts only for current authenticated user
+ ownerPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `authorId` argument
+ // so forcibly set this arg to current user id
+ rp.args.authorId = rp.context.currentUserId;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns all authors only for admin
+ allAuthorsForAdmin: AuthorTC.getResolver('findMany').wrapResolve(next => rp => {
+ // check `isAdmin` property in context, which was somehow setted
+ // on express-graphql or apollo-server level
+ // for regular user return null
+ if (!rp.context.isAdmin) returnnull;
+ // for admin delegate execution to the basic resolver
+ return next(rp);
+ });
+});
+
+
via Resolver.wrap()
+
This is a less-used method. But it's more powerfull. It allows to change simultaneously output type, args and resolve method.
+
What if admin should have all avaliable filter params and add new one for searching but regular user just limited set of arguments?
+
Resolver wrapping creates a new Resolver. So for admin you create a new resolver findManyForAdmin by wrapping a basic resolver, eg. findMany add additional args and logic. For user you create findManyReduced by wrapping existed findMany resolver and removing some filter args.
+
Let write reduced resolver findManyReduced, where we remove some args
+
const findManyReduced = AuthorTC.getResolver('findMany').wrap(newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeFields(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
via TC.wrapResolverAs()
+
Also you may want to modify already existed Resolver in some ObjectTypeComposer, like it did Resolver.wrap() method.
+
For simplifying this process you may use ObjectTypeComposer.wrapResolverAs() method.
+Let take AuthorTCs findMany resolver and create a new one with name findManyReduced.
+
AuthorTC.wrapResolverAs('findManyReduced', 'findMany', newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeField(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
Advanced
+
How Resolver.wrapResolve() work internally
+
+
capturing phase, when you may change resolveParams (rp in the code) before it will pass to next resolve
+
bubbling phase, when you may change response from underlying resolve
+
+
Resolver.wrapResolve(next => rp => {
+ // [CAPTURING PHASE]:
+ // `rp` consist from { source, args, context, info, projection }
+ // you may change `source`, `args`, `context`, `info`, `projection` before it will pass to `next` underlying resolve function.
+
+ // ...some code which modify `rp` (resolveParams)
+
+ // ... or just stop propagation
+ // throw new Error();
+ // or
+ // return Promise.resolve(null);
+
+ // pass request to underlying middleware and get result promise from it
+ const resultPromise = next(rp);
+
+ // [BUBBLING PHASE]: here you may change payload of underlying resolve method, via promise syntax
+ // ...some code, which may add `then()` or `catch()` to result promise
+ // resultPromise.then(payload => { console.log(payload); return payload; })
+
+ return resultPromise; // return payload promise to upper wrapper
+});
+
\ No newline at end of file
diff --git a/docs/7.x.x/guide/elasticsearch-with-mongoose.html b/docs/7.x.x/guide/elasticsearch-with-mongoose.html
new file mode 100644
index 00000000..8f2826d3
--- /dev/null
+++ b/docs/7.x.x/guide/elasticsearch-with-mongoose.html
@@ -0,0 +1,385 @@
+[WIP] Use ElasticSearch with Mongoose · graphql-compose
Connect MongoDB with ElasticSearch and GraphQL quite complex and long task and consist of a bunch of steps. Every step can be tuned for your needs.
+
1. Extending Mongoose ORM with elasticsearch data
+
For working with MongoDB collections and documents is good practice to use some ORM. For nodejs better solution is mongoose. Also exists cool mongoose-elasticsearch-xp (by @jbdemonte) package (plugin for mongoose) which provides useful methods and hooks which ridiculously simplify data syncing with MongoDB and ElasticSearch.
+
1.1. Defining Mongoose schema with settings for elasticsearch-xp [SCHEMA DEFINITION]
1.2 Plug mongoose-elasticsearch-xp to your Mongoose Schema with data filtering [SYNC MONGO & ES DATA]
+
/* elastic */
+JobSchema.plugin(mongooseElasticsearch, {
+ client: elasticClient, // <------ see `graphql-elasticsearch-xp` for details
+ filter: doc => {
+ if (doc.visibility !== 'published') {
+ // add to index new record with visibility='published'
+ // or remove existed record from index if `visibility` changed and not 'published' anymore
+ returnfalse;
+ }
+ returntrue;
+ },
+});
+
+
By default mongoose-elasticsearch-xp will track add/remove operations and update your data in elasticsearch. In this case I provide filter option, now it will track more clever model's inserts/updates and send proper changes to your elasticsearch server.
+
Already existed data can be synced via esSynchronize method.
+
1.3 Connection with elasticsearch server elasticClient [ES CLIENT]
+
You should provide elasticClient in step 1.2 (for mongoose plugin [UPDATING DATA]) and 1.4 (for graphql resolvers [SEARCH]). It holds connection of your nodejs server with elasticsearch server.
import { composeWithElastic } from'graphql-compose-elasticsearch';
+import { generate } from'mongoose-elasticsearch-xp/lib/mapping';
+
+exportconst JobEsTC = composeWithElastic({
+ graphqlTypeName: 'JobES',
+ elasticIndex: 'job',
+ elasticType: 'job',
+ elasticMapping: {
+ properties: generate(JobSchema),
+ },
+ elasticClient,
+ // elastic mapping does not contain information about is fields are arrays or not
+ // so provide this information explicitly for obtaining correct types in GraphQL
+ pluralFields: ['employment'],
+});
+
fragment on Query {
+ jobEsConnection(first: $first, query: $query, sort: $sort, aggs: $aggs) {
+ count
+ aggregations
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ cursor
+ node {
+ _score# meta-data from ES
+ _id# meta-data from ES
+
+ _source {
+ employment # record data from ES
+ position# record data from ES
+ }
+
+ fromMongo { # data from Mongo
+ _id
+ onlyMongooseData
+ visibility
+ salary { fromto currency}
+ position
+ }
+ }
+ }
+ }
+}
+
+
See https://github.com/nodkz/graphql-compose
+Sorry bad docs in graphql-compose. Really do not have time to write it. So try to see issues they contain a lot of info.
+
1.7 Add needed resolvers to schema [BUILD GRAPHQL SCHEMA]
import { GQC } from 'graphql-compose';
+import { elasticApiFieldConfig } from 'graphql-compose-elasticsearch';
+import elasticClient from 'schema/elasticClient';
+
+export const ElasticTC = GQC.get('ELASTIC');
+
+ElasticTC.addResolver({
+ name: 'onlyForAdmins',
+ type: ElasticTC,
+ resolve: ({ context }) => {
+ if (!isAdmin({ context })) { // <--- somehow check that you are admin
+ throw new Error('You should be admin, to have access to this area.');
+ }
+ return {};
+ },
+});
+
+# expose all elastic api via graphql
+ElasticTC.addFields({
+ api: elasticApiFieldConfig(elasticClient),
+});
+
+// DONT FORGET TO add elastic to your schema (eg. to ROOT query)
+GQC.rootQuery().addFields({
+ elastic: ElasticTC.getResolver('onlyForAdmins'),
+});
+
Now you may call reindexing all your data in elasticsearch via following graphql query:
+
query {
+ elastic {
+ reindexJob
+ }
+}
+
+
\ No newline at end of file
diff --git a/docs/7.x.x/guide/file-uploads.html b/docs/7.x.x/guide/file-uploads.html
new file mode 100644
index 00000000..c6fc8751
--- /dev/null
+++ b/docs/7.x.x/guide/file-uploads.html
@@ -0,0 +1,256 @@
+File uploads · graphql-compose
If you decide how to upload files via some REST endpoint or GraphQL. So I recommend to upload via some REST API and then provide a path of the uploaded file to your mutation request. GraphQL designed to provide typed data according to client request shape. With files (binary data) it works too, but better to do it via well-recommended REST calls. In such case, you separate highly costed upload logic from data manipulation logic. In the future, this will help you diagnose problems with the load more easily.
+
Anyway products have different scenarios and you may be forced to upload files via GraphQL. For uploading files via GraphQL you will need:
apollo-upload-server - for parsing multipart/form-data POST requests via busboy and providing Files data to resolve function as argument.
+
+
Tutorial
+
1. Preparing express-graphql server
+
This is most important part of enabling file uploads on server-side. You need to parse body data via bodyParser.json() and multipart form data via apolloUploadExpress(/* Options */).
This is a most problematic part and it's out of scope of graphql-compose (it's client-side problem). You must correctly send HTTP request from the client. But if you very carefully read graphql-multipart-request-spec, then you should not have any questions.
+
Here's an example of proper multipart/form-data POST request with
+
+
operations key for GraphQL request with query and variables
+
map key with mapping some multipart-data to exact GraphQL variable
+
and other keys for multipart-data which contains binary data of files
\ No newline at end of file
diff --git a/docs/7.x.x/guide/mongoose.html b/docs/7.x.x/guide/mongoose.html
new file mode 100644
index 00000000..f00308a0
--- /dev/null
+++ b/docs/7.x.x/guide/mongoose.html
@@ -0,0 +1,133 @@
+[WIP] Generate types from Mongoose Models · graphql-compose
Well TypeComposers generated by graphql-compose-mongoose ships with resolvers for create, update and remove.
+Looking like this:
+
UserTC.getResolver('createOne').getFieldConfig();
+UserTC.getResolver('updateById').getFieldConfig();
+// or for shorthand
+UserTC.get('$removeMany').getFieldConfig();
+// and buch of other resolvers
+
+
Lets add a working example from the preview UserTC we have created
// user.js
+
+UserTC.addResolver({
+ name: 'myCustomUpdate',
+ kind: 'mutation',
+ args: {
+ id: 'String',
+ firstName: 'String',
+ lastName: 'String',
+ complexArg: `input SomeComplexInput {
+ min: Int
+ max: Int
+ }`,
+ },
+ type: UserTC,
+ resolve: ({ _, args, context, info }) => {
+ //edit and do what you need..
+ return user;
+ },
+});
+
+// so now you may add you custom mutation to schema
+GQC.rootMutation().addFields({
+ customUserUpdate: UserTC.get('$myCustomUpdate'),
+});
+
+
\ No newline at end of file
diff --git a/docs/7.x.x/guide/relay.html b/docs/7.x.x/guide/relay.html
new file mode 100644
index 00000000..ba5492ad
--- /dev/null
+++ b/docs/7.x.x/guide/relay.html
@@ -0,0 +1,73 @@
+[WIP] Relay Schema · graphql-compose
Adding support for Relay is done via plugin graphql-compose-relay For more detailed descriptions on how to use and reporting issues please use the link.
\ No newline at end of file
diff --git a/docs/7.x.x/guide/wrapping-rest-api.html b/docs/7.x.x/guide/wrapping-rest-api.html
new file mode 100644
index 00000000..347668fc
--- /dev/null
+++ b/docs/7.x.x/guide/wrapping-rest-api.html
@@ -0,0 +1,220 @@
+Wrapping REST API · graphql-compose
Many developers are attracted by GraphQL’s benefits over REST. The reason for that is its query language enabling to stick to the data that the client needs at the moment and not to restructure the client to fit API structure. Single endpoint, but flexible data shape.
+
Let’s imagine you already have an existing RESTful API, but your task requires using GraphQL either you just want to try it out of curiosity. If that's the case, you would need to wrap your REST in GraphQL Schema and hardcoding all the GraphQL Types is a real pain.
+
That's why we came up with a RESTful API wrapper for GraphQL featuring automatic GraphQL Type generation.
+
Installation
+
npm install graphql-compose-json
+
+
Demo
+
We've wrapped SWAPI RESTful API in to show capabilities of graphq-compose-json
Using graphql-compose is easy — it's just one, but helpful function:
+
import composeWithJson from'graphql-compose-json';
+
+const restApiResponse = {
+ name: 'Anakin Skywalker',
+ birth_year: '41.9BBY',
+ starships: [
+ 'https://swapi.co/api/starships/59/',
+ 'https://swapi.co/api/starships/65/',
+ 'https://swapi.co/api/starships/39/',
+ ],
+ mass: () =>'Int!', // by default JSON numbers are coerced to Float, here we've set it to Integer
+ starships_count: () => ({ // granular inline field config with resolve function
+ type: 'Int',
+ resolve: source => source.starships.length,
+ }),
+};
+
+exportconst CustomPersonTC = composeWithJson('CustomPerson', restApiResponse);
+
+
That's it! The Type is ready to be used and have its resolvers defined. CustomPersonTC contains all things you need to compose Resolvers and Schema.
+
Specifying data fetching method
+
What we're trying to do is to wrap an existing RESTful API in GraphQL Schema, but it is not yet aware of where the data is stored, it knows only the possible data shape; thus we need to specify how to fetch the API data.
+
Valid GraphQL data request requires three pieces: resolve(data fetching method), args(list of acceptable input arguments) and type(data representation form, which we already have thanks to graphql-compose-json). GraphQL terms label these three a Field Config (or Resolver).
It's unlikely that the Schema will have only one Type, hence we've got to link our scattered types. Imagine we want Person Type to return the list of movies they starred in. Assuming that Person has links to them, all we need is to add a resolver to FilmTC.
Defining Resolvers within ObjectTypeComposers they belong to helps to keep your code DRY, as further on you'll be able to reuse them with just one line of code:
+
Planet.getResolver('findMany');
+
+
Composing the Schema
+
Now with Types and Resolvers created it's time to put them into Schema.
\ No newline at end of file
diff --git a/docs/7.x.x/intro/installation.html b/docs/7.x.x/intro/installation.html
new file mode 100644
index 00000000..37790c33
--- /dev/null
+++ b/docs/7.x.x/intro/installation.html
@@ -0,0 +1,114 @@
+Installation · graphql-compose
Module graphql is declared in peerDependencies, so it should be installed explicitly in your project. This helps to solve a common problem when some of your other dependencies (like Relay, GraphiQL, graphql-compose) can leave your node_modules directory with duplicate installs of GraphQL.js. In such case graphql-js may throw errors stating that some classes are not instances of duplicate module.
+
Also you may need to install some graphql-compose plugins. Each plugin has own Install section with instructions.
\ No newline at end of file
diff --git a/docs/7.x.x/intro/live-demos.html b/docs/7.x.x/intro/live-demos.html
new file mode 100644
index 00000000..df4e2916
--- /dev/null
+++ b/docs/7.x.x/intro/live-demos.html
@@ -0,0 +1,120 @@
+Live Demos · graphql-compose
graphql-compose-boilerplate - ready to run a skeleton app for GraphQL server. It contains the example from Quick Start. This boilerplate includes Babel (ES6, babel-preset-env), ESLint, Flowtype, express, express-graphql, graphql, graphql-compose, nodemon.
+
+
Other demos
+
+
nodkz.github.io/relay-northwind - live demo of Relay Client App working with GraphQL Northwind Schema (8 crazy pages, 47 files, ~3000 LOC)
\ No newline at end of file
diff --git a/docs/7.x.x/intro/prerequisites.html b/docs/7.x.x/intro/prerequisites.html
new file mode 100644
index 00000000..2348e4e7
--- /dev/null
+++ b/docs/7.x.x/intro/prerequisites.html
@@ -0,0 +1,116 @@
+Prerequisites · graphql-compose
To use this package it would be a good idea to know the basics of GraphQL, and how the Type System works. Since you are going to generate and edit its types you should start out there first.
+
Node.js
+
This package generates GraphQL Schema on the server side. And it will be great if you have experience with Node.js and ES6 syntax.
+
For serving requests to your generated Schema you should use one of the following packages express-graphql or apollo-server.
+
Flowtype/TypeScript
+
This is optional but quite recommended feature which covers your javascript code with static type-checking. It will help you with autosuggestion and method call validation in your IDE. This package contains built-in type definitions for Flowtype and TypeScript.
+
Internally source code of this package is written with Flowtype and has deep static type-checking with graphq-js which is also written with Flow.
\ No newline at end of file
diff --git a/docs/7.x.x/intro/quick-start.html b/docs/7.x.x/intro/quick-start.html
new file mode 100644
index 00000000..45aca8da
--- /dev/null
+++ b/docs/7.x.x/intro/quick-start.html
@@ -0,0 +1,273 @@
+Quick Start Guide · graphql-compose
For simplicity, this example works with arrays, but in future, it will not be a problem to change data-source to any your favorite DB or a mix of them.
+
Creating Types
+
Building a GraphQL Schema starts with complex Types declaration. In order to create a Type, you have to give it a unique name and specify it’s fields list. So let's create Types which will describe our data. For this purpose need to take ObjectTypeComposer helper from graphql-compose package.
Now as we can declare Types, request them, it’s time to link these Types with each other. This is the exact stage where GraphQL enormously simplifies work for clients that request data. A typical scenario of a query to RESTful API: client requests a piece of data, receives it and request other resources according to the first server response it got, while GraphQL implements the same logic on the server’s side and sends back nested data of any depth.
+
To make such nesting possible you’ve got to link Author and Post Types with each other. For that you need to create author field in your Post Type, it will resolve author's data for every post. And for Author Type create posts field which will resolve for each author its posts.
+
PostTC.addFields({
+ author: {
+ // you may provide type name as string 'Author',
+ // but for better developer experience use Type instance `AuthorTC`
+ // it allows to jump to type declaration via Ctrl+Click in your IDE
+ type: AuthorTC,
+ // resolve method as first argument will receive data for some Post
+ // from this data you should somehow fetch Author's data
+ // let's take lodash `find` method, for searching by `authorId`
+ // PS. `resolve` method may be async for fetching data from DB
+ // resolve: async (source, args, context, info) => { return DB.find(); }
+ resolve: post => find(authors, { id: post.authorId }),
+ },
+});
+
+AuthorTC.addFields({
+ posts: {
+ // Array of posts may be described as string in SDL in such way '[Post]'
+ // But graphql-compose allow to use Type instance wrapped in array
+ type: [PostTC],
+ // for obtaining list of post we get current author.id
+ // and scan and filter all Posts with desired authorId
+ resolve: author => filter(posts, { authorId: author.id }),
+ },
+ postCount: {
+ type: 'Int',
+ description: 'Number of Posts written by Author',
+ resolve: author => filter(posts, { authorId: author.id }).length,
+ },
+});
+
+
Building Schema
+
Now that you’ve got your Types created, linked and taught how to fetch data, it’s time to create your Schema. For this purpose, you will need to use schemaComposer. It has three Root Types (entry points): Query, Mutation and Subscription and at least one of them must have defined fields.
+
import { schemaComposer } from'graphql-compose';
+
+// Requests which read data put into Query
+schemaComposer.Query.addFields({
+ posts: {
+ type: '[Post]',
+ resolve: () => posts,
+ },
+ author: {
+ type: 'Author',
+ args: { id: 'Int!' },
+ resolve: (_, { id }) => find(authors, { id }),
+ },
+});
+
+// Requests which modify data put into Mutation
+schemaComposer.Mutation.addFields({
+ upvotePost: {
+ type: 'Post',
+ args: {
+ postId: 'Int!',
+ },
+ resolve: (_, { postId }) => {
+ const post = find(posts, { id: postId });
+ if (!post) {
+ thrownewError(`Couldn't find post with id ${postId}`);
+ }
+ post.votes += 1;
+ return post;
+ },
+ },
+});
+
+// After Root type definition, you are ready to build Schema
+// which should be passed to `express-graphql` or `apollo-server`
+exportconst schema = schemaComposer.buildSchema();
+
+
Creating HTTP server
+
When your Schema is constructed, it needs to implement a server. It will serve client requests, execute them and send responses back. Let's construct a simple express app which will accept POST requests at http://localhost:4000/graphql endpoint for serving graphql queries. And GET requests with same address for providing GraphiQL an in-browser IDE for exploring GraphQL.
Graphql-compose has following built-in scalar types: String, Float, Int, Boolean, ID, Date, JSON. If you need to create some complex type, you will need to use schemaComposer.createObjectTC().
+
Let demonstrate another way of type creation via SDL:
+
const AddressTC = schemaComposer.createObjectTC(`
+ type Address {
+ city: String
+ country: String
+ street: String
+ }
+`);
+
+// and now we can extend existed Author Type with a new field with complex type
+AuthorTC.addFields({
+ address: {
+ type: AddressTC, // or 'Address'
+ description: "Author's address",
+ },
+})
+
+
More useful information about type creation can be found here.
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/list-of-plugins.html b/docs/7.x.x/plugins/list-of-plugins.html
new file mode 100644
index 00000000..a916ae06
--- /dev/null
+++ b/docs/7.x.x/plugins/list-of-plugins.html
@@ -0,0 +1,128 @@
+Plugins list · graphql-compose
graphql-compose – the imperative tool which worked on top of graphql-js. It provides useful methods for creating GraphQL Types and GraphQL Models (type with a list of
+resolvers) for further building of complex relations in your Schema. With graphql-compose you may fastly write own functions/generators for common tasks.
+
graphql-compose-[plugin] – is a declarative generator/plugin that build on top of graphql-compose, which take some ORMs, schema definitions and creates GraphQL Models from them or modify existed GraphQL Types.
+
Type generator plugins
+
+
graphql-compose-json - generates GraphQL type from JSON (a good helper for wrapping REST APIs)
+
graphql-compose-mongoose - generates GraphQL types from mongoose (MongoDB models) with Resolvers.
+
graphql-compose-elasticsearch - generates GraphQL types from elastic mappings; ElasticSearch REST API proxy via GraphQL.
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-aws.html b/docs/7.x.x/plugins/plugin-aws.html
new file mode 100644
index 00000000..88eb58b0
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-aws.html
@@ -0,0 +1,153 @@
+graphql-compose-aws · graphql-compose
Generated Schema Introspection in SDL format can be found here (more than 10k types, ~2MB).
+
AWS SDK GraphQL
+
Supported all AWS SDK versions via official aws-sdk js client. Internally it generates Types and FieldConfigs from AWS SDK configs. You may put this generated types to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import awsSDK from'aws-sdk';
+import { AwsApiParser } from'graphql-compose-aws';
+
+const awsApiParser = new AwsApiParser({
+ awsSDK,
+});
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ // Full API
+ aws: awsApiParser.getFieldConfig(),
+
+ // Partial API with desired services
+ s3: awsApiParser.getService('s3').getFieldConfig(),
+ ec2: awsApiParser.getService('ec2').getFieldConfig(),
+ },
+ }),
+});
+
+exportdefault schema;
+
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-connection.html b/docs/7.x.x/plugins/plugin-connection.html
new file mode 100644
index 00000000..4999db14
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-connection.html
@@ -0,0 +1,207 @@
+graphql-compose-connection · graphql-compose
Besides standard connection arguments first, last, before and after, also added significant arguments:
+
+
filter arg - for filtering records
+
sort arg - for sorting records. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
+
Example
+
import composeWithConnection from'graphql-compose-connection';
+import userTypeComposer from'./user.js';
+
+composeWithConnection(userTypeComposer, {
+ findResolverName: 'findMany',
+ countResolverName: 'count',
+ sort: {
+ // Sorting key, visible for users in GraphQL Schema
+ _ID_ASC: {
+ // Sorting value for ORM/Driver
+ value: { _id: 1 },
+
+ // Field names in record, which data will be packed in `cursor`
+ // edges {
+ // cursor <- base64(cursorData), for this example `cursorData` = { _id: 334ae453 }
+ // node <- record from DB
+ // }
+ // By this fields MUST be created UNIQUE index in database!
+ cursorFields: ['_id'],
+
+ // If for connection query provided `before` argument with above `cursor`.
+ // We should construct (`rawQuery`) which will be point to dataset before cursor.
+ // Unpacked data from `cursor` will be available in (`cursorData`) argument.
+ // PS. All other filter options provided via GraphQL query will be added automatically.
+ // ----- [record] ----- sorted dataset, according to above option with `value` name
+ // ^^^^^ `rawQuery` should filter this set
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+
+ // Constructing `rawQuery` for connection `after` argument.
+ // ----- [record] ----- sorted dataset
+ // ^^^^^ `rawQuery` should filter this set
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ },
+
+ _ID_DESC: {
+ value: { _id: -1 },
+ cursorFields: ['_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+ },
+
+ // More complex sorting parameter with 2 fields
+ AGE_ID_ASC: {
+ value: { age: 1, _id: -1 },
+ // By these fields MUST be created COMPOUND UNIQUE index in database!
+ cursorFields: ['age', '_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$lt = cursorData.age;
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$gt = cursorData.age;
+ rawQuery._id.$lt = cursorData._id;
+ },
+ }
+ },
+});
+
+
+
Requirements
+
Types should have following resolvers:
+
+
count - for counting records
+
findMany - for filtering records. Also required that this resolver supports search with operators (lt, gt), which used in directionFilter option. Resolver findMany should have filter argument, which will be copied to connection. Also should have limit and skip args.
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-elasticsearch.html b/docs/7.x.x/plugins/plugin-elasticsearch.html
new file mode 100644
index 00000000..039a8ef2
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-elasticsearch.html
@@ -0,0 +1,234 @@
+graphql-compose-elasticsearch · graphql-compose
This module expose Elastic Search REST API via GraphQL.
+
Elastic Search REST API proxy
+
Supported all elastic versions that support official elasticsearch-js client. Internally it parses its source code annotations and generates all available methods with params and descriptions to GraphQL Field Config Map. You may put this config map to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import elasticsearch from'elasticsearch';
+import { elasticApiFieldConfig } from'graphql-compose-elasticsearch';
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ elastic50: elasticApiFieldConfig(
+ // you may provide existed Elastic Client instance
+ new elasticsearch.Client({
+ host: 'http://localhost:9200',
+ apiVersion: '5.0',
+ })
+ ),
+
+ // or may provide just config
+ elastic24: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '2.4',
+ }),
+
+ elastic17: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '1.7',
+ }),
+ },
+ }),
+});
+
In other side this module is a plugin for graphql-compose, which derives GraphQLType from your elastic mapping generates tons of types, provides all available methods in QueryDSL, Aggregations, Sorting with field autocompletion according to types in your mapping (like Dev Tools Console in Kibana).
+
Generated ObjectTypeComposer model has several awesome resolvers:
+
+
search - greatly simplified elastic search method. According to GraphQL adaptation and its projection bunch of params setup automatically due your graphql query (eg _source, explain, version, trackScores), other rare fine tuning params moved to opts input field.
+
searchConnection - elastic search method that implements Relay Cursor Connection spec for infinite lists. Internally it uses cheap search_after API. One downside, Elastic does not support backward scrolling, so before argument will not work.
+
more resolvers will be later after my vacation: suggest, getById, updateById and others
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-json.html b/docs/7.x.x/plugins/plugin-json.html
new file mode 100644
index 00000000..37503fd3
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-json.html
@@ -0,0 +1,311 @@
+graphql-compose-json · graphql-compose
This is a plugin for graphql-compose, which generates GraphQLTypes from REST response or any JSON. It takes fields from object, determines their types and construct GraphQLObjectType with same shape.
+
Demo
+
We have a Live demo (source code repo) which shows how to build an API upon SWAPI using graphql-compose-json.
Modules graphql, graphql-compose, are located in peerDependencies, so they should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
You have a sample response object restApiResponse which you can pass to graphql-compose-json along with desired type name as your first argument and it will automatically generate a composed GraphQL type PersonTC.
graphql-compose provides a vast variety of methods for fields and resolvers (aka field configs in vanilla GraphQL) management of GraphQL types. To learn more visit graphql-compose repo.
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-mongoose.html b/docs/7.x.x/plugins/plugin-mongoose.html
new file mode 100644
index 00000000..1c4ad491
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-mongoose.html
@@ -0,0 +1,639 @@
+graphql-compose-mongoose · graphql-compose
This is a plugin for graphql-compose, which derives GraphQLType from your mongoose model. Also derives bunch of internal GraphQL Types. Provide all CRUD resolvers, including graphql connection, also provided basic search via operators ($lt, $gt and so on).
Modules graphql, graphql-compose, mongoose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
If you want to add additional resolvers connection and/or pagination - just install following packages and graphql-compose-mongoose will add them automatically.
UserTC - this is a ObjectTypeComposer instance for User. ObjectTypeComposer has GraphQLObjectType inside, avaliable via method UserTC.getType().
+
Here and in all other places of code variables suffix ...TC means that this is ObjectTypeComposer instance, ...ITC - InputTypeComposer, ...ETC - EnumTypeComposer.
+
+
import mongoose from'mongoose';
+import { composeWithMongoose } from'graphql-compose-mongoose';
+import { schemaComposer } from'graphql-compose';
+
+// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
+const LanguagesSchema = new mongoose.Schema({
+ language: String,
+ skill: {
+ type: String,
+ enum: [ 'basic', 'fluent', 'native' ],
+ },
+});
+
+const UserSchema = new mongoose.Schema({
+ name: String, // standard types
+ age: {
+ type: Number,
+ index: true,
+ },
+ languages: {
+ type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
+ default: [],
+ },
+ contacts: { // another mongoose way for providing embedded documents
+ email: String,
+ phones: [String], // array of strings
+ },
+ gender: { // enum field with values
+ type: String,
+ enum: ['male', 'female', 'ladyboy'],
+ },
+ someMixed: {
+ type: mongoose.Schema.Types.Mixed,
+ description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
+ },
+});
+const User = mongoose.model('User', UserSchema);
+
+
+
+// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
+const customizationOptions = {}; // left it empty for simplicity, described below
+const UserTC = composeWithMongoose(User, customizationOptions);
+
+// STEP 3: Add needed CRUD User operations to the GraphQL Schema
+// via graphql-compose it will be much much easier, with less typing
+schemaComposer.Query.addFields({
+ userById: UserTC.getResolver('findById'),
+ userByIds: UserTC.getResolver('findByIds'),
+ userOne: UserTC.getResolver('findOne'),
+ userMany: UserTC.getResolver('findMany'),
+ userCount: UserTC.getResolver('count'),
+ userConnection: UserTC.getResolver('connection'),
+ userPagination: UserTC.getResolver('pagination'),
+});
+
+schemaComposer.Mutation.addFields({
+ userCreateOne: UserTC.getResolver('createOne'),
+ userCreateMany: UserTC.getResolver('createMany'),
+ userUpdateById: UserTC.getResolver('updateById'),
+ userUpdateOne: UserTC.getResolver('updateOne'),
+ userUpdateMany: UserTC.getResolver('updateMany'),
+ userRemoveById: UserTC.getResolver('removeById'),
+ userRemoveOne: UserTC.getResolver('removeOne'),
+ userRemoveMany: UserTC.getResolver('removeMany'),
+});
+
+const graphqlSchema = schemaComposer.buildSchema();
+exportdefault graphqlSchema;
+
+
That's all!
+You think that is to much code?
+I don't think so, because by default internally was created about 55 graphql types (for input, sorting, filtering). So you will need much much more lines of code to implement all these CRUD operations by hands.
+
Working with Mongoose Collection Level Discriminators
+
Variable Namings
+
+
...DTC - Suffix for a DiscriminatorTypeComposer instance, which is also an instance of ObjectTypeComposer. All fields and Relations manipulations on this instance affects all registered discriminators and the Discriminator Interface.
const UserTC = composeWithMongoose(User);
+UserTC.getType(); // returns GraphQLObjectType
+UserTC.getInputType(); // returns GraphQLInputObjectType, eg. for args
+UserTC.get('languages').getType(); // get GraphQLObjectType for nested field
+UserTC.get('fieldWithNesting.subNesting').getType(); // get GraphQL type of deep nested field
+
Suppose you User model has friendsIds field with array of user ids. So let build some relations:
+
UserTC.addRelation(
+ 'friends',
+ {
+ resolver: () => UserTC.getResolver('findByIds'),
+ prepareArgs: { // resolver `findByIds` has `_ids` arg, let provide value to it
+ _ids: (source) => source.friendsIds,
+ },
+ projection: { friendsIds: 1 }, // point fields in source object, which should be fetched from DB
+ }
+);
+UserTC.addRelation(
+ 'adultFriendsWithSameGender',
+ {
+ resolver: () => UserTC.get('$findMany'), // shorthand for `UserTC.getResolver('findMany')`
+ prepareArgs: { // resolver `findMany` has `filter` arg, we may provide mongoose query to it
+ filter: (source) => ({
+ _operators : { // Applying criteria on fields which have
+ // operators enabled for them (by default, indexed fields only)
+ _id : { in: source.friendsIds },
+ age: { gt: 21 }
+ },
+ gender: source.gender,
+ }),
+ limit: 10,
+ },
+ projection: { friendsIds: 1, gender: 1 }, // required fields from source object
+ }
+);
+
+
Reusing the same mongoose Schema in embedded object fields
+
Suppose you have a common structure you use as embedded object in multiple Schemas.
+Also suppose you want the structure to have the same GraphQL type across all parent types.
+(For instance, to allow reuse of fragments for this type)
+Here are Schemas to demonstrate:
If you want the ImageDataStructure to use the same GraphQL type in both Article and UserProfile you will need create it as a mongoose schema (not a standard javascript object) and to explicitly tell graphql-compose-mongoose the name you want it to have. Otherwise, without the name, it would generate the name according to the first parent this type was embedded in.
+
Do the following:
+
import { schemaComposer } from'graphql-compose'; // get the default schemaComposer or your created schemaComposer
+import { convertSchemaToGraphQL } from'graphql-compose-mongoose';
+
+convertSchemaToGraphQL(ImageDataStructure, 'EmbeddedImage', schemaComposer); // Force this type on this mongoose schema
+
+
Before continuing to convert your models to TypeComposers:
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can create your own resolver of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts before save hook which can be provided in resolver params as param named beforeRecordMutate. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
This is opts.resolvers level of options.
+If you set the option to false it will disable resolver or some of its input args.
+Every resolver's arg has it own options. They described below.
This is opts.resolvers.[resolverName].[filter|sort|record|limit] level of options.
+You may tune every resolver's args independently as you wish.
+Here you may setup every argument and override some fields from the default input object type, described above in opts.inputType.
+
export type filterHelperArgsOpts = {
+ filterTypeName?: string, // type name for `filter`
+ isRequired?: boolean, // set `filter` arg as required (wraps in GraphQLNonNull)
+ onlyIndexed?: boolean, // leave only that fields, which is indexed in mongodb
+ requiredFields?: string | string[], // provide fieldNames, that should be required
+ operators?: filterOperatorsOpts | false, // provide filtering fields by operators, eg. $lt, $gt
+ // if left empty - provides all operators on indexed fields
+};
+
+// supported operators names in filter `arg`
+export type filterOperatorNames = 'gt' | 'gte' | 'lt' | 'lte' | 'ne' | 'in[]' | 'nin[]';
+export type filterOperatorsOpts = { [fieldName: string]: filterOperatorNames[] | false };
+
+export type sortHelperArgsOpts = {
+ sortTypeName?: string, // type name for `sort`
+};
+
+export type recordHelperArgsOpts = {
+ recordTypeName?: string, // type name for `record`
+ isRequired?: boolean, // set `record` arg as required (wraps in GraphQLNonNull)
+ removeFields?: string[], // provide fieldNames, that should be removed
+ requiredFields?: string[], // provide fieldNames, that should be required
+};
+
+export type limitHelperArgsOpts = {
+ defaultValue?: number, // set your default limit, if it not provided in query (default: 1000)
+};
+
This plugin adds connection resolver. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
+
Besides standard connection arguments first, last, before and after, also added great arguments:
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-pagination.html b/docs/7.x.x/plugins/plugin-pagination.html
new file mode 100644
index 00000000..41cebe20
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-pagination.html
@@ -0,0 +1,135 @@
+graphql-compose-pagination · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-relay.html b/docs/7.x.x/plugins/plugin-relay.html
new file mode 100644
index 00000000..7ca253ab
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-relay.html
@@ -0,0 +1,148 @@
+graphql-compose-relay · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
ObjectTypeComposer is a graphql-compose utility, that wraps GraphQL types and provide bunch of useful methods for type manipulation.
+
import composeWithRelay from'graphql-compose-relay';
+import { ObjectTypeComposer } from'graphql-compose';
+import { RootQueryType, UserType } from'./my-graphq-object-types';
+
+const rootQueryTypeComposer = new ObjectTypeComposer(RootQueryType);
+const userTypeComposer = new ObjectTypeComposer(UserType);
+
+// If passed RootQuery, then will be added only `node` field to this type.
+// Via RootQuery.node you may find objects by globally unique ID among all types.
+composeWithRelay(rootQueryTypeComposer);
+
+// Other types, like User, will be wrapped with middlewares that:
+// - add relay's id field. Field will be added or wrapped to return Relay's globally unique ID.
+// - for mutations will be added clientMutationId to input and output objects types
+// - this type will be added to NodeInterface for resolving via RootQuery.node
+composeWithRelay(userTypeComposer);
+
+
That's all!
+
All mutations resolvers' arguments will be placed into input field, and added clientMutationId. If input fields already exists in resolver, then clientMutationId will be added to it, rest argument stays untouched. Accepted value via args.input.clientMutationId will be transfer to payload.clientMutationId, as Relay required it.
+
To all wrapped Types with Relay, will be added id field or wrapped, if it exist already. This field will return globally unique ID among all types in the following format base64(TypeName + ':' + recordId).
+
For RootQuery will be added node field, that will resolve by globalId only that types, which you wrap with composeWithRelay.
+
All this annoying operations is too fatigue to do by hands. So this middleware done all Relay magic implicitly for you.
+
Requirements
+
Method composeWithRelay accept ObjectTypeComposer as input argument. So ObjectTypeComposer should meet following requirements:
+
+
has defined recordIdFn (function that from object of this type, returns you id for the globalId construction)
+
should have findById resolver (that will be used by RootQuery.node)
+
+
If something is missing composeWithRelay throws error.
\ No newline at end of file
diff --git a/docs/7.x.x/plugins/plugin-writing-custom-plugin.html b/docs/7.x.x/plugins/plugin-writing-custom-plugin.html
new file mode 100644
index 00000000..8c3f269b
--- /dev/null
+++ b/docs/7.x.x/plugins/plugin-writing-custom-plugin.html
@@ -0,0 +1,59 @@
+[WIP] How to write a custom plugin · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/recipes/authorization.html b/docs/7.x.x/recipes/authorization.html
new file mode 100644
index 00000000..0b87cb3d
--- /dev/null
+++ b/docs/7.x.x/recipes/authorization.html
@@ -0,0 +1,59 @@
+[WIP] Authorization · graphql-compose
\ No newline at end of file
diff --git a/docs/7.x.x/recipes/writing-tests.html b/docs/7.x.x/recipes/writing-tests.html
new file mode 100644
index 00000000..7a8d75a0
--- /dev/null
+++ b/docs/7.x.x/recipes/writing-tests.html
@@ -0,0 +1,59 @@
+[WIP] Writing tests · graphql-compose
\ No newline at end of file
diff --git a/docs/api/EnumTypeComposer.html b/docs/api/EnumTypeComposer.html
new file mode 100644
index 00000000..5f7279bb
--- /dev/null
+++ b/docs/api/EnumTypeComposer.html
@@ -0,0 +1,495 @@
+EnumTypeComposer · graphql-compose
merge(
+ type: GraphQLEnumType | EnumTypeComposer<any>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
removeFieldExtension(
+ fieldName: string,
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/api/InputTypeComposer.html b/docs/api/InputTypeComposer.html
new file mode 100644
index 00000000..9c5e907e
--- /dev/null
+++ b/docs/api/InputTypeComposer.html
@@ -0,0 +1,619 @@
+InputTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Clone this type to another SchemaComposer.
+Also will be cloned all sub-types.
+
merge()
+
merge(
+ type: GraphQLInputObjectType | InputTypeComposer<any>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
removeFieldExtension(
+ fieldName: string,
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/api/InterfaceTypeComposer.html b/docs/api/InterfaceTypeComposer.html
new file mode 100644
index 00000000..d214cf59
--- /dev/null
+++ b/docs/api/InterfaceTypeComposer.html
@@ -0,0 +1,906 @@
+InterfaceTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify args types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+isFieldArgPlural() – checks is arg wrapped in ListComposer or not
+makeFieldArgPlural() – for arg wrapping in ListComposer
+makeFieldArgNonPlural() – for arg unwrapping from ListComposer
+isFieldArgNonNull() – checks is arg wrapped in NonNullComposer or not
+makeFieldArgNonNull() – for arg wrapping in NonNullComposer
+makeFieldArgNullable() – for arg unwrapping from NonNullComposer
removeInterface(
+ iface: InterfaceTypeComposerDefinition<any, TContext>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/api/ListComposer.html b/docs/api/ListComposer.html
new file mode 100644
index 00000000..1ea42f9a
--- /dev/null
+++ b/docs/api/ListComposer.html
@@ -0,0 +1,165 @@
+ListComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/api/NonNullComposer.html b/docs/api/NonNullComposer.html
new file mode 100644
index 00000000..4abe4252
--- /dev/null
+++ b/docs/api/NonNullComposer.html
@@ -0,0 +1,165 @@
+NonNullComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/api/ObjectTypeComposer.html b/docs/api/ObjectTypeComposer.html
new file mode 100644
index 00000000..939243bd
--- /dev/null
+++ b/docs/api/ObjectTypeComposer.html
@@ -0,0 +1,1121 @@
+ObjectTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify args types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+isFieldArgPlural() – checks is arg wrapped in ListComposer or not
+makeFieldArgPlural() – for arg wrapping in ListComposer
+makeFieldArgNonPlural() – for arg unwrapping from ListComposer
+isFieldArgNonNull() – checks is arg wrapped in NonNullComposer or not
+makeFieldArgNonNull() – for arg wrapping in NonNullComposer
+makeFieldArgNullable() – for arg unwrapping from NonNullComposer
Merge fields and interfaces from provided GraphQLObjectType, or ObjectTypeComposer.
+Also you may provide GraphQLInterfaceType or InterfaceTypeComposer for adding fields.
+
InputType methods
+
getInputType()
+
getInputType(): GraphQLInputObjectType
+
+
hasInputTypeComposer()
+
hasInputTypeComposer(): boolean
+
+
setInputTypeComposer()
+
setInputTypeComposer(
+ itc: InputTypeComposer<TContext>
+): this
+
removeInterface(
+ iface: InterfaceTypeComposerDefinition<any, TContext>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/api/Resolver.html b/docs/api/Resolver.html
new file mode 100644
index 00000000..0b89cca2
--- /dev/null
+++ b/docs/api/Resolver.html
@@ -0,0 +1,637 @@
+Resolver · graphql-compose
The most interesting class in graphql-compose. The main goal of Resolver is to keep available resolve methods for Type and use them for building relation with other types.
Clone this Resolver with overriding of some options.
+Internally it just copies all properties.
+But for args and projection it recreates objects with the same type & values (it allows to add or remove properties without affection old Resolver).
\ No newline at end of file
diff --git a/docs/api/ScalarTypeComposer.html b/docs/api/ScalarTypeComposer.html
new file mode 100644
index 00000000..2a56362e
--- /dev/null
+++ b/docs/api/ScalarTypeComposer.html
@@ -0,0 +1,353 @@
+ScalarTypeComposer · graphql-compose
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
setExtension(
+ extensionName: string,
+ value: unknown
+): this
+
+
removeExtension()
+
removeExtension(
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/api/SchemaComposer.html b/docs/api/SchemaComposer.html
new file mode 100644
index 00000000..19e30dc6
--- /dev/null
+++ b/docs/api/SchemaComposer.html
@@ -0,0 +1,512 @@
+SchemaComposer · graphql-compose
Create GraphQLSchema instance from defined types.
+This instance can be provided to express-graphql, apollo-server, graphql-yoga etc.
+
addSchemaMustHaveType()
+
addSchemaMustHaveType(
+ type: AnyType<TContext>
+): this
+
+
When using Interfaces you may have such Types which are hidden under Interface.resolveType method. In such cases you should add these types explicitly. Cause buildSchema() will take only real used types and types which added via addSchemaMustHaveType() method.
Creates or return existed TypeComposer from SDL or object.
+If you call this method again with same params should be returned the same TypeComposer instance.
\ No newline at end of file
diff --git a/docs/api/ThunkComposer.html b/docs/api/ThunkComposer.html
new file mode 100644
index 00000000..d66d1f28
--- /dev/null
+++ b/docs/api/ThunkComposer.html
@@ -0,0 +1,150 @@
+ThunkComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/api/TypeComposer.html b/docs/api/TypeComposer.html
new file mode 100644
index 00000000..1d0e1b14
--- /dev/null
+++ b/docs/api/TypeComposer.html
@@ -0,0 +1,549 @@
+TypeComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/api/TypeMapper.html b/docs/api/TypeMapper.html
new file mode 100644
index 00000000..3cb53862
--- /dev/null
+++ b/docs/api/TypeMapper.html
@@ -0,0 +1,422 @@
+TypeMapper · graphql-compose
Type storage and type generator from Schema Definition Language (SDL).
+This is slightly rewritten buildASTSchema
+utility from graphql-js that allows to create type from a string (SDL).
\ No newline at end of file
diff --git a/docs/api/UnionTypeComposer.html b/docs/api/UnionTypeComposer.html
new file mode 100644
index 00000000..b814039d
--- /dev/null
+++ b/docs/api/UnionTypeComposer.html
@@ -0,0 +1,448 @@
+UnionTypeComposer · graphql-compose
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
setExtension(
+ extensionName: string,
+ value: unknown
+): this
+
+
removeExtension()
+
removeExtension(
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/api/misc-api-methods.html b/docs/api/misc-api-methods.html
new file mode 100644
index 00000000..48523b43
--- /dev/null
+++ b/docs/api/misc-api-methods.html
@@ -0,0 +1,427 @@
+API misc · graphql-compose
The same as getProjectionFromAST except that all nested fields will not be extracted. Complex address will be just true, not { city: true, street: true },
graphql-compose re-exports GraphQL.js package for its plugins. It helps to avoid the hell with maintaining versions of graphql and graphql-compose in plugins' package.json files.
+
If you want to write a plugin for graphql-compose and publish it to npm, just add graphql-compose in dependencies of its package.json. And if you will need GraphQL.js objects and methods you may import them in such way:
+
// My awesome Plugin for graphql-compose
+import { graphql } from'graphql-compose';
+
+const { GraphQLNonNull, GraphQLObjectType } = graphql;
+
+
graphqlVersion
+
Sometimes it need to know which version of GraphQL.js is installed in the project.
+It may be used in graphql-compose plugins, cause different versions of GraphQL.js may have breaking changes and your plugins may have workarounds for different behavior.
+
const graphqlVersion: number;
+
+
import { graphqlVersion } from'graphql-compose';
+
+if (graphqlVersion < 13) {
+ throwError(`This plugin does not work with GraphQL.js v${graphqlVersion}`);
+}
+
+
Scalar Types
+
GraphQLDate
+
GraphQL scalar type that converts javascript Date object to string YYYY-MM-DDTHH:MM:SS.SSSZ and back.
+
import { GraphQLDate } from'graphql-compose';
+
+
GraphQLJSON
+
GraphQL scalar type that represents JSON. Field with this type may have arbitrary structure. Copied from @taion's graphql-type-json for reducing dependencies tree.
+
import { GraphQLJSON } from'graphql-compose';
+
+
TypeStorage
+
You may need some isolated storage for keeping types in your plugins. So TypeStorage is the easy way to obtain such storage.
\ No newline at end of file
diff --git a/docs/basics/generating-schema.html b/docs/basics/generating-schema.html
new file mode 100644
index 00000000..a68a062d
--- /dev/null
+++ b/docs/basics/generating-schema.html
@@ -0,0 +1,214 @@
+Generating Schema · graphql-compose
SchemaComposer allows to build a GraphQLSchema instance. The Schema obtained calling the buildSchema() method may be used in express-graphql, apollo-server and other libs that use GraphQL.js under the hood for query execution at runtime.
+
Create Schema
+
SchemaComposer provides three basic root types: Query, Mutation and Subscription. It's imperative to initialize at least one of those, otherwise our Schema would not build.
+
import { schemaComposer } from'graphql-compose';
+import { AuthorTC } from'./author';
+
+schemaComposer.Query.addFields({
+ // add field with regular FieldConfig
+ currentTime: {
+ type: 'Date',
+ resolve: () =>Date.now(),
+ },
+ // Assume that `AuthorTC` build with `graphql-compose-mongoose` which has CRUD resolvers
+ // in such case we can use pre-generated Resolvers as a FieldConfig
+ authorById: AuthorTC.getResolver('findById'),
+ authorMany: AuthorTC.getResolver('findMany'),
+ // ...
+});
+
+schemaComposer.Mutation.addNestedFields({
+ // also it may be very useful define nested fields
+ // Mutation will have `author` field, `author` will have `create` and `update` fields inside
+ 'author.create': AuthorTC.getResolver('createOne'),
+ 'author.update': AuthorTC.getResolver('updateById'),
+ // ...
+});
+
+exportdefault schemaComposer.buildSchema(); // exports GraphQLSchema
+
+
Restrict access
+
GraphQL.js does not provide any access rights checks, so a developer would need to implemented them manually in the resolve methods. With graphql-compose it can be done by wrapping Resolvers:
+
// rootMutation.js
+import { schemaComposer } from'graphql-compose';
+
+import { CommentTC } from'./comment';
+import { UserTC } from'./user';
+
+schemaComposer.Mutation.addNestedFields({
+ commentCreate: CommentTC.getResolver('createOne'), // may anybody
+
+ ...adminAccess({
+ // only for admins
+ 'user.create': UserTC.getResolver('createOne'),
+ 'user.update': UserTC.getResolver('updateById'),
+ 'user.remove': UserTC.getResolver('removeById'),
+ }),
+});
+
+functionadminAccess(resolvers) {
+ Object.keys(resolvers).forEach(k => {
+ resolvers[k] = resolvers[k].wrapResolve(next => rp => {
+ if (!rp.context.isAdmin) {
+ thrownewError('You should be admin, to have access to this action.');
+ }
+ return next(rp);
+ });
+ });
+ return resolvers;
+}
+
+
The isAdmin property from the above example must be defined in express-graphql or apollo-server, in order to retrieve it from context:
In some complex scenarios we may need several GraphQL Schemas within a single app. graphql-compose by default exports the following classes/instances for single schema mode:
The equivalent class for multi-schema mode is called SchemaComposer (with a capital S). Unlike with single-schema where we have a static class, SchemaComposer has a constructor and allows creating multiple instances:
Types created via ObjectTypeComposer1 and ObjectTypeComposer2 will not be visible to each other: name-clashing and overriding would not be isssues, and multiple definitions for types with the same name are allowed, as long as they live in separate SchemaComposer instances.
\ No newline at end of file
diff --git a/docs/basics/type-modification.html b/docs/basics/type-modification.html
new file mode 100644
index 00000000..d5bfb787
--- /dev/null
+++ b/docs/basics/type-modification.html
@@ -0,0 +1,209 @@
+Type modification · graphql-compose
This is the most important part of graphql-compose and the main difference in Schema creation with GraphQL.js. In GraphQL.js you have strict abilities in type definition and its further modification. But graphql-compose allows to you modify types after creation in very convenient ways.
+
+
Note: With graphql-compose you may modify types before GraphQLSchema object creation. When schema was created you cannot change types.
+
+
Fields modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer instances:
+
+
getFields()
+
setFields()
+
getFieldNames()
+
hasField(name)
+
setField(name, fieldConfig)
+
addFields(newFieldsConfig)
+
getField(name)
+
removeField(nameOrArray)
+
removeOtherFields(nameOrArray)
+
extendField(name, partialFieldConfig)
+
reorderFields(names)
+
deprecateFields(nameOrMap)
+
+
Additional methods in ObjectTypeComposer, InputTypeComposer, InterfaceTypeComposer instances:
+
+
getFieldType(name)
+
getFieldTC(name)
+
getFieldConfig(name)
+
makeFieldNonNull(nameOrArray)
+
makeFieldNullable(nameOrArray)
+
addNestedFields(newFields)
+
+
// add description to `firstName`
+AuthorTC.extendField('firstName', {
+ description: "This field returns Author's first name",
+});
+
+// Add new field `status` with Enum type
+AuthorTC.addField('status', `enum AuthorStatus { ACTIVE INACTIVE }`);
+
+// Change order of fields in type
+// unlisted fields will be added to the end of field list with old order
+AuthorTC.reorderFields(['status', 'firstName']);
+
+// Mark fields as deprecated with some message
+AuthorTC.deprecateFields({
+ rating: 'This field will be removed in June 2018',
+ dob: 'Use `age` field instead. This field will be removed in June 2018',
+});
+
+// Add new field with `address` name and for type
+// create a new object type with `city` and `country` fields
+AuthorTC.addNestedFields({
+ 'address.city': 'String',
+ 'address.country': 'String',
+});
+
+
Type modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer, UnionTypeComposer instances:
+
+
getType()
+
getTypePlural()
+
getTypeNonNull()
+
getTypeName()
+
setTypeName(newName)
+
getDescription()
+
setDescription()
+
clone(newTypeName)
+
+
Additional methods in ObjectTypeComposer
+
+
getInterfaces()
+
setInterfaces(interfaces)
+
hasInterface(interfaceObj)
+
addInterface(interfaceObj)
+
removeInterface(interfaceObj)
+
getInputType()
+
getITC()
+
+
Create your custom modification function
+
With this set of methods, you may write your own type modification functions. It may greatly reduce repetitive code across your schema definition.
+
As an example, lets write a function which will add rawData field with full record data from database. Also check isAdmin = true in context and if so return data, otherwise return null.
+
functionaddRawData(tc: ObjectTypeComposer<any, any>) {
+ if (!tc.hasField('rawData')) {
+ tc.addField('rawData', {
+ type: 'JSON',
+ resolve: (source, args, context) => {
+ if (context.isAdmin) {
+ return source;
+ }
+ returnnull;
+ },
+ // add magic property `projection`
+ // which request all fields from database
+ // when requested this `rawData` field in the query
+ projection: { '*': 1 },
+ });
+ }
+}
+addRawData(AuthorTC);
+addRawData(PostTC);
+
+
Or even more
+
You may write your own plugins which will generate types from some models or non-graphql schemas. Take a look on avaliable list of plugins build on top of graphql-compose.
\ No newline at end of file
diff --git a/docs/basics/understanding-relations.html b/docs/basics/understanding-relations.html
new file mode 100644
index 00000000..17a80a5a
--- /dev/null
+++ b/docs/basics/understanding-relations.html
@@ -0,0 +1,311 @@
+Relations between Types · graphql-compose
GraphQL allows to create additional fields in our types, thus providing data from another type. For example, we may add a field posts to the Author type and write a resolve function, so that this field will return an array of posts only for the current Author.
What if we want to provide a filter argument, which adds the ability to filter by creation date, and min number of votes?
+That would be achieved by the following code:
This would work just fine, but it has become quite a lot of code. And what if we have other Types with relations with Posts (eg. Reviewer, Reader)? Copy/pasting our resolve method is probably not a good idea. That's because in the future we may want to add a new filter property, and that would mean scanning all of our code to add additional logic in all FieldConfigs. The next section will detail a better approach to this problem.
+
Relation via Resolver
+
graphql-compose provides a Resolver class that allows using the same FieldConfigs in different Types. We may create a Resolver defining type, args and a resolve function, then reuse it everywhere we need it in our Schema.
+
However if we define our posts resolver in a separate file, we'll then face another problem:
+
+
in Author type we will use criteria = { authorId: source.id } for the resolve method;
+
in Reviewer - criteria = { reviewers: { $has: source.id } } and so on.
+
+
In this case it's better to improve args.filter by allowing to set authorId and reviewerId via arguments:
Should be an arrow function that returns Resolver. Wrapping resolver in an arrow function helps solving the hoisting problem (when two types import each other).
+
prepareArgs
+
At runtime we should have the ability to prepare (ie. assign a value to) the args that will be passed to Resolver.
+
For example our Resolver has the arguments filter, limit, skip and sort.
+prepareArgs provides a way to set them up:
+
+
limit: 10 - hides limit arg from schema and set it equal to 10
+
filter: (source) => value - hides filter arg form schema and at runtime evaluate its value
+
sort: null - disables argument (hides it from schema and do not pass it to resolver)
+
all undescribed args (like skip) will be avaliable in the schema and will be avaliable in query
+
+
projection
+
Is a very useful option for extending requested fields in your query. It's very good practice to request from database only the fields included in our query. But sometimes we need additional fields, for example to provide the findById resolver with an authorId. For this purpose we can use projection.
Without projection the resolver would try to populate the author field, but args.authorId would be undefined. It would therefore be impossible for the query filter to find matching authors and populate the author field. Normally when a client wants to retrieve the author field in a GraphQL Query, it would also need to provide the authorId explicitly. By using a projection we lift that responsility from the client, making querying easier and less cluttered.
\ No newline at end of file
diff --git a/docs/basics/understanding-types.html b/docs/basics/understanding-types.html
new file mode 100644
index 00000000..f2e7b7a4
--- /dev/null
+++ b/docs/basics/understanding-types.html
@@ -0,0 +1,401 @@
+Type creation · graphql-compose
With graphql-compose you need to create types under some schemaComposer instance. By default graphql-compose has a global schemaComposer instance which can be obtained in the following manner:
But if you need to create several GraphQL schemas in your app, you may import SchemaComposer class and create schemaComposer instances as much as you need:
+
import { SchemaComposer } from'graphql-compose';
+
+const schemaComposer1 = new SchemaComposer();
+const schemaComposer2 = new SchemaComposer();
+
+
Take a note that schemaComposer1 and schemaComposer2 will have different type storages. And types in schemaComposer1 will not be avaliable in schemaComposer2 and vice versa.
+
Scalar types
+
Graphql-compose has following built-in scalar types:
+
+
String
+
Float
+
Int
+
Boolean
+
ID
+
Date
+
JSON
+
+
via config
+
You may create scalar types via config, like with GraphQLScalarType:
If you need to create some complex type with several properties (fields), you will need to use ObjectTypeComposer. It's a builder for GraphQLObjectType object.
+
ObjectTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Output type. Such definition provides better developer experience with jumping to the type declarations.
+
const AuthorTC = schemaComposer.createObjectTC({
+ name: 'Author',
+ fields: {
+ id: 'Int!',
+ firstName: 'String',
+ lastName: 'String',
+ posts: {
+ type: () => [PostTC], // arrow function for `type` helps to solve hoisting problems and keep ability to list all fields
+ args: {
+ limit: { type: 'Int', defaultValue: 20 },
+ skip: 'Int', // shortand to `{ type: 'Int' }`
+ sort: `enum AuthorPostsSortEnum { ASC DESC }`, // type creation via SDL
+ },
+ resolve: () => { ... },
+ }
+ },
+});
+
+
Also this way of definition provides a lot of syntax sugar for field definition:
+
const AuthorTC = schemaComposer.createObjectTC({
+ posts: {
+ // wrapping Type with arrow function helps to solve a hoisting problem
+ // also using type instances provides better DX
+ // (ctrl+click allows to jump to PostTC type declaration in your IDE)
+ type: () => PostTC,
+ description: 'Posts written by Author',
+ resolve: (source, args, context, info) => {},
+ },
+ // using standard GraphQL Type
+ ucFirstName: {
+ type: GraphQLString,
+ resolve: (source) => { return source.firstName.toUpperCase(); },
+ // also request `firstName` field which must be loaded from database
+ projection: { firstName: true },
+ },
+ // fast way if you need to define only type
+ counter: 'Int',
+ // using SDL for definition new ObjectType
+ complex: `type ComplexType {
+ subField1: String
+ subField2: Float
+ subField3: Boolean
+ subField4: ID
+ subField5: JSON
+ subField6: Date
+ }`,
+ // SDL for defining array of strings, which is NonNull
+ list0: {
+ type: '[String]!',
+ description: 'Array of strings',
+ },
+ list1: '[String]',
+ list2: ['String'],
+ list3: [GraphQLString],
+ list4: [`type Complex2Type { f1: Float, f2: Int }`],
+});
+
+
via SDL
+
May have hoisting problems. Be aware that all used complex types must be already defined.
GraphQL allows to pass arguments for fields. You may freely use Scalars, Enums when describing input args. But what you should do in the case of mutations, where you might want to pass in a whole object to be created? For such cases for complex types instead of GraphQLObjectType you should use GraphQLInputObjectType. They they have small differences in its fields declaration:
+
+
input object type has defaultValue
+
input object type does not have args
+
input object type does not have resolve method
+
+
If you need to create some complex type with several properties, you will need to use InputTypeComposer. It's a builder for GraphQLInputObjectType object.
+
InputTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Input type. Such definition provides hoisting problems solution via wrapping types by arrow function. Better developer experience with jumping to the type declarations.
+
InputTypeComposer has the same type definition capabilities for describing fields as ObjectTypeComposer - as string, as arrow function, as SDL.
Useful when you write your own type generators. Enum has values (not fields), but for similar method naming with ObjectTypeComposer and InputTypeComposer in graphql-compose methods for value modification have field keyword.
Graphql-compose provides the following helper for Interfaces - InterfaceTypeComposer.
+
import { schemaComposer } from'graphql-compose';
+
+const TimestampInterface = schemaComposer.createInterfaceTC({
+ name: 'Timestampable',
+ description: 'An object with createdAt and updatedAt fields',
+ fields: {
+ createdAt: 'Date',
+ updatedAt: 'Date',
+ },
+});
+
+// When you create Interface, you need to provide instructions how to determine exact ObjectType from `value`.
+// So if `value` is instance of UserDoc, then use `UserTC` as exact type.
+TimestampInterface.addTypeResolver(UserTC, value => (value instanceof UserDoc));
+TimestampInterface.addTypeResolver(ArticleTC, value => (value instanceof UserDoc));
+
+
Lists
+
If you want indicate that field or argument return an array of some type, you may do the following:
+
import { GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field1: [AuthorTC], // RECOMMENDED just wrap in the regular js array
+ field2: AuthorTC.getTypePlural(), // call specific ObjectTypeComposer method
+ field3: '[Author]', // use SDL format
+ field4: new GraphQLList(AuthorTC.getType()) // use standard GraphQLList
+});
+
+
Non-Null
+
If you want indicate that field is not empty or argument is required:
+
import { GraphQLNonNull } from'graphql';
+
+SomeTypeComposer.addFields({
+ // field1: ???, // doesn't exists any regular object in js for expressing NonNull value
+ field2: AuthorTC.getTypeNonNull(), // call specific ObjectTypeComposer method
+ field3: 'Author!', // use SDL format
+ field4: new GraphQLNonNull(AuthorTC.getType()) // use standard GraphQLNonNull
+});
+
+
Non-Null List of Non-Null values may be expressed in following way:
+
import { GraphQLNonNull, GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field3: '[Author!]!', // use SDL format
+ field4: new GraphQLNonNull( // use standard GraphQLNonNull & GraphQLList
+ new GraphQLList(
+ new GraphQLNonNull(AuthorTC.getType())
+ )
+ )
+});
+
\ No newline at end of file
diff --git a/docs/basics/what-is-resolver.html b/docs/basics/what-is-resolver.html
new file mode 100644
index 00000000..f1acfb31
--- /dev/null
+++ b/docs/basics/what-is-resolver.html
@@ -0,0 +1,320 @@
+Resolvers · graphql-compose
Shortly, Resolver is an object which knows how to process data and what to return. It's like a function definition in static language where you give it name, describe types for input arguments and output result.
+
GraphQL.js describes such functions in complex output types via GraphQLFieldConfig:
GraphQLFieldConfig has information about returned type, available args, implementation of resolve logic and some other properties. In terms of graphql-compose this field config is called as Resolver.
+
The main aim of Resolver is to keep available resolve methods for Type and use them for building relation with other types. Resolver provide following abilities:
+
+
add, remove, get, make optional/required arguments
+
clone Resolver for further logic extension
+
wrap args, type, resolve (get resolver and create new one with extended/modified functionality)
+
provide helper methods addFilterArg and addSortArg which wrap resolver by adding argument and additional resolve logic
+
+
Resolver has following properties:
+
+
type output complex or scalar type (resolver returns data of this type)
+
args list of fields of input or scalar types (resolver accept input arguments for resolve method)
+
resolve method which contains your bussiness logic, for fetching, processing and returning data. BE AWARE: that all arguments (source, args, context, info) are passed inside one argument called as resolveParams (rp for brevity in the code).
+
description public description which will be passed to graphql schema and will be available via introspection
+
deprecationReason if you want to hide field from schema, but leave it working for old clients
+
name any name for resolver that allow to you identify what it does, eg findById, updateMany, removeOne
+
kind type of resolver query (resolver just fetch data) or mutation (resolver change data)
+
parent you may wrap existed Resolver for adding additional checks, modifying result, adding arguments. This property keeps reference to existed unwrapped Resolver
+
+
Why do we need the Resolver?
+
Graphql-compose allows creating such "functions" or "FieldConfigs" via giving it names and keep in your ObjectTypeComposer. You may create any number of Resolvers and store them in your type.
+
Assume you have an Author type. And you have different standard CRUD operations for fetching and modifying this type:
+
+
findById
+
findMany
+
updateById
+
removeById
+
etc
+
+
When you will construct your Schema, you may need several times the same logic from standard Resolvers. For example
+
+
in the Query type may be added fields
+
+
authorById for finding Author by id arg via findById resolver
+
authorMany for finding list of Author with some filter criteria via findMany resolver
+
+
in the Post type may be added
+
+
author field which request Author by id from current post.authorId value via findById resolver
+
reviewers field which request Authors via findMany resolver with custom filtering
+
+
+
Resolvers helps to describe CRUD operations logic only once and then reuse them in different scenarios. For Query.authorById provides its full functionality from findById resolver. For Post.author you wrap findById resolver where should be hidden id arg and its value automatically will be set from post.authorId. For wrapping Resolvers graphql-compose provides a bunch of methods.
+
Creating Resolver
+
via TC.addResolver()
+
Mostly Resolvers are created according to the specific Type. So it's better to create them and store in some ObjectTypeComposer instance.
+
Lets's take AuthorTC and describe how it can be found by id:
+
AuthorTC.addResolver({
+ name: 'findById',
+ args: { id: 'Int' },
+ type: AuthorTC,
+ resolve: async ({ source, args }) => {
+ const res = await fetch(`/endpoint/${args.id}`); // or some fetch from any database
+ const data = await res.json();
+ // here you may clean up `data` response from API or Database,
+ // it should has same shape like AuthorTC fields
+ // eg. { firstName: 'Peter', nickname: 'peet', views: 20 }
+ // if some fields in `data`:
+ // are undefined or missing - graphql returns `null` for that fields
+ // are not described in output `type` - graphql will remove them from responce
+ return data;
+ },
+});
+
+
And in any place of your schema you will able to use this Resolver in such way:
You may create instance of Resolver without attaching it to some ObjectTypeComposer. It can be done in following way:
+
import { schemaComposer } from'graphql-compose';
+
+const findCityLocationByIdResolver = schemaComposer.createResolver({
+ name: 'findCityLocationById',
+ type: `type CityLocation { lon: Float, lat: Float }`,
+ args: {
+ id: 'Int!',
+ },
+ // BE AWARE! `resolve` method in `Resolver` accept only one argument `resolveParams`
+ // which contains
+ // standard properties from `GraphQLFieldResolveFn`: source, args, context, info
+ // and additional properties: projection
+ resolve: async ({ source, args, context, info }) => {
+ const city = await DB.city.findById(args.id);
+ if (!city) returnnull;
+ return {
+ lon: city.longitude,
+ lat: city.latitude,
+ };
+ }
+});
+
+// And add this resolver to your Schema
+schemaComposer.Query.addFields({
+ cityLocation: findCityLocationByIdResolver,
+});
+
+
Wrapping Resolver
+
In many cases, it is very convenient to create a Resolver which just fetch data providing rich filter and sort arguments (also it may modify data).
+But what if we need to restrict access or set up some arguments of Resolver from source (parent) object or context?
+
Yep, you need to wrap the Resolver! Wrap just resolve method via Resolver.wrapResolve(). Or Resolver.wrap() if we want to change simultaneously output type, args and resolve method.
+
via Resolver.wrapResolve()
+
The most commonly used method for wrapping is Resolver.wrapResolve(). Let take a look how can be it used in your Schema:
+
schemaComposer.Query.addFields({
+ // add endpoint which returns only visible posts
+ publicPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `visibility` argument
+ // so forcibly set this arg to true
+ rp.args.visibility = true;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns posts only for current authenticated user
+ ownerPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `authorId` argument
+ // so forcibly set this arg to current user id
+ rp.args.authorId = rp.context.currentUserId;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns all authors only for admin
+ allAuthorsForAdmin: AuthorTC.getResolver('findMany').wrapResolve(next => rp => {
+ // check `isAdmin` property in context, which was somehow setted
+ // on express-graphql or apollo-server level
+ // for regular user return null
+ if (!rp.context.isAdmin) returnnull;
+ // for admin delegate execution to the basic resolver
+ return next(rp);
+ });
+});
+
+
via Resolver.wrap()
+
This is a less-used method. But it's more powerfull. It allows to change simultaneously output type, args and resolve method.
+
What if admin should have all avaliable filter params and add new one for searching but regular user just limited set of arguments?
+
Resolver wrapping creates a new Resolver. So for admin you create a new resolver findManyForAdmin by wrapping a basic resolver, eg. findMany add additional args and logic. For user you create findManyReduced by wrapping existed findMany resolver and removing some filter args.
+
Let write reduced resolver findManyReduced, where we remove some args
+
const findManyReduced = AuthorTC.getResolver('findMany').wrap(newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgITC('filter').removeFields(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
via TC.wrapResolverAs()
+
Also you may want to modify already existed Resolver in some ObjectTypeComposer, like it did Resolver.wrap() method.
+
For simplifying this process you may use ObjectTypeComposer.wrapResolverAs() method.
+Let take AuthorTCs findMany resolver and create a new one with name findManyReduced.
+
AuthorTC.wrapResolverAs('findManyReduced', 'findMany', newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeField(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
Advanced
+
How Resolver.wrapResolve() work internally
+
+
capturing phase, when you may change resolveParams (rp in the code) before it will pass to next resolve
+
bubbling phase, when you may change response from underlying resolve
+
+
Resolver.wrapResolve(next => rp => {
+ // [CAPTURING PHASE]:
+ // `rp` consist from { source, args, context, info, projection }
+ // you may change `source`, `args`, `context`, `info`, `projection` before it will pass to `next` underlying resolve function.
+
+ // ...some code which modify `rp` (resolveParams)
+
+ // ... or just stop propagation
+ // throw new Error();
+ // or
+ // return Promise.resolve(null);
+
+ // pass request to underlying middleware and get result promise from it
+ const resultPromise = next(rp);
+
+ // [BUBBLING PHASE]: here you may change payload of underlying resolve method, via promise syntax
+ // ...some code, which may add `then()` or `catch()` to result promise
+ // resultPromise.then(payload => { console.log(payload); return payload; })
+
+ return resultPromise; // return payload promise to upper wrapper
+});
+
\ No newline at end of file
diff --git a/docs/guide/elasticsearch-with-mongoose.html b/docs/guide/elasticsearch-with-mongoose.html
new file mode 100644
index 00000000..e83a99bf
--- /dev/null
+++ b/docs/guide/elasticsearch-with-mongoose.html
@@ -0,0 +1,385 @@
+[WIP] Use ElasticSearch with Mongoose · graphql-compose
Connect MongoDB with ElasticSearch and GraphQL quite complex and long task and consist of a bunch of steps. Every step can be tuned for your needs.
+
1. Extending Mongoose ORM with elasticsearch data
+
For working with MongoDB collections and documents is good practice to use some ORM. For nodejs better solution is mongoose. Also exists cool mongoose-elasticsearch-xp (by @jbdemonte) package (plugin for mongoose) which provides useful methods and hooks which ridiculously simplify data syncing with MongoDB and ElasticSearch.
+
1.1. Defining Mongoose schema with settings for elasticsearch-xp [SCHEMA DEFINITION]
1.2 Plug mongoose-elasticsearch-xp to your Mongoose Schema with data filtering [SYNC MONGO & ES DATA]
+
/* elastic */
+JobSchema.plugin(mongooseElasticsearch, {
+ client: elasticClient, // <------ see `graphql-elasticsearch-xp` for details
+ filter: doc => {
+ if (doc.visibility !== 'published') {
+ // add to index new record with visibility='published'
+ // or remove existed record from index if `visibility` changed and not 'published' anymore
+ returnfalse;
+ }
+ returntrue;
+ },
+});
+
+
By default mongoose-elasticsearch-xp will track add/remove operations and update your data in elasticsearch. In this case I provide filter option, now it will track more clever model's inserts/updates and send proper changes to your elasticsearch server.
+
Already existed data can be synced via esSynchronize method.
+
1.3 Connection with elasticsearch server elasticClient [ES CLIENT]
+
You should provide elasticClient in step 1.2 (for mongoose plugin [UPDATING DATA]) and 1.4 (for graphql resolvers [SEARCH]). It holds connection of your nodejs server with elasticsearch server.
import { composeWithElastic } from'graphql-compose-elasticsearch';
+import { generate } from'mongoose-elasticsearch-xp/lib/mapping';
+
+exportconst JobEsTC = composeWithElastic({
+ graphqlTypeName: 'JobES',
+ elasticIndex: 'job',
+ elasticType: 'job',
+ elasticMapping: {
+ properties: generate(JobSchema),
+ },
+ elasticClient,
+ // elastic mapping does not contain information about is fields are arrays or not
+ // so provide this information explicitly for obtaining correct types in GraphQL
+ pluralFields: ['employment'],
+});
+
fragment on Query {
+ jobEsConnection(first: $first, query: $query, sort: $sort, aggs: $aggs) {
+ count
+ aggregations
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ cursor
+ node {
+ _score# meta-data from ES
+ _id# meta-data from ES
+
+ _source {
+ employment # record data from ES
+ position# record data from ES
+ }
+
+ fromMongo { # data from Mongo
+ _id
+ onlyMongooseData
+ visibility
+ salary { fromto currency}
+ position
+ }
+ }
+ }
+ }
+}
+
+
See https://github.com/nodkz/graphql-compose
+Sorry bad docs in graphql-compose. Really do not have time to write it. So try to see issues they contain a lot of info.
+
1.7 Add needed resolvers to schema [BUILD GRAPHQL SCHEMA]
import { schemaComposer } from 'graphql-compose';
+import { elasticApiFieldConfig } from 'graphql-compose-elasticsearch';
+import elasticClient from 'schema/elasticClient';
+
+export const ElasticTC = schemaComposer.getOTC('ELASTIC');
+
+ElasticTC.addResolver({
+ name: 'onlyForAdmins',
+ type: ElasticTC,
+ resolve: ({ context }) => {
+ if (!isAdmin({ context })) { // <--- somehow check that you are admin
+ throw new Error('You should be admin, to have access to this area.');
+ }
+ return {};
+ },
+});
+
+# expose all elastic api via graphql
+ElasticTC.addFields({
+ api: elasticApiFieldConfig(elasticClient),
+});
+
+// DONT FORGET TO add elastic to your schema (eg. to ROOT query)
+schemaComposer.Query.addFields({
+ elastic: ElasticTC.getResolver('onlyForAdmins'),
+});
+
Now you may call reindexing all your data in elasticsearch via following graphql query:
+
query {
+ elastic {
+ reindexJob
+ }
+}
+
+
\ No newline at end of file
diff --git a/docs/guide/file-uploads.html b/docs/guide/file-uploads.html
new file mode 100644
index 00000000..395c78bc
--- /dev/null
+++ b/docs/guide/file-uploads.html
@@ -0,0 +1,256 @@
+File uploads · graphql-compose
If you decide how to upload files via some REST endpoint or GraphQL. So I recommend to upload via some REST API and then provide a path of the uploaded file to your mutation request. GraphQL designed to provide typed data according to client request shape. With files (binary data) it works too, but better to do it via well-recommended REST calls. In such case, you separate highly costed upload logic from data manipulation logic. In the future, this will help you diagnose problems with the load more easily.
+
Anyway products have different scenarios and you may be forced to upload files via GraphQL. For uploading files via GraphQL you will need:
apollo-upload-server - for parsing multipart/form-data POST requests via busboy and providing Files data to resolve function as argument.
+
+
Tutorial
+
1. Preparing express-graphql server
+
This is most important part of enabling file uploads on server-side. You need to parse body data via bodyParser.json() and multipart form data via apolloUploadExpress(/* Options */).
This is a most problematic part and it's out of scope of graphql-compose (it's client-side problem). You must correctly send HTTP request from the client. But if you very carefully read graphql-multipart-request-spec, then you should not have any questions.
+
Here's an example of proper multipart/form-data POST request with
+
+
operations key for GraphQL request with query and variables
+
map key with mapping some multipart-data to exact GraphQL variable
+
and other keys for multipart-data which contains binary data of files
\ No newline at end of file
diff --git a/docs/guide/mongoose.html b/docs/guide/mongoose.html
new file mode 100644
index 00000000..dd702b92
--- /dev/null
+++ b/docs/guide/mongoose.html
@@ -0,0 +1,133 @@
+[WIP] Generate types from Mongoose Models · graphql-compose
Well TypeComposers generated by graphql-compose-mongoose ships with resolvers for create, update and remove.
+Looking like this:
+
UserTC.getResolver('createOne').getFieldConfig();
+UserTC.getResolver('updateById').getFieldConfig();
+// or for shorthand
+UserTC.get('$removeMany').getFieldConfig();
+// and buch of other resolvers
+
+
Lets add a working example from the preview UserTC we have created
// user.js
+
+UserTC.addResolver({
+ name: 'myCustomUpdate',
+ kind: 'mutation',
+ args: {
+ id: 'String',
+ firstName: 'String',
+ lastName: 'String',
+ complexArg: `input SomeComplexInput {
+ min: Int
+ max: Int
+ }`,
+ },
+ type: UserTC,
+ resolve: ({ _, args, context, info }) => {
+ //edit and do what you need..
+ return user;
+ },
+});
+
+// so now you may add you custom mutation to schema
+schemaComposer.Mutation.addFields({
+ customUserUpdate: UserTC.getResolver('myCustomUpdate'),
+});
+
+
\ No newline at end of file
diff --git a/docs/guide/relay.html b/docs/guide/relay.html
new file mode 100644
index 00000000..98584d27
--- /dev/null
+++ b/docs/guide/relay.html
@@ -0,0 +1,73 @@
+[WIP] Relay Schema · graphql-compose
Adding support for Relay is done via plugin graphql-compose-relay For more detailed descriptions on how to use and reporting issues please use the link.
\ No newline at end of file
diff --git a/docs/guide/wrapping-rest-api.html b/docs/guide/wrapping-rest-api.html
new file mode 100644
index 00000000..0f8a77f0
--- /dev/null
+++ b/docs/guide/wrapping-rest-api.html
@@ -0,0 +1,220 @@
+Wrapping REST API · graphql-compose
Many developers are attracted by GraphQL’s benefits over REST. The reason for that is its query language enabling to stick to the data that the client needs at the moment and not to restructure the client to fit API structure. Single endpoint, but flexible data shape.
+
Let’s imagine you already have an existing RESTful API, but your task requires using GraphQL either you just want to try it out of curiosity. If that's the case, you would need to wrap your REST in GraphQL Schema and hardcoding all the GraphQL Types is a real pain.
+
That's why we came up with a RESTful API wrapper for GraphQL featuring automatic GraphQL Type generation.
+
Installation
+
npm install graphql-compose-json
+
+
Demo
+
We've wrapped SWAPI RESTful API in to show capabilities of graphq-compose-json
Using graphql-compose is easy — it's just one, but helpful function:
+
import composeWithJson from'graphql-compose-json';
+
+const restApiResponse = {
+ name: 'Anakin Skywalker',
+ birth_year: '41.9BBY',
+ starships: [
+ 'https://swapi.co/api/starships/59/',
+ 'https://swapi.co/api/starships/65/',
+ 'https://swapi.co/api/starships/39/',
+ ],
+ mass: () =>'Int!', // by default JSON numbers are coerced to Float, here we've set it to Integer
+ starships_count: () => ({ // granular inline field config with resolve function
+ type: 'Int',
+ resolve: source => source.starships.length,
+ }),
+};
+
+exportconst CustomPersonTC = composeWithJson('CustomPerson', restApiResponse);
+
+
That's it! The Type is ready to be used and have its resolvers defined. CustomPersonTC contains all things you need to compose Resolvers and Schema.
+
Specifying data fetching method
+
What we're trying to do is to wrap an existing RESTful API in GraphQL Schema, but it is not yet aware of where the data is stored, it knows only the possible data shape; thus we need to specify how to fetch the API data.
+
Valid GraphQL data request requires three pieces: resolve(data fetching method), args(list of acceptable input arguments) and type(data representation form, which we already have thanks to graphql-compose-json). GraphQL terms label these three a Field Config (or Resolver).
It's unlikely that the Schema will have only one Type, hence we've got to link our scattered types. Imagine we want Person Type to return the list of movies they starred in. Assuming that Person has links to them, all we need is to add a resolver to FilmTC.
Defining Resolvers within ObjectTypeComposers they belong to helps to keep your code DRY, as further on you'll be able to reuse them with just one line of code:
+
Planet.getResolver('findMany');
+
+
Composing the Schema
+
Now with Types and Resolvers created it's time to put them into Schema.
\ No newline at end of file
diff --git a/docs/intro/installation.html b/docs/intro/installation.html
new file mode 100644
index 00000000..7d74b4f6
--- /dev/null
+++ b/docs/intro/installation.html
@@ -0,0 +1,114 @@
+Installation · graphql-compose
Module graphql is declared in peerDependencies, so it should be installed explicitly in your project. This helps to solve a common problem when some of your other dependencies (like Relay, GraphiQL, graphql-compose) can leave your node_modules directory with duplicate installs of GraphQL.js. In such case graphql-js may throw errors stating that some classes are not instances of duplicate module.
+
Also you may need to install some graphql-compose plugins. Each plugin has own Install section with instructions.
\ No newline at end of file
diff --git a/docs/intro/live-demos.html b/docs/intro/live-demos.html
new file mode 100644
index 00000000..78b6d73b
--- /dev/null
+++ b/docs/intro/live-demos.html
@@ -0,0 +1,120 @@
+Live Demos · graphql-compose
graphql-compose-boilerplate - ready to run a skeleton app for GraphQL server. It contains the example from Quick Start. This boilerplate includes Babel (ES6, babel-preset-env), ESLint, Flowtype, express, express-graphql, graphql, graphql-compose, nodemon.
+
+
Other demos
+
+
nodkz.github.io/relay-northwind - live demo of Relay Client App working with GraphQL Northwind Schema (8 crazy pages, 47 files, ~3000 LOC)
\ No newline at end of file
diff --git a/docs/intro/prerequisites.html b/docs/intro/prerequisites.html
new file mode 100644
index 00000000..d1b57061
--- /dev/null
+++ b/docs/intro/prerequisites.html
@@ -0,0 +1,116 @@
+Prerequisites · graphql-compose
To use this package it would be a good idea to know the basics of GraphQL, and how the Type System works. Since you are going to generate and edit its types you should start out there first.
+
Node.js
+
This package generates GraphQL Schema on the server side. And it will be great if you have experience with Node.js and ES6 syntax.
+
For serving requests to your generated Schema you should use one of the following packages express-graphql or apollo-server.
+
Flowtype/TypeScript
+
This is optional but quite recommended feature which covers your javascript code with static type-checking. It will help you with autosuggestion and method call validation in your IDE. This package contains built-in type definitions for Flowtype and TypeScript.
+
Internally source code of this package is written with Flowtype and has deep static type-checking with graphq-js which is also written with Flow.
\ No newline at end of file
diff --git a/docs/intro/quick-start.html b/docs/intro/quick-start.html
new file mode 100644
index 00000000..9ab17ecc
--- /dev/null
+++ b/docs/intro/quick-start.html
@@ -0,0 +1,273 @@
+Quick Start Guide · graphql-compose
For simplicity this example works with arrays, but in the future, it will not be a problem to change data-source to any of your favorite DBs or use a mix of them.
+
Creating Types
+
Building a GraphQL Schema starts with complex Types declaration. In order to create a Type, you have to give it a unique name and specify its fields list. So let's create some Types to describe our data. For this purpose we need to import the ObjectTypeComposer helper from the graphql-compose package.
Now that we have declared Types, it’s time to link them with each other. This is the exact stage where GraphQL enormously simplifies work for clients that request data. A typical scenario when using a RESTful API goes like this: a client requests some data, and typically makes a second or even third request based on the previous response. GraphQL allows to perform a single deeply nested query, composing the result on the server’s side and sending it back to the client.
+
To make such nesting possible we need to link Author and Post Types with each other. For that we will define an author field in your Post Type, which will allow GraphQL to resolve the author's data for every post. And for Author Type we will define a posts field to make retrieving each Author's posts possible.
+
PostTC.addFields({
+ author: {
+ // you may provide the type name as a string (eg. 'Author'),
+ // but for better developer experience you should use a Type instance `AuthorTC`.
+ // This allows jumping to the type declaration via Ctrl+Click in your IDE
+ type: AuthorTC,
+ // resolve method as first argument will receive data for some Post.
+ // From this data you should then fetch Author's data.
+ // let's take lodash `find` method, for searching by `authorId`
+ // PS. `resolve` method may be async for fetching data from DB
+ // resolve: async (source, args, context, info) => { return DB.find(); }
+ resolve: post => find(authors, { id: post.authorId }),
+ },
+});
+
+AuthorTC.addFields({
+ posts: {
+ // Array of posts may be described as string in SDL in such way '[Post]'
+ // But graphql-compose allow to use Type instance wrapped in array
+ type: [PostTC],
+ // for obtaining list of post we get current author.id
+ // and scan and filter all Posts with desired authorId
+ resolve: author => filter(posts, { authorId: author.id }),
+ },
+ postCount: {
+ type: 'Int',
+ description: 'Number of Posts written by Author',
+ resolve: author => filter(posts, { authorId: author.id }).length,
+ },
+});
+
+
Building Schema
+
Now that we have our Types created, linked and we've seen how to fetch data, it’s time to create our Schema. For this purpose, we will need to use schemaComposer. It has three Root Types (entry points): Query, Mutation and Subscription and at least one of them must have defined fields.
+
import { schemaComposer } from'graphql-compose';
+
+// Requests which read data put into Query
+schemaComposer.Query.addFields({
+ posts: {
+ type: '[Post]',
+ resolve: () => posts,
+ },
+ author: {
+ type: 'Author',
+ args: { id: 'Int!' },
+ resolve: (_, { id }) => find(authors, { id }),
+ },
+});
+
+// Requests which modify data put into Mutation
+schemaComposer.Mutation.addFields({
+ upvotePost: {
+ type: 'Post',
+ args: {
+ postId: 'Int!',
+ },
+ resolve: (_, { postId }) => {
+ const post = find(posts, { id: postId });
+ if (!post) {
+ thrownewError(`Couldn't find post with id ${postId}`);
+ }
+ post.votes += 1;
+ return post;
+ },
+ },
+});
+
+// After Root type definition, you are ready to build Schema
+// which should be passed to `express-graphql` or `apollo-server`
+exportconst schema = schemaComposer.buildSchema();
+
+
Creating HTTP server
+
After constructing a Schema, let's see how to implement a server to handle client requests and send back responses using GraphQL. Let's construct a simple express app which will accept POST requests at the http://localhost:4000/graphql endpoint, handling graphql queries. Our app will also accept GET requests at the same address, providing an in-browser IDE for exploring GraphQL called GraphiQL.
Graphql-compose has the following built-in scalar types: String, Float, Int, Boolean, ID, Date, JSON. If we need to create a complex type, we will need to use schemaComposer.createObjectTC().
+
Let's see another way of creating a type via SDL:
+
const AddressTC = schemaComposer.createObjectTC(`
+ type Address {
+ city: String
+ country: String
+ street: String
+ }
+`);
+
+// and now we can extend existed Author Type with a new field with complex type
+AuthorTC.addFields({
+ address: {
+ type: AddressTC, // or 'Address'
+ description: "Author's address",
+ },
+})
+
+
More useful information about type creation can be found here.
\ No newline at end of file
diff --git a/docs/next/README.html b/docs/next/README.html
new file mode 100644
index 00000000..f170c3f8
--- /dev/null
+++ b/docs/next/README.html
@@ -0,0 +1,112 @@
+README · graphql-compose
\ No newline at end of file
diff --git a/docs/next/api/EnumTypeComposer.html b/docs/next/api/EnumTypeComposer.html
new file mode 100644
index 00000000..7011a1e4
--- /dev/null
+++ b/docs/next/api/EnumTypeComposer.html
@@ -0,0 +1,495 @@
+EnumTypeComposer · graphql-compose
merge(
+ type: GraphQLEnumType | EnumTypeComposer<any>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
removeFieldExtension(
+ fieldName: string,
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/next/api/InputTypeComposer.html b/docs/next/api/InputTypeComposer.html
new file mode 100644
index 00000000..099d9ccd
--- /dev/null
+++ b/docs/next/api/InputTypeComposer.html
@@ -0,0 +1,619 @@
+InputTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Clone this type to another SchemaComposer.
+Also will be cloned all sub-types.
+
merge()
+
merge(
+ type: GraphQLInputObjectType | InputTypeComposer<any>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
removeFieldExtension(
+ fieldName: string,
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/next/api/InterfaceTypeComposer.html b/docs/next/api/InterfaceTypeComposer.html
new file mode 100644
index 00000000..3cc72ce0
--- /dev/null
+++ b/docs/next/api/InterfaceTypeComposer.html
@@ -0,0 +1,906 @@
+InterfaceTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify args types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+isFieldArgPlural() – checks is arg wrapped in ListComposer or not
+makeFieldArgPlural() – for arg wrapping in ListComposer
+makeFieldArgNonPlural() – for arg unwrapping from ListComposer
+isFieldArgNonNull() – checks is arg wrapped in NonNullComposer or not
+makeFieldArgNonNull() – for arg wrapping in NonNullComposer
+makeFieldArgNullable() – for arg unwrapping from NonNullComposer
removeInterface(
+ iface: InterfaceTypeComposerDefinition<any, TContext>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/next/api/ListComposer.html b/docs/next/api/ListComposer.html
new file mode 100644
index 00000000..e00891a6
--- /dev/null
+++ b/docs/next/api/ListComposer.html
@@ -0,0 +1,165 @@
+ListComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/next/api/NonNullComposer.html b/docs/next/api/NonNullComposer.html
new file mode 100644
index 00000000..0314be32
--- /dev/null
+++ b/docs/next/api/NonNullComposer.html
@@ -0,0 +1,165 @@
+NonNullComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/next/api/ObjectTypeComposer.html b/docs/next/api/ObjectTypeComposer.html
new file mode 100644
index 00000000..bce7ac83
--- /dev/null
+++ b/docs/next/api/ObjectTypeComposer.html
@@ -0,0 +1,1121 @@
+ObjectTypeComposer · graphql-compose
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify fields types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+
+
TC.getField().type // returns real wrapped TypeComposer
+
TC.isFieldNonNull() // checks is field NonNull or not
+
TC.makeFieldNonNull() // for wrapping in NonNullComposer
+
TC.makeFieldNullable() // for unwrapping from NonNullComposer
+
TC.isFieldPlural() // checks is field wrapped in ListComposer or not
+
TC.makeFieldPlural() // for wrapping in ListComposer
+
TC.makeFieldNonPlural() // for unwrapping from ListComposer
Automatically unwrap from List, NonNull, ThunkComposer
+It's important! Cause greatly helps to modify args types in a real code
+without manual unwrap writing.
+
If you need to work with wrappers, you may use the following code:
+isFieldArgPlural() – checks is arg wrapped in ListComposer or not
+makeFieldArgPlural() – for arg wrapping in ListComposer
+makeFieldArgNonPlural() – for arg unwrapping from ListComposer
+isFieldArgNonNull() – checks is arg wrapped in NonNullComposer or not
+makeFieldArgNonNull() – for arg wrapping in NonNullComposer
+makeFieldArgNullable() – for arg unwrapping from NonNullComposer
Merge fields and interfaces from provided GraphQLObjectType, or ObjectTypeComposer.
+Also you may provide GraphQLInterfaceType or InterfaceTypeComposer for adding fields.
+
InputType methods
+
getInputType()
+
getInputType(): GraphQLInputObjectType
+
+
hasInputTypeComposer()
+
hasInputTypeComposer(): boolean
+
+
setInputTypeComposer()
+
setInputTypeComposer(
+ itc: InputTypeComposer<TContext>
+): this
+
removeInterface(
+ iface: InterfaceTypeComposerDefinition<any, TContext>
+): this
+
+
Extensions methods
+
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/next/api/Resolver.html b/docs/next/api/Resolver.html
new file mode 100644
index 00000000..f81c01ad
--- /dev/null
+++ b/docs/next/api/Resolver.html
@@ -0,0 +1,637 @@
+Resolver · graphql-compose
The most interesting class in graphql-compose. The main goal of Resolver is to keep available resolve methods for Type and use them for building relation with other types.
Clone this Resolver with overriding of some options.
+Internally it just copies all properties.
+But for args and projection it recreates objects with the same type & values (it allows to add or remove properties without affection old Resolver).
\ No newline at end of file
diff --git a/docs/next/api/ScalarTypeComposer.html b/docs/next/api/ScalarTypeComposer.html
new file mode 100644
index 00000000..bcb69c25
--- /dev/null
+++ b/docs/next/api/ScalarTypeComposer.html
@@ -0,0 +1,353 @@
+ScalarTypeComposer · graphql-compose
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
setExtension(
+ extensionName: string,
+ value: unknown
+): this
+
+
removeExtension()
+
removeExtension(
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/next/api/SchemaComposer.html b/docs/next/api/SchemaComposer.html
new file mode 100644
index 00000000..073f097b
--- /dev/null
+++ b/docs/next/api/SchemaComposer.html
@@ -0,0 +1,512 @@
+SchemaComposer · graphql-compose
Create GraphQLSchema instance from defined types.
+This instance can be provided to express-graphql, apollo-server, graphql-yoga etc.
+
addSchemaMustHaveType()
+
addSchemaMustHaveType(
+ type: AnyType<TContext>
+): this
+
+
When using Interfaces you may have such Types which are hidden under Interface.resolveType method. In such cases you should add these types explicitly. Cause buildSchema() will take only real used types and types which added via addSchemaMustHaveType() method.
Creates or return existed TypeComposer from SDL or object.
+If you call this method again with same params should be returned the same TypeComposer instance.
\ No newline at end of file
diff --git a/docs/next/api/ThunkComposer.html b/docs/next/api/ThunkComposer.html
new file mode 100644
index 00000000..e2a0586f
--- /dev/null
+++ b/docs/next/api/ThunkComposer.html
@@ -0,0 +1,150 @@
+ThunkComposer · graphql-compose
\ No newline at end of file
diff --git a/docs/next/api/TypeMapper.html b/docs/next/api/TypeMapper.html
new file mode 100644
index 00000000..c057e848
--- /dev/null
+++ b/docs/next/api/TypeMapper.html
@@ -0,0 +1,422 @@
+TypeMapper · graphql-compose
Type storage and type generator from Schema Definition Language (SDL).
+This is slightly rewritten buildASTSchema
+utility from graphql-js that allows to create type from a string (SDL).
\ No newline at end of file
diff --git a/docs/next/api/UnionTypeComposer.html b/docs/next/api/UnionTypeComposer.html
new file mode 100644
index 00000000..b1f73792
--- /dev/null
+++ b/docs/next/api/UnionTypeComposer.html
@@ -0,0 +1,448 @@
+UnionTypeComposer · graphql-compose
Extensions is a property on type/field/arg definitions to pass private extra metadata.
+It's used only on the server-side with the Code-First approach,
+mostly for 3rd party server middlewares & plugins.
+Property extensions may contain private server metadata of any type (even functions)
+and does not available via SDL.
setExtension(
+ extensionName: string,
+ value: unknown
+): this
+
+
removeExtension()
+
removeExtension(
+ extensionName: string
+): this
+
+
Directive methods
+
Directives provide the ability to work with public metadata which is available via SDL.
+Directives can be used on type/field/arg definitions. The most famous directives are
+@deprecated(reason: "...") and @specifiedBy(url: "...") which are present in GraphQL spec.
+GraphQL spec allows to you add any own directives.
\ No newline at end of file
diff --git a/docs/next/api/misc-api-methods.html b/docs/next/api/misc-api-methods.html
new file mode 100644
index 00000000..4242b168
--- /dev/null
+++ b/docs/next/api/misc-api-methods.html
@@ -0,0 +1,427 @@
+API misc · graphql-compose
The same as getProjectionFromAST except that all nested fields will not be extracted. Complex address will be just true, not { city: true, street: true },
graphql-compose re-exports GraphQL.js package for its plugins. It helps to avoid the hell with maintaining versions of graphql and graphql-compose in plugins' package.json files.
+
If you want to write a plugin for graphql-compose and publish it to npm, just add graphql-compose in dependencies of its package.json. And if you will need GraphQL.js objects and methods you may import them in such way:
+
// My awesome Plugin for graphql-compose
+import { graphql } from'graphql-compose';
+
+const { GraphQLNonNull, GraphQLObjectType } = graphql;
+
+
graphqlVersion
+
Sometimes it need to know which version of GraphQL.js is installed in the project.
+It may be used in graphql-compose plugins, cause different versions of GraphQL.js may have breaking changes and your plugins may have workarounds for different behavior.
+
const graphqlVersion: number;
+
+
import { graphqlVersion } from'graphql-compose';
+
+if (graphqlVersion < 13) {
+ throwError(`This plugin does not work with GraphQL.js v${graphqlVersion}`);
+}
+
+
Scalar Types
+
GraphQLDate
+
GraphQL scalar type that converts javascript Date object to string YYYY-MM-DDTHH:MM:SS.SSSZ and back.
+
import { GraphQLDate } from'graphql-compose';
+
+
GraphQLJSON
+
GraphQL scalar type that represents JSON. Field with this type may have arbitrary structure. Copied from @taion's graphql-type-json for reducing dependencies tree.
+
import { GraphQLJSON } from'graphql-compose';
+
+
TypeStorage
+
You may need some isolated storage for keeping types in your plugins. So TypeStorage is the easy way to obtain such storage.
\ No newline at end of file
diff --git a/docs/next/basics/generating-schema.html b/docs/next/basics/generating-schema.html
new file mode 100644
index 00000000..dc9363c5
--- /dev/null
+++ b/docs/next/basics/generating-schema.html
@@ -0,0 +1,214 @@
+Generating Schema · graphql-compose
SchemaComposer allows to build a GraphQLSchema instance. The Schema obtained calling the buildSchema() method may be used in express-graphql, apollo-server and other libs that use GraphQL.js under the hood for query execution at runtime.
+
Create Schema
+
SchemaComposer provides three basic root types: Query, Mutation and Subscription. It's imperative to initialize at least one of those, otherwise our Schema would not build.
+
import { schemaComposer } from'graphql-compose';
+import { AuthorTC } from'./author';
+
+schemaComposer.Query.addFields({
+ // add field with regular FieldConfig
+ currentTime: {
+ type: 'Date',
+ resolve: () =>Date.now(),
+ },
+ // Assume that `AuthorTC` build with `graphql-compose-mongoose` which has CRUD resolvers
+ // in such case we can use pre-generated Resolvers as a FieldConfig
+ authorById: AuthorTC.getResolver('findById'),
+ authorMany: AuthorTC.getResolver('findMany'),
+ // ...
+});
+
+schemaComposer.Mutation.addNestedFields({
+ // also it may be very useful define nested fields
+ // Mutation will have `author` field, `author` will have `create` and `update` fields inside
+ 'author.create': AuthorTC.getResolver('createOne'),
+ 'author.update': AuthorTC.getResolver('updateById'),
+ // ...
+});
+
+exportdefault schemaComposer.buildSchema(); // exports GraphQLSchema
+
+
Restrict access
+
GraphQL.js does not provide any access rights checks, so a developer would need to implemented them manually in the resolve methods. With graphql-compose it can be done by wrapping Resolvers:
+
// rootMutation.js
+import { schemaComposer } from'graphql-compose';
+
+import { CommentTC } from'./comment';
+import { UserTC } from'./user';
+
+schemaComposer.Mutation.addNestedFields({
+ commentCreate: CommentTC.getResolver('createOne'), // may anybody
+
+ ...adminAccess({
+ // only for admins
+ 'user.create': UserTC.getResolver('createOne'),
+ 'user.update': UserTC.getResolver('updateById'),
+ 'user.remove': UserTC.getResolver('removeById'),
+ }),
+});
+
+functionadminAccess(resolvers) {
+ Object.keys(resolvers).forEach(k => {
+ resolvers[k] = resolvers[k].wrapResolve(next => rp => {
+ if (!rp.context.isAdmin) {
+ thrownewError('You should be admin, to have access to this action.');
+ }
+ return next(rp);
+ });
+ });
+ return resolvers;
+}
+
+
The isAdmin property from the above example must be defined in express-graphql or apollo-server, in order to retrieve it from context:
In some complex scenarios we may need several GraphQL Schemas within a single app. graphql-compose by default exports the following classes/instances for single schema mode:
The equivalent class for multi-schema mode is called SchemaComposer (with a capital S). Unlike with single-schema where we have a static class, SchemaComposer has a constructor and allows creating multiple instances:
Types created via ObjectTypeComposer1 and ObjectTypeComposer2 will not be visible to each other: name-clashing and overriding would not be isssues, and multiple definitions for types with the same name are allowed, as long as they live in separate SchemaComposer instances.
\ No newline at end of file
diff --git a/docs/next/basics/type-modification.html b/docs/next/basics/type-modification.html
new file mode 100644
index 00000000..b6d07ec0
--- /dev/null
+++ b/docs/next/basics/type-modification.html
@@ -0,0 +1,209 @@
+Type modification · graphql-compose
This is the most important part of graphql-compose and the main difference in Schema creation with GraphQL.js. In GraphQL.js you have strict abilities in type definition and its further modification. But graphql-compose allows to you modify types after creation in very convenient ways.
+
+
Note: With graphql-compose you may modify types before GraphQLSchema object creation. When schema was created you cannot change types.
+
+
Fields modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer instances:
+
+
getFields()
+
setFields()
+
getFieldNames()
+
hasField(name)
+
setField(name, fieldConfig)
+
addFields(newFieldsConfig)
+
getField(name)
+
removeField(nameOrArray)
+
removeOtherFields(nameOrArray)
+
extendField(name, partialFieldConfig)
+
reorderFields(names)
+
deprecateFields(nameOrMap)
+
+
Additional methods in ObjectTypeComposer, InputTypeComposer, InterfaceTypeComposer instances:
+
+
getFieldType(name)
+
getFieldTC(name)
+
getFieldConfig(name)
+
makeFieldNonNull(nameOrArray)
+
makeFieldNullable(nameOrArray)
+
addNestedFields(newFields)
+
+
// add description to `firstName`
+AuthorTC.extendField('firstName', {
+ description: "This field returns Author's first name",
+});
+
+// Add new field `status` with Enum type
+AuthorTC.addField('status', `enum AuthorStatus { ACTIVE INACTIVE }`);
+
+// Change order of fields in type
+// unlisted fields will be added to the end of field list with old order
+AuthorTC.reorderFields(['status', 'firstName']);
+
+// Mark fields as deprecated with some message
+AuthorTC.deprecateFields({
+ rating: 'This field will be removed in June 2018',
+ dob: 'Use `age` field instead. This field will be removed in June 2018',
+});
+
+// Add new field with `address` name and for type
+// create a new object type with `city` and `country` fields
+AuthorTC.addNestedFields({
+ 'address.city': 'String',
+ 'address.country': 'String',
+});
+
+
Type modification
+
Available methods in ObjectTypeComposer, InputTypeComposer, EnumTypeComposer, InterfaceTypeComposer, UnionTypeComposer instances:
+
+
getType()
+
getTypePlural()
+
getTypeNonNull()
+
getTypeName()
+
setTypeName(newName)
+
getDescription()
+
setDescription()
+
clone(newTypeName)
+
+
Additional methods in ObjectTypeComposer
+
+
getInterfaces()
+
setInterfaces(interfaces)
+
hasInterface(interfaceObj)
+
addInterface(interfaceObj)
+
removeInterface(interfaceObj)
+
getInputType()
+
getITC()
+
+
Create your custom modification function
+
With this set of methods, you may write your own type modification functions. It may greatly reduce repetitive code across your schema definition.
+
As an example, lets write a function which will add rawData field with full record data from database. Also check isAdmin = true in context and if so return data, otherwise return null.
+
functionaddRawData(tc: ObjectTypeComposer<any, any>) {
+ if (!tc.hasField('rawData')) {
+ tc.addField('rawData', {
+ type: 'JSON',
+ resolve: (source, args, context) => {
+ if (context.isAdmin) {
+ return source;
+ }
+ returnnull;
+ },
+ // add magic property `projection`
+ // which request all fields from database
+ // when requested this `rawData` field in the query
+ projection: { '*': 1 },
+ });
+ }
+}
+addRawData(AuthorTC);
+addRawData(PostTC);
+
+
Or even more
+
You may write your own plugins which will generate types from some models or non-graphql schemas. Take a look on avaliable list of plugins build on top of graphql-compose.
\ No newline at end of file
diff --git a/docs/next/basics/understanding-relations.html b/docs/next/basics/understanding-relations.html
new file mode 100644
index 00000000..666342c5
--- /dev/null
+++ b/docs/next/basics/understanding-relations.html
@@ -0,0 +1,311 @@
+Relations between Types · graphql-compose
GraphQL allows to create additional fields in our types, thus providing data from another type. For example, we may add a field posts to the Author type and write a resolve function, so that this field will return an array of posts only for the current Author.
What if we want to provide a filter argument, which adds the ability to filter by creation date, and min number of votes?
+That would be achieved by the following code:
This would work just fine, but it has become quite a lot of code. And what if we have other Types with relations with Posts (eg. Reviewer, Reader)? Copy/pasting our resolve method is probably not a good idea. That's because in the future we may want to add a new filter property, and that would mean scanning all of our code to add additional logic in all FieldConfigs. The next section will detail a better approach to this problem.
+
Relation via Resolver
+
graphql-compose provides a Resolver class that allows using the same FieldConfigs in different Types. We may create a Resolver defining type, args and a resolve function, then reuse it everywhere we need it in our Schema.
+
However if we define our posts resolver in a separate file, we'll then face another problem:
+
+
in Author type we will use criteria = { authorId: source.id } for the resolve method;
+
in Reviewer - criteria = { reviewers: { $has: source.id } } and so on.
+
+
In this case it's better to improve args.filter by allowing to set authorId and reviewerId via arguments:
Should be an arrow function that returns Resolver. Wrapping resolver in an arrow function helps solving the hoisting problem (when two types import each other).
+
prepareArgs
+
At runtime we should have the ability to prepare (ie. assign a value to) the args that will be passed to Resolver.
+
For example our Resolver has the arguments filter, limit, skip and sort.
+prepareArgs provides a way to set them up:
+
+
limit: 10 - hides limit arg from schema and set it equal to 10
+
filter: (source) => value - hides filter arg form schema and at runtime evaluate its value
+
sort: null - disables argument (hides it from schema and do not pass it to resolver)
+
all undescribed args (like skip) will be avaliable in the schema and will be avaliable in query
+
+
projection
+
Is a very useful option for extending requested fields in your query. It's very good practice to request from database only the fields included in our query. But sometimes we need additional fields, for example to provide the findById resolver with an authorId. For this purpose we can use projection.
Without projection the resolver would try to populate the author field, but args.authorId would be undefined. It would therefore be impossible for the query filter to find matching authors and populate the author field. Normally when a client wants to retrieve the author field in a GraphQL Query, it would also need to provide the authorId explicitly. By using a projection we lift that responsility from the client, making querying easier and less cluttered.
\ No newline at end of file
diff --git a/docs/next/basics/understanding-types.html b/docs/next/basics/understanding-types.html
new file mode 100644
index 00000000..be702bbc
--- /dev/null
+++ b/docs/next/basics/understanding-types.html
@@ -0,0 +1,401 @@
+Type creation · graphql-compose
With graphql-compose you need to create types under some schemaComposer instance. By default graphql-compose has a global schemaComposer instance which can be obtained in the following manner:
But if you need to create several GraphQL schemas in your app, you may import SchemaComposer class and create schemaComposer instances as much as you need:
+
import { SchemaComposer } from'graphql-compose';
+
+const schemaComposer1 = new SchemaComposer();
+const schemaComposer2 = new SchemaComposer();
+
+
Take a note that schemaComposer1 and schemaComposer2 will have different type storages. And types in schemaComposer1 will not be avaliable in schemaComposer2 and vice versa.
+
Scalar types
+
Graphql-compose has following built-in scalar types:
+
+
String
+
Float
+
Int
+
Boolean
+
ID
+
Date
+
JSON
+
+
via config
+
You may create scalar types via config, like with GraphQLScalarType:
If you need to create some complex type with several properties (fields), you will need to use ObjectTypeComposer. It's a builder for GraphQLObjectType object.
+
ObjectTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Output type. Such definition provides better developer experience with jumping to the type declarations.
+
const AuthorTC = schemaComposer.createObjectTC({
+ name: 'Author',
+ fields: {
+ id: 'Int!',
+ firstName: 'String',
+ lastName: 'String',
+ posts: {
+ type: () => [PostTC], // arrow function for `type` helps to solve hoisting problems and keep ability to list all fields
+ args: {
+ limit: { type: 'Int', defaultValue: 20 },
+ skip: 'Int', // shortand to `{ type: 'Int' }`
+ sort: `enum AuthorPostsSortEnum { ASC DESC }`, // type creation via SDL
+ },
+ resolve: () => { ... },
+ }
+ },
+});
+
+
Also this way of definition provides a lot of syntax sugar for field definition:
+
const AuthorTC = schemaComposer.createObjectTC({
+ posts: {
+ // wrapping Type with arrow function helps to solve a hoisting problem
+ // also using type instances provides better DX
+ // (ctrl+click allows to jump to PostTC type declaration in your IDE)
+ type: () => PostTC,
+ description: 'Posts written by Author',
+ resolve: (source, args, context, info) => {},
+ },
+ // using standard GraphQL Type
+ ucFirstName: {
+ type: GraphQLString,
+ resolve: (source) => { return source.firstName.toUpperCase(); },
+ // also request `firstName` field which must be loaded from database
+ projection: { firstName: true },
+ },
+ // fast way if you need to define only type
+ counter: 'Int',
+ // using SDL for definition new ObjectType
+ complex: `type ComplexType {
+ subField1: String
+ subField2: Float
+ subField3: Boolean
+ subField4: ID
+ subField5: JSON
+ subField6: Date
+ }`,
+ // SDL for defining array of strings, which is NonNull
+ list0: {
+ type: '[String]!',
+ description: 'Array of strings',
+ },
+ list1: '[String]',
+ list2: ['String'],
+ list3: [GraphQLString],
+ list4: [`type Complex2Type { f1: Float, f2: Int }`],
+});
+
+
via SDL
+
May have hoisting problems. Be aware that all used complex types must be already defined.
GraphQL allows to pass arguments for fields. You may freely use Scalars, Enums when describing input args. But what you should do in the case of mutations, where you might want to pass in a whole object to be created? For such cases for complex types instead of GraphQLObjectType you should use GraphQLInputObjectType. They they have small differences in its fields declaration:
+
+
input object type has defaultValue
+
input object type does not have args
+
input object type does not have resolve method
+
+
If you need to create some complex type with several properties, you will need to use InputTypeComposer. It's a builder for GraphQLInputObjectType object.
+
InputTypeComposer has very convenient ways of type creation.
+
via config
+
Most recommended way to define your Input type. Such definition provides hoisting problems solution via wrapping types by arrow function. Better developer experience with jumping to the type declarations.
+
InputTypeComposer has the same type definition capabilities for describing fields as ObjectTypeComposer - as string, as arrow function, as SDL.
Useful when you write your own type generators. Enum has values (not fields), but for similar method naming with ObjectTypeComposer and InputTypeComposer in graphql-compose methods for value modification have field keyword.
Graphql-compose provides the following helper for Interfaces - InterfaceTypeComposer.
+
import { schemaComposer } from'graphql-compose';
+
+const TimestampInterface = schemaComposer.createInterfaceTC({
+ name: 'Timestampable',
+ description: 'An object with createdAt and updatedAt fields',
+ fields: {
+ createdAt: 'Date',
+ updatedAt: 'Date',
+ },
+});
+
+// When you create Interface, you need to provide instructions how to determine exact ObjectType from `value`.
+// So if `value` is instance of UserDoc, then use `UserTC` as exact type.
+TimestampInterface.addTypeResolver(UserTC, value => (value instanceof UserDoc));
+TimestampInterface.addTypeResolver(ArticleTC, value => (value instanceof UserDoc));
+
+
Lists
+
If you want indicate that field or argument return an array of some type, you may do the following:
+
import { GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field1: [AuthorTC], // RECOMMENDED just wrap in the regular js array
+ field2: AuthorTC.getTypePlural(), // call specific ObjectTypeComposer method
+ field3: '[Author]', // use SDL format
+ field4: new GraphQLList(AuthorTC.getType()) // use standard GraphQLList
+});
+
+
Non-Null
+
If you want indicate that field is not empty or argument is required:
+
import { GraphQLNonNull } from'graphql';
+
+SomeTypeComposer.addFields({
+ // field1: ???, // doesn't exists any regular object in js for expressing NonNull value
+ field2: AuthorTC.getTypeNonNull(), // call specific ObjectTypeComposer method
+ field3: 'Author!', // use SDL format
+ field4: new GraphQLNonNull(AuthorTC.getType()) // use standard GraphQLNonNull
+});
+
+
Non-Null List of Non-Null values may be expressed in following way:
+
import { GraphQLNonNull, GraphQLList } from'graphql';
+
+SomeTypeComposer.addFields({
+ field3: '[Author!]!', // use SDL format
+ field4: new GraphQLNonNull( // use standard GraphQLNonNull & GraphQLList
+ new GraphQLList(
+ new GraphQLNonNull(AuthorTC.getType())
+ )
+ )
+});
+
\ No newline at end of file
diff --git a/docs/next/basics/what-is-resolver.html b/docs/next/basics/what-is-resolver.html
new file mode 100644
index 00000000..261a5924
--- /dev/null
+++ b/docs/next/basics/what-is-resolver.html
@@ -0,0 +1,320 @@
+Resolvers · graphql-compose
Shortly, Resolver is an object which knows how to process data and what to return. It's like a function definition in static language where you give it name, describe types for input arguments and output result.
+
GraphQL.js describes such functions in complex output types via GraphQLFieldConfig:
GraphQLFieldConfig has information about returned type, available args, implementation of resolve logic and some other properties. In terms of graphql-compose this field config is called as Resolver.
+
The main aim of Resolver is to keep available resolve methods for Type and use them for building relation with other types. Resolver provide following abilities:
+
+
add, remove, get, make optional/required arguments
+
clone Resolver for further logic extension
+
wrap args, type, resolve (get resolver and create new one with extended/modified functionality)
+
provide helper methods addFilterArg and addSortArg which wrap resolver by adding argument and additional resolve logic
+
+
Resolver has following properties:
+
+
type output complex or scalar type (resolver returns data of this type)
+
args list of fields of input or scalar types (resolver accept input arguments for resolve method)
+
resolve method which contains your bussiness logic, for fetching, processing and returning data. BE AWARE: that all arguments (source, args, context, info) are passed inside one argument called as resolveParams (rp for brevity in the code).
+
description public description which will be passed to graphql schema and will be available via introspection
+
deprecationReason if you want to hide field from schema, but leave it working for old clients
+
name any name for resolver that allow to you identify what it does, eg findById, updateMany, removeOne
+
kind type of resolver query (resolver just fetch data) or mutation (resolver change data)
+
parent you may wrap existed Resolver for adding additional checks, modifying result, adding arguments. This property keeps reference to existed unwrapped Resolver
+
+
Why do we need the Resolver?
+
Graphql-compose allows creating such "functions" or "FieldConfigs" via giving it names and keep in your ObjectTypeComposer. You may create any number of Resolvers and store them in your type.
+
Assume you have an Author type. And you have different standard CRUD operations for fetching and modifying this type:
+
+
findById
+
findMany
+
updateById
+
removeById
+
etc
+
+
When you will construct your Schema, you may need several times the same logic from standard Resolvers. For example
+
+
in the Query type may be added fields
+
+
authorById for finding Author by id arg via findById resolver
+
authorMany for finding list of Author with some filter criteria via findMany resolver
+
+
in the Post type may be added
+
+
author field which request Author by id from current post.authorId value via findById resolver
+
reviewers field which request Authors via findMany resolver with custom filtering
+
+
+
Resolvers helps to describe CRUD operations logic only once and then reuse them in different scenarios. For Query.authorById provides its full functionality from findById resolver. For Post.author you wrap findById resolver where should be hidden id arg and its value automatically will be set from post.authorId. For wrapping Resolvers graphql-compose provides a bunch of methods.
+
Creating Resolver
+
via TC.addResolver()
+
Mostly Resolvers are created according to the specific Type. So it's better to create them and store in some ObjectTypeComposer instance.
+
Lets's take AuthorTC and describe how it can be found by id:
+
AuthorTC.addResolver({
+ name: 'findById',
+ args: { id: 'Int' },
+ type: AuthorTC,
+ resolve: async ({ source, args }) => {
+ const res = await fetch(`/endpoint/${args.id}`); // or some fetch from any database
+ const data = await res.json();
+ // here you may clean up `data` response from API or Database,
+ // it should has same shape like AuthorTC fields
+ // eg. { firstName: 'Peter', nickname: 'peet', views: 20 }
+ // if some fields in `data`:
+ // are undefined or missing - graphql returns `null` for that fields
+ // are not described in output `type` - graphql will remove them from responce
+ return data;
+ },
+});
+
+
And in any place of your schema you will able to use this Resolver in such way:
You may create instance of Resolver without attaching it to some ObjectTypeComposer. It can be done in following way:
+
import { schemaComposer } from'graphql-compose';
+
+const findCityLocationByIdResolver = schemaComposer.createResolver({
+ name: 'findCityLocationById',
+ type: `type CityLocation { lon: Float, lat: Float }`,
+ args: {
+ id: 'Int!',
+ },
+ // BE AWARE! `resolve` method in `Resolver` accept only one argument `resolveParams`
+ // which contains
+ // standard properties from `GraphQLFieldResolveFn`: source, args, context, info
+ // and additional properties: projection
+ resolve: async ({ source, args, context, info }) => {
+ const city = await DB.city.findById(args.id);
+ if (!city) returnnull;
+ return {
+ lon: city.longitude,
+ lat: city.latitude,
+ };
+ }
+});
+
+// And add this resolver to your Schema
+schemaComposer.Query.addFields({
+ cityLocation: findCityLocationByIdResolver,
+});
+
+
Wrapping Resolver
+
In many cases, it is very convenient to create a Resolver which just fetch data providing rich filter and sort arguments (also it may modify data).
+But what if we need to restrict access or set up some arguments of Resolver from source (parent) object or context?
+
Yep, you need to wrap the Resolver! Wrap just resolve method via Resolver.wrapResolve(). Or Resolver.wrap() if we want to change simultaneously output type, args and resolve method.
+
via Resolver.wrapResolve()
+
The most commonly used method for wrapping is Resolver.wrapResolve(). Let take a look how can be it used in your Schema:
+
schemaComposer.Query.addFields({
+ // add endpoint which returns only visible posts
+ publicPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `visibility` argument
+ // so forcibly set this arg to true
+ rp.args.visibility = true;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns posts only for current authenticated user
+ ownerPosts: PostTC.getResolver('findMany').wrapResolve(next => rp => {
+ // assume that your basic `findMany` resolver has `authorId` argument
+ // so forcibly set this arg to current user id
+ rp.args.authorId = rp.context.currentUserId;
+ // after that delegate finding to basic `findMany` with modified resolveParams
+ return next(rp);
+ });
+
+ // add endpoint which returns all authors only for admin
+ allAuthorsForAdmin: AuthorTC.getResolver('findMany').wrapResolve(next => rp => {
+ // check `isAdmin` property in context, which was somehow setted
+ // on express-graphql or apollo-server level
+ // for regular user return null
+ if (!rp.context.isAdmin) returnnull;
+ // for admin delegate execution to the basic resolver
+ return next(rp);
+ });
+});
+
+
via Resolver.wrap()
+
This is a less-used method. But it's more powerfull. It allows to change simultaneously output type, args and resolve method.
+
What if admin should have all avaliable filter params and add new one for searching but regular user just limited set of arguments?
+
Resolver wrapping creates a new Resolver. So for admin you create a new resolver findManyForAdmin by wrapping a basic resolver, eg. findMany add additional args and logic. For user you create findManyReduced by wrapping existed findMany resolver and removing some filter args.
+
Let write reduced resolver findManyReduced, where we remove some args
+
const findManyReduced = AuthorTC.getResolver('findMany').wrap(newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgITC('filter').removeFields(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
via TC.wrapResolverAs()
+
Also you may want to modify already existed Resolver in some ObjectTypeComposer, like it did Resolver.wrap() method.
+
For simplifying this process you may use ObjectTypeComposer.wrapResolverAs() method.
+Let take AuthorTCs findMany resolver and create a new one with name findManyReduced.
+
AuthorTC.wrapResolverAs('findManyReduced', 'findMany', newResolver => {
+ // for new created resolver, clone its `filter` argument with a new name
+ newResolver.cloneArg('filter', 'AuthorFilterForUsers');
+ // remove some filter fields to which regular users should not have access
+ newResolver.getArgTC('filter').removeField(['age', 'other_sensetive_filter']);
+ // and return modified resolver with new set of args
+ return newResolver;
+});
+
+
Advanced
+
How Resolver.wrapResolve() work internally
+
+
capturing phase, when you may change resolveParams (rp in the code) before it will pass to next resolve
+
bubbling phase, when you may change response from underlying resolve
+
+
Resolver.wrapResolve(next => rp => {
+ // [CAPTURING PHASE]:
+ // `rp` consist from { source, args, context, info, projection }
+ // you may change `source`, `args`, `context`, `info`, `projection` before it will pass to `next` underlying resolve function.
+
+ // ...some code which modify `rp` (resolveParams)
+
+ // ... or just stop propagation
+ // throw new Error();
+ // or
+ // return Promise.resolve(null);
+
+ // pass request to underlying middleware and get result promise from it
+ const resultPromise = next(rp);
+
+ // [BUBBLING PHASE]: here you may change payload of underlying resolve method, via promise syntax
+ // ...some code, which may add `then()` or `catch()` to result promise
+ // resultPromise.then(payload => { console.log(payload); return payload; })
+
+ return resultPromise; // return payload promise to upper wrapper
+});
+
\ No newline at end of file
diff --git a/docs/next/guide/elasticsearch-with-mongoose.html b/docs/next/guide/elasticsearch-with-mongoose.html
new file mode 100644
index 00000000..65d309e2
--- /dev/null
+++ b/docs/next/guide/elasticsearch-with-mongoose.html
@@ -0,0 +1,385 @@
+[WIP] Use ElasticSearch with Mongoose · graphql-compose
Connect MongoDB with ElasticSearch and GraphQL quite complex and long task and consist of a bunch of steps. Every step can be tuned for your needs.
+
1. Extending Mongoose ORM with elasticsearch data
+
For working with MongoDB collections and documents is good practice to use some ORM. For nodejs better solution is mongoose. Also exists cool mongoose-elasticsearch-xp (by @jbdemonte) package (plugin for mongoose) which provides useful methods and hooks which ridiculously simplify data syncing with MongoDB and ElasticSearch.
+
1.1. Defining Mongoose schema with settings for elasticsearch-xp [SCHEMA DEFINITION]
1.2 Plug mongoose-elasticsearch-xp to your Mongoose Schema with data filtering [SYNC MONGO & ES DATA]
+
/* elastic */
+JobSchema.plugin(mongooseElasticsearch, {
+ client: elasticClient, // <------ see `graphql-elasticsearch-xp` for details
+ filter: doc => {
+ if (doc.visibility !== 'published') {
+ // add to index new record with visibility='published'
+ // or remove existed record from index if `visibility` changed and not 'published' anymore
+ returnfalse;
+ }
+ returntrue;
+ },
+});
+
+
By default mongoose-elasticsearch-xp will track add/remove operations and update your data in elasticsearch. In this case I provide filter option, now it will track more clever model's inserts/updates and send proper changes to your elasticsearch server.
+
Already existed data can be synced via esSynchronize method.
+
1.3 Connection with elasticsearch server elasticClient [ES CLIENT]
+
You should provide elasticClient in step 1.2 (for mongoose plugin [UPDATING DATA]) and 1.4 (for graphql resolvers [SEARCH]). It holds connection of your nodejs server with elasticsearch server.
import { composeWithElastic } from'graphql-compose-elasticsearch';
+import { generate } from'mongoose-elasticsearch-xp/lib/mapping';
+
+exportconst JobEsTC = composeWithElastic({
+ graphqlTypeName: 'JobES',
+ elasticIndex: 'job',
+ elasticType: 'job',
+ elasticMapping: {
+ properties: generate(JobSchema),
+ },
+ elasticClient,
+ // elastic mapping does not contain information about is fields are arrays or not
+ // so provide this information explicitly for obtaining correct types in GraphQL
+ pluralFields: ['employment'],
+});
+
fragment on Query {
+ jobEsConnection(first: $first, query: $query, sort: $sort, aggs: $aggs) {
+ count
+ aggregations
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ cursor
+ node {
+ _score# meta-data from ES
+ _id# meta-data from ES
+
+ _source {
+ employment # record data from ES
+ position# record data from ES
+ }
+
+ fromMongo { # data from Mongo
+ _id
+ onlyMongooseData
+ visibility
+ salary { fromto currency}
+ position
+ }
+ }
+ }
+ }
+}
+
+
See https://github.com/nodkz/graphql-compose
+Sorry bad docs in graphql-compose. Really do not have time to write it. So try to see issues they contain a lot of info.
+
1.7 Add needed resolvers to schema [BUILD GRAPHQL SCHEMA]
import { schemaComposer } from 'graphql-compose';
+import { elasticApiFieldConfig } from 'graphql-compose-elasticsearch';
+import elasticClient from 'schema/elasticClient';
+
+export const ElasticTC = schemaComposer.getOTC('ELASTIC');
+
+ElasticTC.addResolver({
+ name: 'onlyForAdmins',
+ type: ElasticTC,
+ resolve: ({ context }) => {
+ if (!isAdmin({ context })) { // <--- somehow check that you are admin
+ throw new Error('You should be admin, to have access to this area.');
+ }
+ return {};
+ },
+});
+
+# expose all elastic api via graphql
+ElasticTC.addFields({
+ api: elasticApiFieldConfig(elasticClient),
+});
+
+// DONT FORGET TO add elastic to your schema (eg. to ROOT query)
+schemaComposer.Query.addFields({
+ elastic: ElasticTC.getResolver('onlyForAdmins'),
+});
+
Now you may call reindexing all your data in elasticsearch via following graphql query:
+
query {
+ elastic {
+ reindexJob
+ }
+}
+
+
\ No newline at end of file
diff --git a/docs/next/guide/file-uploads.html b/docs/next/guide/file-uploads.html
new file mode 100644
index 00000000..b7975f27
--- /dev/null
+++ b/docs/next/guide/file-uploads.html
@@ -0,0 +1,256 @@
+File uploads · graphql-compose
If you decide how to upload files via some REST endpoint or GraphQL. So I recommend to upload via some REST API and then provide a path of the uploaded file to your mutation request. GraphQL designed to provide typed data according to client request shape. With files (binary data) it works too, but better to do it via well-recommended REST calls. In such case, you separate highly costed upload logic from data manipulation logic. In the future, this will help you diagnose problems with the load more easily.
+
Anyway products have different scenarios and you may be forced to upload files via GraphQL. For uploading files via GraphQL you will need:
apollo-upload-server - for parsing multipart/form-data POST requests via busboy and providing Files data to resolve function as argument.
+
+
Tutorial
+
1. Preparing express-graphql server
+
This is most important part of enabling file uploads on server-side. You need to parse body data via bodyParser.json() and multipart form data via apolloUploadExpress(/* Options */).
This is a most problematic part and it's out of scope of graphql-compose (it's client-side problem). You must correctly send HTTP request from the client. But if you very carefully read graphql-multipart-request-spec, then you should not have any questions.
+
Here's an example of proper multipart/form-data POST request with
+
+
operations key for GraphQL request with query and variables
+
map key with mapping some multipart-data to exact GraphQL variable
+
and other keys for multipart-data which contains binary data of files
\ No newline at end of file
diff --git a/docs/next/guide/mongoose.html b/docs/next/guide/mongoose.html
new file mode 100644
index 00000000..f28a2ac6
--- /dev/null
+++ b/docs/next/guide/mongoose.html
@@ -0,0 +1,133 @@
+[WIP] Generate types from Mongoose Models · graphql-compose
Well TypeComposers generated by graphql-compose-mongoose ships with resolvers for create, update and remove.
+Looking like this:
+
UserTC.getResolver('createOne').getFieldConfig();
+UserTC.getResolver('updateById').getFieldConfig();
+// or for shorthand
+UserTC.get('$removeMany').getFieldConfig();
+// and buch of other resolvers
+
+
Lets add a working example from the preview UserTC we have created
// user.js
+
+UserTC.addResolver({
+ name: 'myCustomUpdate',
+ kind: 'mutation',
+ args: {
+ id: 'String',
+ firstName: 'String',
+ lastName: 'String',
+ complexArg: `input SomeComplexInput {
+ min: Int
+ max: Int
+ }`,
+ },
+ type: UserTC,
+ resolve: ({ _, args, context, info }) => {
+ //edit and do what you need..
+ return user;
+ },
+});
+
+// so now you may add you custom mutation to schema
+schemaComposer.Mutation.addFields({
+ customUserUpdate: UserTC.getResolver('myCustomUpdate'),
+});
+
+
\ No newline at end of file
diff --git a/docs/next/guide/relay.html b/docs/next/guide/relay.html
new file mode 100644
index 00000000..d59e7b4d
--- /dev/null
+++ b/docs/next/guide/relay.html
@@ -0,0 +1,73 @@
+[WIP] Relay Schema · graphql-compose
Adding support for Relay is done via plugin graphql-compose-relay For more detailed descriptions on how to use and reporting issues please use the link.
\ No newline at end of file
diff --git a/docs/next/guide/wrapping-rest-api.html b/docs/next/guide/wrapping-rest-api.html
new file mode 100644
index 00000000..670bbc7d
--- /dev/null
+++ b/docs/next/guide/wrapping-rest-api.html
@@ -0,0 +1,220 @@
+Wrapping REST API · graphql-compose
Many developers are attracted by GraphQL’s benefits over REST. The reason for that is its query language enabling to stick to the data that the client needs at the moment and not to restructure the client to fit API structure. Single endpoint, but flexible data shape.
+
Let’s imagine you already have an existing RESTful API, but your task requires using GraphQL either you just want to try it out of curiosity. If that's the case, you would need to wrap your REST in GraphQL Schema and hardcoding all the GraphQL Types is a real pain.
+
That's why we came up with a RESTful API wrapper for GraphQL featuring automatic GraphQL Type generation.
+
Installation
+
npm install graphql-compose-json
+
+
Demo
+
We've wrapped SWAPI RESTful API in to show capabilities of graphq-compose-json
Using graphql-compose is easy — it's just one, but helpful function:
+
import composeWithJson from'graphql-compose-json';
+
+const restApiResponse = {
+ name: 'Anakin Skywalker',
+ birth_year: '41.9BBY',
+ starships: [
+ 'https://swapi.co/api/starships/59/',
+ 'https://swapi.co/api/starships/65/',
+ 'https://swapi.co/api/starships/39/',
+ ],
+ mass: () =>'Int!', // by default JSON numbers are coerced to Float, here we've set it to Integer
+ starships_count: () => ({ // granular inline field config with resolve function
+ type: 'Int',
+ resolve: source => source.starships.length,
+ }),
+};
+
+exportconst CustomPersonTC = composeWithJson('CustomPerson', restApiResponse);
+
+
That's it! The Type is ready to be used and have its resolvers defined. CustomPersonTC contains all things you need to compose Resolvers and Schema.
+
Specifying data fetching method
+
What we're trying to do is to wrap an existing RESTful API in GraphQL Schema, but it is not yet aware of where the data is stored, it knows only the possible data shape; thus we need to specify how to fetch the API data.
+
Valid GraphQL data request requires three pieces: resolve(data fetching method), args(list of acceptable input arguments) and type(data representation form, which we already have thanks to graphql-compose-json). GraphQL terms label these three a Field Config (or Resolver).
It's unlikely that the Schema will have only one Type, hence we've got to link our scattered types. Imagine we want Person Type to return the list of movies they starred in. Assuming that Person has links to them, all we need is to add a resolver to FilmTC.
Defining Resolvers within ObjectTypeComposers they belong to helps to keep your code DRY, as further on you'll be able to reuse them with just one line of code:
+
Planet.getResolver('findMany');
+
+
Composing the Schema
+
Now with Types and Resolvers created it's time to put them into Schema.
\ No newline at end of file
diff --git a/docs/next/intro/installation.html b/docs/next/intro/installation.html
new file mode 100644
index 00000000..9c8fcd2c
--- /dev/null
+++ b/docs/next/intro/installation.html
@@ -0,0 +1,114 @@
+Installation · graphql-compose
Module graphql is declared in peerDependencies, so it should be installed explicitly in your project. This helps to solve a common problem when some of your other dependencies (like Relay, GraphiQL, graphql-compose) can leave your node_modules directory with duplicate installs of GraphQL.js. In such case graphql-js may throw errors stating that some classes are not instances of duplicate module.
+
Also you may need to install some graphql-compose plugins. Each plugin has own Install section with instructions.
\ No newline at end of file
diff --git a/docs/next/intro/live-demos.html b/docs/next/intro/live-demos.html
new file mode 100644
index 00000000..f9a3e644
--- /dev/null
+++ b/docs/next/intro/live-demos.html
@@ -0,0 +1,120 @@
+Live Demos · graphql-compose
graphql-compose-boilerplate - ready to run a skeleton app for GraphQL server. It contains the example from Quick Start. This boilerplate includes Babel (ES6, babel-preset-env), ESLint, Flowtype, express, express-graphql, graphql, graphql-compose, nodemon.
+
+
Other demos
+
+
nodkz.github.io/relay-northwind - live demo of Relay Client App working with GraphQL Northwind Schema (8 crazy pages, 47 files, ~3000 LOC)
\ No newline at end of file
diff --git a/docs/next/intro/prerequisites.html b/docs/next/intro/prerequisites.html
new file mode 100644
index 00000000..a1301abe
--- /dev/null
+++ b/docs/next/intro/prerequisites.html
@@ -0,0 +1,116 @@
+Prerequisites · graphql-compose
To use this package it would be a good idea to know the basics of GraphQL, and how the Type System works. Since you are going to generate and edit its types you should start out there first.
+
Node.js
+
This package generates GraphQL Schema on the server side. And it will be great if you have experience with Node.js and ES6 syntax.
+
For serving requests to your generated Schema you should use one of the following packages express-graphql or apollo-server.
+
Flowtype/TypeScript
+
This is optional but quite recommended feature which covers your javascript code with static type-checking. It will help you with autosuggestion and method call validation in your IDE. This package contains built-in type definitions for Flowtype and TypeScript.
+
Internally source code of this package is written with Flowtype and has deep static type-checking with graphq-js which is also written with Flow.
\ No newline at end of file
diff --git a/docs/next/intro/quick-start.html b/docs/next/intro/quick-start.html
new file mode 100644
index 00000000..6577e8ba
--- /dev/null
+++ b/docs/next/intro/quick-start.html
@@ -0,0 +1,273 @@
+Quick Start Guide · graphql-compose
For simplicity this example works with arrays, but in the future, it will not be a problem to change data-source to any of your favorite DBs or use a mix of them.
+
Creating Types
+
Building a GraphQL Schema starts with complex Types declaration. In order to create a Type, you have to give it a unique name and specify its fields list. So let's create some Types to describe our data. For this purpose we need to import the ObjectTypeComposer helper from the graphql-compose package.
Now that we have declared Types, it’s time to link them with each other. This is the exact stage where GraphQL enormously simplifies work for clients that request data. A typical scenario when using a RESTful API goes like this: a client requests some data, and typically makes a second or even third request based on the previous response. GraphQL allows to perform a single deeply nested query, composing the result on the server’s side and sending it back to the client.
+
To make such nesting possible we need to link Author and Post Types with each other. For that we will define an author field in your Post Type, which will allow GraphQL to resolve the author's data for every post. And for Author Type we will define a posts field to make retrieving each Author's posts possible.
+
PostTC.addFields({
+ author: {
+ // you may provide the type name as a string (eg. 'Author'),
+ // but for better developer experience you should use a Type instance `AuthorTC`.
+ // This allows jumping to the type declaration via Ctrl+Click in your IDE
+ type: AuthorTC,
+ // resolve method as first argument will receive data for some Post.
+ // From this data you should then fetch Author's data.
+ // let's take lodash `find` method, for searching by `authorId`
+ // PS. `resolve` method may be async for fetching data from DB
+ // resolve: async (source, args, context, info) => { return DB.find(); }
+ resolve: post => find(authors, { id: post.authorId }),
+ },
+});
+
+AuthorTC.addFields({
+ posts: {
+ // Array of posts may be described as string in SDL in such way '[Post]'
+ // But graphql-compose allow to use Type instance wrapped in array
+ type: [PostTC],
+ // for obtaining list of post we get current author.id
+ // and scan and filter all Posts with desired authorId
+ resolve: author => filter(posts, { authorId: author.id }),
+ },
+ postCount: {
+ type: 'Int',
+ description: 'Number of Posts written by Author',
+ resolve: author => filter(posts, { authorId: author.id }).length,
+ },
+});
+
+
Building Schema
+
Now that we have our Types created, linked and we've seen how to fetch data, it’s time to create our Schema. For this purpose, we will need to use schemaComposer. It has three Root Types (entry points): Query, Mutation and Subscription and at least one of them must have defined fields.
+
import { schemaComposer } from'graphql-compose';
+
+// Requests which read data put into Query
+schemaComposer.Query.addFields({
+ posts: {
+ type: '[Post]',
+ resolve: () => posts,
+ },
+ author: {
+ type: 'Author',
+ args: { id: 'Int!' },
+ resolve: (_, { id }) => find(authors, { id }),
+ },
+});
+
+// Requests which modify data put into Mutation
+schemaComposer.Mutation.addFields({
+ upvotePost: {
+ type: 'Post',
+ args: {
+ postId: 'Int!',
+ },
+ resolve: (_, { postId }) => {
+ const post = find(posts, { id: postId });
+ if (!post) {
+ thrownewError(`Couldn't find post with id ${postId}`);
+ }
+ post.votes += 1;
+ return post;
+ },
+ },
+});
+
+// After Root type definition, you are ready to build Schema
+// which should be passed to `express-graphql` or `apollo-server`
+exportconst schema = schemaComposer.buildSchema();
+
+
Creating HTTP server
+
After constructing a Schema, let's see how to implement a server to handle client requests and send back responses using GraphQL. Let's construct a simple express app which will accept POST requests at the http://localhost:4000/graphql endpoint, handling graphql queries. Our app will also accept GET requests at the same address, providing an in-browser IDE for exploring GraphQL called GraphiQL.
Graphql-compose has the following built-in scalar types: String, Float, Int, Boolean, ID, Date, JSON. If we need to create a complex type, we will need to use schemaComposer.createObjectTC().
+
Let's see another way of creating a type via SDL:
+
const AddressTC = schemaComposer.createObjectTC(`
+ type Address {
+ city: String
+ country: String
+ street: String
+ }
+`);
+
+// and now we can extend existed Author Type with a new field with complex type
+AuthorTC.addFields({
+ address: {
+ type: AddressTC, // or 'Address'
+ description: "Author's address",
+ },
+})
+
+
More useful information about type creation can be found here.
\ No newline at end of file
diff --git a/docs/next/plugins/list-of-plugins.html b/docs/next/plugins/list-of-plugins.html
new file mode 100644
index 00000000..f3ded02c
--- /dev/null
+++ b/docs/next/plugins/list-of-plugins.html
@@ -0,0 +1,128 @@
+Plugins list · graphql-compose
graphql-compose – the imperative tool which worked on top of graphql-js. It provides useful methods for creating GraphQL Types and GraphQL Models (type with a list of
+resolvers) for further building of complex relations in your Schema. With graphql-compose you may fastly write own functions/generators for common tasks.
+
graphql-compose-[plugin] – is a declarative generator/plugin that build on top of graphql-compose, which take some ORMs, schema definitions and creates GraphQL Models from them or modify existed GraphQL Types.
+
Type generator plugins
+
+
graphql-compose-json - generates GraphQL type from JSON (a good helper for wrapping REST APIs)
+
graphql-compose-mongoose - generates GraphQL types from mongoose (MongoDB models) with Resolvers.
+
graphql-compose-elasticsearch - generates GraphQL types from elastic mappings; ElasticSearch REST API proxy via GraphQL.
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-aws.html b/docs/next/plugins/plugin-aws.html
new file mode 100644
index 00000000..2d8b8b71
--- /dev/null
+++ b/docs/next/plugins/plugin-aws.html
@@ -0,0 +1,153 @@
+graphql-compose-aws · graphql-compose
Generated Schema Introspection in SDL format can be found here (more than 10k types, ~2MB).
+
AWS SDK GraphQL
+
Supported all AWS SDK versions via official aws-sdk js client. Internally it generates Types and FieldConfigs from AWS SDK configs. You may put this generated types to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import awsSDK from'aws-sdk';
+import { AwsApiParser } from'graphql-compose-aws';
+
+const awsApiParser = new AwsApiParser({
+ awsSDK,
+});
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ // Full API
+ aws: awsApiParser.getFieldConfig(),
+
+ // Partial API with desired services
+ s3: awsApiParser.getService('s3').getFieldConfig(),
+ ec2: awsApiParser.getService('ec2').getFieldConfig(),
+ },
+ }),
+});
+
+exportdefault schema;
+
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-connection.html b/docs/next/plugins/plugin-connection.html
new file mode 100644
index 00000000..5e6f072f
--- /dev/null
+++ b/docs/next/plugins/plugin-connection.html
@@ -0,0 +1,207 @@
+graphql-compose-connection · graphql-compose
Besides standard connection arguments first, last, before and after, also added significant arguments:
+
+
filter arg - for filtering records
+
sort arg - for sorting records. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
+
Example
+
import composeWithConnection from'graphql-compose-connection';
+import userTypeComposer from'./user.js';
+
+composeWithConnection(userTypeComposer, {
+ findResolverName: 'findMany',
+ countResolverName: 'count',
+ sort: {
+ // Sorting key, visible for users in GraphQL Schema
+ _ID_ASC: {
+ // Sorting value for ORM/Driver
+ value: { _id: 1 },
+
+ // Field names in record, which data will be packed in `cursor`
+ // edges {
+ // cursor <- base64(cursorData), for this example `cursorData` = { _id: 334ae453 }
+ // node <- record from DB
+ // }
+ // By this fields MUST be created UNIQUE index in database!
+ cursorFields: ['_id'],
+
+ // If for connection query provided `before` argument with above `cursor`.
+ // We should construct (`rawQuery`) which will be point to dataset before cursor.
+ // Unpacked data from `cursor` will be available in (`cursorData`) argument.
+ // PS. All other filter options provided via GraphQL query will be added automatically.
+ // ----- [record] ----- sorted dataset, according to above option with `value` name
+ // ^^^^^ `rawQuery` should filter this set
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+
+ // Constructing `rawQuery` for connection `after` argument.
+ // ----- [record] ----- sorted dataset
+ // ^^^^^ `rawQuery` should filter this set
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ },
+
+ _ID_DESC: {
+ value: { _id: -1 },
+ cursorFields: ['_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+ },
+
+ // More complex sorting parameter with 2 fields
+ AGE_ID_ASC: {
+ value: { age: 1, _id: -1 },
+ // By these fields MUST be created COMPOUND UNIQUE index in database!
+ cursorFields: ['age', '_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$lt = cursorData.age;
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$gt = cursorData.age;
+ rawQuery._id.$lt = cursorData._id;
+ },
+ }
+ },
+});
+
+
+
Requirements
+
Types should have following resolvers:
+
+
count - for counting records
+
findMany - for filtering records. Also required that this resolver supports search with operators (lt, gt), which used in directionFilter option. Resolver findMany should have filter argument, which will be copied to connection. Also should have limit and skip args.
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-elasticsearch.html b/docs/next/plugins/plugin-elasticsearch.html
new file mode 100644
index 00000000..930a7b48
--- /dev/null
+++ b/docs/next/plugins/plugin-elasticsearch.html
@@ -0,0 +1,234 @@
+graphql-compose-elasticsearch · graphql-compose
This module expose Elastic Search REST API via GraphQL.
+
Elastic Search REST API proxy
+
Supported all elastic versions that support official elasticsearch-js client. Internally it parses its source code annotations and generates all available methods with params and descriptions to GraphQL Field Config Map. You may put this config map to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import elasticsearch from'elasticsearch';
+import { elasticApiFieldConfig } from'graphql-compose-elasticsearch';
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ elastic50: elasticApiFieldConfig(
+ // you may provide existed Elastic Client instance
+ new elasticsearch.Client({
+ host: 'http://localhost:9200',
+ apiVersion: '5.0',
+ })
+ ),
+
+ // or may provide just config
+ elastic24: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '2.4',
+ }),
+
+ elastic17: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '1.7',
+ }),
+ },
+ }),
+});
+
In other side this module is a plugin for graphql-compose, which derives GraphQLType from your elastic mapping generates tons of types, provides all available methods in QueryDSL, Aggregations, Sorting with field autocompletion according to types in your mapping (like Dev Tools Console in Kibana).
+
Generated ObjectTypeComposer model has several awesome resolvers:
+
+
search - greatly simplified elastic search method. According to GraphQL adaptation and its projection bunch of params setup automatically due your graphql query (eg _source, explain, version, trackScores), other rare fine tuning params moved to opts input field.
+
searchConnection - elastic search method that implements Relay Cursor Connection spec for infinite lists. Internally it uses cheap search_after API. One downside, Elastic does not support backward scrolling, so before argument will not work.
+
more resolvers will be later after my vacation: suggest, getById, updateById and others
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-json.html b/docs/next/plugins/plugin-json.html
new file mode 100644
index 00000000..f4059070
--- /dev/null
+++ b/docs/next/plugins/plugin-json.html
@@ -0,0 +1,311 @@
+graphql-compose-json · graphql-compose
This is a plugin for graphql-compose, which generates GraphQLTypes from REST response or any JSON. It takes fields from object, determines their types and construct GraphQLObjectType with same shape.
+
Demo
+
We have a Live demo (source code repo) which shows how to build an API upon SWAPI using graphql-compose-json.
Modules graphql, graphql-compose, are located in peerDependencies, so they should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
You have a sample response object restApiResponse which you can pass to graphql-compose-json along with desired type name as your first argument and it will automatically generate a composed GraphQL type PersonTC.
graphql-compose provides a vast variety of methods for fields and resolvers (aka field configs in vanilla GraphQL) management of GraphQL types. To learn more visit graphql-compose repo.
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-mongoose.html b/docs/next/plugins/plugin-mongoose.html
new file mode 100644
index 00000000..edcdb44c
--- /dev/null
+++ b/docs/next/plugins/plugin-mongoose.html
@@ -0,0 +1,957 @@
+graphql-compose-mongoose · graphql-compose
This is a plugin for graphql-compose, which derives GraphQLType from your mongoose model. Also derives bunch of internal GraphQL Types. Provide all CRUD resolvers, including graphql connection, also provided basic search via operators ($lt, $gt and so on).
+
Release Notes for v9.0.0 contains a lot of improvements. It's strongly recommended for reading before upgrading from v8.
Modules graphql, graphql-compose, mongoose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Intro video
+
Viktor Kjartansson created a quite solid intro for graphql-compose-mongoose in comparison with graphql-tools:
UserTC - this is a ObjectTypeComposer instance for User. ObjectTypeComposer has GraphQLObjectType inside, available via method UserTC.getType().
+
Here and in all other places of code variables suffix ...TC means that this is ObjectTypeComposer instance, ...ITC - InputTypeComposer, ...ETC - EnumTypeComposer.
+
+
import mongoose from'mongoose';
+import { composeMongoose } from'graphql-compose-mongoose';
+import { schemaComposer } from'graphql-compose';
+
+// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
+const LanguagesSchema = new mongoose.Schema({
+ language: String,
+ skill: {
+ type: String,
+ enum: [ 'basic', 'fluent', 'native' ],
+ },
+});
+
+const UserSchema = new mongoose.Schema({
+ name: String, // standard types
+ age: {
+ type: Number,
+ index: true,
+ },
+ ln: {
+ type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
+ default: [],
+ alias: 'languages', // in schema `ln` will be named as `languages`
+ },
+ contacts: { // another mongoose way for providing embedded documents
+ email: String,
+ phones: [String], // array of strings
+ },
+ gender: { // enum field with values
+ type: String,
+ enum: ['male', 'female'],
+ },
+ someMixed: {
+ type: mongoose.Schema.Types.Mixed,
+ description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
+ },
+});
+const User = mongoose.model('User', UserSchema);
+
+
+// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
+const customizationOptions = {}; // left it empty for simplicity, described below
+const UserTC = composeMongoose(User, customizationOptions);
+
+// STEP 3: Add needed CRUD User operations to the GraphQL Schema
+// via graphql-compose it will be much much easier, with less typing
+schemaComposer.Query.addFields({
+ userById: UserTC.mongooseResolvers.findById(),
+ userByIds: UserTC.mongooseResolvers.findByIds(),
+ userOne: UserTC.mongooseResolvers.findOne(),
+ userMany: UserTC.mongooseResolvers.findMany(),
+ userDataLoader: UserTC.mongooseResolvers.dataLoader(),
+ userDataLoaderMany: UserTC.mongooseResolvers.dataLoaderMany(),
+ userByIdLean: UserTC.mongooseResolvers.findByIdLean(),
+ userByIdsLean: UserTC.mongooseResolvers.findByIdsLean(),
+ userOneLean: UserTC.mongooseResolvers.findOneLean(),
+ userManyLean: UserTC.mongooseResolvers.findManyLean(),
+ userDataLoaderLean: UserTC.mongooseResolvers.dataLoaderLean(),
+ userDataLoaderManyLean: UserTC.mongooseResolvers.dataLoaderManyLean(),
+ userCount: UserTC.mongooseResolvers.count(),
+ userConnection: UserTC.mongooseResolvers.connection(),
+ userPagination: UserTC.mongooseResolvers.pagination(),
+});
+
+schemaComposer.Mutation.addFields({
+ userCreateOne: UserTC.mongooseResolvers.createOne(),
+ userCreateMany: UserTC.mongooseResolvers.createMany(),
+ userUpdateById: UserTC.mongooseResolvers.updateById(),
+ userUpdateOne: UserTC.mongooseResolvers.updateOne(),
+ userUpdateMany: UserTC.mongooseResolvers.updateMany(),
+ userRemoveById: UserTC.mongooseResolvers.removeById(),
+ userRemoveOne: UserTC.mongooseResolvers.removeOne(),
+ userRemoveMany: UserTC.mongooseResolvers.removeMany(),
+});
+
+const graphqlSchema = schemaComposer.buildSchema();
+exportdefault graphqlSchema;
+
+
That's all!
+You think that is to much code?
+I don't think so, because by default internally was created about 55 graphql types (for input, sorting, filtering). So you will need much much more lines of code to implement all these CRUD operations by hands.
+
Working with Mongoose Collection Level Discriminators
+
Variable Namings
+
+
...DTC - Suffix for a DiscriminatorTypeComposer instance, which is also an instance of ObjectTypeComposer. All fields and Relations manipulations on this instance affects all registered discriminators and the Discriminator Interface.
When you converting mongoose model const UserTC = composeMongoose(User, opts: ComposeMongooseOpts); you may tune every piece of future derived types – setup name and description for the main type, remove fields or leave only desired fields.
+
type ComposeMongooseOpts = {
+ /**
+ * Which type registry use for generated types.
+ * By default is used global default registry.
+ */
+ schemaComposer?: SchemaComposer<TContext>;
+ /**
+ * What should be base type name for generated type from mongoose model.
+ */
+ name?: string;
+ /**
+ * Provide arbitrary description for generated type.
+ */
+ description?: string;
+ /**
+ * You can leave only whitelisted fields in type via this option.
+ * Any other fields will be removed.
+ */
+ onlyFields?: string[];
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string[];
+ /**
+ * You may configure generated InputType
+ */
+ inputType?: TypeConverterInputTypeOpts;
+ /**
+ * You can make fields as NonNull if they have default value in mongoose model.
+ */
+ defaultsAsNonNull?: boolean;
+};
+
+
This is opts.inputType options for default InputTypeObject which will be provided to all resolvers for filter and input args.
+
type TypeConverterInputTypeOpts = {
+ /**
+ * What should be input type name.
+ * By default: baseTypeName + 'Input'
+ */
+ name?: string;
+ /**
+ * Provide arbitrary description for generated type.
+ */
+ description?: string;
+ /**
+ * You can leave only whitelisted fields in type via this option.
+ * Any other fields will be removed.
+ */
+ onlyFields?: string[];
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string[];
+ /**
+ * This option makes provided fieldNames as required
+ */
+ requiredFields?: string[];
+};
+
+
Resolvers customization options
+
When you are creating resolvers from mongooseResolvers factory, you may provide customizationOptions to it:
interface CountResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+}
+
+
createMany(opts?: CreateManyResolverOpts)
+
interface CreateManyResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `records` argument. */
+ records?: RecordHelperArgsOpts;
+ /** Customize payload.recordIds field. If false, then this field will be removed. */
+ recordIds?: PayloadRecordIdsHelperOpts | false;
+}
+
+
createOne(opts?: CreateOneResolverOpts)
+
interface CreateOneResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument */
+ record?: RecordHelperArgsOpts;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
dataLoader(opts?: DataLoaderResolverOpts)
+
interface DataLoaderResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+}
+
+
dataLoaderMany(opts?: DataLoaderManyResolverOpts)
+
interface DataLoaderManyResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+}
+
+
findById(opts?: FindByIdResolverOpts)
+
interface FindByIdResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+}
+
+
findByIds(opts?: FindByIdsResolverOpts)
+
interface FindByIdsResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+ limit?: LimitHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+}
+
+
findMany(opts?: FindManyResolverOpts)
+
interface FindManyResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ limit?: LimitHelperArgsOpts | false;
+ skip?: false;
+}
+
+
findOne(opts?: FindOneResolverOpts)
+
interface FindOneResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ skip?: false;
+}
+
interface RemoveByIdResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
removeMany(opts?: RemoveManyResolverOpts)
+
interface RemoveManyResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ limit?: LimitHelperArgsOpts | false;
+}
+
+
removeOne(opts?: RemoveOneResolverOpts)
+
interface RemoveOneResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
updateById(opts?: UpdateByIdResolverOpts)
+
interface UpdateByIdResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument. */
+ record?: RecordHelperArgsOpts;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
updateMany(opts?: UpdateManyResolverOpts)
+
interface UpdateManyResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument. */
+ record?: RecordHelperArgsOpts;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ limit?: LimitHelperArgsOpts | false;
+ skip?: false;
+}
+
+
updateOne(opts?: UpdateOneResolverOpts)
+
interface UpdateOneResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument. */
+ record?: RecordHelperArgsOpts;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ skip?: false;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
Description of common resolvers' options
+
FilterHelperArgsOpts
+
type FilterHelperArgsOpts = {
+ /**
+ * Add to filter arg only that fields which are indexed.
+ * If false then all fields will be available for filtering.
+ * By default: true
+ */
+ onlyIndexed?: boolean;
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string | string[];
+ /**
+ * This option makes provided fieldNames as required
+ */
+ requiredFields?: string | string[];
+ /**
+ * Customize operators filtering or disable it at all.
+ * By default, for performance reason, `graphql-compose-mongoose` generates operators
+ * *only for indexed* fields.
+ *
+ * BUT you may enable operators for all fields when creating resolver in the following way:
+ * // enables all operators for all fields
+ * operators: true,
+ * OR provide a more granular `operators` configuration to suit your needs:
+ * operators: {
+ * // for `age` field add just 3 operators
+ * age: ['in', 'gt', 'lt'],
+ * // for non-indexed `amount` field add all operators
+ * amount: true,
+ * // don't add this field to operators
+ * indexedField: false,
+ * }
+ *
+ * Available logic operators: AND, OR
+ * Available field operators: gt, gte, lt, lte, ne, in, nin, regex, exists
+ */
+ operators?: FieldsOperatorsConfig | false;
+ /**
+ * Make arg `filter` as required if this option is true.
+ */
+ isRequired?: boolean;
+ /**
+ * Base type name for generated filter argument.
+ */
+ baseTypeName?: string;
+ /**
+ * Provide custom prefix for Type name
+ */
+ prefix?: string;
+ /**
+ * Provide custom suffix for Type name
+ */
+ suffix?: string;
+};
+
+
SortHelperArgsOpts
+
type SortHelperArgsOpts = {
+ /**
+ * Allow sort by several fields.
+ * This makes arg as array of sort values.
+ */
+ multi?: boolean;
+ /**
+ * This option set custom type name for generated sort argument.
+ */
+ sortTypeName?: string;
+};
+
+
RecordHelperArgsOpts
+
type RecordHelperArgsOpts = {
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string[];
+ /**
+ * This option makes provided fieldNames as required
+ */
+ requiredFields?: string[];
+ /**
+ * This option makes all fields nullable by default.
+ * May be overridden by `requiredFields` property
+ */
+ allFieldsNullable?: boolean;
+ /**
+ * Provide custom prefix for Type name
+ */
+ prefix?: string;
+ /**
+ * Provide custom suffix for Type name
+ */
+ suffix?: string;
+ /**
+ * Make arg `record` as required if this option is true.
+ */
+ isRequired?: boolean;
+};
+
+
LimitHelperArgsOpts
+
type LimitHelperArgsOpts = {
+ /**
+ * Set limit for default number of returned records
+ * if it does not provided in query.
+ * By default: 100
+ */
+ defaultValue?: number;
+};
+
+
FAQ
+
Can I get generated vanilla GraphQL types?
+
const UserTC = composeMongoose(User);
+UserTC.getType(); // returns GraphQLObjectType
+UserTC.getInputType(); // returns GraphQLInputObjectType, eg. for args
+UserTC.get('languages').getType(); // get GraphQLObjectType for nested field
+UserTC.get('fieldWithNesting.subNesting').getType(); // get GraphQL type of deep nested field
+
Suppose you User model has friendsIds field with array of user ids. So let build some relations:
+
UserTC.addRelation(
+ 'friends',
+ {
+ resolver: () => UserTC.mongooseResolvers.dataLoaderMany(),
+ prepareArgs: { // resolver `findByIds` has `_ids` arg, let provide value to it
+ _ids: (source) => source.friendsIds,
+ },
+ projection: { friendsIds: 1 }, // point fields in source object, which should be fetched from DB
+ }
+);
+UserTC.addRelation(
+ 'adultFriendsWithSameGender',
+ {
+ resolver: () => UserTC.mongooseResolvers.findMany(),
+ prepareArgs: { // resolver `findMany` has `filter` arg, we may provide mongoose query to it
+ filter: (source) => ({
+ _operators : { // Applying criteria on fields which have
+ // operators enabled for them (by default, indexed fields only)
+ _id : { in: source.friendsIds },
+ age: { gt: 21 }
+ },
+ gender: source.gender,
+ }),
+ limit: 10,
+ },
+ projection: { friendsIds: 1, gender: 1 }, // required fields from source object
+ }
+);
+
+
Reusing the same mongoose Schema in embedded object fields
+
Suppose you have a common structure you use as embedded object in multiple Schemas.
+Also suppose you want the structure to have the same GraphQL type across all parent types.
+(For instance, to allow reuse of fragments for this type)
+Here are Schemas to demonstrate:
If you want the ImageDataStructure to use the same GraphQL type in both Article and UserProfile you will need create it as a mongoose schema (not a standard javascript object) and to explicitly tell graphql-compose-mongoose the name you want it to have. Otherwise, without the name, it would generate the name according to the first parent this type was embedded in.
+
Do the following:
+
import { schemaComposer } from'graphql-compose'; // get the default schemaComposer or your created schemaComposer
+import { convertSchemaToGraphQL } from'graphql-compose-mongoose';
+
+convertSchemaToGraphQL(ImageDataStructure, 'EmbeddedImage', schemaComposer); // Force this type on this mongoose schema
+
+
Before continuing to convert your models to TypeComposers:
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can create your own resolver of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts before save hook which can be provided in resolver params as param named beforeRecordMutate. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
How can I push/pop or add/remove values to arrays?
+
The default resolvers, by design, will replace (overwrite) any supplied array object when using e.g. updateById. If you want to push or pop a value in an array you can use a custom resolver with a native MongoDB call.
User is the corresponding Mongoose model. If you do not wish to allow duplicates in the array then replace $push with $addToSet. Read the graphql-compose docs on custom resolvers for more info: https://graphql-compose.github.io/docs/en/basics-resolvers.html
+
NB if you set unique: true on the array then using the update$push approach will not check for duplicates, this is due to a MongoDB bug: https://jira.mongodb.org/browse/SERVER-1068. For more usage examples with $push and arrays see the MongoDB docs here https://docs.mongodb.com/manual/reference/operator/update/push/. Also note that $push will preserve order in the array (append to end of array) whereas $addToSet will not.
+
Is it possible to use several schemas?
+
By default composeMongoose uses global schemaComposer for generated types. If you need to create different GraphQL schemas you need create own schemaComposers and provide them to customizationOptions:
Can field name in schema have different name in database?
+
Yes, it can. This package understands mongoose alias option for fields. Just provide alias: 'country' for field c and you get country field name in GraphQL schema and Mongoose model but c field in database:
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-pagination.html b/docs/next/plugins/plugin-pagination.html
new file mode 100644
index 00000000..5bfa9d8b
--- /dev/null
+++ b/docs/next/plugins/plugin-pagination.html
@@ -0,0 +1,135 @@
+graphql-compose-pagination · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-relay.html b/docs/next/plugins/plugin-relay.html
new file mode 100644
index 00000000..62bae094
--- /dev/null
+++ b/docs/next/plugins/plugin-relay.html
@@ -0,0 +1,148 @@
+graphql-compose-relay · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
ObjectTypeComposer is a graphql-compose utility, that wraps GraphQL types and provide bunch of useful methods for type manipulation.
+
import composeWithRelay from'graphql-compose-relay';
+import { ObjectTypeComposer } from'graphql-compose';
+import { RootQueryType, UserType } from'./my-graphq-object-types';
+
+const rootQueryTypeComposer = new ObjectTypeComposer(RootQueryType);
+const userTypeComposer = new ObjectTypeComposer(UserType);
+
+// If passed RootQuery, then will be added only `node` field to this type.
+// Via RootQuery.node you may find objects by globally unique ID among all types.
+composeWithRelay(rootQueryTypeComposer);
+
+// Other types, like User, will be wrapped with middlewares that:
+// - add relay's id field. Field will be added or wrapped to return Relay's globally unique ID.
+// - for mutations will be added clientMutationId to input and output objects types
+// - this type will be added to NodeInterface for resolving via RootQuery.node
+composeWithRelay(userTypeComposer);
+
+
That's all!
+
All mutations resolvers' arguments will be placed into input field, and added clientMutationId. If input fields already exists in resolver, then clientMutationId will be added to it, rest argument stays untouched. Accepted value via args.input.clientMutationId will be transfer to payload.clientMutationId, as Relay required it.
+
To all wrapped Types with Relay, will be added id field or wrapped, if it exist already. This field will return globally unique ID among all types in the following format base64(TypeName + ':' + recordId).
+
For RootQuery will be added node field, that will resolve by globalId only that types, which you wrap with composeWithRelay.
+
All this annoying operations is too fatigue to do by hands. So this middleware done all Relay magic implicitly for you.
+
Requirements
+
Method composeWithRelay accept ObjectTypeComposer as input argument. So ObjectTypeComposer should meet following requirements:
+
+
has defined recordIdFn (function that from object of this type, returns you id for the globalId construction)
+
should have findById resolver (that will be used by RootQuery.node)
+
+
If something is missing composeWithRelay throws error.
\ No newline at end of file
diff --git a/docs/next/plugins/plugin-writing-custom-plugin.html b/docs/next/plugins/plugin-writing-custom-plugin.html
new file mode 100644
index 00000000..532fc525
--- /dev/null
+++ b/docs/next/plugins/plugin-writing-custom-plugin.html
@@ -0,0 +1,59 @@
+[WIP] How to write a custom plugin · graphql-compose
\ No newline at end of file
diff --git a/docs/next/recipes/authorization.html b/docs/next/recipes/authorization.html
new file mode 100644
index 00000000..69ff9cf5
--- /dev/null
+++ b/docs/next/recipes/authorization.html
@@ -0,0 +1,59 @@
+[WIP] Authorization · graphql-compose
\ No newline at end of file
diff --git a/docs/next/recipes/writing-tests.html b/docs/next/recipes/writing-tests.html
new file mode 100644
index 00000000..819c6b3f
--- /dev/null
+++ b/docs/next/recipes/writing-tests.html
@@ -0,0 +1,59 @@
+[WIP] Writing tests · graphql-compose
\ No newline at end of file
diff --git a/docs/plugins/list-of-plugins.html b/docs/plugins/list-of-plugins.html
new file mode 100644
index 00000000..8f1ce16c
--- /dev/null
+++ b/docs/plugins/list-of-plugins.html
@@ -0,0 +1,128 @@
+Plugins list · graphql-compose
graphql-compose – the imperative tool which worked on top of graphql-js. It provides useful methods for creating GraphQL Types and GraphQL Models (type with a list of
+resolvers) for further building of complex relations in your Schema. With graphql-compose you may fastly write own functions/generators for common tasks.
+
graphql-compose-[plugin] – is a declarative generator/plugin that build on top of graphql-compose, which take some ORMs, schema definitions and creates GraphQL Models from them or modify existed GraphQL Types.
+
Type generator plugins
+
+
graphql-compose-json - generates GraphQL type from JSON (a good helper for wrapping REST APIs)
+
graphql-compose-mongoose - generates GraphQL types from mongoose (MongoDB models) with Resolvers.
+
graphql-compose-elasticsearch - generates GraphQL types from elastic mappings; ElasticSearch REST API proxy via GraphQL.
\ No newline at end of file
diff --git a/docs/plugins/plugin-aws.html b/docs/plugins/plugin-aws.html
new file mode 100644
index 00000000..44900244
--- /dev/null
+++ b/docs/plugins/plugin-aws.html
@@ -0,0 +1,153 @@
+graphql-compose-aws · graphql-compose
Generated Schema Introspection in SDL format can be found here (more than 10k types, ~2MB).
+
AWS SDK GraphQL
+
Supported all AWS SDK versions via official aws-sdk js client. Internally it generates Types and FieldConfigs from AWS SDK configs. You may put this generated types to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import awsSDK from'aws-sdk';
+import { AwsApiParser } from'graphql-compose-aws';
+
+const awsApiParser = new AwsApiParser({
+ awsSDK,
+});
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ // Full API
+ aws: awsApiParser.getFieldConfig(),
+
+ // Partial API with desired services
+ s3: awsApiParser.getService('s3').getFieldConfig(),
+ ec2: awsApiParser.getService('ec2').getFieldConfig(),
+ },
+ }),
+});
+
+exportdefault schema;
+
\ No newline at end of file
diff --git a/docs/plugins/plugin-connection.html b/docs/plugins/plugin-connection.html
new file mode 100644
index 00000000..13f8b93f
--- /dev/null
+++ b/docs/plugins/plugin-connection.html
@@ -0,0 +1,207 @@
+graphql-compose-connection · graphql-compose
Besides standard connection arguments first, last, before and after, also added significant arguments:
+
+
filter arg - for filtering records
+
sort arg - for sorting records. Build in mechanism allows sort by any unique indexes (not only by id). Also supported compound sorting (by several fields).
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
+
Example
+
import composeWithConnection from'graphql-compose-connection';
+import userTypeComposer from'./user.js';
+
+composeWithConnection(userTypeComposer, {
+ findResolverName: 'findMany',
+ countResolverName: 'count',
+ sort: {
+ // Sorting key, visible for users in GraphQL Schema
+ _ID_ASC: {
+ // Sorting value for ORM/Driver
+ value: { _id: 1 },
+
+ // Field names in record, which data will be packed in `cursor`
+ // edges {
+ // cursor <- base64(cursorData), for this example `cursorData` = { _id: 334ae453 }
+ // node <- record from DB
+ // }
+ // By this fields MUST be created UNIQUE index in database!
+ cursorFields: ['_id'],
+
+ // If for connection query provided `before` argument with above `cursor`.
+ // We should construct (`rawQuery`) which will be point to dataset before cursor.
+ // Unpacked data from `cursor` will be available in (`cursorData`) argument.
+ // PS. All other filter options provided via GraphQL query will be added automatically.
+ // ----- [record] ----- sorted dataset, according to above option with `value` name
+ // ^^^^^ `rawQuery` should filter this set
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+
+ // Constructing `rawQuery` for connection `after` argument.
+ // ----- [record] ----- sorted dataset
+ // ^^^^^ `rawQuery` should filter this set
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ },
+
+ _ID_DESC: {
+ value: { _id: -1 },
+ cursorFields: ['_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery._id.$lt = cursorData._id;
+ },
+ },
+
+ // More complex sorting parameter with 2 fields
+ AGE_ID_ASC: {
+ value: { age: 1, _id: -1 },
+ // By these fields MUST be created COMPOUND UNIQUE index in database!
+ cursorFields: ['age', '_id'],
+ beforeCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$lt = cursorData.age;
+ rawQuery._id.$gt = cursorData._id;
+ },
+ afterCursorQuery: (rawQuery, cursorData, resolveParams) => {
+ if (!rawQuery.age) rawQuery.age = {};
+ if (!rawQuery._id) rawQuery._id = {};
+ rawQuery.age.$gt = cursorData.age;
+ rawQuery._id.$lt = cursorData._id;
+ },
+ }
+ },
+});
+
+
+
Requirements
+
Types should have following resolvers:
+
+
count - for counting records
+
findMany - for filtering records. Also required that this resolver supports search with operators (lt, gt), which used in directionFilter option. Resolver findMany should have filter argument, which will be copied to connection. Also should have limit and skip args.
\ No newline at end of file
diff --git a/docs/plugins/plugin-elasticsearch.html b/docs/plugins/plugin-elasticsearch.html
new file mode 100644
index 00000000..1c979cd7
--- /dev/null
+++ b/docs/plugins/plugin-elasticsearch.html
@@ -0,0 +1,234 @@
+graphql-compose-elasticsearch · graphql-compose
This module expose Elastic Search REST API via GraphQL.
+
Elastic Search REST API proxy
+
Supported all elastic versions that support official elasticsearch-js client. Internally it parses its source code annotations and generates all available methods with params and descriptions to GraphQL Field Config Map. You may put this config map to any GraphQL Schema.
+
import { GraphQLSchema, GraphQLObjectType } from'graphql';
+import elasticsearch from'elasticsearch';
+import { elasticApiFieldConfig } from'graphql-compose-elasticsearch';
+
+const schema = new GraphQLSchema({
+ query: new GraphQLObjectType({
+ name: 'Query',
+ fields: {
+ elastic50: elasticApiFieldConfig(
+ // you may provide existed Elastic Client instance
+ new elasticsearch.Client({
+ host: 'http://localhost:9200',
+ apiVersion: '5.0',
+ })
+ ),
+
+ // or may provide just config
+ elastic24: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '2.4',
+ }),
+
+ elastic17: elasticApiFieldConfig({
+ host: 'http://user:pass@localhost:9200',
+ apiVersion: '1.7',
+ }),
+ },
+ }),
+});
+
In other side this module is a plugin for graphql-compose, which derives GraphQLType from your elastic mapping generates tons of types, provides all available methods in QueryDSL, Aggregations, Sorting with field autocompletion according to types in your mapping (like Dev Tools Console in Kibana).
+
Generated ObjectTypeComposer model has several awesome resolvers:
+
+
search - greatly simplified elastic search method. According to GraphQL adaptation and its projection bunch of params setup automatically due your graphql query (eg _source, explain, version, trackScores), other rare fine tuning params moved to opts input field.
+
searchConnection - elastic search method that implements Relay Cursor Connection spec for infinite lists. Internally it uses cheap search_after API. One downside, Elastic does not support backward scrolling, so before argument will not work.
+
more resolvers will be later after my vacation: suggest, getById, updateById and others
\ No newline at end of file
diff --git a/docs/plugins/plugin-json.html b/docs/plugins/plugin-json.html
new file mode 100644
index 00000000..21e87cf3
--- /dev/null
+++ b/docs/plugins/plugin-json.html
@@ -0,0 +1,311 @@
+graphql-compose-json · graphql-compose
This is a plugin for graphql-compose, which generates GraphQLTypes from REST response or any JSON. It takes fields from object, determines their types and construct GraphQLObjectType with same shape.
+
Demo
+
We have a Live demo (source code repo) which shows how to build an API upon SWAPI using graphql-compose-json.
Modules graphql, graphql-compose, are located in peerDependencies, so they should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
You have a sample response object restApiResponse which you can pass to graphql-compose-json along with desired type name as your first argument and it will automatically generate a composed GraphQL type PersonTC.
graphql-compose provides a vast variety of methods for fields and resolvers (aka field configs in vanilla GraphQL) management of GraphQL types. To learn more visit graphql-compose repo.
\ No newline at end of file
diff --git a/docs/plugins/plugin-mongoose.html b/docs/plugins/plugin-mongoose.html
new file mode 100644
index 00000000..9b55c195
--- /dev/null
+++ b/docs/plugins/plugin-mongoose.html
@@ -0,0 +1,957 @@
+graphql-compose-mongoose · graphql-compose
This is a plugin for graphql-compose, which derives GraphQLType from your mongoose model. Also derives bunch of internal GraphQL Types. Provide all CRUD resolvers, including graphql connection, also provided basic search via operators ($lt, $gt and so on).
+
Release Notes for v9.0.0 contains a lot of improvements. It's strongly recommended for reading before upgrading from v8.
Modules graphql, graphql-compose, mongoose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Intro video
+
Viktor Kjartansson created a quite solid intro for graphql-compose-mongoose in comparison with graphql-tools:
UserTC - this is a ObjectTypeComposer instance for User. ObjectTypeComposer has GraphQLObjectType inside, available via method UserTC.getType().
+
Here and in all other places of code variables suffix ...TC means that this is ObjectTypeComposer instance, ...ITC - InputTypeComposer, ...ETC - EnumTypeComposer.
+
+
import mongoose from'mongoose';
+import { composeMongoose } from'graphql-compose-mongoose';
+import { schemaComposer } from'graphql-compose';
+
+// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
+const LanguagesSchema = new mongoose.Schema({
+ language: String,
+ skill: {
+ type: String,
+ enum: [ 'basic', 'fluent', 'native' ],
+ },
+});
+
+const UserSchema = new mongoose.Schema({
+ name: String, // standard types
+ age: {
+ type: Number,
+ index: true,
+ },
+ ln: {
+ type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
+ default: [],
+ alias: 'languages', // in schema `ln` will be named as `languages`
+ },
+ contacts: { // another mongoose way for providing embedded documents
+ email: String,
+ phones: [String], // array of strings
+ },
+ gender: { // enum field with values
+ type: String,
+ enum: ['male', 'female'],
+ },
+ someMixed: {
+ type: mongoose.Schema.Types.Mixed,
+ description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
+ },
+});
+const User = mongoose.model('User', UserSchema);
+
+
+// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
+const customizationOptions = {}; // left it empty for simplicity, described below
+const UserTC = composeMongoose(User, customizationOptions);
+
+// STEP 3: Add needed CRUD User operations to the GraphQL Schema
+// via graphql-compose it will be much much easier, with less typing
+schemaComposer.Query.addFields({
+ userById: UserTC.mongooseResolvers.findById(),
+ userByIds: UserTC.mongooseResolvers.findByIds(),
+ userOne: UserTC.mongooseResolvers.findOne(),
+ userMany: UserTC.mongooseResolvers.findMany(),
+ userDataLoader: UserTC.mongooseResolvers.dataLoader(),
+ userDataLoaderMany: UserTC.mongooseResolvers.dataLoaderMany(),
+ userByIdLean: UserTC.mongooseResolvers.findByIdLean(),
+ userByIdsLean: UserTC.mongooseResolvers.findByIdsLean(),
+ userOneLean: UserTC.mongooseResolvers.findOneLean(),
+ userManyLean: UserTC.mongooseResolvers.findManyLean(),
+ userDataLoaderLean: UserTC.mongooseResolvers.dataLoaderLean(),
+ userDataLoaderManyLean: UserTC.mongooseResolvers.dataLoaderManyLean(),
+ userCount: UserTC.mongooseResolvers.count(),
+ userConnection: UserTC.mongooseResolvers.connection(),
+ userPagination: UserTC.mongooseResolvers.pagination(),
+});
+
+schemaComposer.Mutation.addFields({
+ userCreateOne: UserTC.mongooseResolvers.createOne(),
+ userCreateMany: UserTC.mongooseResolvers.createMany(),
+ userUpdateById: UserTC.mongooseResolvers.updateById(),
+ userUpdateOne: UserTC.mongooseResolvers.updateOne(),
+ userUpdateMany: UserTC.mongooseResolvers.updateMany(),
+ userRemoveById: UserTC.mongooseResolvers.removeById(),
+ userRemoveOne: UserTC.mongooseResolvers.removeOne(),
+ userRemoveMany: UserTC.mongooseResolvers.removeMany(),
+});
+
+const graphqlSchema = schemaComposer.buildSchema();
+exportdefault graphqlSchema;
+
+
That's all!
+You think that is to much code?
+I don't think so, because by default internally was created about 55 graphql types (for input, sorting, filtering). So you will need much much more lines of code to implement all these CRUD operations by hands.
+
Working with Mongoose Collection Level Discriminators
+
Variable Namings
+
+
...DTC - Suffix for a DiscriminatorTypeComposer instance, which is also an instance of ObjectTypeComposer. All fields and Relations manipulations on this instance affects all registered discriminators and the Discriminator Interface.
When you converting mongoose model const UserTC = composeMongoose(User, opts: ComposeMongooseOpts); you may tune every piece of future derived types – setup name and description for the main type, remove fields or leave only desired fields.
+
type ComposeMongooseOpts = {
+ /**
+ * Which type registry use for generated types.
+ * By default is used global default registry.
+ */
+ schemaComposer?: SchemaComposer<TContext>;
+ /**
+ * What should be base type name for generated type from mongoose model.
+ */
+ name?: string;
+ /**
+ * Provide arbitrary description for generated type.
+ */
+ description?: string;
+ /**
+ * You can leave only whitelisted fields in type via this option.
+ * Any other fields will be removed.
+ */
+ onlyFields?: string[];
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string[];
+ /**
+ * You may configure generated InputType
+ */
+ inputType?: TypeConverterInputTypeOpts;
+ /**
+ * You can make fields as NonNull if they have default value in mongoose model.
+ */
+ defaultsAsNonNull?: boolean;
+};
+
+
This is opts.inputType options for default InputTypeObject which will be provided to all resolvers for filter and input args.
+
type TypeConverterInputTypeOpts = {
+ /**
+ * What should be input type name.
+ * By default: baseTypeName + 'Input'
+ */
+ name?: string;
+ /**
+ * Provide arbitrary description for generated type.
+ */
+ description?: string;
+ /**
+ * You can leave only whitelisted fields in type via this option.
+ * Any other fields will be removed.
+ */
+ onlyFields?: string[];
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string[];
+ /**
+ * This option makes provided fieldNames as required
+ */
+ requiredFields?: string[];
+};
+
+
Resolvers customization options
+
When you are creating resolvers from mongooseResolvers factory, you may provide customizationOptions to it:
interface CountResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+}
+
+
createMany(opts?: CreateManyResolverOpts)
+
interface CreateManyResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `records` argument. */
+ records?: RecordHelperArgsOpts;
+ /** Customize payload.recordIds field. If false, then this field will be removed. */
+ recordIds?: PayloadRecordIdsHelperOpts | false;
+}
+
+
createOne(opts?: CreateOneResolverOpts)
+
interface CreateOneResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument */
+ record?: RecordHelperArgsOpts;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
dataLoader(opts?: DataLoaderResolverOpts)
+
interface DataLoaderResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+}
+
+
dataLoaderMany(opts?: DataLoaderManyResolverOpts)
+
interface DataLoaderManyResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+}
+
+
findById(opts?: FindByIdResolverOpts)
+
interface FindByIdResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+}
+
+
findByIds(opts?: FindByIdsResolverOpts)
+
interface FindByIdsResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+ limit?: LimitHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+}
+
+
findMany(opts?: FindManyResolverOpts)
+
interface FindManyResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ limit?: LimitHelperArgsOpts | false;
+ skip?: false;
+}
+
+
findOne(opts?: FindOneResolverOpts)
+
interface FindOneResolverOpts {
+ /**
+ * Enabling the lean option tells Mongoose to skip instantiating
+ * a full Mongoose document and just give you the plain JavaScript objects.
+ * Documents are much heavier than vanilla JavaScript objects,
+ * because they have a lot of internal state for change tracking.
+ * The downside of enabling lean is that lean docs don't have:
+ * Default values
+ * Getters and setters
+ * Virtuals
+ * Read more about `lean`: https://mongoosejs.com/docs/tutorials/lean.html
+ */
+ lean?: boolean;
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ skip?: false;
+}
+
interface RemoveByIdResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
removeMany(opts?: RemoveManyResolverOpts)
+
interface RemoveManyResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ limit?: LimitHelperArgsOpts | false;
+}
+
+
removeOne(opts?: RemoveOneResolverOpts)
+
interface RemoveOneResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
updateById(opts?: UpdateByIdResolverOpts)
+
interface UpdateByIdResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument. */
+ record?: RecordHelperArgsOpts;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
updateMany(opts?: UpdateManyResolverOpts)
+
interface UpdateManyResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument. */
+ record?: RecordHelperArgsOpts;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ limit?: LimitHelperArgsOpts | false;
+ skip?: false;
+}
+
+
updateOne(opts?: UpdateOneResolverOpts)
+
interface UpdateOneResolverOpts {
+ /** If you want to generate different resolvers you may avoid Type name collision by adding a suffix to type names */
+ suffix?: string;
+ /** Customize input-type for `record` argument. */
+ record?: RecordHelperArgsOpts;
+ /** Customize input-type for `filter` argument. If `false` then arg will be removed. */
+ filter?: FilterHelperArgsOpts | false;
+ sort?: SortHelperArgsOpts | false;
+ skip?: false;
+ /** Customize payload.recordId field. If false, then this field will be removed. */
+ recordId?: PayloadRecordIdHelperOpts | false;
+}
+
+
Description of common resolvers' options
+
FilterHelperArgsOpts
+
type FilterHelperArgsOpts = {
+ /**
+ * Add to filter arg only that fields which are indexed.
+ * If false then all fields will be available for filtering.
+ * By default: true
+ */
+ onlyIndexed?: boolean;
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string | string[];
+ /**
+ * This option makes provided fieldNames as required
+ */
+ requiredFields?: string | string[];
+ /**
+ * Customize operators filtering or disable it at all.
+ * By default, for performance reason, `graphql-compose-mongoose` generates operators
+ * *only for indexed* fields.
+ *
+ * BUT you may enable operators for all fields when creating resolver in the following way:
+ * // enables all operators for all fields
+ * operators: true,
+ * OR provide a more granular `operators` configuration to suit your needs:
+ * operators: {
+ * // for `age` field add just 3 operators
+ * age: ['in', 'gt', 'lt'],
+ * // for non-indexed `amount` field add all operators
+ * amount: true,
+ * // don't add this field to operators
+ * indexedField: false,
+ * }
+ *
+ * Available logic operators: AND, OR
+ * Available field operators: gt, gte, lt, lte, ne, in, nin, regex, exists
+ */
+ operators?: FieldsOperatorsConfig | false;
+ /**
+ * Make arg `filter` as required if this option is true.
+ */
+ isRequired?: boolean;
+ /**
+ * Base type name for generated filter argument.
+ */
+ baseTypeName?: string;
+ /**
+ * Provide custom prefix for Type name
+ */
+ prefix?: string;
+ /**
+ * Provide custom suffix for Type name
+ */
+ suffix?: string;
+};
+
+
SortHelperArgsOpts
+
type SortHelperArgsOpts = {
+ /**
+ * Allow sort by several fields.
+ * This makes arg as array of sort values.
+ */
+ multi?: boolean;
+ /**
+ * This option set custom type name for generated sort argument.
+ */
+ sortTypeName?: string;
+};
+
+
RecordHelperArgsOpts
+
type RecordHelperArgsOpts = {
+ /**
+ * You an remove some fields from type via this option.
+ */
+ removeFields?: string[];
+ /**
+ * This option makes provided fieldNames as required
+ */
+ requiredFields?: string[];
+ /**
+ * This option makes all fields nullable by default.
+ * May be overridden by `requiredFields` property
+ */
+ allFieldsNullable?: boolean;
+ /**
+ * Provide custom prefix for Type name
+ */
+ prefix?: string;
+ /**
+ * Provide custom suffix for Type name
+ */
+ suffix?: string;
+ /**
+ * Make arg `record` as required if this option is true.
+ */
+ isRequired?: boolean;
+};
+
+
LimitHelperArgsOpts
+
type LimitHelperArgsOpts = {
+ /**
+ * Set limit for default number of returned records
+ * if it does not provided in query.
+ * By default: 100
+ */
+ defaultValue?: number;
+};
+
+
FAQ
+
Can I get generated vanilla GraphQL types?
+
const UserTC = composeMongoose(User);
+UserTC.getType(); // returns GraphQLObjectType
+UserTC.getInputType(); // returns GraphQLInputObjectType, eg. for args
+UserTC.get('languages').getType(); // get GraphQLObjectType for nested field
+UserTC.get('fieldWithNesting.subNesting').getType(); // get GraphQL type of deep nested field
+
Suppose you User model has friendsIds field with array of user ids. So let build some relations:
+
UserTC.addRelation(
+ 'friends',
+ {
+ resolver: () => UserTC.mongooseResolvers.dataLoaderMany(),
+ prepareArgs: { // resolver `findByIds` has `_ids` arg, let provide value to it
+ _ids: (source) => source.friendsIds,
+ },
+ projection: { friendsIds: 1 }, // point fields in source object, which should be fetched from DB
+ }
+);
+UserTC.addRelation(
+ 'adultFriendsWithSameGender',
+ {
+ resolver: () => UserTC.mongooseResolvers.findMany(),
+ prepareArgs: { // resolver `findMany` has `filter` arg, we may provide mongoose query to it
+ filter: (source) => ({
+ _operators : { // Applying criteria on fields which have
+ // operators enabled for them (by default, indexed fields only)
+ _id : { in: source.friendsIds },
+ age: { gt: 21 }
+ },
+ gender: source.gender,
+ }),
+ limit: 10,
+ },
+ projection: { friendsIds: 1, gender: 1 }, // required fields from source object
+ }
+);
+
+
Reusing the same mongoose Schema in embedded object fields
+
Suppose you have a common structure you use as embedded object in multiple Schemas.
+Also suppose you want the structure to have the same GraphQL type across all parent types.
+(For instance, to allow reuse of fragments for this type)
+Here are Schemas to demonstrate:
If you want the ImageDataStructure to use the same GraphQL type in both Article and UserProfile you will need create it as a mongoose schema (not a standard javascript object) and to explicitly tell graphql-compose-mongoose the name you want it to have. Otherwise, without the name, it would generate the name according to the first parent this type was embedded in.
+
Do the following:
+
import { schemaComposer } from'graphql-compose'; // get the default schemaComposer or your created schemaComposer
+import { convertSchemaToGraphQL } from'graphql-compose-mongoose';
+
+convertSchemaToGraphQL(ImageDataStructure, 'EmbeddedImage', schemaComposer); // Force this type on this mongoose schema
+
+
Before continuing to convert your models to TypeComposers:
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can create your own resolver of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts before save hook which can be provided in resolver params as param named beforeRecordMutate. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
How can I push/pop or add/remove values to arrays?
+
The default resolvers, by design, will replace (overwrite) any supplied array object when using e.g. updateById. If you want to push or pop a value in an array you can use a custom resolver with a native MongoDB call.
User is the corresponding Mongoose model. If you do not wish to allow duplicates in the array then replace $push with $addToSet. Read the graphql-compose docs on custom resolvers for more info: https://graphql-compose.github.io/docs/en/basics-resolvers.html
+
NB if you set unique: true on the array then using the update$push approach will not check for duplicates, this is due to a MongoDB bug: https://jira.mongodb.org/browse/SERVER-1068. For more usage examples with $push and arrays see the MongoDB docs here https://docs.mongodb.com/manual/reference/operator/update/push/. Also note that $push will preserve order in the array (append to end of array) whereas $addToSet will not.
+
Is it possible to use several schemas?
+
By default composeMongoose uses global schemaComposer for generated types. If you need to create different GraphQL schemas you need create own schemaComposers and provide them to customizationOptions:
Can field name in schema have different name in database?
+
Yes, it can. This package understands mongoose alias option for fields. Just provide alias: 'country' for field c and you get country field name in GraphQL schema and Mongoose model but c field in database:
\ No newline at end of file
diff --git a/docs/plugins/plugin-pagination.html b/docs/plugins/plugin-pagination.html
new file mode 100644
index 00000000..88ba213d
--- /dev/null
+++ b/docs/plugins/plugin-pagination.html
@@ -0,0 +1,135 @@
+graphql-compose-pagination · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They should not installed as submodules, cause internally checks the classes instances.
\ No newline at end of file
diff --git a/docs/plugins/plugin-relay.html b/docs/plugins/plugin-relay.html
new file mode 100644
index 00000000..e1bab07d
--- /dev/null
+++ b/docs/plugins/plugin-relay.html
@@ -0,0 +1,148 @@
+graphql-compose-relay · graphql-compose
Modules graphql and graphql-compose are in peerDependencies, so should be installed explicitly in your app. They have global objects and should not have ability to be installed as submodule.
+
Example
+
ObjectTypeComposer is a graphql-compose utility, that wraps GraphQL types and provide bunch of useful methods for type manipulation.
+
import composeWithRelay from'graphql-compose-relay';
+import { ObjectTypeComposer } from'graphql-compose';
+import { RootQueryType, UserType } from'./my-graphq-object-types';
+
+const rootQueryTypeComposer = new ObjectTypeComposer(RootQueryType);
+const userTypeComposer = new ObjectTypeComposer(UserType);
+
+// If passed RootQuery, then will be added only `node` field to this type.
+// Via RootQuery.node you may find objects by globally unique ID among all types.
+composeWithRelay(rootQueryTypeComposer);
+
+// Other types, like User, will be wrapped with middlewares that:
+// - add relay's id field. Field will be added or wrapped to return Relay's globally unique ID.
+// - for mutations will be added clientMutationId to input and output objects types
+// - this type will be added to NodeInterface for resolving via RootQuery.node
+composeWithRelay(userTypeComposer);
+
+
That's all!
+
All mutations resolvers' arguments will be placed into input field, and added clientMutationId. If input fields already exists in resolver, then clientMutationId will be added to it, rest argument stays untouched. Accepted value via args.input.clientMutationId will be transfer to payload.clientMutationId, as Relay required it.
+
To all wrapped Types with Relay, will be added id field or wrapped, if it exist already. This field will return globally unique ID among all types in the following format base64(TypeName + ':' + recordId).
+
For RootQuery will be added node field, that will resolve by globalId only that types, which you wrap with composeWithRelay.
+
All this annoying operations is too fatigue to do by hands. So this middleware done all Relay magic implicitly for you.
+
Requirements
+
Method composeWithRelay accept ObjectTypeComposer as input argument. So ObjectTypeComposer should meet following requirements:
+
+
has defined recordIdFn (function that from object of this type, returns you id for the globalId construction)
+
should have findById resolver (that will be used by RootQuery.node)
+
+
If something is missing composeWithRelay throws error.
\ No newline at end of file
diff --git a/docs/plugins/plugin-writing-custom-plugin.html b/docs/plugins/plugin-writing-custom-plugin.html
new file mode 100644
index 00000000..f03c8f6d
--- /dev/null
+++ b/docs/plugins/plugin-writing-custom-plugin.html
@@ -0,0 +1,59 @@
+[WIP] How to write a custom plugin · graphql-compose
\ No newline at end of file
diff --git a/docs/recipes/authorization.html b/docs/recipes/authorization.html
new file mode 100644
index 00000000..b9ec96c2
--- /dev/null
+++ b/docs/recipes/authorization.html
@@ -0,0 +1,59 @@
+[WIP] Authorization · graphql-compose
\ No newline at end of file
diff --git a/docs/recipes/writing-tests.html b/docs/recipes/writing-tests.html
new file mode 100644
index 00000000..7bc8f1b9
--- /dev/null
+++ b/docs/recipes/writing-tests.html
@@ -0,0 +1,59 @@
+[WIP] Writing tests · graphql-compose
\ No newline at end of file
diff --git a/en/help-with-translations.html b/en/help-with-translations.html
new file mode 100644
index 00000000..adf11707
--- /dev/null
+++ b/en/help-with-translations.html
@@ -0,0 +1,62 @@
+graphql-compose · Toolkit for generating complex GraphQL schemas in Node.js
\ No newline at end of file
diff --git a/en/help.html b/en/help.html
new file mode 100644
index 00000000..737ff9d1
--- /dev/null
+++ b/en/help.html
@@ -0,0 +1,62 @@
+graphql-compose · Toolkit for generating complex GraphQL schemas in Node.js
\ No newline at end of file
diff --git a/en/index.html b/en/index.html
new file mode 100644
index 00000000..c971ad3d
--- /dev/null
+++ b/en/index.html
@@ -0,0 +1,135 @@
+graphql-compose · Toolkit for generating complex GraphQL schemas in Node.js
All created types avaliable in SchemaComposer storage
+
Static Analysis
+
Includes Flowtype & TypeScript definitions
+
Amazing Plugins
+
Plugin may generate and modify your types
+
Additional Types
+
Commonly used basic types Date, JSON
+
Type creation
AuthorTC.js
import { schemaComposer } from'graphql-compose';
+
+const AuthorTC = schemaComposer.createObjectTC({
+ posts: {
+ // wrapping Type with arrow function helps to solve a hoisting problem
+ // also using type instances provides better DX
+ // (ctrl+click allows to jump to PostTC type declaration in your IDE)
+ type: () => PostTC,
+ description: 'Posts written by Author',
+ resolve: (source, args, context, info) => {},
+ },
+ // using standard GraphQL Type
+ ucFirstName: {
+ type: GraphQLString,
+ resolve: (source) => { return source.firstName.toUpperCase(); },
+ // also request `firstName` field which must be loaded from database
+ projection: { firstName: true },
+ },
+ // fast way if you need to define only type
+ counter: 'Int',
+ // using SDL for definition new ObjectType
+ complex: `type ComplexType {
+ subField1: String
+ subField2: Float
+ subField3: Boolean
+ subField4: ID
+ subField5: JSON
+ subField6: Date
+ }`,
+ // SDL for defining array of strings, which is NonNull
+ list0: {
+ type: '[String]!',
+ description: 'Array of strings',
+ },
+ list1: '[String]',
+ list2: ['String'],
+ list3: [GraphQLString],
+ list4: [`type Complex2Type { f1: Float, f2: Int }`],
+});
+
+
More details about type creation you can find in the following article.
Graphql-compose allows to call addTypeDefs() and addResolveMethods() as many times as you need, before you call buildSchema().
+
Amazing plugins
Thousands lines of code may be replaced just by several lines
graphql-compose-mongoose
+
Derives GraphQLType from your mongoose model.
+Also derives bunch of internal GraphQL Types.
+Provide convenient CRUD resolvers, including relay connection and pagination.
+
graphql-compose-elasticsearch
+
Derives GraphQLType from your elastic mapping.
+Generates tons of types, provides all available methods in QueryDSL, Aggregations, Sorting
+with field autocompletion according to types in your mapping (like Dev Tools Console in Kibana).
+
graphql-compose-aws
+
Expose AWS Cloud API via GraphQL.
+Internally it generates Types and FieldConfigs from AWS SDK configs.
+You may put this generated types to any GraphQL Schema.
\ No newline at end of file
diff --git a/en/users.html b/en/users.html
new file mode 100644
index 00000000..270d2189
--- /dev/null
+++ b/en/users.html
@@ -0,0 +1,56 @@
+graphql-compose · Toolkit for generating complex GraphQL schemas in Node.js
\ No newline at end of file
diff --git a/en/versions.html b/en/versions.html
new file mode 100644
index 00000000..9190725c
--- /dev/null
+++ b/en/versions.html
@@ -0,0 +1,56 @@
+graphql-compose · Toolkit for generating complex GraphQL schemas in Node.js