• Stars
    star
    187
  • Rank 206,464 (Top 5 %)
  • Language
    JavaScript
  • License
    Other
  • Created over 7 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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.

This project is still maintained but has been superseded by graphql-schemax. graphql-schemax takes the approach of compiling standard JSON object into a GraphQL Schema string.

GraghQL Schema-2-Schema Transpiler ยท NPM Tests License Neap npm downloads

Table Of Contents

What It Does

GraphQL S2S enriches the standard GraphQL Schema string used by both graphql.js and the Apollo Server. The enriched schema supports:

Install

node

npm install graphql-s2s --save

browser

<script src="https://unpkg.com/[email protected]/lib/graphqls2s.min.js"></script>

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:

<script src="./node_modules/graphql-s2s/lib/graphqls2s.min.js"></script>

Getting Started

Basic

const { transpileSchema } = require('graphql-s2s').graphqls2s
const { makeExecutableSchema } = require('graphql-tools')

const schema = `
type Node {
	id: ID!
}

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
})

Type Inheritance

Single Inheritance

const schema = `
type Node {
	id: ID!
}

# Inheriting from the 'Node' type
type Person inherits Node {
	firstname: String
	lastname: String
}

# Inheriting from the 'Person' type
type Student inherits Person {
	nickname: String
}
`

Multiple Inheritance

const schema = `

type Node {
	id: ID!
}

type Address {
	streetAddress: String
	city: String
	state: String
}

# Inheriting from the 'Node' & 'Adress' type
type Person inherits Node, Address {
	id: ID!
	streetAddress: String
	city: String
	state: String
	firstname: String
	lastname: String
}

`

More details in the code below.

Generic Types

const schema = `
# Defining a generic type
type Paged<T> {
	data: [T]
	cursor: ID
}

type Question {
	name: String!
	text: String!
}

# Using the generic type
type Student {
	name: String
	questions: Paged<Question>
}

# Using the generic type
type Teacher {
	name: String
	students: Paged<Student>
}
`

More details in the code below.

Metadata Decoration

const schema = `
# Defining a custom 'node' metadata attribute
@node
type Node {
	id: ID!
}

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?.

Deconstructing - Transforming - Rebuilding Queries

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.

How To

How to use a custom name on generic types?

Use the special keyword @alias as follow:

const schema = `
  type Post {
    code: String
  }

  type Brand {
    id: ID!
    name: String
    posts: Page<Post>
  }

  @alias((T) => T + 's')
  type Page<T> {
    data: [T]
  }
  `

After transpilation, the resulting schema is:

const output = transpileSchema(schema)
// output:
// =======
// 	type Post {
// 		code: String
// 	}
// 
// 	type Brand {
// 		id: ID!
// 		name: String
// 		posts: Posts
// 	}
// 
// 	type Posts {
// 		data: [Post]
// 	}

Examples

WARNING: the following examples will be based on 'graphql-tools' from the Apollo team, but the string schema could also be used with the 'buildSchema' method from graphql.js

Type Inheritance

NOTE: The examples below only use 'type', but it would also work on 'input' and 'interface'

Before graphql-s2s

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

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

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
})

Generic Types

NOTE: The examples below only use 'type', but it would also work on 'input'

Before graphql-s2s

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

const schema = `
type Paged<T> {
	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<Question>
}

type Question inherits Node {
	name: String!
	text: String!
}

input Filter<FilterFields> {
  field: FilterFields!,
  value: String!
}

enum TeachersFilterFields {
  firstName
  lastName
}

type Query {
  # ### GET all users
  #
  students: Paged<Student>

  # ### GET all teachers
  # You can use generic types on parameters, too.
  #
  teachers(filter: Filter<TeachersFilterFields>): Paged<Teacher>
}
`

This is very similar to C# or Java generic classes. What the transpiler will do is to simply recreate 3 types (one for Paged<Question>, Paged<Student> and Paged<Teacher>), and one input (Filter<TeachersFilterFields>). If we take the Paged<Question> example, the transpiled type will be:

type PagedQuestion {
	data: [Question]
	cursor: ID
}

Full code example

const { transpileSchema } = require('graphql-s2s').graphqls2s
const { makeExecutableSchema } = require('graphql-tools')
const { students, teachers } = require('./dummydata.json')

const schema = `
type Paged<T> {
	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<Question>
}

type Question inherits Node {
	name: String!
	text: String!
}

type Query {
  # ### GET all users
  #
  students: Paged<Student>

  # ### GET all teachers
  #
  teachers: Paged<Teacher>
}
`

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
})

Metadata Decoration

Define your own custom metadata and decorate your GraphQL schema with new types of data. Let's imagine we want to explicitely add metadata about the type of relations between nodes, we could write something like this:

const { getSchemaAST } = require('graphql-s2s').graphqls2s
const schema = `
@node
type User {
	@edge('<-[CREATEDBY]-')
	posts: [Post]
}
`

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 
//	}

Deconstructing - Transforming - Rebuilding Queries

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.

const { getQueryAST, buildQuery, getSchemaAST } = require('graphql-s2s').graphqls2s
const schema = `
	type Property {
		name: String
		@auth
		address: String
	}
	
	input InputWhere {
		name: String
		locations: [LocationInput]
	}
	
	input LocationInput {
		type: String 
		value: String
	}
	
	type Query {
		properties(where: InputWhere): [Property]
	}`

const query = `
	query {
		properties(where: { name: "Love", locations: [{ type: "house", value: "Bellevue hill" }] }){
			name
			address
		}
	}`

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:

