diff --git a/README.md b/README.md
index 82d39296..8d5ce61a 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ To learn more check out the following [examples](examples/):
- [Flask SQLAlchemy example](examples/flask_sqlalchemy)
- [Nameko SQLAlchemy example](examples/nameko_sqlalchemy)
+- [Falcon SQLAlchemy example](examples/falcon_sqlalchemy)
## Contributing
diff --git a/README.rst b/README.rst
index d82b8071..339d3db1 100644
--- a/README.rst
+++ b/README.rst
@@ -75,8 +75,12 @@ Then you can simply query the schema:
To learn more check out the following `examples `__:
-- **Full example**: `Flask SQLAlchemy
+- **Full example (Flask)**: `Flask SQLAlchemy
example `__
+- **Full example (Nameko)**: `Nameko SQLAlchemy
+ example `__
+- **Full example (Falcon)**: `Falcon SQLAlchemy
+ example `__
Contributing
------------
diff --git a/examples/falcon_sqlalchemy/README.md b/examples/falcon_sqlalchemy/README.md
new file mode 100644
index 00000000..aa3aeb7f
--- /dev/null
+++ b/examples/falcon_sqlalchemy/README.md
@@ -0,0 +1,313 @@
+# demo-graphql-sqlalchemy-falcon
+
+## Overview
+
+This is a simple project demonstrating the implementation of a GraphQL server in Python using:
+
+- [SQLAlchemy](https://github.com/zzzeek/sqlalchemy).
+- [Falcon](https://github.com/falconry/falcon).
+- [Graphene](https://github.com/graphql-python/graphene).
+- [Graphene-SQLAlchemy](https://github.com/graphql-python/graphene-sqlalchemy).
+- [Gunicorn](https://github.com/benoitc/gunicorn).
+
+The objective is to demonstrate how these different libraries can be integrated.
+
+## Features
+
+The primary feature offered by this demo are:
+
+- [SQLAlchemy](https://github.com/zzzeek/sqlalchemy) ORM against a local SQLite database. The ORM is super simple but showcases a many-to-many relationship between `authors` and `books` via an `author_books` association table.
+- [Falcon](https://github.com/falconry/falcon) resources to serve both [GraphQL](https://github.com/facebook/graphql) and [GraphiQL](https://github.com/graphql/graphiql).
+
+> The [Falcon](https://github.com/falconry/falcon) resources are slightly modified versions of the ones under [https://github.com/alecrasmussen/falcon-graphql-server](https://github.com/alecrasmussen/falcon-graphql-server) so all credits to [Alec Rasmussen](https://github.com/alecrasmussen).
+
+- Basic [GraphQL](https://github.com/facebook/graphql) schema automatically derived from the [SQLAlchemy](https://github.com/zzzeek/sqlalchemy) ORM via [Graphene](https://github.com/graphql-python/graphene) and [Graphene-SQLAlchemy](https://github.com/graphql-python/graphene-sqlalchemy).
+- API setup via [Falcon](https://github.com/falconry/falcon) with the whole thing served via [Gunicorn](https://github.com/benoitc/gunicorn).
+
+## Usage
+
+All instructions and commands below are meant to be run from the root dir of this repo.
+
+### Prerequisites
+
+You are strongly encouraged to use a virtualenv here but I can be assed writing down the instructions for that.
+
+Install all requirements through:
+
+```
+pip install -r requirements.txt
+```
+
+### Sample Database
+
+The sample SQLite database has been committed in this repo but can easily be rebuilt through:
+
+```
+python -m demo.orm
+```
+
+at which point it will create a `demo.db` in the root of this repo.
+
+> The sample data are defined under `data.py` while they're ingested with the code under the `main` sentinel in `orm.py`. Feel free to tinker.
+
+### Running Server
+
+The [Gunicorn](https://github.com/benoitc/gunicorn) is configured via the `gunicorn_config.py` module and binds by default to `localhost:5432/`. You can change all gunicorn configuration options under the aforementioned module.
+
+The server can be run through:
+
+```
+gunicorn -c gunicorn_config.py "demo.demo:main()"
+```
+
+The server exposes two endpoints:
+
+- `/graphql`: The standard GraphQL endpoint which can receive the queries directly (accessible by default under [http://localhost:5432/graphql](http://localhost:5432/graphql)).
+- `/graphiql`: The [GraphiQL](https://github.com/graphql/graphiql) interface (accessible by default under [http://localhost:5432/graphiql](http://localhost:5432/graphiql)).
+
+### Queries
+
+Here's a couple example queries you can either run directly in [GraphiQL](https://github.com/graphql/graphiql) or by performing POST requests against the [GraphQL](https://github.com/facebook/graphql) server.
+
+#### Get an author by ID
+
+Query:
+
+```
+query getAuthor{
+ author(authorId: 1) {
+ nameFirst,
+ nameLast
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "author": {
+ "nameFirst": "Robert",
+ "nameLast": "Jordan"
+ }
+ }
+}
+```
+
+#### Get an author by first name
+
+```
+query getAuthor{
+ author(nameFirst: "Robert") {
+ nameFirst,
+ nameLast
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "author": {
+ "nameFirst": "Robert",
+ "nameLast": "Jordan"
+ }
+ }
+}
+```
+
+### Get an author and their books
+
+Query:
+
+```
+query getAuthor{
+ author(nameFirst: "Brandon") {
+ nameFirst,
+ nameLast,
+ books {
+ title,
+ year
+ }
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "author": {
+ "nameFirst": "Brandon",
+ "nameLast": "Sanderson",
+ "books": [
+ {
+ "title": "The Gathering Storm",
+ "year": 2009
+ },
+ {
+ "title": "Towers of Midnight",
+ "year": 2010
+ },
+ {
+ "title": "A Memory of Light",
+ "year": 2013
+ }
+ ]
+ }
+ }
+}
+```
+
+#### Get books by year
+
+Query:
+
+```
+query getBooks{
+ books(year: 1990) {
+ title,
+ year
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "books": [
+ {
+ "title": "The Eye of the World",
+ "year": 1990
+ },
+ {
+ "title": "The Great Hunt",
+ "year": 1990
+ }
+ ]
+ }
+}
+```
+
+#### Get books and their authors by their title
+
+Query:
+
+```
+query getBooks{
+ books(title: "A Memory of Light") {
+ title,
+ year,
+ authors {
+ nameFirst,
+ nameLast
+ }
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "books": [
+ {
+ "title": "A Memory of Light",
+ "year": 2013,
+ "authors": [
+ {
+ "nameFirst": "Robert",
+ "nameLast": "Jordan"
+ },
+ {
+ "nameFirst": "Brandon",
+ "nameLast": "Sanderson"
+ }
+ ]
+ }
+ ]
+ }
+}
+```
+
+#### Get number of books by cover-artist
+
+Query:
+
+```
+query getCountBooksByCoverArtist{
+ stats {
+ countBooksByCoverArtist {
+ coverArtist,
+ countBooks
+ }
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "stats": {
+ "countBooksByCoverArtist": [
+ {
+ "coverArtist": null,
+ "countBooks": 1
+ },
+ {
+ "coverArtist": "Darrell K. Sweet",
+ "countBooks": 12
+ },
+ {
+ "coverArtist": "Michael Whelan",
+ "countBooks": 1
+ }
+ ]
+ }
+ }
+}
+```
+
+#### Add new author
+
+Query:
+
+```
+mutation createAuthor{
+ createAuthor(author: {
+ nameFirst: "First Name",
+ nameLast: "Last Name"
+ }) {
+ author {
+ authorId
+ nameFirst
+ nameLast
+ }
+ }
+}
+```
+
+Response:
+
+```
+{
+ "data": {
+ "createAuthor": {
+ "author": {
+ "authorId": "3",
+ "nameFirst": "First Name",
+ "nameLast": "Last Name"
+ }
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/examples/falcon_sqlalchemy/demo.db b/examples/falcon_sqlalchemy/demo.db
new file mode 100644
index 00000000..29283108
Binary files /dev/null and b/examples/falcon_sqlalchemy/demo.db differ
diff --git a/examples/falcon_sqlalchemy/demo/__init__.py b/examples/falcon_sqlalchemy/demo/__init__.py
new file mode 100644
index 00000000..e8b5cba7
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/__init__.py
@@ -0,0 +1,11 @@
+# coding=utf-8
+
+from demo import api
+from demo import data
+from demo import demo
+from demo import orm
+from demo import orm_base
+from demo import resources
+from demo import schema
+from demo import schema_types
+from demo import utils
diff --git a/examples/falcon_sqlalchemy/demo/api.py b/examples/falcon_sqlalchemy/demo/api.py
new file mode 100644
index 00000000..0fbce509
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/api.py
@@ -0,0 +1,41 @@
+# coding=utf-8
+
+import sqlalchemy.orm
+import falcon
+import graphene
+
+from demo.resources import ResourceGraphQlSqlAlchemy
+from demo.resources import ResourceGraphiQL
+
+
+def create_app(
+ schema: graphene.Schema,
+ scoped_session: sqlalchemy.orm.scoped_session,
+ do_enable_graphiql: bool,
+):
+ # Create the API.
+ app = falcon.API()
+
+ app.add_route(
+ uri_template="/graphql",
+ resource=ResourceGraphQlSqlAlchemy(
+ schema=schema,
+ scoped_session=scoped_session,
+ )
+ )
+
+ if do_enable_graphiql:
+ app.add_route(
+ uri_template="/graphiql/",
+ resource=ResourceGraphiQL(
+ path_graphiql="graphiql",
+ )
+ )
+ app.add_route(
+ uri_template="/graphiql/{static_file}",
+ resource=ResourceGraphiQL(
+ path_graphiql="graphiql",
+ )
+ )
+
+ return app
diff --git a/examples/falcon_sqlalchemy/demo/data.py b/examples/falcon_sqlalchemy/demo/data.py
new file mode 100644
index 00000000..50ab1830
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/data.py
@@ -0,0 +1,130 @@
+# coding=utf-8
+
+from __future__ import unicode_literals
+
+authors = {
+ "data": [
+ {
+ "author_id": 1,
+ "name_first": "Robert",
+ "name_last": "Jordan",
+ },
+ {
+ "author_id": 2,
+ "name_first": "Brandon",
+ "name_last": "Sanderson",
+ }
+ ]
+}
+
+books = {
+ "data": [
+ {
+ "book_id": 1,
+ "title": "The Eye of the World",
+ "year": 1990,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 2,
+ "title": "The Great Hunt",
+ "year": 1990,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 3,
+ "title": "The Dragon Reborn",
+ "year": 1991,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 4,
+ "title": "The Shadow Rising",
+ "year": 1992,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 5,
+ "title": "The Fires of Heaven",
+ "year": 1993,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 6,
+ "title": "Lord of Chaos",
+ "year": 1994,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 7,
+ "title": "A Crown of Swords",
+ "year": 1996,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 8,
+ "title": "The Path of Daggers",
+ "year": 1998,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 9,
+ "title": "Winter's Heart",
+ "year": 2000,
+ "cover_artist": None,
+ },
+ {
+ "book_id": 10,
+ "title": "Crossroads of Twilight",
+ "year": 2003,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 11,
+ "title": "Knife of Dreams",
+ "year": 2005,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 12,
+ "title": "The Gathering Storm",
+ "year": 2009,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 13,
+ "title": "Towers of Midnight",
+ "year": 2010,
+ "cover_artist": "Darrell K. Sweet",
+ },
+ {
+ "book_id": 14,
+ "title": "A Memory of Light",
+ "year": 2013,
+ "cover_artist": "Michael Whelan",
+ },
+ ]
+}
+
+
+author_books = {
+ "data": [
+ {"author_book_id": 1, "author_id": 1, "book_id": 1},
+ {"author_book_id": 2, "author_id": 1, "book_id": 2},
+ {"author_book_id": 3, "author_id": 1, "book_id": 3},
+ {"author_book_id": 4, "author_id": 1, "book_id": 4},
+ {"author_book_id": 5, "author_id": 1, "book_id": 5},
+ {"author_book_id": 6, "author_id": 1, "book_id": 6},
+ {"author_book_id": 7, "author_id": 1, "book_id": 7},
+ {"author_book_id": 8, "author_id": 1, "book_id": 8},
+ {"author_book_id": 9, "author_id": 1, "book_id": 9},
+ {"author_book_id": 10, "author_id": 1, "book_id": 10},
+ {"author_book_id": 11, "author_id": 1, "book_id": 11},
+ {"author_book_id": 12, "author_id": 1, "book_id": 12},
+ {"author_book_id": 13, "author_id": 1, "book_id": 13},
+ {"author_book_id": 14, "author_id": 1, "book_id": 14},
+ {"author_book_id": 15, "author_id": 2, "book_id": 12},
+ {"author_book_id": 16, "author_id": 2, "book_id": 13},
+ {"author_book_id": 17, "author_id": 2, "book_id": 14},
+ ]
+}
diff --git a/examples/falcon_sqlalchemy/demo/demo.py b/examples/falcon_sqlalchemy/demo/demo.py
new file mode 100644
index 00000000..e37456f6
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/demo.py
@@ -0,0 +1,31 @@
+# coding: utf-8
+
+"""Main module."""
+
+import sqlalchemy.orm
+
+from demo.api import create_app
+from demo.schema import schema
+
+
+def main():
+
+ # Create engine to local SQLite database.
+ engine = sqlalchemy.create_engine("sqlite:///demo.db", echo=True)
+
+ # Prepare a DB session.
+ session_maker = sqlalchemy.orm.sessionmaker(bind=engine)
+ scoped_session = sqlalchemy.orm.scoped_session(session_maker)
+
+ app = create_app(
+ schema=schema,
+ scoped_session=scoped_session,
+ do_enable_graphiql=True,
+ )
+
+ return app
+
+
+# main sentinel
+if __name__ == "__main__":
+ main()
diff --git a/examples/falcon_sqlalchemy/demo/orm.py b/examples/falcon_sqlalchemy/demo/orm.py
new file mode 100644
index 00000000..bb5ef193
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/orm.py
@@ -0,0 +1,130 @@
+# coding=utf-8
+
+import sqlalchemy.orm
+
+from demo import data
+from demo.orm_base import Base, OrmBaseMixin
+
+
+class Author(Base, OrmBaseMixin):
+ __tablename__ = "authors"
+
+ author_id = sqlalchemy.Column(
+ sqlalchemy.types.Integer(),
+ primary_key=True,
+ )
+
+ name_first = sqlalchemy.Column(
+ sqlalchemy.types.Unicode(length=80),
+ nullable=False,
+ )
+
+ name_last = sqlalchemy.Column(
+ sqlalchemy.types.Unicode(length=80),
+ nullable=False,
+ )
+
+ books = sqlalchemy.orm.relationship(
+ argument="Book",
+ secondary="author_books",
+ back_populates="authors",
+ )
+
+
+class Book(Base, OrmBaseMixin):
+ __tablename__ = "books"
+
+ book_id = sqlalchemy.Column(
+ sqlalchemy.types.Integer(),
+ primary_key=True,
+ )
+
+ title = sqlalchemy.Column(
+ sqlalchemy.types.Unicode(length=80),
+ nullable=False,
+ )
+
+ year = sqlalchemy.Column(
+ sqlalchemy.types.Integer(),
+ nullable=False,
+ )
+
+ cover_artist = sqlalchemy.Column(
+ sqlalchemy.types.Unicode(length=80),
+ nullable=True,
+ )
+
+ authors = sqlalchemy.orm.relationship(
+ argument="Author",
+ secondary="author_books",
+ back_populates="books",
+ )
+
+
+class AuthorBook(Base, OrmBaseMixin):
+ __tablename__ = "author_books"
+
+ author_book_id = sqlalchemy.Column(
+ sqlalchemy.types.Integer(),
+ primary_key=True,
+ )
+
+ author_id = sqlalchemy.Column(
+ sqlalchemy.types.Integer(),
+ sqlalchemy.ForeignKey("authors.author_id"),
+ index=True,
+ )
+
+ book_id = sqlalchemy.Column(
+ sqlalchemy.types.Integer(),
+ sqlalchemy.ForeignKey("books.book_id"),
+ index=True,
+ )
+
+
+if __name__ == "__main__":
+
+ # Create engine to local SQLite database.
+ engine = sqlalchemy.create_engine("sqlite:///demo.db", echo=True)
+
+ # Drop and recreate the entire schema
+ Base.metadata.drop_all(engine)
+ Base.metadata.create_all(engine)
+
+ # Prepare a DB session.
+ session_maker = sqlalchemy.orm.sessionmaker(bind=engine)
+ session = session_maker()
+
+ # Create `Author` records.
+ author_objs = []
+ for _author_item in data.authors["data"]:
+ author_obj = Author()
+ author_obj.author_id = _author_item["author_id"]
+ author_obj.name_first = _author_item["name_first"]
+ author_obj.name_last = _author_item["name_last"]
+ author_objs.append(author_obj)
+ session.add_all(author_objs)
+ session.commit()
+
+ # Create `Book` authors.
+ book_objs = []
+ for _book_item in data.books["data"]:
+ book_obj = Book()
+ book_obj.book_id = _book_item["book_id"]
+ book_obj.title = _book_item["title"]
+ book_obj.year = _book_item["year"]
+ book_obj.cover_artist = _book_item["cover_artist"]
+ book_objs.append(book_obj)
+ session.add_all(book_objs)
+ session.commit()
+
+ # Create `AuthorBook` records.
+ author_book_objs = []
+ for _author_book_item in data.author_books["data"]:
+ author_book_obj = AuthorBook()
+ author_book_obj.author_book_id = _author_book_item["author_book_id"]
+ author_book_obj.author_id = _author_book_item["author_id"]
+ author_book_obj.book_id = _author_book_item["book_id"]
+ author_book_objs.append(author_book_obj)
+ session.add_all(author_book_objs)
+ session.commit()
diff --git a/examples/falcon_sqlalchemy/demo/orm_base.py b/examples/falcon_sqlalchemy/demo/orm_base.py
new file mode 100644
index 00000000..7626f0bf
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/orm_base.py
@@ -0,0 +1,239 @@
+# coding=utf-8
+
+from __future__ import unicode_literals
+
+import inspect
+import datetime
+import binascii
+
+import sqlalchemy
+import sqlalchemy.sql.sqltypes
+import sqlalchemy.types
+import sqlalchemy.dialects.mysql
+from sqlalchemy.ext.declarative import declarative_base
+import uuid
+from decimal import Decimal
+
+
+# Create schema metadata with a constraint naming convention so that all
+# constraints are named automatically based on the tables and columns they're
+# defined upon. This ensures that all constraints will be given a unique name
+# regardless of the backend database which allows for `alembic` to create
+# comprehensive migrations of the defined schemata.
+metadata = sqlalchemy.MetaData(
+ naming_convention={
+ "ix": "ix_%(column_0_label)s",
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+ }
+)
+# create declarative base
+Base = declarative_base(metadata=metadata)
+
+
+class OrmBaseMixin(object):
+ # take sqla type and value, produce converted value
+ _sqla_types_convert = {
+ bytes: lambda t, v: binascii.hexlify(v),
+ sqlalchemy.types.Binary: lambda t, v: binascii.hexlify(v),
+ }
+
+ _python_instance_convert = {
+ datetime.datetime: lambda v: v.isoformat() if v else None,
+ datetime.date: lambda v: v.isoformat() if v else None,
+ Decimal: lambda v: float(v),
+ uuid.UUID: lambda v: v.hex,
+ }
+
+ @staticmethod
+ def _dictify_scalar(scalar, column, serializable=False):
+ """Converts scalar values into a serializable format.
+
+ Args:
+ scalar: The value to be converted.
+ column (sqlalchemy.Column): The SQLAlchemy column the ``scalar``
+ was stored under.
+ serializable (bool): Whether to convert ``scalar`` into a format
+ that can be serialized.
+
+ Returns:
+ The (optionally) serialized version of ``scalar``.
+ """
+
+ val = scalar
+
+ # if data must be serializable, apply conversions into base types
+ if serializable:
+ # first check for conversions of the underlying column type
+ col_type = None
+ try:
+ col_type = getattr(column, "type")
+ except Exception:
+ # "col" might be a list in the case of a one to many join, skip.
+ # we'll see it again when the outer loop opens the container
+ pass
+ if col_type:
+ col_type_type = type(col_type)
+ if col_type_type in OrmBaseMixin._sqla_types_convert:
+ val = OrmBaseMixin._sqla_types_convert[col_type_type](
+ col_type,
+ scalar
+ )
+
+ # Convert (some) complex python types into base types
+ instance_converters = OrmBaseMixin._python_instance_convert.items()
+ for instance, converter in instance_converters:
+ if isinstance(scalar, instance):
+ val = converter(scalar)
+ break
+
+ return val
+
+ def _collect_attributes(self):
+ """Handles removal of any meta/internal data that is not from our
+ underlying table.
+
+ Returns:
+ dict: A dictionary keyed on the field name with the value being a
+ tuple of the column type and value.
+ """
+
+ attributes = {}
+
+ obj_type = type(self)
+ column_inspection = sqlalchemy.inspect(obj_type).c
+ relationship_inspection = sqlalchemy.inspect(obj_type).relationships
+
+ for member_name, member_value in self.__dict__.items():
+ # drop magic sqla keys.
+ if member_name.startswith("_"):
+ continue
+
+ if (
+ inspect.isfunction(member_value) or
+ inspect.ismethod(member_value)
+ ):
+ continue
+
+ if member_name in column_inspection:
+ member_inspection = column_inspection[member_name]
+ elif member_name in relationship_inspection:
+ member_inspection = relationship_inspection[member_name]
+ else:
+ continue
+
+ attributes[member_name] = (member_inspection, member_value)
+
+ return attributes
+
+ def to_dict(self, deep=False, serializable=False):
+ """Returns a ``dict`` representation of the ORM'ed DB record.
+
+ Args:
+ deep (bool): Whether the perform a recursive conversion of joined
+ ORM objects and include them into the ``dict``.
+ serializable (bool): Whether to convert leaf-nodes into a format
+ that can be serialized.
+
+ Returns:
+ dict: A ``dict`` representation of the ORM'ed DB record.
+ """
+
+ results = {}
+
+ # walk top level
+ attributes = self._collect_attributes()
+ for attr_name, (attr_column, attr_value) in attributes.items():
+
+ # if value is compound type and deep=True
+ # recursively collect contents.
+ if isinstance(attr_value, OrmBaseMixin):
+ if not deep:
+ continue
+ val = attr_value.to_dict(
+ deep=deep,
+ serializable=serializable
+ )
+
+ elif isinstance(attr_value, list):
+ if not deep:
+ continue
+
+ val = []
+ for sub_attr_value in attr_value:
+ val.append(sub_attr_value.to_dict(
+ deep=deep,
+ serializable=serializable
+ ))
+
+ elif isinstance(attr_value, dict):
+ if not deep:
+ continue
+
+ val = {}
+ for sub_attr_name, sub_attr_value in attr_value.items():
+ val[sub_attr_name] = sub_attr_value.to_dict(
+ deep=deep,
+ serialisable=serializable
+ )
+
+ # value if scalar, perform any final conversions
+ else:
+ val = self._dictify_scalar(
+ scalar=attr_value,
+ column=attr_column,
+ serializable=serializable
+ )
+
+ results[attr_name] = val
+
+ return results
+
+ def to_string(self, deep=False):
+ """Returns a unicode string representation of the ORM'ed DB record.
+
+ Args:
+ deep (bool): Whether the perform a recursive conversion of joined
+ ORM objects and include them into the string.
+
+ Returns:
+ str: A unicode string representation of the ORM'ed DB record.
+ """
+
+ attributes = self._collect_attributes()
+
+ msg = "<{0}("
+ for attr_idx, attr_name in enumerate(attributes.keys()):
+ msg += attr_name + "='{" + str(attr_idx + 1) + "}'"
+ if attr_idx < len(attributes) - 1:
+ msg += ", "
+ msg += ")>"
+
+ values = [type(self).__name__]
+
+ for attr_name, (attr_column, attr_value) in attributes.items():
+
+ if isinstance(attr_value, OrmBaseMixin):
+ if not deep:
+ val = "<{0}()>".format(type(attr_value).__name__)
+ else:
+ val = attr_value.to_string(deep=deep)
+ else:
+ val = self._dictify_scalar(
+ scalar=attr_value,
+ column=attr_column,
+ serializable=True
+ )
+
+ values.append(val)
+
+ return msg.format(*values)
+
+ def __repr__(self):
+ """Returns a unicode string representation of the object
+
+ Returns:
+ unicode: A unicode string representation of the object.
+ """
+ return self.to_string(deep=False)
diff --git a/examples/falcon_sqlalchemy/demo/resources.py b/examples/falcon_sqlalchemy/demo/resources.py
new file mode 100644
index 00000000..13bc61f1
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/resources.py
@@ -0,0 +1,352 @@
+# coding=utf-8
+
+import os
+from os import devnull
+import logging
+import functools
+from collections import OrderedDict
+from contextlib import redirect_stdout
+import json
+
+import graphene
+import falcon
+
+
+def set_graphql_allow_header(
+ req: falcon.Request,
+ resp: falcon.Response,
+ resource: object,
+):
+ """Sets the 'Allow' header on responses to GraphQL requests.
+
+ Args:
+ req (falcon.Request): The incoming request.
+ resp (falcon.Response): The outgoing response.
+ resource (object): The falcon resource-class associated with the
+ incoming request.
+ """
+
+ # Set the `Allow` header to permit given commands.
+ resp.set_header('Allow', 'GET, POST, OPTIONS')
+
+
+@falcon.after(set_graphql_allow_header)
+class ResourceGraphQl:
+ """Main GraphQL server. Integrates with the predefined Graphene schema."""
+
+ def __init__(
+ self,
+ schema: graphene.Schema,
+ ):
+
+ # Internalize arguments.
+ self.schema = schema
+
+ self._respond_invalid_method = functools.partial(
+ self._respond_error,
+ status=falcon.HTTP_405,
+ message="GraphQL only supports GET and POST requests.",
+ )
+
+ self._respond_no_query = functools.partial(
+ self._respond_error,
+ status=falcon.HTTP_400,
+ message="Must provide query string.",
+ )
+
+ self._respond_invalid_variables = functools.partial(
+ self._respond_error,
+ status=falcon.HTTP_400,
+ message="Variables are invalid JSON.",
+ )
+
+ self._respond_invalid_body = functools.partial(
+ self._respond_error,
+ status=falcon.HTTP_400,
+ message="POST body sent invalid JSON.",
+ )
+
+ def _execute_query(
+ self,
+ query,
+ variable_values,
+ operation_name=None,
+ ):
+
+ result = self.schema.execute(
+ query,
+ variable_values=variable_values,
+ operation_name=operation_name
+ )
+
+ return result
+
+ @staticmethod
+ def _respond_error(
+ resp: falcon.Response,
+ status: str,
+ message: str,
+ ):
+
+ resp.status = status
+ resp.body = json.dumps(
+ {"errors": [{"message": message}]},
+ separators=(',', ':')
+ )
+
+ def on_options(self, req, resp):
+ """Handles OPTIONS requests."""
+
+ resp.status = falcon.HTTP_204
+ pass
+
+ def on_head(self, req, resp):
+ """Handles HEAD requests. No content."""
+
+ pass
+
+ def on_get(self, req, resp):
+ """Handles GraphQL GET requests."""
+
+ if req.params and 'query' in req.params and req.params['query']:
+ query = str(req.params['query'])
+ else:
+ # this means that there aren't any query params in the url
+ return self._respond_no_query(resp=resp)
+
+ if 'variables' in req.params and req.params['variables']:
+ try:
+ variables = json.loads(str(req.params['variables']),
+ object_pairs_hook=OrderedDict)
+ except json.decoder.JSONDecodeError:
+ return self._respond_invalid_variables(resp=resp)
+ else:
+ variables = ""
+
+ if 'operationName' in req.params and req.params['operationName']:
+ operation_name = str(req.params['operationName'])
+ else:
+ operation_name = None
+
+ # redirect stdout of schema.execute to /dev/null
+ with open(devnull, 'w') as f:
+ with redirect_stdout(f):
+ # run the query
+ result = self._execute_query(
+ query=query,
+ variable_values=variables,
+ operation_name=operation_name
+ )
+
+ # construct the response and return the result
+ if result.data:
+ data_ret = {'data': result.data}
+ resp.status = falcon.HTTP_200
+ resp.body = json.dumps(data_ret, separators=(',', ':'))
+ return
+ elif result.errors:
+ # NOTE: these errors don't include the optional 'locations' key
+ err_msgs = [{'message': str(i)} for i in result.errors]
+ resp.status = falcon.HTTP_400
+ resp.body = json.dumps({'errors': err_msgs}, separators=(',', ':'))
+ return
+ else:
+ # responses should always have either data or errors
+ raise RuntimeError
+
+ def on_post(self, req, resp):
+ """Handles GraphQL POST requests."""
+
+ # parse url parameters in the request first
+ if req.params and 'query' in req.params and req.params['query']:
+ query = str(req.params['query'])
+ else:
+ query = None
+
+ if 'variables' in req.params and req.params['variables']:
+ try:
+ variables = json.loads(str(req.params['variables']),
+ object_pairs_hook=OrderedDict)
+ except json.decoder.JSONDecodeError:
+ return self._respond_invalid_variables(resp=resp)
+ else:
+ variables = None
+
+ if 'operationName' in req.params and req.params['operationName']:
+ operation_name = str(req.params['operationName'])
+ else:
+ operation_name = None
+
+ # Next, handle 'content-type: application/json' requests
+ if req.content_type and 'application/json' in req.content_type:
+ # error for requests with no content
+ if req.content_length in (None, 0):
+ return self._respond_invalid_body(resp=resp)
+
+ # read and decode request body
+ raw_json = req.stream.read()
+ try:
+ req.context['post_data'] = json.loads(
+ raw_json.decode('utf-8'),
+ object_pairs_hook=OrderedDict
+ )
+ except json.decoder.JSONDecodeError:
+ return self._respond_invalid_body(resp=resp)
+
+ # build the query string (Graph Query Language string)
+ if (
+ query is None and req.context['post_data'] and
+ 'query' in req.context['post_data']
+ ):
+ query = str(req.context['post_data']['query'])
+ elif query is None:
+ return self._respond_no_query(resp=resp)
+
+ # build the variables string (JSON string of key/value pairs)
+ if (
+ variables is None and
+ req.context['post_data'] and
+ 'variables' in req.context['post_data'] and
+ req.context['post_data']['variables']
+ ):
+ try:
+ variables = req.context['post_data']['variables']
+ if not isinstance(variables, OrderedDict):
+ json_str = str(req.context['post_data']['variables'])
+ variables = json.loads(
+ json_str,
+ object_pairs_hook=OrderedDict
+ )
+ except json.decoder.JSONDecodeError:
+ logging.exception(variables)
+ return self._respond_invalid_variables(resp=resp)
+
+ elif variables is None:
+ variables = ""
+
+ # build the operationName string (matches a query or mutation name)
+ if (
+ operation_name is None and
+ 'operationName' in req.context['post_data'] and
+ req.context['post_data']['operationName']
+ ):
+ operation_name = str(req.context['post_data']['operationName'])
+
+ # Alternately, handle 'content-type: application/graphql' requests
+ elif req.content_type and 'application/graphql' in req.content_type:
+ # read and decode request body
+ req.context['post_data'] = req.stream.read().decode('utf-8')
+
+ # build the query string
+ if query is None and req.context['post_data']:
+ query = str(req.context['post_data'])
+
+ elif query is None:
+ return self._respond_no_query(resp=resp)
+
+ # Skip application/x-www-form-urlencoded since they are automatically
+ # included by setting req_options.auto_parse_form_urlencoded = True
+
+ elif query is None:
+ # this means that the content-type is wrong and there aren't any
+ # query params in the url
+ return self._respond_no_query(resp=resp)
+
+ # redirect stdout of schema.execute to /dev/null
+ with open(devnull, 'w') as f:
+ with redirect_stdout(f):
+ # run the query
+ result = self._execute_query(
+ query=query,
+ variable_values=variables,
+ operation_name=operation_name
+ )
+
+ # construct the response and return the result
+ if result.data:
+ data_ret = {'data': result.data}
+ resp.status = falcon.HTTP_200
+ resp.body = json.dumps(data_ret, separators=(',', ':'))
+ return
+ elif result.errors:
+ # NOTE: these errors don't include the optional 'locations' key
+ err_msgs = [{'message': str(i)} for i in result.errors]
+ resp.status = falcon.HTTP_400
+ resp.body = json.dumps({'errors': err_msgs}, separators=(',', ':'))
+ return
+ else:
+ # responses should always have either data or errors
+ raise RuntimeError
+
+ def on_put(self, req, resp):
+ """Handles PUT requests."""
+
+ self._respond_invalid_method(resp=resp)
+
+ def on_patch(self, req, resp):
+ """Handles PATCH requests."""
+
+ self._respond_invalid_method(resp=resp)
+
+ def on_delete(self, req, resp):
+ """Handles DELETE requests."""
+
+ self._respond_invalid_method(resp=resp)
+
+
+@falcon.after(set_graphql_allow_header)
+class ResourceGraphQlSqlAlchemy(ResourceGraphQl):
+ """Main GraphQL server. Integrates with the predefined Graphene schema."""
+
+ def __init__(
+ self,
+ schema,
+ scoped_session,
+ ):
+ # Internalize arguments.
+ self.scoped_session = scoped_session
+
+ super(ResourceGraphQlSqlAlchemy, self).__init__(schema=schema)
+
+ def _execute_query(
+ self,
+ query,
+ variable_values,
+ operation_name=None,
+ ):
+ msg_fmt = "Executing query: {} with variables".format(query)
+ logging.debug(msg_fmt)
+
+ result = self.schema.execute(
+ query,
+ variable_values=variable_values,
+ operation_name=operation_name,
+ context_value={"session": self.scoped_session}
+ )
+
+ return result
+
+
+class ResourceGraphiQL(object):
+ """Serves GraphiQL dashboard. Meant to be used during development only."""
+
+ def __init__(
+ self,
+ path_graphiql,
+ ):
+
+ self.path_graphiql = path_graphiql
+
+ def on_get(self, req, resp, static_file=None):
+ """Handles GraphiQL GET requests."""
+
+ if static_file is None:
+ static_file = 'graphiql.html'
+ resp.content_type = 'text/html; charset=UTF-8'
+ elif static_file == 'graphiql.css':
+ resp.content_type = 'text/css; charset=UTF-8'
+ else:
+ resp.content_type = 'application/javascript; charset=UTF-8'
+
+ resp.status = falcon.HTTP_200
+ resp.stream = open(os.path.join(self.path_graphiql, static_file), 'rb')
diff --git a/examples/falcon_sqlalchemy/demo/schema.py b/examples/falcon_sqlalchemy/demo/schema.py
new file mode 100644
index 00000000..e8e604b7
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/schema.py
@@ -0,0 +1,107 @@
+# coding=utf-8
+
+from typing import List, Dict, Union
+
+import graphql
+import graphene
+
+from demo.orm import Author
+from demo.orm import Book
+from demo.schema_types import TypeAuthor
+from demo.schema_types import TypeBook
+from demo.schema_types import TypeAuthorBook
+from demo.schema_types import TypeStats
+from demo.schema_types import TypeCountBooksCoverArtist
+from demo.schema_mutations import MutationAuthorCreate
+from demo.utils import apply_requested_fields
+
+
+class Query(graphene.ObjectType):
+
+ author = graphene.Field(
+ TypeAuthor,
+ author_id=graphene.Argument(type=graphene.Int, required=False),
+ name_first=graphene.Argument(type=graphene.String, required=False),
+ name_last=graphene.Argument(type=graphene.String, required=False),
+ )
+
+ books = graphene.List(
+ of_type=TypeBook,
+ title=graphene.Argument(type=graphene.String, required=False),
+ year=graphene.Argument(type=graphene.Int, required=False),
+ )
+
+ stats = graphene.Field(type=TypeStats)
+
+ @staticmethod
+ def resolve_stats(
+ args: Dict,
+ info: graphql.execution.base.ResolveInfo,
+ ):
+ return TypeStats
+
+ @staticmethod
+ def resolve_author(
+ args: Dict,
+ info: graphql.execution.base.ResolveInfo,
+ author_id: Union[int, None] = None,
+ name_first: Union[str, None] = None,
+ name_last: Union[str, None] = None,
+ ):
+
+ query = TypeAuthor.get_query(info=info)
+
+ if author_id:
+ query = query.filter(Author.author_id == author_id)
+
+ if name_first:
+ query = query.filter(Author.name_first == name_first)
+
+ if name_last:
+ query = query.filter(Author.name_last == name_last)
+
+ # Limit query to the requested fields only.
+ query = apply_requested_fields(info=info, query=query, orm_class=Author)
+
+ author = query.first()
+
+ return author
+
+ @staticmethod
+ def resolve_books(
+ args: Dict,
+ info: graphql.execution.base.ResolveInfo,
+ title: Union[str, None] = None,
+ year: Union[int, None] = None,
+ ):
+ query = TypeBook.get_query(info=info)
+
+ if title:
+ query = query.filter(Book.title == title)
+
+ if year:
+ query = query.filter(Book.year == year)
+
+ # Limit query to the requested fields only.
+ query = apply_requested_fields(info=info, query=query, orm_class=Book)
+
+ books = query.all()
+
+ return books
+
+
+class Mutation(graphene.ObjectType):
+ create_author = MutationAuthorCreate.Field()
+
+
+schema = graphene.Schema(
+ query=Query,
+ mutation=Mutation,
+ types=[
+ TypeAuthor,
+ TypeBook,
+ TypeAuthorBook,
+ TypeStats,
+ TypeCountBooksCoverArtist
+ ]
+)
diff --git a/examples/falcon_sqlalchemy/demo/schema_mutations.py b/examples/falcon_sqlalchemy/demo/schema_mutations.py
new file mode 100644
index 00000000..1210af59
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/schema_mutations.py
@@ -0,0 +1,43 @@
+# coding=utf-8
+
+from typing import Dict
+
+import graphql
+import graphene
+import sqlalchemy.orm
+
+from demo.orm import Author
+from demo.schema_types import TypeAuthor
+
+
+class InputAuthor(graphene.InputObjectType):
+ name_first = graphene.String(required=True)
+ name_last = graphene.String(required=True)
+
+
+class MutationAuthorCreate(graphene.Mutation):
+
+ class Arguments:
+ author = InputAuthor(required=True)
+
+ author = graphene.Field(TypeAuthor)
+
+ @staticmethod
+ def mutate(
+ args: Dict,
+ info: graphql.execution.base.ResolveInfo,
+ author=None
+ ):
+
+ # Retrieve the session out of the context as the `get_query` method
+ # automatically selects the model.
+ session = info.context.get("session") # type: sqlalchemy.orm.Session
+
+ obj = Author()
+ obj.name_first = author.name_first
+ obj.name_last = author.name_last
+
+ session.add(obj)
+ session.commit()
+
+ return MutationAuthorCreate(author=obj)
diff --git a/examples/falcon_sqlalchemy/demo/schema_types.py b/examples/falcon_sqlalchemy/demo/schema_types.py
new file mode 100644
index 00000000..d7a7ca44
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/schema_types.py
@@ -0,0 +1,68 @@
+# coding=utf-8
+
+from typing import List, Dict
+
+import graphql
+import graphene
+from graphene_sqlalchemy import SQLAlchemyObjectType
+import sqlalchemy.orm
+from sqlalchemy import func as sqlalchemy_func
+
+from demo.orm import Author
+from demo.orm import Book
+from demo.orm import AuthorBook
+
+
+class TypeAuthor(SQLAlchemyObjectType):
+ class Meta:
+ model = Author
+
+
+class TypeBook(SQLAlchemyObjectType):
+ class Meta:
+ model = Book
+
+
+class TypeAuthorBook(SQLAlchemyObjectType):
+ class Meta:
+ model = AuthorBook
+
+
+class TypeCountBooksCoverArtist(graphene.ObjectType):
+ cover_artist = graphene.String()
+ count_books = graphene.Int()
+
+
+class TypeStats(graphene.ObjectType):
+
+ count_books_by_cover_artist = graphene.List(
+ of_type=TypeCountBooksCoverArtist
+ )
+
+ @staticmethod
+ def resolve_count_books_by_cover_artist(
+ args: Dict,
+ info: graphql.execution.base.ResolveInfo,
+ ) -> List[TypeCountBooksCoverArtist]:
+ # Retrieve the session out of the context as the `get_query` method
+ # automatically selects the model.
+ session = info.context.get("session") # type: sqlalchemy.orm.Session
+
+ # Define the `COUNT(books.book_id)` function.
+ func_count_books = sqlalchemy_func.count(Book.book_id)
+
+ # Query out the count of books by cover-artist
+ query = session.query(Book.cover_artist, func_count_books)
+ query = query.group_by(Book.cover_artist)
+ results = query.all()
+
+ # Wrap the results of the aggregation in `TypeCountBooksCoverArtist`
+ # objects.
+ objs = [
+ TypeCountBooksCoverArtist(
+ cover_artist=result[0],
+ count_books=result[1]
+ ) for result in results
+ ]
+
+ return objs
diff --git a/examples/falcon_sqlalchemy/demo/utils.py b/examples/falcon_sqlalchemy/demo/utils.py
new file mode 100644
index 00000000..ebe4029a
--- /dev/null
+++ b/examples/falcon_sqlalchemy/demo/utils.py
@@ -0,0 +1,168 @@
+# coding=utf-8
+
+from typing import List, Dict, Union, Type
+
+import graphql
+from graphql.language.ast import FragmentSpread
+from graphql.language.ast import Field
+from graphene.utils.str_converters import to_snake_case
+import sqlalchemy.orm
+
+from demo.orm_base import OrmBaseMixin
+
+
+def extract_requested_fields(
+ info: graphql.execution.base.ResolveInfo,
+ fields: List[Union[Field, FragmentSpread]],
+ do_convert_to_snake_case: bool = True,
+) -> Dict:
+ """Extracts the fields requested in a GraphQL query by processing the AST
+ and returns a nested dictionary representing the requested fields.
+
+ Note:
+ This function should support arbitrarily nested field structures
+ including fragments.
+
+ Example:
+ Consider the following query passed to a resolver and running this
+ function with the `ResolveInfo` object passed to the resolver.
+
+ >>> query = "query getAuthor{author(authorId: 1){nameFirst, nameLast}}"
+ >>> extract_requested_fields(info, info.field_asts, True)
+ {'author': {'name_first': None, 'name_last': None}}
+
+ Args:
+ info (graphql.execution.base.ResolveInfo): The GraphQL query info passed
+ to the resolver function.
+ fields (List[Union[Field, FragmentSpread]]): The list of `Field` or
+ `FragmentSpread` objects parsed out of the GraphQL query and stored
+ in the AST.
+ do_convert_to_snake_case (bool): Whether to convert the fields as they
+ appear in the GraphQL query (typically in camel-case) back to
+ snake-case (which is how they typically appear in ORM classes).
+
+ Returns:
+ Dict: The nested dictionary containing all the requested fields.
+ """
+
+ result = {}
+ for field in fields:
+
+ # Set the `key` as the field name.
+ key = field.name.value
+
+ # Convert the key from camel-case to snake-case (if required).
+ if do_convert_to_snake_case:
+ key = to_snake_case(name=key)
+
+ # Initialize `val` to `None`. Fields without nested-fields under them
+ # will have a dictionary value of `None`.
+ val = None
+
+ # If the field is of type `Field` then extract the nested fields under
+ # the `selection_set` (if defined). These nested fields will be
+ # extracted recursively and placed in a dictionary under the field
+ # name in the `result` dictionary.
+ if isinstance(field, Field):
+ if (
+ hasattr(field, "selection_set") and
+ field.selection_set is not None
+ ):
+ # Extract field names out of the field selections.
+ val = extract_requested_fields(
+ info=info,
+ fields=field.selection_set.selections,
+ )
+ result[key] = val
+ # If the field is of type `FragmentSpread` then retrieve the fragment
+ # from `info.fragments` and recursively extract the nested fields but
+ # as we don't want the name of the fragment appearing in the result
+ # dictionary (since it does not match anything in the ORM classes) the
+ # result will simply be result of the extraction.
+ elif isinstance(field, FragmentSpread):
+ # Retrieve referened fragment.
+ fragment = info.fragments[field.name.value]
+ # Extract field names out of the fragment selections.
+ val = extract_requested_fields(
+ info=info,
+ fields=fragment.selection_set.selections,
+ )
+ result = val
+
+ return result
+
+
+def apply_requested_fields(
+ info: graphql.execution.base.ResolveInfo,
+ query: sqlalchemy.orm.Query,
+ orm_class: Type[OrmBaseMixin]
+) -> sqlalchemy.orm.Query:
+ """Updates the SQLAlchemy Query object by limiting the loaded fields of the
+ table and its relationship to the ones explicitly requested in the GraphQL
+ query.
+
+ Note:
+ This function is fairly simplistic in that it assumes that (1) the
+ SQLAlchemy query only selects a single ORM class/table and that (2)
+ relationship fields are only one level deep, i.e., that requestd fields
+ are either table fields or fields of the table relationship, e.g., it
+ does not support fields of relationship relationships.
+
+ Args:
+ info (graphql.execution.base.ResolveInfo): The GraphQL query info passed
+ to the resolver function.
+ query (sqlalchemy.orm.Query): The SQLAlchemy Query object to be updated.
+ orm_class (Type[OrmBaseMixin]): The ORM class of the selected table.
+
+ Returns:
+ sqlalchemy.orm.Query: The updated SQLAlchemy Query object.
+ """
+
+ # Extract the fields requested in the GraphQL query.
+ fields = extract_requested_fields(
+ info=info,
+ fields=info.field_asts,
+ do_convert_to_snake_case=True,
+ )
+
+ # We assume that the top level of the `fields` dictionary only contains a
+ # single key referring to the GraphQL resource being resolved.
+ tl_key = list(fields.keys())[0]
+ # We assume that any keys that have a value of `None` (as opposed to
+ # dictionaries) are fields of the primary table.
+ table_fields = [
+ key for key, val in fields[tl_key].items()
+ if val is None
+ ]
+
+ # We assume that any keys that have a value being a dictionary are
+ # relationship attributes on the primary table with the keys in the
+ # dictionary being fields on that relationship. Thus we create a list of
+ # `[relatioship_name, relationship_fields]` lists to be used in the
+ # `joinedload` definitions.
+ relationship_fieldsets = [
+ [key, val.keys()]
+ for key, val in fields[tl_key].items()
+ if isinstance(val, dict)
+ ]
+
+ # Assemble a list of `joinedload` definitions on the defined relationship
+ # attribute name and the requested fields on that relationship.
+ options_joinedloads = []
+ for relationship_fieldset in relationship_fieldsets:
+ relationship = relationship_fieldset[0]
+ rel_fields = relationship_fieldset[1]
+ options_joinedloads.append(
+ sqlalchemy.orm.joinedload(
+ getattr(orm_class, relationship)
+ ).load_only(*rel_fields)
+ )
+
+ # Update the SQLAlchemy query by limiting the loaded fields on the primary
+ # table as well as by including the `joinedload` definitions.
+ query = query.options(
+ sqlalchemy.orm.load_only(*table_fields),
+ *options_joinedloads
+ )
+
+ return query
diff --git a/examples/falcon_sqlalchemy/graphiql/graphiql.css b/examples/falcon_sqlalchemy/graphiql/graphiql.css
new file mode 100644
index 00000000..d1fc1428
--- /dev/null
+++ b/examples/falcon_sqlalchemy/graphiql/graphiql.css
@@ -0,0 +1,1263 @@
+.graphiql-container {
+ color: #141823;
+ display: flex;
+ display: -webkit-flex;
+ flex-direction: row;
+ -webkit-flex-direction: row;
+ font-family:
+ system,
+ -apple-system,
+ 'San Francisco',
+ '.SFNSDisplay-Regular',
+ 'Segoe UI',
+ Segoe,
+ 'Segoe WP',
+ 'Helvetica Neue',
+ helvetica,
+ 'Lucida Grande',
+ arial,
+ sans-serif;
+ font-size: 14px;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ width: 100%;
+}
+
+.graphiql-container .editorWrap {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: column;
+ flex-direction: column;
+ -webkit-flex: 1;
+ flex: 1;
+}
+
+.graphiql-container .title {
+ font-size: 18px;
+}
+
+.graphiql-container .title em {
+ font-family: georgia;
+ font-size: 19px;
+}
+
+.graphiql-container .topBarWrap {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+}
+
+.graphiql-container .topBar {
+ -webkit-align-items: center;
+ align-items: center;
+ background: -webkit-linear-gradient(#f7f7f7, #e2e2e2);
+ background: linear-gradient(#f7f7f7, #e2e2e2);
+ border-bottom: 1px solid #d0d0d0;
+ cursor: default;
+ display: -webkit-flex;
+ display: flex;
+ height: 34px;
+ padding: 7px 14px 6px;
+ -webkit-flex: 1;
+ flex: 1;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.graphiql-container .toolbar {
+ overflow-x: auto;
+}
+
+.graphiql-container .docExplorerShow {
+ background: -webkit-linear-gradient(#f7f7f7, #e2e2e2);
+ background: linear-gradient(#f7f7f7, #e2e2e2);
+ border-bottom: 1px solid #d0d0d0;
+ border-left: 1px solid rgba(0, 0, 0, 0.2);
+ border-right: none;
+ border-top: none;
+ color: #3B5998;
+ cursor: pointer;
+ font-size: 14px;
+ outline: 0;
+ margin: 0;
+ padding: 2px 20px 0 18px;
+}
+
+.graphiql-container .docExplorerShow:before {
+ border-left: 2px solid #3B5998;
+ border-top: 2px solid #3B5998;
+ content: '';
+ display: inline-block;
+ height: 9px;
+ margin: 0 3px -1px 0;
+ position: relative;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ width: 9px;
+}
+
+.graphiql-container .editorBar {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex: 1;
+ flex: 1;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+}
+
+.graphiql-container .queryWrap {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex: 1;
+ flex: 1;
+ -webkit-flex-direction: column;
+ flex-direction: column;
+}
+
+.graphiql-container .resultWrap {
+ border-left: solid 1px #e0e0e0;
+ display: -webkit-flex;
+ display: flex;
+ position: relative;
+ -webkit-flex: 1;
+ flex: 1;
+ -webkit-flex-direction: column;
+ flex-direction: column;
+}
+
+.graphiql-container .docExplorerWrap {
+ background: white;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
+ position: relative;
+ z-index: 3;
+}
+
+.graphiql-container .docExplorerResizer {
+ cursor: col-resize;
+ height: 100%;
+ left: -5px;
+ position: absolute;
+ top: 0;
+ width: 10px;
+ z-index: 10;
+}
+
+.graphiql-container .docExplorerHide {
+ cursor: pointer;
+ font-size: 18px;
+ margin: -7px -8px -6px 0;
+ padding: 18px 16px 15px 12px;
+}
+
+.graphiql-container .query-editor {
+ -webkit-flex: 1;
+ flex: 1;
+ position: relative;
+}
+
+.graphiql-container .variable-editor {
+ display: -webkit-flex;
+ display: flex;
+ height: 29px;
+ -webkit-flex-direction: column;
+ flex-direction: column;
+ position: relative;
+}
+
+.graphiql-container .variable-editor-title {
+ background: #eeeeee;
+ border-bottom: 1px solid #d6d6d6;
+ border-top: 1px solid #e0e0e0;
+ color: #777;
+ font-variant: small-caps;
+ font-weight: bold;
+ letter-spacing: 1px;
+ line-height: 14px;
+ padding: 6px 0 8px 43px;
+ text-transform: lowercase;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.graphiql-container .codemirrorWrap {
+ -webkit-flex: 1;
+ flex: 1;
+ position: relative;
+}
+
+.graphiql-container .result-window {
+ -webkit-flex: 1;
+ flex: 1;
+ position: relative;
+}
+
+.graphiql-container .footer {
+ background: #f6f7f8;
+ border-left: 1px solid #e0e0e0;
+ border-top: 1px solid #e0e0e0;
+ margin-left: 12px;
+ position: relative;
+}
+
+.graphiql-container .footer:before {
+ background: #eeeeee;
+ bottom: 0;
+ content: " ";
+ left: -13px;
+ position: absolute;
+ top: -1px;
+ width: 12px;
+}
+
+.graphiql-container .result-window .CodeMirror {
+ background: #f6f7f8;
+}
+
+.graphiql-container .result-window .CodeMirror-gutters {
+ background-color: #eeeeee;
+ border-color: #e0e0e0;
+ cursor: col-resize;
+}
+
+.graphiql-container .result-window .CodeMirror-foldgutter,
+.graphiql-container .result-window .CodeMirror-foldgutter-open:after,
+.graphiql-container .result-window .CodeMirror-foldgutter-folded:after {
+ padding-left: 3px;
+}
+
+.graphiql-container .toolbar-button {
+ background: #fdfdfd;
+ background: -webkit-linear-gradient(#fbfbfb, #f8f8f8);
+ background: linear-gradient(#fbfbfb, #f8f8f8);
+ border-width: 0.5px;
+ border-style: solid;
+ border-color: #d3d3d3 #d0d0d0 #bababa;
+ border-radius: 4px;
+ box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.13), inset 0 1px #fff;
+ color: #444;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0 5px 0;
+ padding: 2px 8px 4px;
+ text-decoration: none;
+}
+
+.graphiql-container .toolbar-button:active {
+ background: -webkit-linear-gradient(#ececec, #d8d8d8);
+ background: linear-gradient(#ececec, #d8d8d8);
+ border-color: #cacaca #c9c9c9 #b0b0b0;
+ box-shadow:
+ 0 1px 0 #fff,
+ inset 0 1px rgba(255, 255, 255, 0.2),
+ inset 0 1px 1px rgba(0, 0, 0, 0.08);
+}
+
+.graphiql-container .toolbar-button.error {
+ background: -webkit-linear-gradient(#fdf3f3, #e6d6d7);
+ background: linear-gradient(#fdf3f3, #e6d6d7);
+ color: #b00;
+}
+
+.graphiql-container .execute-button-wrap {
+ position: relative;
+ margin: 0 14px 0 28px;
+ height: 34px;
+}
+
+.graphiql-container .execute-button {
+ background: -webkit-linear-gradient(#fdfdfd, #d2d3d6);
+ background: linear-gradient(#fdfdfd, #d2d3d6);
+ border: 1px solid rgba(0,0,0,0.25);
+ border-radius: 17px;
+ box-shadow: 0 1px 0 #fff;
+ cursor: pointer;
+ fill: #444;
+ height: 34px;
+ margin: 0;
+ padding: 0;
+ width: 34px;
+}
+
+.graphiql-container .execute-button svg {
+ pointer-events: none;
+}
+
+.graphiql-container .execute-button:active {
+ background: -webkit-linear-gradient(#e6e6e6, #c0c0c0);
+ background: linear-gradient(#e6e6e6, #c0c0c0);
+ box-shadow:
+ 0 1px 0 #fff,
+ inset 0 0 2px rgba(0, 0, 0, 0.3),
+ inset 0 0 6px rgba(0, 0, 0, 0.2);
+}
+
+.graphiql-container .execute-button:focus {
+ outline: 0;
+}
+
+.graphiql-container .execute-options {
+ background: #fff;
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.1),
+ 0 2px 4px rgba(0,0,0,0.25);
+ left: -1px;
+ margin: 0;
+ padding: 8px 0;
+ position: absolute;
+ top: 37px;
+ z-index: 100;
+}
+
+.graphiql-container .execute-options li {
+ padding: 2px 30px 4px 10px;
+ list-style: none;
+ min-width: 100px;
+ cursor: pointer;
+}
+
+.graphiql-container .execute-options li.selected {
+ background: #e10098;
+ color: white;
+}
+
+.graphiql-container .CodeMirror-scroll {
+ -webkit-overflow-scrolling: touch;
+}
+
+.graphiql-container .CodeMirror {
+ color: #141823;
+ font-family:
+ 'Consolas',
+ 'Inconsolata',
+ 'Droid Sans Mono',
+ 'Monaco',
+ monospace;
+ font-size: 13px;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.graphiql-container .CodeMirror-lines {
+ padding: 20px 0;
+}
+
+.CodeMirror-hint-information .content {
+ -webkit-box-orient: vertical;
+ box-orient: vertical;
+ color: #141823;
+ display: -webkit-box;
+ font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Segoe UI', Segoe, 'Segoe WP', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif;
+ font-size: 13px;
+ -webkit-line-clamp: 3;
+ line-clamp: 3;
+ line-height: 16px;
+ max-height: 48px;
+ overflow: hidden;
+ text-overflow: -o-ellipsis-lastline;
+}
+
+.CodeMirror-hint-information .content p:first-child {
+ margin-top: 0;
+}
+
+.CodeMirror-hint-information .content p:last-child {
+ margin-bottom: 0;
+}
+
+.CodeMirror-hint-information .infoType {
+ color: #30a;
+ cursor: pointer;
+ display: inline;
+ margin-right: 0.5em;
+}
+
+.autoInsertedLeaf.cm-property {
+ -webkit-animation-duration: 6s;
+ -moz-animation-duration: 6s;
+ animation-duration: 6s;
+ -webkit-animation-name: insertionFade;
+ -moz-animation-name: insertionFade;
+ animation-name: insertionFade;
+ border-bottom: 2px solid rgba(255, 255, 255, 0);
+ border-radius: 2px;
+ margin: -2px -4px -1px;
+ padding: 2px 4px 1px;
+}
+
+@-moz-keyframes insertionFade {
+ from, to {
+ background: rgba(255, 255, 255, 0);
+ border-color: rgba(255, 255, 255, 0);
+ }
+
+ 15%, 85% {
+ background: #fbffc9;
+ border-color: #f0f3c0;
+ }
+}
+
+@-webkit-keyframes insertionFade {
+ from, to {
+ background: rgba(255, 255, 255, 0);
+ border-color: rgba(255, 255, 255, 0);
+ }
+
+ 15%, 85% {
+ background: #fbffc9;
+ border-color: #f0f3c0;
+ }
+}
+
+@keyframes insertionFade {
+ from, to {
+ background: rgba(255, 255, 255, 0);
+ border-color: rgba(255, 255, 255, 0);
+ }
+
+ 15%, 85% {
+ background: #fbffc9;
+ border-color: #f0f3c0;
+ }
+}
+
+div.CodeMirror-lint-tooltip {
+ background-color: white;
+ border: 0;
+ border-radius: 2px;
+ color: #141823;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ font-family:
+ system,
+ -apple-system,
+ 'San Francisco',
+ '.SFNSDisplay-Regular',
+ 'Segoe UI',
+ Segoe,
+ 'Segoe WP',
+ 'Helvetica Neue',
+ helvetica,
+ 'Lucida Grande',
+ arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 16px;
+ opacity: 0;
+ padding: 6px 10px;
+ -webkit-transition: opacity 0.15s;
+ -moz-transition: opacity 0.15s;
+ -ms-transition: opacity 0.15s;
+ -o-transition: opacity 0.15s;
+ transition: opacity 0.15s;
+}
+
+div.CodeMirror-lint-message-error, div.CodeMirror-lint-message-warning {
+ padding-left: 23px;
+}
+
+/* COLORS */
+
+.graphiql-container .CodeMirror-foldmarker {
+ border-radius: 4px;
+ background: #08f;
+ background: -webkit-linear-gradient(#43A8FF, #0F83E8);
+ background: linear-gradient(#43A8FF, #0F83E8);
+ box-shadow:
+ 0 1px 1px rgba(0, 0, 0, 0.2),
+ inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+ color: white;
+ font-family: arial;
+ font-size: 12px;
+ line-height: 0;
+ margin: 0 3px;
+ padding: 0px 4px 1px;
+ text-shadow: 0 -1px rgba(0, 0, 0, 0.1);
+}
+
+.graphiql-container div.CodeMirror span.CodeMirror-matchingbracket {
+ color: #555;
+ text-decoration: underline;
+}
+
+.graphiql-container div.CodeMirror span.CodeMirror-nonmatchingbracket {
+ color: #f00;
+}
+
+/* Comment */
+.cm-comment {
+ color: #999;
+}
+
+/* Punctuation */
+.cm-punctuation {
+ color: #555;
+}
+
+/* Keyword */
+.cm-keyword {
+ color: #B11A04;
+}
+
+/* OperationName, FragmentName */
+.cm-def {
+ color: #D2054E;
+}
+
+/* FieldName */
+.cm-property {
+ color: #1F61A0;
+}
+
+/* FieldAlias */
+.cm-qualifier {
+ color: #1C92A9;
+}
+
+/* ArgumentName and ObjectFieldName */
+.cm-attribute {
+ color: #8B2BB9;
+}
+
+/* Number */
+.cm-number {
+ color: #2882F9;
+}
+
+/* String */
+.cm-string {
+ color: #D64292;
+}
+
+/* Boolean */
+.cm-builtin {
+ color: #D47509;
+}
+
+/* EnumValue */
+.cm-string-2 {
+ color: #0B7FC7;
+}
+
+/* Variable */
+.cm-variable {
+ color: #397D13;
+}
+
+/* Directive */
+.cm-meta {
+ color: #B33086;
+}
+
+/* Type */
+.cm-atom {
+ color: #CA9800;
+}
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+ border-left: 1px solid black;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+}
+.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+}
+@-moz-keyframes blink {
+ 0% { background: #7e7; }
+ 50% { background: none; }
+ 100% { background: #7e7; }
+}
+@-webkit-keyframes blink {
+ 0% { background: #7e7; }
+ 50% { background: none; }
+ 100% { background: #7e7; }
+}
+@keyframes blink {
+ 0% { background: #7e7; }
+ 50% { background: none; }
+ 100% { background: #7e7; }
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+div.CodeMirror-overwrite div.CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor { position: absolute; }
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
+.graphiql-container .doc-explorer {
+ background: white;
+}
+
+.graphiql-container .doc-explorer-title-bar {
+ cursor: default;
+ display: -webkit-flex;
+ display: flex;
+ height: 34px;
+ line-height: 14px;
+ padding: 8px 8px 5px;
+ position: relative;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.graphiql-container .doc-explorer-title {
+ padding: 10px 0 10px 10px;
+ font-weight: bold;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow-x: hidden;
+ -webkit-flex: 1;
+ flex: 1;
+}
+
+.graphiql-container .doc-explorer-back {
+ color: #3B5998;
+ cursor: pointer;
+ margin: -7px 0 -6px -8px;
+ overflow-x: hidden;
+ padding: 17px 12px 16px 16px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.graphiql-container .doc-explorer-back:before {
+ border-left: 2px solid #3B5998;
+ border-top: 2px solid #3B5998;
+ content: '';
+ display: inline-block;
+ height: 9px;
+ margin: 0 3px -1px 0;
+ position: relative;
+ width: 9px;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+}
+
+.graphiql-container .doc-explorer-rhs {
+ position: relative;
+}
+
+.graphiql-container .doc-explorer-contents {
+ background-color: #ffffff;
+ border-top: 1px solid #d6d6d6;
+ bottom: 0;
+ left: 0;
+ min-width: 300px;
+ overflow-y: auto;
+ padding: 20px 15px;
+ position: absolute;
+ right: 0;
+ top: 47px;
+}
+
+.graphiql-container .doc-type-description p:first-child ,
+.graphiql-container .doc-type-description blockquote:first-child {
+ margin-top: 0;
+}
+
+.graphiql-container .doc-explorer-contents a {
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.graphiql-container .doc-explorer-contents a:hover {
+ text-decoration: underline;
+}
+
+.graphiql-container .doc-value-description {
+ padding: 4px 0 8px 12px;
+}
+
+.graphiql-container .doc-category {
+ margin: 20px 0;
+}
+
+.graphiql-container .doc-category-title {
+ border-bottom: 1px solid #e0e0e0;
+ color: #777;
+ cursor: default;
+ font-size: 14px;
+ font-variant: small-caps;
+ font-weight: bold;
+ letter-spacing: 1px;
+ margin: 0 -15px 10px 0;
+ padding: 10px 0;
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+.graphiql-container .doc-category-item {
+ margin: 12px 0;
+ color: #555;
+}
+
+.graphiql-container .keyword {
+ color: #B11A04;
+}
+
+.graphiql-container .type-name {
+ color: #CA9800;
+}
+
+.graphiql-container .field-name {
+ color: #1F61A0;
+}
+
+.graphiql-container .value-name {
+ color: #0B7FC7;
+}
+
+.graphiql-container .arg-name {
+ color: #8B2BB9;
+}
+
+.graphiql-container .arg:after {
+ content: ', ';
+}
+
+.graphiql-container .arg:last-child:after {
+ content: '';
+}
+
+.graphiql-container .doc-alert-text {
+ color: #F00F00;
+ font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace;
+ font-size: 13px;
+}
+
+.graphiql-container .search-box-outer {
+ border: 1px solid #d3d6db;
+ box-sizing: border-box;
+ display: inline-block;
+ font-size: 12px;
+ height: 24px;
+ margin-bottom: 12px;
+ padding: 3px 8px 5px;
+ vertical-align: middle;
+ width: 100%;
+}
+
+.graphiql-container .search-box-input {
+ border: 0;
+ font-size: 12px;
+ margin: 0;
+ outline: 0;
+ padding: 0;
+ width: 100%;
+}
+.CodeMirror-foldmarker {
+ color: blue;
+ text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
+ font-family: arial;
+ line-height: .3;
+ cursor: pointer;
+}
+.CodeMirror-foldgutter {
+ width: .7em;
+}
+.CodeMirror-foldgutter-open,
+.CodeMirror-foldgutter-folded {
+ cursor: pointer;
+}
+.CodeMirror-foldgutter-open:after {
+ content: "\25BE";
+}
+.CodeMirror-foldgutter-folded:after {
+ content: "\25B8";
+}
+/* The lint marker gutter */
+.CodeMirror-lint-markers {
+ width: 16px;
+}
+
+.CodeMirror-lint-tooltip {
+ background-color: infobackground;
+ border: 1px solid black;
+ border-radius: 4px 4px 4px 4px;
+ color: infotext;
+ font-family: monospace;
+ font-size: 10pt;
+ overflow: hidden;
+ padding: 2px 5px;
+ position: fixed;
+ white-space: pre;
+ white-space: pre-wrap;
+ z-index: 100;
+ max-width: 600px;
+ opacity: 0;
+ transition: opacity .4s;
+ -moz-transition: opacity .4s;
+ -webkit-transition: opacity .4s;
+ -o-transition: opacity .4s;
+ -ms-transition: opacity .4s;
+}
+
+.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
+ background-position: left bottom;
+ background-repeat: repeat-x;
+}
+
+.CodeMirror-lint-mark-error {
+ background-image:
+ url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
+ ;
+}
+
+.CodeMirror-lint-mark-warning {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
+}
+
+.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
+ background-position: center center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ display: inline-block;
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
+ position: relative;
+}
+
+.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
+ padding-left: 18px;
+ background-position: top left;
+ background-repeat: no-repeat;
+}
+
+.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
+}
+
+.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
+}
+
+.CodeMirror-lint-marker-multiple {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
+ background-repeat: no-repeat;
+ background-position: right bottom;
+ width: 100%; height: 100%;
+}
+.graphiql-container .spinner-container {
+ position: absolute;
+ top: 50%;
+ height: 36px;
+ width: 36px;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 10;
+}
+
+.graphiql-container .spinner {
+ vertical-align: middle;
+ display: inline-block;
+ height: 24px;
+ width: 24px;
+ position: absolute;
+ -webkit-animation: rotation .6s infinite linear;
+ -moz-animation: rotation .6s infinite linear;
+ -o-animation: rotation .6s infinite linear;
+ animation: rotation .6s infinite linear;
+ border-left: 6px solid rgba(150, 150, 150, .15);
+ border-right: 6px solid rgba(150, 150, 150, .15);
+ border-bottom: 6px solid rgba(150, 150, 150, .15);
+ border-top: 6px solid rgba(150, 150, 150, .8);
+ border-radius: 100%;
+}
+
+@-webkit-keyframes rotation {
+ from { -webkit-transform: rotate(0deg); }
+ to { -webkit-transform: rotate(359deg); }
+}
+
+@-moz-keyframes rotation {
+ from { -moz-transform: rotate(0deg); }
+ to { -moz-transform: rotate(359deg); }
+}
+
+@-o-keyframes rotation {
+ from { -o-transform: rotate(0deg); }
+ to { -o-transform: rotate(359deg); }
+}
+
+@keyframes rotation {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(359deg); }
+}
+.CodeMirror-hints {
+ background: white;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace;
+ font-size: 13px;
+ list-style: none;
+ margin: 0;
+ margin-left: -6px;
+ max-height: 14.5em;
+ overflow-y: auto;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ z-index: 10;
+}
+
+.CodeMirror-hints-wrapper {
+ background: white;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ margin-left: -6px;
+ position: absolute;
+ z-index: 10;
+}
+
+.CodeMirror-hints-wrapper .CodeMirror-hints {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ position: relative;
+ margin-left: 0;
+ z-index: 0;
+}
+
+.CodeMirror-hint {
+ border-top: solid 1px #f7f7f7;
+ color: #141823;
+ cursor: pointer;
+ margin: 0;
+ max-width: 300px;
+ overflow: hidden;
+ padding: 2px 6px;
+ white-space: pre;
+}
+
+li.CodeMirror-hint-active {
+ background-color: #08f;
+ border-top-color: white;
+ color: white;
+}
+
+.CodeMirror-hint-information {
+ border-top: solid 1px #c0c0c0;
+ max-width: 300px;
+ padding: 4px 6px;
+ position: relative;
+ z-index: 1;
+}
+
+.CodeMirror-hint-information:first-child {
+ border-bottom: solid 1px #c0c0c0;
+ border-top: none;
+ margin-bottom: -1px;
+}
diff --git a/examples/falcon_sqlalchemy/graphiql/graphiql.html b/examples/falcon_sqlalchemy/graphiql/graphiql.html
new file mode 100644
index 00000000..09178173
--- /dev/null
+++ b/examples/falcon_sqlalchemy/graphiql/graphiql.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
\ No newline at end of file
diff --git a/examples/falcon_sqlalchemy/graphiql/graphiql.min.js b/examples/falcon_sqlalchemy/graphiql/graphiql.min.js
new file mode 100644
index 00000000..dc67b3d9
--- /dev/null
+++ b/examples/falcon_sqlalchemy/graphiql/graphiql.min.js
@@ -0,0 +1,16 @@
+!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{var g;g="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,g.GraphiQL=f()}}(function(){var define;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o0&&(a=t[t.length-1]);var r=void 0,n=void 0;a?"Search Results"===a.name?(r=a.name,n=_react2.default.createElement(SearchDoc,{searchValue:a.searchValue,schema:e,onClickType:this.handleClickTypeOrField,onClickField:this.handleClickTypeOrField})):(r=a.name,n=(0,_graphql.isType)(a)?_react2.default.createElement(TypeDoc,{key:a.name,schema:e,type:a,onClickType:this.handleClickTypeOrField,onClickField:this.handleClickTypeOrField}):_react2.default.createElement(FieldDoc,{key:a.name,field:a,onClickType:this.handleClickTypeOrField})):e&&(r="Documentation Explorer",n=_react2.default.createElement(SchemaDoc,{schema:e,onClickType:this.handleClickTypeOrField}));var c=void 0;1===t.length?c="Schema":t.length>1&&(c=t[t.length-2].name);var l=_react2.default.createElement("div",{className:"spinner-container"},_react2.default.createElement("div",{className:"spinner"})),o=n&&(n.type===SearchDoc||n.type===SchemaDoc);return _react2.default.createElement("div",{className:"doc-explorer"},_react2.default.createElement("div",{className:"doc-explorer-title-bar"},c&&_react2.default.createElement("div",{className:"doc-explorer-back",onClick:this.handleNavBackClick},c),_react2.default.createElement("div",{className:"doc-explorer-title"},r),_react2.default.createElement("div",{className:"doc-explorer-rhs"},this.props.children)),_react2.default.createElement("div",{className:"doc-explorer-contents"},_react2.default.createElement(SearchBox,{isShown:o,onSearch:this.handleSearch}),this.props.schema?n:l))}},{key:"showDoc",value:function(e){var t=this.state.navStack,a=t.length>0&&t[t.length-1]===e;a||(t=t.concat([e])),this.setState({navStack:t})}},{key:"showSearch",value:function(e){var t=this.state.navStack,a=t.length>0&&t[t.length-1];a?a.searchValue!==e.searchValue&&(t=t.slice(0,-1).concat([e])):t=t.concat([e]),this.setState({navStack:t})}}]),t}(_react2.default.Component);DocExplorer.propTypes={schema:_react.PropTypes.instanceOf(_graphql.GraphQLSchema)};var SearchBox=function(e){function t(e){_classCallCheck(this,t);var a=_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return a.handleChange=function(e){a.setState({value:e.target.value}),a._debouncedOnSearch()},a.state={value:""},a._debouncedOnSearch=(0,_debounce2.default)(200,function(){a.props.onSearch(a.state.value)}),a}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e,t){return e.isShown!==this.props.isShown||t.value!==this.state.value}},{key:"render",value:function(){return _react2.default.createElement("div",null,this.props.isShown&&_react2.default.createElement("label",{className:"search-box-outer"},_react2.default.createElement("input",{className:"search-box-input",onChange:this.handleChange,type:"text",value:this.state.value,placeholder:"Search the schema ..."})))}}]),t}(_react2.default.Component);SearchBox.propTypes={isShown:_react.PropTypes.bool,onSearch:_react.PropTypes.func};var SearchDoc=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e){return this.props.schema!==e.schema||this.props.searchValue!==e.searchValue}},{key:"render",value:function(){var e=this,t=this.props.searchValue,a=this.props.schema,r=this.props.onClickType,n=this.props.onClickField,c=a.getTypeMap(),l=[],o=[],s=Object.keys(c),i=!0,p=!1,u=void 0;try{for(var d,m=function(){var a=d.value;if(l.length+o.length>=100)return"break";var s=c[a],i=[];e._isMatch(a,t)&&i.push("Type Name"),i.length&&l.push(_react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement(TypeLink,{type:s,onClick:r}))),s.getFields&&!function(){var a=s.getFields();Object.keys(a).forEach(function(c){var l=a[c];if(e._isMatch(c,t))o.push(_react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("a",{className:"field-name",onClick:function(e){return n(l,s,e)}},l.name)," on ",_react2.default.createElement(TypeLink,{type:s,onClick:r})));else if(l.args&&l.args.length){var i=l.args.filter(function(a){return e._isMatch(a.name,t)});i.length>0&&o.push(_react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("a",{className:"field-name",onClick:function(e){return n(l,s,e)}},l.name),"(",_react2.default.createElement("span",null,i.map(function(e){return _react2.default.createElement("span",{className:"arg",key:e.name},_react2.default.createElement("span",{className:"arg-name"},e.name),": ",_react2.default.createElement(TypeLink,{type:e.type,onClick:r}))})),")"," on ",_react2.default.createElement(TypeLink,{type:s,onClick:r})))}})}()},h=s[Symbol.iterator]();!(i=(d=h.next()).done);i=!0){var f=m();if("break"===f)break}}catch(e){p=!0,u=e}finally{try{!i&&h.return&&h.return()}finally{if(p)throw u}}return 0===l.length&&0===o.length?_react2.default.createElement("span",{className:"doc-alert-text"},"No results found."):_react2.default.createElement("div",null,_react2.default.createElement("div",{className:"doc-category"},(l.length>0||o.length>0)&&_react2.default.createElement("div",{className:"doc-category-title"},"search results"),l,o))}},{key:"_isMatch",value:function(e,t){try{var a=t.replace(/[^_0-9A-Za-z]/g,function(e){return"\\"+e});return e.search(new RegExp(a,"i"))!==-1}catch(a){return e.toLowerCase().indexOf(t.toLowerCase())!==-1}}}]),t}(_react2.default.Component);SearchDoc.propTypes={schema:_react.PropTypes.object,searchValue:_react.PropTypes.string,onClickType:_react.PropTypes.func,onClickField:_react.PropTypes.func};var SchemaDoc=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e){return this.props.schema!==e.schema}},{key:"render",value:function(){var e=this.props.schema,t=e.getQueryType(),a=e.getMutationType&&e.getMutationType(),r=e.getSubscriptionType&&e.getSubscriptionType();return _react2.default.createElement("div",null,_react2.default.createElement(MarkdownContent,{className:"doc-type-description",markdown:"A GraphQL schema provides a root type for each kind of operation."}),_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"root types"),_react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("span",{className:"keyword"},"query"),": ",_react2.default.createElement(TypeLink,{type:t,onClick:this.props.onClickType})),a&&_react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("span",{className:"keyword"},"mutation"),": ",_react2.default.createElement(TypeLink,{type:a,onClick:this.props.onClickType})),r&&_react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("span",{className:"keyword"},"subscription"),": ",_react2.default.createElement(TypeLink,{type:r,onClick:this.props.onClickType}))))}}]),t}(_react2.default.Component);SchemaDoc.propTypes={schema:_react.PropTypes.object,onClickType:_react.PropTypes.func};var TypeDoc=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e){return this.props.type!==e.type}},{key:"render",value:function(){var e=this.props.schema,t=this.props.type,a=this.props.onClickType,r=this.props.onClickField,n=void 0,c=void 0;t instanceof _graphql.GraphQLUnionType?(n="possible types",c=e.getPossibleTypes(t)):t instanceof _graphql.GraphQLInterfaceType?(n="implementations",c=e.getPossibleTypes(t)):t instanceof _graphql.GraphQLObjectType&&(n="implements",c=t.getInterfaces());var l=void 0;c&&c.length>0&&(l=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},n),c.map(function(e){return _react2.default.createElement("div",{key:e.name,className:"doc-category-item"},_react2.default.createElement(TypeLink,{type:e,onClick:a}))})));var o=void 0;t.getFields&&!function(){var e=t.getFields(),n=Object.keys(e).map(function(t){return e[t]});o=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"fields"),n.map(function(e){var n=void 0;return e.args&&e.args.length>0&&(n=e.args.map(function(e){return _react2.default.createElement("span",{className:"arg",key:e.name},_react2.default.createElement("span",{className:"arg-name"},e.name),": ",_react2.default.createElement(TypeLink,{type:e.type,onClick:a}))})),_react2.default.createElement("div",{key:e.name,className:"doc-category-item"},_react2.default.createElement("a",{className:"field-name",onClick:function(a){return r(e,t,a)}},e.name),n&&["(",_react2.default.createElement("span",{key:"args"},n),")"],": ",_react2.default.createElement(TypeLink,{type:e.type,onClick:a}),e.isDeprecated&&_react2.default.createElement("span",{className:"doc-alert-text"}," (DEPRECATED)"))}))}();var s=void 0;return t instanceof _graphql.GraphQLEnumType&&(s=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"values"),t.getValues().map(function(e){return _react2.default.createElement("div",{key:e.name,className:"doc-category-item"},_react2.default.createElement("div",{className:"enum-value"},e.name,e.isDeprecated&&_react2.default.createElement("span",{className:"doc-alert-text"}," (DEPRECATED)")),_react2.default.createElement(MarkdownContent,{className:"doc-value-description",markdown:e.description}),e.deprecationReason&&_react2.default.createElement(MarkdownContent,{className:"doc-alert-text",markdown:e.deprecationReason}))}))),_react2.default.createElement("div",null,_react2.default.createElement(MarkdownContent,{className:"doc-type-description",markdown:t.description||"No Description"}),t instanceof _graphql.GraphQLObjectType&&l,o,s,!(t instanceof _graphql.GraphQLObjectType)&&l)}}]),t}(_react2.default.Component);TypeDoc.propTypes={schema:_react.PropTypes.instanceOf(_graphql.GraphQLSchema),type:_react.PropTypes.object,onClickType:_react.PropTypes.func,onClickField:_react.PropTypes.func};var FieldDoc=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e){return this.props.field!==e.field}},{key:"render",value:function(){var e=this,t=this.props.field,a=void 0;return t.args&&t.args.length>0&&(a=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"arguments"),t.args.map(function(t){return _react2.default.createElement("div",{key:t.name,className:"doc-category-item"},_react2.default.createElement("div",null,_react2.default.createElement("span",{className:"arg-name"},t.name),": ",_react2.default.createElement(TypeLink,{type:t.type,onClick:e.props.onClickType})),_react2.default.createElement(MarkdownContent,{className:"doc-value-description",markdown:t.description}))}))),_react2.default.createElement("div",null,_react2.default.createElement(MarkdownContent,{className:"doc-type-description",markdown:t.description||"No Description"}),t.deprecationReason&&_react2.default.createElement(MarkdownContent,{className:"doc-alert-text",markdown:t.deprecationReason}),_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"type"),_react2.default.createElement(TypeLink,{type:t.type,onClick:this.props.onClickType})),a)}}]),t}(_react2.default.Component);FieldDoc.propTypes={field:_react.PropTypes.object,onClickType:_react.PropTypes.func};var TypeLink=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e){return this.props.type!==e.type}},{key:"render",value:function(){return renderType(this.props.type,this.props.onClick)}}]),t}(_react2.default.Component);TypeLink.propTypes={type:_react.PropTypes.object,onClick:_react.PropTypes.func};var MarkdownContent=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e){return this.props.markdown!==e.markdown}},{key:"render",value:function(){var e=this.props.markdown;if(!e)return _react2.default.createElement("div",null);var t=(0,_marked2.default)(e,{sanitize:!0});return _react2.default.createElement("div",{className:this.props.className,dangerouslySetInnerHTML:{__html:t}})}}]),t}(_react2.default.Component);MarkdownContent.propTypes={markdown:_react.PropTypes.string,className:_react.PropTypes.string}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/debounce":10,graphql:55,marked:122}],2:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExecuteButton=void 0;var _createClass=function(){function e(e,t){for(var n=0;n1,r=null;o&&n&&!function(){var n=e.state.highlight;r=_react2.default.createElement("ul",{className:"execute-options"},t.map(function(t){return _react2.default.createElement("li",{key:t.name?t.name.value:"*",className:t===n&&"selected",onMouseOver:function(){return e.setState({highlight:t})},onMouseOut:function(){return e.setState({highlight:null})},onMouseUp:function(){return e._onOptionSelected(t)}},t.name?t.name.value:"")}))}();var a=void 0;!this.props.isRunning&&o||(a=this._onClick);var u=void 0;return this.props.isRunning||!o||n||(u=this._onOptionsOpen),_react2.default.createElement("div",{className:"execute-button-wrap"},_react2.default.createElement("button",{className:"execute-button",onMouseDown:u,onClick:a,title:"Execute Query (Ctrl-Enter)"},_react2.default.createElement("svg",{width:"34",height:"34"},this.props.isRunning?_react2.default.createElement("path",{d:"M 10 10 L 23 10 L 23 23 L 10 23 z"}):_react2.default.createElement("path",{d:"M 11 9 L 24 16 L 11 23 z"}))),r)}}]),t}(_react2.default.Component);ExecuteButton.propTypes={onRun:_react.PropTypes.func,onStop:_react.PropTypes.func,isRunning:_react.PropTypes.bool,operations:_react.PropTypes.array}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function isPromise(e){return"object"===("undefined"==typeof e?"undefined":_typeof(e))&&"function"==typeof e.then}function isObservable(e){return"object"===("undefined"==typeof e?"undefined":_typeof(e))&&"function"==typeof e.subscribe}Object.defineProperty(exports,"__esModule",{value:!0}),exports.GraphiQL=void 0;var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},_extends=Object.assign||function(e){for(var t=1;t0&&!function(){var t=e.queryEditorComponent.getCodeMirror();t.operation(function(){var e=t.getCursor(),o=t.indexFromPos(e);t.setValue(n);var i=0,a=r.map(function(e){var r=e.index,n=e.string;return t.markText(t.posFromIndex(r+i),t.posFromIndex(r+(i+=n.length)),{className:"autoInsertedLeaf",clearOnEnter:!0,title:"Automatically added leaf fields"})});setTimeout(function(){return a.forEach(function(e){return e.clear()})},7e3);var s=o;r.forEach(function(e){var t=e.index,r=e.string;t=o){e=a.name&&a.name.value;break}}}this.handleRunQuery(e)}},{key:"_didClickDragBar",value:function(e){if(0!==e.button||e.ctrlKey)return!1;var t=e.target;if(0!==t.className.indexOf("CodeMirror-gutter"))return!1;for(var r=_reactDom2.default.findDOMNode(this.resultComponent);t;){if(t===r)return!0;t=t.parentNode}return!1}}]),t}(_react2.default.Component);GraphiQL.propTypes={fetcher:_react.PropTypes.func.isRequired,schema:_react.PropTypes.instanceOf(_graphql.GraphQLSchema),query:_react.PropTypes.string,variables:_react.PropTypes.string,operationName:_react.PropTypes.string,response:_react.PropTypes.string,storage:_react.PropTypes.shape({getItem:_react.PropTypes.func,setItem:_react.PropTypes.func}),defaultQuery:_react.PropTypes.string,onEditQuery:_react.PropTypes.func,onEditVariables:_react.PropTypes.func,onEditOperationName:_react.PropTypes.func,
+onToggleDocs:_react.PropTypes.func,getDefaultFieldNames:_react.PropTypes.func};var _initialiseProps=function(){var e=this;this.handleRunQuery=function(t){e._editorQueryID++;var r=e._editorQueryID,n=e.autoCompleteLeafs()||e.state.query,o=e.state.variables,i=e.state.operationName;if(t&&t!==i){i=t;var a=e.props.onEditOperationName;a&&a(i)}var s=e._fetchQuery(n,o,i,function(t){r===e._editorQueryID&&e.setState({isWaitingForResponse:!1,response:JSON.stringify(t,null,2)})});e.setState({isWaitingForResponse:!0,response:null,subscription:s,operationName:i})},this.handleStopQuery=function(){var t=e.state.subscription;e.setState({isWaitingForResponse:!1,subscription:null}),t&&t.unsubscribe()},this.handlePrettifyQuery=function(){var t=(0,_graphql.print)((0,_graphql.parse)(e.state.query)),r=e.queryEditorComponent.getCodeMirror();r.setValue(t)},this.handleEditQuery=function(t){if(e.state.schema&&e._updateQueryFacts(t),e.setState({query:t}),e.props.onEditQuery)return e.props.onEditQuery(t)},this._updateQueryFacts=(0,_debounce2.default)(150,function(t){var r=(0,_getQueryFacts2.default)(e.state.schema,t);if(r){var n=(0,_getSelectedOperationName2.default)(e.state.operations,e.state.operationName,r.operations),o=e.props.onEditOperationName;o&&n!==e.state.operationName&&o(n),e.setState(_extends({operationName:n},r))}}),this.handleEditVariables=function(t){e.setState({variables:t}),e.props.onEditVariables&&e.props.onEditVariables(t)},this.handleHintInformationRender=function(t){t.addEventListener("click",e._onClickHintInformation);var r=void 0;t.addEventListener("DOMNodeRemoved",r=function(){t.removeEventListener("DOMNodeRemoved",r),t.removeEventListener("click",e._onClickHintInformation)})},this.handleEditorRunQuery=function(){e._runQueryAtCursor()},this._onClickHintInformation=function(t){if("typeName"===t.target.className){var r=t.target.innerHTML,n=e.state.schema;n&&!function(){var t=n.getType(r);t&&e.setState({docExplorerOpen:!0},function(){e.docExplorerComponent.showDoc(t)})}()}},this.handleToggleDocs=function(){"function"==typeof e.props.onToggleDocs&&e.props.onToggleDocs(!e.state.docExplorerOpen),e.setState({docExplorerOpen:!e.state.docExplorerOpen})},this.handleResizeStart=function(t){if(e._didClickDragBar(t)){t.preventDefault();var r=t.clientX-(0,_elementPosition.getLeft)(t.target),n=function(t){if(0===t.buttons)return o();var n=_reactDom2.default.findDOMNode(e.editorBarComponent),i=t.clientX-(0,_elementPosition.getLeft)(n)-r,a=n.clientWidth-i;e.setState({editorFlex:i/a})},o=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o),n=null,o=null});document.addEventListener("mousemove",n),document.addEventListener("mouseup",o)}},this.handleDocsResizeStart=function(t){t.preventDefault();var r=e.state.docExplorerWidth,n=t.clientX-(0,_elementPosition.getLeft)(t.target),o=function(t){if(0===t.buttons)return i();var r=_reactDom2.default.findDOMNode(e),o=t.clientX-(0,_elementPosition.getLeft)(r)-n,a=r.clientWidth-o;a<100?e.setState({docExplorerOpen:!1}):e.setState({docExplorerOpen:!0,docExplorerWidth:Math.min(a,650)})},i=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){e.state.docExplorerOpen||e.setState({docExplorerWidth:r}),document.removeEventListener("mousemove",o),document.removeEventListener("mouseup",i),o=null,i=null});document.addEventListener("mousemove",o),document.addEventListener("mouseup",i)},this.handleVariableResizeStart=function(t){t.preventDefault();var r=!1,n=e.state.variableEditorOpen,o=e.state.variableEditorHeight,i=t.clientY-(0,_elementPosition.getTop)(t.target),a=function(t){if(0===t.buttons)return s();r=!0;var n=_reactDom2.default.findDOMNode(e.editorBarComponent),a=t.clientY-(0,_elementPosition.getTop)(n)-i,u=n.clientHeight-a;u<60?e.setState({variableEditorOpen:!1,variableEditorHeight:o}):e.setState({variableEditorOpen:!0,variableEditorHeight:u})},s=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){r||e.setState({variableEditorOpen:!n}),document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",s),a=null,s=null});document.addEventListener("mousemove",a),document.addEventListener("mouseup",s)}};GraphiQL.Logo=function(e){return _react2.default.createElement("div",{className:"title"},e.children||_react2.default.createElement("span",null,"Graph",_react2.default.createElement("em",null,"i"),"QL"))},GraphiQL.Toolbar=function(e){return _react2.default.createElement("div",{className:"toolbar"},e.children)},GraphiQL.ToolbarButton=_ToolbarButton.ToolbarButton,GraphiQL.Footer=function(e){return _react2.default.createElement("div",{className:"footer"},e.children)};var defaultQuery="# Welcome to GraphiQL\n#\n# GraphiQL is an in-browser IDE for writing, validating, and\n# testing GraphQL queries.\n#\n# Type queries into this side of the screen, and you will\n# see intelligent typeaheads aware of the current GraphQL type schema and\n# live syntax and validation errors highlighted within the text.\n#\n# To bring up the auto-complete at any point, just press Ctrl-Space.\n#\n# Press the run button above, or Cmd-Enter to execute the query, and the result\n# will appear in the pane to the right.\n\n"}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/CodeMirrorSizer":9,"../utility/debounce":10,"../utility/elementPosition":11,"../utility/fillLeafs":12,"../utility/find":13,"../utility/getQueryFacts":14,"../utility/getSelectedOperationName":15,"../utility/introspectionQueries":16,"./DocExplorer":1,"./ExecuteButton":2,"./QueryEditor":4,"./ResultViewer":5,"./ToolbarButton":6,"./VariableEditor":7,graphql:55}],4:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.QueryEditor=void 0;var _createClass=function(){function e(e,t){for(var o=0;o=65&&r<=90||!t.shiftKey&&r>=48&&r<=57||t.shiftKey&&189===r||t.shiftKey&&50===r||t.shiftKey&&57===r)&&o.editor.execCommand("autocomplete")},o._onEdit=function(){o.ignoreChangeEvent||(o.cachedValue=o.editor.getValue(),o.props.onEdit&&o.props.onEdit(o.cachedValue))},o._onHasCompletion=function(e,t){(0,_onHasCompletion2.default)(e,t,o.props.onHintInformationRender)},o.cachedValue=e.value||"",o}return _inherits(t,e),_createClass(t,[{key:"componentDidMount",value:function(){var e=this,t=require("codemirror");require("codemirror/addon/hint/show-hint"),require("codemirror/addon/comment/comment"),require("codemirror/addon/edit/matchbrackets"),require("codemirror/addon/edit/closebrackets"),require("codemirror/addon/fold/foldgutter"),require("codemirror/addon/fold/brace-fold"),require("codemirror/addon/lint/lint"),require("codemirror/keymap/sublime"),require("codemirror-graphql/hint"),require("codemirror-graphql/lint"),require("codemirror-graphql/mode"),this.editor=t(_reactDom2.default.findDOMNode(this),{value:this.props.value||"",lineNumbers:!0,tabSize:2,mode:"graphql",theme:"graphiql",keyMap:"sublime",autoCloseBrackets:!0,matchBrackets:!0,showCursorWhenSelecting:!0,foldGutter:{minFoldSize:4},lint:{schema:this.props.schema},hintOptions:{schema:this.props.schema,closeOnUnfocus:!1,completeSingle:!1},gutters:["CodeMirror-linenumbers","CodeMirror-foldgutter"],extraKeys:{"Cmd-Space":function(){return e.editor.showHint({completeSingle:!0})},"Ctrl-Space":function(){return e.editor.showHint({completeSingle:!0})},"Alt-Space":function(){return e.editor.showHint({completeSingle:!0})},"Shift-Space":function(){return e.editor.showHint({completeSingle:!0})},"Cmd-Enter":function(){e.props.onRunQuery&&e.props.onRunQuery()},"Ctrl-Enter":function(){e.props.onRunQuery&&e.props.onRunQuery()},"Ctrl-Left":"goSubwordLeft","Ctrl-Right":"goSubwordRight","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight"}}),this.editor.on("change",this._onEdit),this.editor.on("keyup",this._onKeyUp),this.editor.on("hasCompletion",this._onHasCompletion)}},{key:"componentDidUpdate",value:function(e){var t=require("codemirror");this.ignoreChangeEvent=!0,this.props.schema!==e.schema&&(this.editor.options.lint.schema=this.props.schema,this.editor.options.hintOptions.schema=this.props.schema,t.signal(this.editor,"change",this.editor)),this.props.value!==e.value&&this.props.value!==this.cachedValue&&(this.cachedValue=this.props.value,this.editor.setValue(this.props.value)),this.ignoreChangeEvent=!1}},{key:"componentWillUnmount",value:function(){this.editor.off("change",this._onEdit),this.editor.off("keyup",this._onKeyUp),this.editor.off("hasCompletion",this._onHasCompletion),this.editor=null}},{key:"render",value:function(){return _react2.default.createElement("div",{className:"query-editor"})}},{key:"getCodeMirror",value:function(){return this.editor}}]),t}(_react2.default.Component);QueryEditor.propTypes={schema:_react.PropTypes.instanceOf(_graphql.GraphQLSchema),value:_react.PropTypes.string,onEdit:_react.PropTypes.func,onHintInformationRender:_react.PropTypes.func,onRunQuery:_react.PropTypes.func}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/onHasCompletion":17,codemirror:45,"codemirror-graphql/hint":18,"codemirror-graphql/lint":19,"codemirror-graphql/mode":20,"codemirror/addon/comment/comment":35,"codemirror/addon/edit/closebrackets":36,"codemirror/addon/edit/matchbrackets":37,"codemirror/addon/fold/brace-fold":38,"codemirror/addon/fold/foldgutter":40,"codemirror/addon/hint/show-hint":41,"codemirror/addon/lint/lint":42,"codemirror/keymap/sublime":44,graphql:55}],5:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ResultViewer=void 0;var _createClass=function(){function e(e,t){for(var r=0;r=65&&r<=90||!t.shiftKey&&r>=48&&r<=57||t.shiftKey&&189===r||t.shiftKey&&222===r)&&o.editor.execCommand("autocomplete")},o._onEdit=function(){o.ignoreChangeEvent||(o.cachedValue=o.editor.getValue(),o.props.onEdit&&o.props.onEdit(o.cachedValue))},o._onHasCompletion=function(e,t){(0,_onHasCompletion2.default)(e,t,o.props.onHintInformationRender)},o.cachedValue=e.value||"",o}return _inherits(t,e),_createClass(t,[{key:"componentDidMount",value:function(){var e=this,t=require("codemirror");require("codemirror/addon/hint/show-hint"),require("codemirror/addon/edit/matchbrackets"),require("codemirror/addon/edit/closebrackets"),require("codemirror/addon/fold/brace-fold"),require("codemirror/addon/fold/foldgutter"),require("codemirror/addon/lint/lint"),require("codemirror/keymap/sublime"),require("codemirror-graphql/variables/hint"),require("codemirror-graphql/variables/lint"),require("codemirror-graphql/variables/mode"),this.editor=t(_reactDom2.default.findDOMNode(this),{value:this.props.value||"",lineNumbers:!0,tabSize:2,mode:"graphql-variables",theme:"graphiql",keyMap:"sublime",autoCloseBrackets:!0,matchBrackets:!0,showCursorWhenSelecting:!0,foldGutter:{minFoldSize:4},lint:{variableToType:this.props.variableToType},hintOptions:{variableToType:this.props.variableToType},gutters:["CodeMirror-linenumbers","CodeMirror-foldgutter"],extraKeys:{"Cmd-Space":function(){return e.editor.showHint({completeSingle:!1})},"Ctrl-Space":function(){return e.editor.showHint({completeSingle:!1})},"Alt-Space":function(){return e.editor.showHint({completeSingle:!1})},"Shift-Space":function(){return e.editor.showHint({completeSingle:!1})},"Cmd-Enter":function(){e.props.onRunQuery&&e.props.onRunQuery()},"Ctrl-Enter":function(){e.props.onRunQuery&&e.props.onRunQuery()},"Ctrl-Left":"goSubwordLeft","Ctrl-Right":"goSubwordRight","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight"}}),this.editor.on("change",this._onEdit),this.editor.on("keyup",this._onKeyUp),this.editor.on("hasCompletion",this._onHasCompletion)}},{key:"componentDidUpdate",value:function(e){var t=require("codemirror");this.ignoreChangeEvent=!0,this.props.variableToType!==e.variableToType&&(this.editor.options.lint.variableToType=this.props.variableToType,this.editor.options.hintOptions.variableToType=this.props.variableToType,t.signal(this.editor,"change",this.editor)),this.props.value!==e.value&&this.props.value!==this.cachedValue&&(this.cachedValue=this.props.value,this.editor.setValue(this.props.value)),this.ignoreChangeEvent=!1}},{key:"componentWillUnmount",value:function(){this.editor.off("change",this._onEdit),this.editor.off("keyup",this._onKeyUp),this.editor.off("hasCompletion",this._onHasCompletion),this.editor=null}},{key:"render",value:function(){return _react2.default.createElement("div",{className:"codemirrorWrap"})}},{key:"getCodeMirror",value:function(){return this.editor}}]),t}(_react2.default.Component);VariableEditor.propTypes={variableToType:_react.PropTypes.object,value:_react.PropTypes.string,onEdit:_react.PropTypes.func,onHintInformationRender:_react.PropTypes.func,onRunQuery:_react.PropTypes.func}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/onHasCompletion":17,codemirror:45,"codemirror-graphql/variables/hint":32,"codemirror-graphql/variables/lint":33,"codemirror-graphql/variables/mode":34,"codemirror/addon/edit/closebrackets":36,"codemirror/addon/edit/matchbrackets":37,"codemirror/addon/fold/brace-fold":38,"codemirror/addon/fold/foldgutter":40,"codemirror/addon/hint/show-hint":41,"codemirror/addon/lint/lint":42,"codemirror/keymap/sublime":44}],8:[function(require,module,exports){"use strict";module.exports=require("./components/GraphiQL").GraphiQL},{"./components/GraphiQL":3}],9:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r'+renderType(r.type)+"":"";a.innerHTML='