Transpile an enriched GraphQL string schema (e.g. support for metadata, inheritance, generic types, ...) into the standard string schema understood by graphql.js and the Apollo server client.
npm install graphql-s2sjs
npm install graphql-s2s --save
`
$3
`html
`
> Using the awesome unpkg.com, all versions are supported at https://unpkg.com/graphql-s2s@__:VERSION__/lib/graphqls2s.min.js.
The API will be accessible through the __graphqls2s__ object.It is also possible to embed it after installing the _graphql-s2s_ npm package:
`html
`Getting Started
Basic
`js
const { transpileSchema } = require('graphql-s2s').graphqls2s
const { makeExecutableSchema } = require('graphql-tools')const schema =
type Person inherits Node {
firstname: String
lastname: String
}
type Student inherits Person {
nickname: String
}
type Query {
students: [Student]
}
const resolver = {
Query: {
students(root, args, context) {
// replace this dummy code with your own logic to extract students.
return [{ id: 1, firstname: "Carry", lastname: "Connor", nickname: "Cannie" }]
}
}
};
const executableSchema = makeExecutableSchema({
typeDefs: [transpileSchema(schema)],
resolvers: resolver
})
`
`js
const schema =
type Node {
id: ID!
}
`$3
`js
const schema = type Node {
id: ID!
}
type Address {
streetAddress: String
city: String
state: String
}
`
More details in the code below.
`js
const schema =
type Question {
name: String!
text: String!
}
`More details in the code below.
Metadata Decoration
`js
const schema = type Student inherits Node {
name: String
# Defining another custom 'edge' metadata, and supporting a generic type
@edge(some other metadata using whatever syntax I want)
questions: [String]
}`
The enriched schema provides a richer and more compact notation. The transpiler converts the enriched schema into the standard expected by graphql.js (using the _buildSchema_ method) as well as the Apollo Server. For more details on how to extract those extra information from the string schema, use the method _getSchemaAST_ (example in section _Metadata Decoration_).
_Metadata_ can be added to decorate the schema types and properties. Add whatever you want as long as it starts with _@_ and start hacking your schema. The original intent of that feature was to decorate the schema with metadata _@node_ and _@edge_ so we could add metadata about the nature of the relations between types.
Metadata can also be used to customize generic types names as shown in section How to use a custom name on generic types?.
This feature allows your GraphQl server to deconstruct any GraphQl query as an AST that can then be filtered and modified based on your requirements. That AST can then be rebuilt as a valid GraphQL query. A great example of that feature in action is the __graphql-authorize__ middleware for __graphql-serverless__ which filters query's properties based on the user's rights.
For a concrete example, refer to the code below.
Use the special keyword @alias as follow:
`js
const schema =
type Post {
code: String
}
type Brand {
id: ID!
name: String
posts: Page
}
@alias((T) => T + 's')
type Page
data: [T]
}
`
After transpilation, the resulting schema is:
`js
const output = transpileSchema(schema)
// output:
// =======
// type Post {
// code: String
// }
//
// type Brand {
// id: ID!
// name: String
// posts: Posts
// }
//
// type Posts {
// data: [Post]
// }
``
__Before graphql-s2s__
`js
const schema =
type Teacher {
id: ID!
creationDate: String
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
title: String!
}
type Student {
id: ID!
creationDate: String
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
nickname: String!
}
``
__After graphql-s2s__js
const schema =
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
}
`
__Full code example__
`js
const { transpileSchema } = require('graphql-s2s').graphqls2s
const { makeExecutableSchema } = require('graphql-tools')
const { students, teachers } = require('./dummydata.json')
const schema =
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
questions: [Question]
}
type Question inherits Node {
name: String!
text: String!
}
type Query {
# ### GET all users
#
students: [Student]
# ### GET all teachers
#
teachers: [Teacher]
}
const resolver = {
Query: {
students(root, args, context) {
return Promise.resolve(students)
},
teachers(root, args, context) {
return Promise.resolve(teachers)
}
}
}
const executableSchema = makeExecutableSchema({
typeDefs: [transpileSchema(schema)],
resolvers: resolver
})
`
__Before graphql-s2s__
`js
const schema =
type Teacher {
id: ID!
creationDate: String
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
title: String!
}
type Student {
id: ID!
creationDate: String
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
nickname: String!
questions: Questions
}
type Question {
id: ID!
creationDate: String
name: String!
text: String!
}
type Teachers {
data: [Teacher]
cursor: ID
}
type Students {
data: [Student]
cursor: ID
}
type Questions {
data: [Question]
cursor: ID
}
type Query {
# ### GET all users
#
students: Students
# ### GET all teachers
#
teachers: Teachers
}
``
__After graphql-s2s__js
const schema =
type Paged
data: [T]
cursor: ID
}
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
questions: Paged
}
type Question inherits Node {
name: String!
text: String!
}
input Filter
field: FilterFields!,
value: String!
}
enum TeachersFilterFields {
firstName
lastName
}
type Query {
# ### GET all users
#
students: Paged
# ### GET all teachers
# You can use generic types on parameters, too.
#
teachers(filter: Filter
}``
This is very similar to C# or Java generic classes. What the transpiler will do is to simply recreate 3 types (one for Paged\js`
type PagedQuestion {
data: [Question]
cursor: ID
}
__Full code example__
`js
const { transpileSchema } = require('graphql-s2s').graphqls2s
const { makeExecutableSchema } = require('graphql-tools')
const { students, teachers } = require('./dummydata.json')
const schema =
type Paged
data: [T]
cursor: ID
}
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
questions: Paged
}
type Question inherits Node {
name: String!
text: String!
}
type Query {
# ### GET all users
#
students: Paged
# ### GET all teachers
#
teachers: Paged
}
const resolver = {
Query: {
students(root, args, context) {
return Promise.resolve({ data: students.map(s => ({ __proto__:s, questions: { data: s.questions, cursor: null }})), cursor: null })
},
teachers(root, args, context) {
return Promise.resolve({ data: teachers, cursor: null });
}
}
}
const executableSchema = makeExecutableSchema({
typeDefs: [transpileSchema(schema)],
resolvers: resolver
})
`
js
const { getSchemaAST } = require('graphql-s2s').graphqls2s
const schema = const schemaObjects = getSchemaAST(schema);
// -> schemaObjects
// {
// "type": "TYPE",
// "name": "User",
// "metadata": {
// "name": "node",
// "body": "",
// "schemaType": "TYPE",
// "schemaName": "User", "parent": null
// },
// "genericType": null,
// "blockProps": [{
// "comments": "",
// "details": {
// "name": "posts",
// "metadata": {
// "name": "edge",
// "body": "(\'<-[CREATEDBY]-\')",
// "schemaType": "PROPERTY",
// "schemaName": "posts: [Post]",
// "parent": {
// "type": "TYPE",
// "name": "User",
// "metadata": {
// "type": "TYPE",
// "name": "node"
// }
// }
// },
// "params": null,
// "result": {
// "originName": "[Post]",
// "isGen": false,
// "name": "[Post]"
// }
// },
// "value": "posts: [Post]"
// }],
// "inherits": null,
// "implements": null
// }
`
$3
This feature allows your GraphQl server to deconstruct any GraphQl query as an AST that can then be filtered and modified based on your requirements. That AST can then be rebuilt as a valid GraphQL query. A great example of that feature in action is the __graphql-authorize__ middleware for __graphql-serverless__ which filters query's properties based on the user's rights.`js
const { getQueryAST, buildQuery, getSchemaAST } = require('graphql-s2s').graphqls2s
const schema = const query =
const schemaAST = getSchemaAST(schema)
const queryAST = getQueryAST(query, null, schemaAST)
const rebuiltQuery = buildQuery(queryAST.filter(x => !x.metadata || x.metadata.name != 'auth'))
// query {
// properties(where:{name:"Love",locations:[{type:"house",value:"Bellevue hill"}]}){
// name
// }
// }
`Notice that the original query was requesting the
address property. Because we decorated that property with the custom metadata @auth (feature demonstrated previously Metadata Decoration), we were able to filter that property to then rebuilt the query without it.#### API
__getQueryAST(query, operationName, schemaAST, options): QueryAST__
Returns an GraphQl query AST.
| Arguments | type | Description |
| :------------- |:-------:| :------------ |
| query | String | GraphQl Query. |
| operationName | String | GraphQl query operation. Only useful if multiple operations are defined in a single query, otherwise use
null. |
| schemaAST | Object | Original GraphQl schema AST obtained thanks to the getSchemaAST function. |
| options.defrag | Boolean | If set to true and if the query contained fragments, then all fragments are replaced by their explicit definition in the AST. |__QueryAST Object Structure__
| Properties | type | Description |
| :--------- |:------:| :------------ |
| name | String | Field's name. |
| kind | String | Field's kind. |
| type | String | Field's type. |
| metadata | String | Field's metadata. |
| args | Array | Array of argument objects. |
| properties | Array | Array of QueryAST objects. |
__QueryAST.filter((ast:QueryAST) => ...): QueryAST__
Returns a new QueryAST object where only ASTs complying to the predicate
ast => ... are left.__QueryAST.propertyPaths((ast:QueryAST) => ...): [String]__
Returns an array of strings. Each one represents the path to the query property that matches the predicate
ast => ....__QueryAST.containsProp(property:String): Boolean__
Returns a boolean indicating the presence of a property in the GraphQl query. Example:
`js
const schema = type UserDetails {
gender: String
}
type Query {
users: [User]
}
const query =
{
users {
id
details {
gender
}
}
}`
const schemaAST = getSchemaAST(schema)
const queryAST = getQueryAST(query, null, schemaAST)
queryAST.containsProp('users.id') // true
queryAST.containsProp('users.details.gender') // true
queryAST.containsProp('details.gender') // true
queryAST.containsProp('users.name') // false
__QueryAST.some((ast:QueryAST) => ...): Boolean__
Returns a boolean indicating whether the QueryAST contains at least one AST matching the predicate ast => ....
__buildQuery(ast:QueryAST): String__
Rebuilds a valid GraphQl query from a QueryAST object.
file. Once that's done, simply run your the following command to test your features:
`
npm run test:dev
`
This sets an environment variable that configure the project to load the main dependency from the _src_ folder (source code in ES6) instead of the _lib_ folder (transpiled ES5 code). Step 2. Compile & Rerun Your Test Before Pushing
`
npm run dev
npm run build
npm test
``Our other open-sourced projects:
#### GraphQL
__graphql-s2s*__: Add GraphQL Schema support for type inheritance, generic typing, metadata decoration. Transpile the enriched GraphQL string schema into the standard string schema understood by graphql.js and the Apollo server client.
__schemaglue*__: Naturally breaks down your monolithic graphql schema into bits and pieces and then glue them back together.
__graphql-authorize*__: Authorization middleware for graphql-serverless. Add inline authorization straight into your GraphQl schema to restrict access to certain fields based on your user's rights.
#### React & React Native
__react-native-game-engine*__: A lightweight game engine for react native.
__react-native-game-engine-handbook*__: A React Native app showcasing some examples using react-native-game-engine.
#### General Purposes
__core-async*__: JS implementation of the Clojure core.async library aimed at implementing CSP (Concurrent Sequential Process) programming style. Designed to be used with the npm package 'co'.
__jwt-pwd*__: Tiny encryption helper to manage JWT tokens and encrypt and validate passwords using methods such as md5, sha1, sha256, sha512, ripemd160.
#### Google Cloud Platform
__google-cloud-bucket*__: Nodejs package to manage Google Cloud Buckets and perform CRUD operations against them.
__google-cloud-bigquery*__: Nodejs package to manage Google Cloud BigQuery datasets, and tables and perform CRUD operations against them.
__google-cloud-tasks*__: Nodejs package to push tasks to Google Cloud Tasks. Include pushing batches.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Neap Pty Ltd nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL NEAP PTY LTD BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.