const schema = `
type User {
  id: ID!
  name: String
  details: UserDetails
}

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.

Contribute

Step 1. Don't Forget To Test Your Feature

We only accept pull request that have been thoroughly tested. To do so, simply add your test under the test/browser/graphqls2s.js 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

This project is built using Javascript ES6. Each version is also transpiled to ES5 using Babel through Webpack 2, so this project can run in the browser. In order to write unit test only once instead of duplicating it for each version of Javascript, the all unit tests have been written using Javascript ES5 in mocha. That means that if you want to test the project after some changes, you will need to first transpile the project to ES5.

This Is What We re Up To

We are Neap, an Australian Technology consultancy powering the startup ecosystem in Sydney. We simply love building Tech and also meeting new people, so don't hesitate to connect with us at https://neap.co.

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

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.

License

Copyright (c) 2017-2019, Neap Pty Ltd. All rights reserved.

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.

Neap Pty Ltd logo

More Repositories

1

graphql-serverless

GraphQL (incl. a GraphiQL interface) middleware for the webfunc serverless web framework.
JavaScript
129
star
2

schemaglue

Naturally breaks down your monolithic graphql schema into bits and pieces and then glue them back together.
JavaScript
116
star
3

webfunc

Universal Serverless Web Framework. Write Express apps ready to be deployed to Zeit-Now, Google Cloud Functions (incl. functions reacting to Pub/Sub topics or Storage changes), and AWS Lambdas.
JavaScript
73
star
4

google-graphql-functions

Run graphql queries on Google Cloud Functions (beta). This package can also serve a GraphiQL UI out-of-the-box. This project is mainly a modification of the excellent express-graphiql maintained by Facebook. This project is published using a BSD-style licence.
JavaScript
38
star
5

aws-cloudwatch-logger

Promise based logger for AWS CloudWatch LogStream
JavaScript
19
star
6

graphql-authorize

Authorization middleware for graphql-serverless. Add inline authorization straight into your GraphQl schema.
JavaScript
9
star
7

now-flow

Augment the now-CLI to support AWS Lambdas, GCP that can react to events other then HTTPS requests (Pub/Sub & Storage changes) and better deployment configurations when managing multiple environments (e.g. staging, UAT, production).
JavaScript
7
star
8

google-cloud-bucket

Nodejs package to manage Google Cloud Buckets and perform CRUD operations against them.
JavaScript
6
star
9

gimpy

Automate Your Chores. New Tortures Coming Regularly...
JavaScript
6
star
10

graphql-universal-server

Template to build a GraphQL server ready to be deploy on Zeit Now or Google Cloud Functions (AWS Lambda coming soon).
JavaScript
6
star
11

userin

UserIn is an NodeJS Express middleware to build Authorization Servers that support OAuth 2.0. workflows and integrate with Identity Providers (e.g., Google, Facebook, GitHub). Its openid mode exposes an API that complies to the OpenID Connect specification. With UserIn, the OAuth 2.0/OpenID Connect flows are abstracted so that developers focus only on implementing basic CRUD operations (e.g., get user by ID, insert token's claims object) using the backend storage of their choice.
JavaScript
6
star
12

puffy-core

A collection of ES6 modules to help manage common programming tasks in both NodeJS or native JS.
JavaScript
4
star
13

google-cloud-bigquery

Nodejs package to manage Google Cloud BigQuery datasets, and tables and perform CRUD operations against them.
JavaScript
3
star
14

get-policies

NPX script that lists/searches the AWS managed policies and copy the selected one to the clipboard.
JavaScript
3
star
15

sls-config-parser

Parses serverless.yml files so that their values can be use locally in JS code during development.
JavaScript
3
star
16

apollo-subscription-demo

This is a demo project demoing GraphQL Subscription using SchemaGlue to assemble the Schema
JavaScript
3
star
17

fileman

Delete files with path longer than 260 characters on Windows, and more...
Ruby
2
star
18

graphql-schemax

Creates GraphQL string schema from plain JSON objects.
JavaScript
2
star
19

jwt-pwd

Tiny encryption helper to manage JWT tokens and encrypt and validate passwords using methods such as md5, sha1, sha256, sha512, ripemd160.
JavaScript
2
star
20

switch-profile

NPX script that exposes terminal shortcut to easily switch between cloud configuration (aws, gcp).
JavaScript
2
star
21

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'.
JavaScript
2
star
22

create-keys

`npx create-keys` is a terminal assistant that helps creating RSA or ECDSA asymmetric key pairs (also works in NodeJS). Supported formats: PEM and JWK. Supported encoding: PCKS8 for private keys, PCKS1 for RSA public keys and SPKI for ECDSA public keys. Supported ECDSA curves: P-256 (prime256v1) and P-384 (secp384r1).
JavaScript
2
star
23

gimpy-template-graphql-serverless

Gimpy template for serverless GraphQL projects (as of today Google Cloud Functions & Firebase Functions)
JavaScript
1
star
24

get-regions

NPX script that lists the AWS and GCP regions.
JavaScript
1
star
25

get-raw-policies

Uses the AWS CLI to pull the latest AWS managed policies
JavaScript
1
star
26

get-principals

NPX script that lists/searches the AWS principals and copy the selected one to the clipboard.
JavaScript
1
star
27

marso

Marso is the beginning of a small lightweight BDD project. Currently, this is just a very simple code sample that only displays a custom message in green or in red depending on the value of a predicate.
Ruby
1
star
28

detach-aws-sg-from-eni

NPX script that lists all the ENIs that are attached to a specific security group and gives the option to remove that security group attachement.
JavaScript
1
star
29

userin-core

UserIn core component used to build UserIn plugins.
JavaScript
1
star