├── test
├── .eslintrc
├── fixtures
│ ├── 01-simple.js
│ ├── 19-auto-slug.js
│ ├── 03-multi-type.js
│ ├── 18-manual-slug.js
│ ├── 04-reference.js
│ ├── 05-reference-array.js
│ ├── 08-inline-document.js
│ ├── 06-reference-array-non-nullable.js
│ ├── 09-inline-document-array.js
│ ├── 07-directives.js
│ ├── 16-error-inline-union.js
│ ├── 14-reference-and-inline-array.js
│ ├── 15-forced-inline-array.js
│ ├── 17-error-non-ref-union-field.js
│ ├── 02-basics.js
│ ├── 11-union-array.js
│ ├── 10-unions.js
│ ├── 12-union-of-unions.js
│ ├── 13-union-of-union-array.js
│ └── 99-full.js
├── test.js
└── __snapshots__
│ └── test.js.snap
├── .prettierrc
├── src
├── index.js
├── withoutUndefined.js
├── taggedNoop.js
├── stubExternalTypes.js
├── schemaError.js
├── fromGQL.js
├── coreSchema.js
└── compileSchema.js
├── demo
├── .eslintrc
├── index.html
├── demo.css
├── GithubBadge.js
├── css
│ ├── material.css
│ └── codemirror.css
└── demo.js
├── .babelrc
├── .eslintrc
├── .gitignore
├── package.json
└── README.md
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "printWidth": 100,
4 | "bracketSpacing": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const graphql = require('./taggedNoop')
2 | const fromGQL = require('./fromGQL')
3 |
4 | module.exports = {
5 | graphql,
6 | fromGQL
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/01-simple.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author {
5 | name: String
6 | }
7 | `
8 |
--------------------------------------------------------------------------------
/demo/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "browser": true
5 | },
6 | "parser": "babel-eslint",
7 | "extends": ["sanity", "sanity/react", "prettier", "prettier/react"]
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/19-auto-slug.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author {
5 | num: Float
6 | name: String!
7 | slug: Slug
8 | }
9 | `
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "edge": "14"
8 | }
9 | }
10 | ],
11 | "@babel/preset-react"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["sanity", "prettier"],
3 | "env": {"node": true, "browser": true},
4 | "parserOptions": {
5 | "ecmaVersion": 2018
6 | },
7 | "rules": {
8 | "complexity": ["warn", 20]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/withoutUndefined.js:
--------------------------------------------------------------------------------
1 | module.exports = function withoutUndefined(obj) {
2 | return Object.keys(obj).reduce(
3 | (acc, key) => (typeof obj[key] === 'undefined' ? acc : {...(acc || {}), [key]: obj[key]}),
4 | undefined
5 | )
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixtures/03-multi-type.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author {
5 | name: String!
6 | }
7 |
8 | type Book implements Document {
9 | title: String
10 | }
11 | `
12 |
--------------------------------------------------------------------------------
/test/fixtures/18-manual-slug.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author {
5 | name: String!
6 | description: String
7 | slug: Slug @slug(source: "description")
8 | }
9 | `
10 |
--------------------------------------------------------------------------------
/src/taggedNoop.js:
--------------------------------------------------------------------------------
1 | module.exports = function taggedTemplateNoop(strings, ...keys) {
2 | const lastIndex = strings.length - 1
3 | return (
4 | strings.slice(0, lastIndex).reduce((acc, slice, i) => acc + slice + keys[i], '') +
5 | strings[lastIndex]
6 | )
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/04-reference.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type Book implements Document {
9 | title: String
10 | author: Author!
11 | }
12 | `
13 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | sanity-graphql-schema demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/fixtures/05-reference-array.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type Book implements Document {
9 | title: String
10 | authors: [Author!]
11 | }
12 | `
13 |
--------------------------------------------------------------------------------
/test/fixtures/08-inline-document.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type Book implements Document {
9 | title: String
10 | author: Author! @inline
11 | }
12 | `
13 |
--------------------------------------------------------------------------------
/test/fixtures/06-reference-array-non-nullable.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type Book implements Document {
9 | title: String
10 | authors: [Author!]!
11 | }
12 | `
13 |
--------------------------------------------------------------------------------
/test/fixtures/09-inline-document-array.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type Book implements Document {
9 | title: String
10 | authors: [Author!] @inline
11 | }
12 | `
13 |
--------------------------------------------------------------------------------
/src/stubExternalTypes.js:
--------------------------------------------------------------------------------
1 | const {upperFirst} = require('lodash')
2 |
3 | module.exports = function stubExternalTypes(types) {
4 | return types.reduce((sdl, type) => {
5 | return `${sdl}
6 | # Stub to make external types work
7 | scalar ${upperFirst(type.name)} # ${type.name}\n\n`.replace(/(\n+)\s*/g, '$1')
8 | }, '')
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixtures/07-directives.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | birthDate: Date @hidden
7 | }
8 |
9 | type Book implements Document {
10 | title: String
11 | author: Author
12 | tags: [String!]! @display(layout: "tags")
13 | }
14 | `
15 |
--------------------------------------------------------------------------------
/src/schemaError.js:
--------------------------------------------------------------------------------
1 | const withoutUndefined = require('./withoutUndefined')
2 |
3 | module.exports = function schemaError(message, name = '__root__', base = {}) {
4 | return withoutUndefined({
5 | name,
6 | title: 'GQL schema error',
7 | type: 'string',
8 | ...base,
9 | _problems: [
10 | {
11 | severity: 'error',
12 | message
13 | }
14 | ]
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/test/fixtures/16-error-inline-union.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author {
5 | name: String!
6 | }
7 |
8 | type ExternalImage {
9 | caption: String
10 | url: Url
11 | }
12 |
13 | union Description = Author | ExternalImage
14 |
15 | type Book implements Document {
16 | title: String
17 | author: Author!
18 | description: Description
19 | }
20 | `
21 |
--------------------------------------------------------------------------------
/test/fixtures/14-reference-and-inline-array.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type ExternalImage {
9 | caption: String
10 | url: Url
11 | }
12 |
13 | union Description = Author | ExternalImage
14 |
15 | type Book implements Document {
16 | title: String
17 | author: Author!
18 | description: [Description]
19 | }
20 | `
21 |
--------------------------------------------------------------------------------
/test/fixtures/15-forced-inline-array.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type ExternalImage {
9 | caption: String
10 | url: Url
11 | }
12 |
13 | union Description = Author | ExternalImage
14 |
15 | type Book implements Document {
16 | title: String
17 | author: Author!
18 | description: [Description] @inline
19 | }
20 | `
21 |
--------------------------------------------------------------------------------
/test/fixtures/17-error-non-ref-union-field.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author implements Document {
5 | name: String!
6 | }
7 |
8 | type ExternalImage {
9 | caption: String
10 | url: Url
11 | }
12 |
13 | union Description = Author | ExternalImage
14 |
15 | type Book implements Document {
16 | title: String
17 | author: Author!
18 | description: Description
19 | }
20 | `
21 |
--------------------------------------------------------------------------------
/test/fixtures/02-basics.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Author {
5 | # GraphQL
6 | int: Int
7 | float: Float
8 | string: String
9 | bool: Boolean
10 | id: ID
11 |
12 | # Sanity
13 | number: Number
14 | email: Email
15 | text: Text
16 | date: Date
17 | dateTime: Datetime
18 | url: Url
19 |
20 | # Lists
21 | list: [String]
22 | textList: [Text]
23 | }
24 | `
25 |
--------------------------------------------------------------------------------
/test/fixtures/11-union-array.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Reviewer implements Document {
5 | name: String!
6 | joined: Date
7 | }
8 |
9 | type Author implements Document {
10 | name: String!
11 | firstPublished: Date
12 | }
13 |
14 | union User = Reviewer | Author
15 |
16 | type Book implements Document {
17 | title: String
18 | author: Author
19 | reviewers: [User!]!
20 | }
21 | `
22 |
--------------------------------------------------------------------------------
/test/fixtures/10-unions.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | type Reviewer implements Document {
5 | name: String!
6 | joined: Date
7 | }
8 |
9 | type Author implements Document {
10 | name: String!
11 | firstPublished: Date
12 | }
13 |
14 | union User = Reviewer | Author
15 |
16 | type Book implements Document {
17 | title: String
18 | author: Author
19 | reviewer: User
20 | descisionMaker: User!
21 | }
22 | `
23 |
--------------------------------------------------------------------------------
/demo/demo.css:
--------------------------------------------------------------------------------
1 | @import './css/codemirror.css';
2 | @import './css/material.css';
3 |
4 | * {
5 | box-sizing: border-box;
6 | }
7 |
8 | html,
9 | body {
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
14 | #demo {
15 | background: #263238;
16 | }
17 |
18 | #demo {
19 | width: 100vw;
20 | height: 100vh;
21 | }
22 |
23 | .react-codemirror2 {
24 | width: 49.5vw;
25 | height: 100vh;
26 | display: inline-block;
27 | }
28 |
29 | .react-codemirror2:last-child {
30 | border-left: 1px solid #ccc;
31 | }
32 |
33 | .CodeMirror {
34 | margin: 0.5em 1em;
35 | font-size: 1.2em;
36 | height: 100vh;
37 | }
38 |
--------------------------------------------------------------------------------
/test/fixtures/12-union-of-unions.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | union User = Reviewer | Author
5 | union Work = Book | Magazine
6 | union Entity = User | Work
7 |
8 | type Reviewer implements Document {
9 | name: String!
10 | joined: Date
11 | }
12 |
13 | type Author implements Document {
14 | name: String!
15 | firstPublished: Date
16 | }
17 |
18 | type Book implements Document {
19 | title: String
20 | author: Author
21 | reviewer: User
22 | }
23 |
24 | type Magazine implements Document {
25 | title: String
26 | attachedTo: Entity
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/test/fixtures/13-union-of-union-array.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | union User = Reviewer | Author
5 | union Work = Book | Magazine
6 | union Entity = User | Work
7 |
8 | type Reviewer implements Document {
9 | name: String!
10 | joined: Date
11 | }
12 |
13 | type Author implements Document {
14 | name: String!
15 | firstPublished: Date
16 | }
17 |
18 | type Book implements Document {
19 | title: String
20 | author: Author
21 | reviewer: User
22 | }
23 |
24 | type Magazine implements Document {
25 | title: String
26 | attachedTo: [Entity]
27 | }
28 | `
29 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-sync */
2 | const fs = require('fs')
3 | const path = require('path')
4 | const fromGql = require('../src/fromGQL')
5 |
6 | const defaultOptions = {externalTypes: [], throwOnError: true}
7 | const fixturesDir = path.join(__dirname, 'fixtures')
8 | const fixture = (sdl, opts = defaultOptions) => fromGql(sdl, opts)
9 |
10 | const files = fs.readdirSync(fixturesDir)
11 | files
12 | .filter(file => /^\d+/.test(file) && file.endsWith('.js'))
13 | .map(file => ({name: file.slice(0, -3), path: path.join(fixturesDir, file)}))
14 | .map(file => ({...file, content: require(file.path)}))
15 | .forEach(file => test(file.name, () => expect(fixture(file.content)).toMatchSnapshot()))
16 |
--------------------------------------------------------------------------------
/src/fromGQL.js:
--------------------------------------------------------------------------------
1 | const compileSchema = require('./compileSchema')
2 |
3 | const placeholderSdl = 'type NoTypesDefined implements Document {pleaseFix: String}'
4 | const getPluginTypes = () => require('all:part:@sanity/base/schema-type')
5 |
6 | module.exports = function fromGQL(sdl, opts = {}) {
7 | const externalTypes = opts.externalTypes || getPluginTypes()
8 | const throwOnError = opts.throwOnError || false
9 | const schemaSdl = `${sdl}`.trim() || placeholderSdl
10 |
11 | try {
12 | return compileSchema(schemaSdl, {externalTypes})
13 | } catch (err) {
14 | if (throwOnError) {
15 | throw err
16 | }
17 |
18 | // eslint-disable-next-line no-console
19 | console.error(err)
20 | return [
21 | {
22 | name: 'gqlSchemaErrorCheckConsoleForDetails',
23 | title: 'GQL schema error - check console for details',
24 | type: 'object',
25 | fields: []
26 | }
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # macOS finder cache file
40 | .DS_Store
41 |
42 | # VS Code settings
43 | .vscode
44 |
45 | # Lockfiles
46 | yarn.lock
47 | package-lock.json
48 |
49 | # Cache
50 | .cache
51 |
52 | # build ouput
53 | dist
54 | lib
55 |
--------------------------------------------------------------------------------
/demo/GithubBadge.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => (
4 |
8 |
26 |
27 | )
28 |
--------------------------------------------------------------------------------
/test/fixtures/99-full.js:
--------------------------------------------------------------------------------
1 | const graphql = require('../../src/taggedNoop')
2 |
3 | module.exports = graphql`
4 | """
5 | A blog post
6 |
7 | It can contain newlines
8 | """
9 | type Post implements Document @display(title: "Blog post", icon: "post") {
10 | title: String
11 | leadImage: CaptionedImage @hotspot
12 | author: Author!
13 | body: Text
14 | tags: [String] @display(layout: "tags")
15 | status: String @enum(values: [{name: "active"}, {name: "disabled"}], layout: "radio")
16 | views: Int @readOnly @hidden
17 | """
18 | Comments are sent from the frontend
19 | """
20 | comments: [Comment] @display(title: "User comments")
21 | }
22 |
23 | type Author implements Document
24 | @orderings(
25 | from: [
26 | {
27 | title: "Release Date"
28 | name: "releaseDateDesc"
29 | by: [{field: "releaseDate.utc", direction: "desc"}]
30 | }
31 | ]
32 | ) {
33 | name: String
34 | profileImage: Image
35 | }
36 |
37 | type User implements Document {
38 | name: String
39 | }
40 |
41 | type CaptionedImage implements Image {
42 | caption: String
43 | uploadedBy: Author @inline
44 | }
45 |
46 | type Comment implements Document @fieldsets(from: [{title: "Statistics", name: "stats"}]) {
47 | """
48 | Author of this comment
49 | """
50 | author: Author!
51 | text: String
52 | likes: Int @fieldset(set: "stats")
53 | likedBy: [User]
54 | }
55 |
56 | union Person = Author | User
57 | `
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sanity-graphql-schema",
3 | "version": "0.0.6",
4 | "description": "Declare a Sanity schema using GraphQL SDL syntax",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "build": "babel -d lib src",
9 | "demo": "parcel demo/index.html",
10 | "demo:build": "NODE_ENV=production parcel build --experimental-scope-hoisting demo/index.html --public-url='./'",
11 | "posttest": "eslint .",
12 | "prepublishOnly": "npm run build",
13 | "postpublish": "npm run demo:build && gh-pages -d dist"
14 | },
15 | "devDependencies": {
16 | "@babel/cli": "^7.2.3",
17 | "@babel/core": "^7.2.2",
18 | "@babel/preset-env": "^7.2.3",
19 | "@babel/preset-react": "^7.0.0",
20 | "babel-core": "7.0.0-bridge.0",
21 | "babel-eslint": "^10.0.1",
22 | "babel-jest": "^23.6.0",
23 | "codemirror": "^5.42.2",
24 | "codemirror-graphql": "^0.8.3",
25 | "eslint": "^5.9.0",
26 | "eslint-config-prettier": "^3.3.0",
27 | "eslint-config-sanity": "^0.136.0",
28 | "gh-pages": "^2.0.1",
29 | "jest": "^23.6.0",
30 | "json5": "^2.1.0",
31 | "parcel-bundler": "^1.11.0",
32 | "parcel-plugin-bundle-visualiser": "^1.2.0",
33 | "prettier": "^1.15.2",
34 | "react": "^16.7.0",
35 | "react-codemirror2": "^5.1.0",
36 | "react-dom": "^16.7.0"
37 | },
38 | "dependencies": {
39 | "graphql": "^14.0.2",
40 | "lodash": "^4.17.11",
41 | "oneline": "^1.0.0"
42 | },
43 | "repository": {
44 | "type": "git",
45 | "url": "git+ssh://git@github.com/rexxars/sanity-graphql-schema.git"
46 | },
47 | "keywords": [
48 | "sanity",
49 | "graphql",
50 | "sdl",
51 | "schema"
52 | ],
53 | "author": "Espen Hovlandsdal ",
54 | "license": "MIT",
55 | "bugs": {
56 | "url": "https://github.com/rexxars/sanity-graphql-schema/issues"
57 | },
58 | "homepage": "https://github.com/rexxars/sanity-graphql-schema#readme"
59 | }
60 |
--------------------------------------------------------------------------------
/demo/css/material.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Name: material
4 | Author: Michael Kaminsky (http://github.com/mkaminsky11)
5 |
6 | Original material color scheme by Mattia Astorino (https://github.com/equinusocio/material-theme)
7 |
8 | */
9 |
10 | .cm-s-material.CodeMirror {
11 | background-color: #263238;
12 | color: rgba(233, 237, 237, 1);
13 | }
14 | .cm-s-material .CodeMirror-gutters {
15 | background: #263238;
16 | color: rgb(83,127,126);
17 | border: none;
18 | }
19 | .cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: rgb(83,127,126); }
20 | .cm-s-material .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
21 | .cm-s-material div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); }
22 | .cm-s-material.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
23 | .cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line > span::selection, .cm-s-material .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
24 | .cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line > span::-moz-selection, .cm-s-material .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
25 |
26 | .cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0); }
27 | .cm-s-material .cm-keyword { color: rgba(199, 146, 234, 1); }
28 | .cm-s-material .cm-operator { color: rgba(233, 237, 237, 1); }
29 | .cm-s-material .cm-variable-2 { color: #80CBC4; }
30 | .cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #82B1FF; }
31 | .cm-s-material .cm-builtin { color: #DECB6B; }
32 | .cm-s-material .cm-atom { color: #F77669; }
33 | .cm-s-material .cm-number { color: #F77669; }
34 | .cm-s-material .cm-def { color: rgba(233, 237, 237, 1); }
35 | .cm-s-material .cm-string { color: #C3E88D; }
36 | .cm-s-material .cm-string-2 { color: #80CBC4; }
37 | .cm-s-material .cm-comment { color: #546E7A; }
38 | .cm-s-material .cm-variable { color: #82B1FF; }
39 | .cm-s-material .cm-tag { color: #80CBC4; }
40 | .cm-s-material .cm-meta { color: #80CBC4; }
41 | .cm-s-material .cm-attribute { color: #FFCB6B; }
42 | .cm-s-material .cm-property { color: #80CBAE; }
43 | .cm-s-material .cm-qualifier { color: #DECB6B; }
44 | .cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #DECB6B; }
45 | .cm-s-material .cm-tag { color: rgba(255, 83, 112, 1); }
46 | .cm-s-material .cm-error {
47 | color: rgba(255, 255, 255, 1.0);
48 | background-color: #EC5F67;
49 | }
50 | .cm-s-material .CodeMirror-matchingbracket {
51 | text-decoration: underline;
52 | color: white !important;
53 | }
54 |
--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import {debounce} from 'lodash'
4 | import json5 from 'json5'
5 | import {
6 | UnControlled as UncontrolledCodeMirror,
7 | Controlled as ControlledCodeMirror
8 | } from 'react-codemirror2'
9 | import compileSchema from '../src/compileSchema'
10 | import GithubBadge from './GithubBadge'
11 | import 'codemirror-graphql/mode'
12 | import 'codemirror/mode/javascript/javascript'
13 |
14 | const defaultSdl = `
15 | union BlockContent = Block | Image
16 |
17 | type Post {
18 | title: String!
19 | slug: Slug
20 | author: Author
21 | mainImage: Image @hotspot
22 | categories: [Category]
23 | publishedAt: Datetime
24 | body: [BlockContent]
25 | }
26 |
27 | type Category {
28 | title: String!
29 | description: Text
30 | }
31 |
32 | type Author {
33 | name: String!
34 | slug: Slug
35 | image: Image @hotspot
36 | bio: [Block]
37 | }
38 | `
39 |
40 | const localStorageKey = 'gqlSchemaSdl'
41 |
42 | function getInitialValue() {
43 | try {
44 | const saved = window && window.localStorage && window.localStorage.getItem(localStorageKey)
45 | return saved || defaultSdl
46 | } catch (err) {
47 | return defaultSdl
48 | }
49 | }
50 |
51 | function getCompiled(value) {
52 | let schema
53 | try {
54 | const compiled = compileSchema(value, {externalTypes: []})
55 | schema = json5.stringify(compiled, {space: 2, quote: "'"})
56 | } catch (err) {
57 | schema = err.message
58 | }
59 | return schema
60 | }
61 |
62 | class SanityGraphQLDemo extends React.Component {
63 | constructor(props) {
64 | super(props)
65 | this.state = {schema: getCompiled(props.initialSdl)}
66 | this.handleChange = this.handleChange.bind(this)
67 | this.persist = debounce(this.persist.bind(this), 400)
68 | }
69 |
70 | persist() {
71 | const currentValue = this.state.currentValue || ''
72 | try {
73 | window.localStorage && window.localStorage.setItem(localStorageKey, currentValue)
74 | } catch (err) {
75 | // intentional noop
76 | }
77 | }
78 |
79 | handleChange(editor, data, value) {
80 | const schema = getCompiled(value)
81 | this.setState({schema, currentValue: value})
82 | this.persist()
83 | }
84 |
85 | render() {
86 | return (
87 |
88 |
89 |
97 |
104 |
105 | )
106 | }
107 | }
108 |
109 | ReactDOM.render(
110 | ,
111 | document.getElementById('demo')
112 | )
113 |
--------------------------------------------------------------------------------
/src/coreSchema.js:
--------------------------------------------------------------------------------
1 | const graphql = require('./taggedNoop')
2 |
3 | module.exports = graphql`
4 | directive @display(title: String, icon: String, layout: String) on FIELD_DEFINITION | OBJECT
5 | directive @fieldsets(from: [SanitySchemaFieldSet!]!) on OBJECT
6 | directive @fieldset(set: String!) on FIELD_DEFINITION
7 | directive @orderings(from: [SanitySchemaOrdering!]!) on OBJECT
8 |
9 | directive @hotspot on FIELD_DEFINITION
10 | directive @inline on FIELD_DEFINITION
11 | directive @hidden on FIELD_DEFINITION
12 | directive @readOnly on FIELD_DEFINITION
13 |
14 | # String directives
15 | directive @enum(
16 | values: [SanityNameTitlePair!]!
17 | layout: String
18 | direction: String
19 | ) on FIELD_DEFINITION
20 |
21 | # Image / file directives
22 | directive @extract(metadata: [String!]!, originalFilename: Boolean) on FIELD_DEFINITION
23 |
24 | # Block directives
25 | directive @block(
26 | styles: [SanityNameTitlePair!]
27 | decorators: [SanityNameTitlePair!]
28 | annotations: String
29 | ) on OBJECT
30 |
31 | # Slug directives
32 | directive @slug(source: String, maxLength: Int) on FIELD_DEFINITION
33 |
34 | scalar Number
35 | scalar Email
36 | scalar Text
37 | scalar Date
38 | scalar Datetime
39 | scalar Url
40 |
41 | type SanityNameTitlePair {
42 | name: String!
43 | title: String
44 | }
45 |
46 | type SanitySchemaFieldSet {
47 | name: String!
48 | title: String!
49 | collapsible: Boolean
50 | collapsed: Boolean
51 | }
52 |
53 | type SanitySchemaOrdering {
54 | name: String!
55 | title: String!
56 | by: [SanitySchemaSortOrder!]!
57 | }
58 |
59 | type SanitySchemaSortOrder {
60 | field: String!
61 | direction: String!
62 | }
63 |
64 | interface Document {
65 | _id: ID!
66 | _type: String!
67 | _createdAt: Datetime!
68 | _updatedAt: Datetime!
69 | _rev: String!
70 | }
71 |
72 | interface Image {
73 | _key: String
74 | _type: String
75 | asset: SanityImageAsset
76 | hotspot: SanityImageHotspot
77 | crop: SanityImageCrop
78 | }
79 |
80 | interface File {
81 | _key: String
82 | _type: String
83 | asset: SanityFileAsset
84 | }
85 |
86 | type Geopoint {
87 | _key: String
88 | _type: String
89 | lat: Float
90 | lng: Float
91 | alt: Float
92 | }
93 |
94 | type SanityFileAsset implements Document {
95 | _id: ID!
96 | _type: String!
97 | _createdAt: Datetime!
98 | _updatedAt: Datetime!
99 | _rev: String!
100 | originalFilename: String
101 | label: String
102 | sha1hash: String
103 | extension: String
104 | mimeType: String
105 | size: Float
106 | assetId: String
107 | path: String!
108 | url: String!
109 | }
110 |
111 | type SanityImageAsset implements Document {
112 | _id: ID!
113 | _type: String!
114 | _createdAt: Datetime!
115 | _updatedAt: Datetime!
116 | _rev: String!
117 | originalFilename: String
118 | label: String
119 | sha1hash: String
120 | extension: String
121 | mimeType: String
122 | size: Float
123 | assetId: String
124 | path: String!
125 | url: String!
126 | metadata: SanityImageMetadata
127 | }
128 |
129 | type SanityImageCrop {
130 | _key: String
131 | _type: String
132 | top: Float
133 | bottom: Float
134 | left: Float
135 | right: Float
136 | }
137 |
138 | type SanityImageDimensions {
139 | _key: String
140 | _type: String
141 | height: Float
142 | width: Float
143 | aspectRatio: Float
144 | }
145 |
146 | type SanityImageHotspot {
147 | _key: String
148 | _type: String
149 | x: Float
150 | y: Float
151 | height: Float
152 | width: Float
153 | }
154 |
155 | type SanityImageMetadata {
156 | _key: String
157 | _type: String
158 | location: Geopoint
159 | dimensions: SanityImageDimensions
160 | palette: SanityImagePalette
161 | lqip: String
162 | }
163 |
164 | type SanityImagePalette {
165 | _key: String
166 | _type: String
167 | darkMuted: SanityImagePaletteSwatch
168 | lightVibrant: SanityImagePaletteSwatch
169 | darkVibrant: SanityImagePaletteSwatch
170 | vibrant: SanityImagePaletteSwatch
171 | dominant: SanityImagePaletteSwatch
172 | lightMuted: SanityImagePaletteSwatch
173 | muted: SanityImagePaletteSwatch
174 | }
175 |
176 | type SanityImagePaletteSwatch {
177 | _key: String
178 | _type: String
179 | background: String
180 | foreground: String
181 | population: Float
182 | title: String
183 | }
184 |
185 | type Slug {
186 | _key: String
187 | _type: String
188 | current: String
189 | }
190 |
191 | type Block {
192 | _key: String
193 | _type: String
194 | spans: [Span!]
195 | markDefs: [SpanMark!]
196 | style: String
197 | list: String
198 | }
199 |
200 | interface SpanMark {
201 | _key: String!
202 | }
203 |
204 | type Span {
205 | _key: String
206 | _type: String
207 | text: String
208 | marks: [String!]
209 | }
210 | `
211 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sanity-graphql-schema
2 |
3 | ## DEPRECATED/UNMAINTAINED
4 |
5 | **⚠️ This module is no longer maintained!**
6 |
7 | The type system in Sanity is very expressive. GraphQL directives are neat, but modeling every schema variation through them can be a bit of a hassle. Still - this module/repo serves as an example of the fact that Sanity schemas are "just" javascript - and can be generated using _other_ javascript. Pretty sweet!
8 |
9 | ## Description
10 |
11 | Declare a [Sanity](https://www.sanity.io/) schema using GraphQL SDL syntax. [Try the demo?](https://rexxars.github.io/sanity-graphql-schema/)
12 |
13 | - Auto-infers title from type/field names (`leadAsset` -> `Lead asset`)
14 | - Type names are automatically camelcased (`BlogPost` -> `blogPost`)
15 | - Types that implement "Document" is made into document types
16 | - Document types as field type assumed to be references, object types as inline
17 | - Use `@inline` directive to use document type embedded into parent document
18 | - Directives for most schema type options
19 | - "Non null" treated as "required" validation rule (only enforced in Studio)
20 |
21 | ## Installation
22 |
23 | ```
24 | # In your Sanity studio folder
25 | yarn add sanity-graphql-schema
26 | ```
27 |
28 | ## Basic usage
29 |
30 | In your schema entry point (usually `schemas/schema.js`), you normally have something along the lines of this:
31 |
32 | ```js
33 | import createSchema from 'part:@sanity/base/schema-creator'
34 | import schemaTypes from 'all:part:@sanity/base/schema-type'
35 |
36 | import author from './author'
37 |
38 | export default createSchema({
39 | name: 'default',
40 | types: schemaTypes.concat([author])
41 | })
42 | ```
43 |
44 | To use this module, import it, call the imported function with a GraphQL schema defined in the GraphQL schema definition language, and replace the value passed to `createSchema()` with the output:
45 |
46 | ```js
47 | import createSchema from 'part:@sanity/base/schema-creator'
48 | import {fromGQL, graphql} from 'sanity-graphql-schema'
49 |
50 | const schema = graphql`
51 | type Author implements Document {
52 | name: String!
53 | profileImage: Image
54 | }
55 |
56 | type BlogPost implements Document {
57 | title: String!
58 | slug: Slug
59 | body: Text
60 | leadImage: CaptionedImage
61 | tags: [String!]! @display(layout: "tags")
62 | author: Author!
63 | }
64 |
65 | type CaptionedImage implements Image {
66 | caption: String!
67 | }
68 | `
69 |
70 | export default createSchema({
71 | name: 'default',
72 | types: fromGQL(schema)
73 | })
74 | ```
75 |
76 | ## Directives
77 |
78 | - `@display(title: String, icon: String, layout: String)`
79 | Allows you to:
80 | - Set title of fields and types, overriding the auto-generated title
81 | - Set layout mode for a field (for instance, set `tags` as layout for an array field)
82 | - Set icon for document types by passing an icon ID (see example below)
83 |
84 | * `@fieldsets(from: [SanitySchemaFieldSet!]!)`
85 | Set the fieldsets available for an object/document type. Takes an array of `{name, title}` pairs
86 |
87 | * `@fieldset(set: String!)`
88 | Assign a field to a given fieldset
89 |
90 | * `@orderings(from: [SanitySchemaOrdering!]!)`
91 | Set the available ordering options for a document type.
92 | Takes an array of `{name, title, by}` pairs - see [Sanity documentation](https://www.sanity.io/docs/the-schema/sort-orders) for more information
93 |
94 | - `@enum(values: [SanityNameTitlePair!]!, layout: String, direction: String)`
95 | Set on string fields if you only want to allow certain values.
96 | - `values` takes an array of `{name, title}` pairs.
97 | - `layout` can be one of `dropdown` or `radio`, `dropdown` being the default.
98 | - `direction` determines which way radio buttons flow (`horizontal`, `vertical`)
99 |
100 | * `@extract(metadata: [String!]!, originalFilename: Boolean)`
101 | Set on fields of type `Image` or `File` to determine which metadata to extract, and whether or not to store the original filename
102 |
103 | * `@slug(source: String, maxLength: Int)`
104 | Set on fields of type `Slug`, determines which field to generate slugs from, or set maximum length of the slug.
105 |
106 | * `@hotspot`
107 | Set on image fields to opt-in to the hotspot/crop functionality
108 |
109 | * `@inline`
110 | Set on fields that use a document type as it's value if you want to embed the value instead of referencing an existing document
111 |
112 | * `@hidden`
113 | Set on fields to hide them from the user interface, yet still be recognized by the underlying schema machinery
114 |
115 | * `@readOnly`
116 | Set on fields to that should not be editable from the studio
117 |
118 | ## Todo
119 |
120 | - [x] Generate GQL types for custom types (color input, for instance)
121 | - [x] Custom scalars (`markdown`)
122 | - [x] Throw on non-reference union fields
123 | - [x] Better error handling
124 | - [x] Blocks/spans
125 | - [ ] Validation directives
126 | - [ ] Inject icons, input components as options argument
127 | - [ ] JSON scalar for arbitrary input for custom types? Or options as dotpath? Both?
128 |
129 | ## Developing
130 |
131 | ```bash
132 | git clone git@github.com:rexxars/sanity-graphql-schema.git
133 | cd sanity-graphql-schema
134 | npm install
135 | npm test
136 | ```
137 |
138 | ## License
139 |
140 | MIT © [Espen Hovlandsdal](https://espen.codes/)
141 |
--------------------------------------------------------------------------------
/demo/css/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | color: black;
8 | direction: ltr;
9 | }
10 |
11 | /* PADDING */
12 |
13 | .CodeMirror-lines {
14 | padding: 4px 0; /* Vertical padding around content */
15 | }
16 | .CodeMirror pre {
17 | padding: 0 4px; /* Horizontal padding of content */
18 | }
19 |
20 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
21 | background-color: white; /* The little square between H and V scrollbars */
22 | }
23 |
24 | /* GUTTER */
25 |
26 | .CodeMirror-gutters {
27 | border-right: 1px solid #ddd;
28 | background-color: #f7f7f7;
29 | white-space: nowrap;
30 | }
31 | .CodeMirror-linenumbers {}
32 | .CodeMirror-linenumber {
33 | padding: 0 3px 0 5px;
34 | min-width: 20px;
35 | text-align: right;
36 | color: #999;
37 | white-space: nowrap;
38 | }
39 |
40 | .CodeMirror-guttermarker { color: black; }
41 | .CodeMirror-guttermarker-subtle { color: #999; }
42 |
43 | /* CURSOR */
44 |
45 | .CodeMirror-cursor {
46 | border-left: 1px solid black;
47 | border-right: none;
48 | width: 0;
49 | }
50 | /* Shown when moving in bi-directional text */
51 | .CodeMirror div.CodeMirror-secondarycursor {
52 | border-left: 1px solid silver;
53 | }
54 | .cm-fat-cursor .CodeMirror-cursor {
55 | width: auto;
56 | border: 0 !important;
57 | background: #7e7;
58 | }
59 | .cm-fat-cursor div.CodeMirror-cursors {
60 | z-index: 1;
61 | }
62 | .cm-fat-cursor-mark {
63 | background-color: rgba(20, 255, 20, 0.5);
64 | -webkit-animation: blink 1.06s steps(1) infinite;
65 | -moz-animation: blink 1.06s steps(1) infinite;
66 | animation: blink 1.06s steps(1) infinite;
67 | }
68 | .cm-animate-fat-cursor {
69 | width: auto;
70 | border: 0;
71 | -webkit-animation: blink 1.06s steps(1) infinite;
72 | -moz-animation: blink 1.06s steps(1) infinite;
73 | animation: blink 1.06s steps(1) infinite;
74 | background-color: #7e7;
75 | }
76 | @-moz-keyframes blink {
77 | 0% {}
78 | 50% { background-color: transparent; }
79 | 100% {}
80 | }
81 | @-webkit-keyframes blink {
82 | 0% {}
83 | 50% { background-color: transparent; }
84 | 100% {}
85 | }
86 | @keyframes blink {
87 | 0% {}
88 | 50% { background-color: transparent; }
89 | 100% {}
90 | }
91 |
92 | /* Can style cursor different in overwrite (non-insert) mode */
93 | .CodeMirror-overwrite .CodeMirror-cursor {}
94 |
95 | .cm-tab { display: inline-block; text-decoration: inherit; }
96 |
97 | .CodeMirror-rulers {
98 | position: absolute;
99 | left: 0; right: 0; top: -50px; bottom: -20px;
100 | overflow: hidden;
101 | }
102 | .CodeMirror-ruler {
103 | border-left: 1px solid #ccc;
104 | top: 0; bottom: 0;
105 | position: absolute;
106 | }
107 |
108 | /* DEFAULT THEME */
109 |
110 | .cm-s-default .cm-header {color: blue;}
111 | .cm-s-default .cm-quote {color: #090;}
112 | .cm-negative {color: #d44;}
113 | .cm-positive {color: #292;}
114 | .cm-header, .cm-strong {font-weight: bold;}
115 | .cm-em {font-style: italic;}
116 | .cm-link {text-decoration: underline;}
117 | .cm-strikethrough {text-decoration: line-through;}
118 |
119 | .cm-s-default .cm-keyword {color: #708;}
120 | .cm-s-default .cm-atom {color: #219;}
121 | .cm-s-default .cm-number {color: #164;}
122 | .cm-s-default .cm-def {color: #00f;}
123 | .cm-s-default .cm-variable,
124 | .cm-s-default .cm-punctuation,
125 | .cm-s-default .cm-property,
126 | .cm-s-default .cm-operator {}
127 | .cm-s-default .cm-variable-2 {color: #05a;}
128 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
129 | .cm-s-default .cm-comment {color: #a50;}
130 | .cm-s-default .cm-string {color: #a11;}
131 | .cm-s-default .cm-string-2 {color: #f50;}
132 | .cm-s-default .cm-meta {color: #555;}
133 | .cm-s-default .cm-qualifier {color: #555;}
134 | .cm-s-default .cm-builtin {color: #30a;}
135 | .cm-s-default .cm-bracket {color: #997;}
136 | .cm-s-default .cm-tag {color: #170;}
137 | .cm-s-default .cm-attribute {color: #00c;}
138 | .cm-s-default .cm-hr {color: #999;}
139 | .cm-s-default .cm-link {color: #00c;}
140 |
141 | .cm-s-default .cm-error {color: #f00;}
142 | .cm-invalidchar {color: #f00;}
143 |
144 | .CodeMirror-composing { border-bottom: 2px solid; }
145 |
146 | /* Default styles for common addons */
147 |
148 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
149 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
150 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
151 | .CodeMirror-activeline-background {background: #e8f2ff;}
152 |
153 | /* STOP */
154 |
155 | /* The rest of this file contains styles related to the mechanics of
156 | the editor. You probably shouldn't touch them. */
157 |
158 | .CodeMirror {
159 | position: relative;
160 | overflow: hidden;
161 | background: white;
162 | }
163 |
164 | .CodeMirror-scroll {
165 | overflow: scroll !important; /* Things will break if this is overridden */
166 | /* 30px is the magic margin used to hide the element's real scrollbars */
167 | /* See overflow: hidden in .CodeMirror */
168 | margin-bottom: -30px; margin-right: -30px;
169 | padding-bottom: 30px;
170 | height: 100%;
171 | outline: none; /* Prevent dragging from highlighting the element */
172 | position: relative;
173 | }
174 | .CodeMirror-sizer {
175 | position: relative;
176 | border-right: 30px solid transparent;
177 | }
178 |
179 | /* The fake, visible scrollbars. Used to force redraw during scrolling
180 | before actual scrolling happens, thus preventing shaking and
181 | flickering artifacts. */
182 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
183 | position: absolute;
184 | z-index: 6;
185 | display: none;
186 | }
187 | .CodeMirror-vscrollbar {
188 | right: 0; top: 0;
189 | overflow-x: hidden;
190 | overflow-y: scroll;
191 | }
192 | .CodeMirror-hscrollbar {
193 | bottom: 0; left: 0;
194 | overflow-y: hidden;
195 | overflow-x: scroll;
196 | }
197 | .CodeMirror-scrollbar-filler {
198 | right: 0; bottom: 0;
199 | }
200 | .CodeMirror-gutter-filler {
201 | left: 0; bottom: 0;
202 | }
203 |
204 | .CodeMirror-gutters {
205 | position: absolute; left: 0; top: 0;
206 | min-height: 100%;
207 | z-index: 3;
208 | }
209 | .CodeMirror-gutter {
210 | white-space: normal;
211 | height: 100%;
212 | display: inline-block;
213 | vertical-align: top;
214 | margin-bottom: -30px;
215 | }
216 | .CodeMirror-gutter-wrapper {
217 | position: absolute;
218 | z-index: 4;
219 | background: none !important;
220 | border: none !important;
221 | }
222 | .CodeMirror-gutter-background {
223 | position: absolute;
224 | top: 0; bottom: 0;
225 | z-index: 4;
226 | }
227 | .CodeMirror-gutter-elt {
228 | position: absolute;
229 | cursor: default;
230 | z-index: 4;
231 | }
232 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
233 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
234 |
235 | .CodeMirror-lines {
236 | cursor: text;
237 | min-height: 1px; /* prevents collapsing before first draw */
238 | }
239 | .CodeMirror pre {
240 | /* Reset some styles that the rest of the page might have set */
241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
242 | border-width: 0;
243 | background: transparent;
244 | font-family: inherit;
245 | font-size: inherit;
246 | margin: 0;
247 | white-space: pre;
248 | word-wrap: normal;
249 | line-height: inherit;
250 | color: inherit;
251 | z-index: 2;
252 | position: relative;
253 | overflow: visible;
254 | -webkit-tap-highlight-color: transparent;
255 | -webkit-font-variant-ligatures: contextual;
256 | font-variant-ligatures: contextual;
257 | }
258 | .CodeMirror-wrap pre {
259 | word-wrap: break-word;
260 | white-space: pre-wrap;
261 | word-break: normal;
262 | }
263 |
264 | .CodeMirror-linebackground {
265 | position: absolute;
266 | left: 0; right: 0; top: 0; bottom: 0;
267 | z-index: 0;
268 | }
269 |
270 | .CodeMirror-linewidget {
271 | position: relative;
272 | z-index: 2;
273 | padding: 0.1px; /* Force widget margins to stay inside of the container */
274 | }
275 |
276 | .CodeMirror-widget {}
277 |
278 | .CodeMirror-rtl pre { direction: rtl; }
279 |
280 | .CodeMirror-code {
281 | outline: none;
282 | }
283 |
284 | /* Force content-box sizing for the elements where we expect it */
285 | .CodeMirror-scroll,
286 | .CodeMirror-sizer,
287 | .CodeMirror-gutter,
288 | .CodeMirror-gutters,
289 | .CodeMirror-linenumber {
290 | -moz-box-sizing: content-box;
291 | box-sizing: content-box;
292 | }
293 |
294 | .CodeMirror-measure {
295 | position: absolute;
296 | width: 100%;
297 | height: 0;
298 | overflow: hidden;
299 | visibility: hidden;
300 | }
301 |
302 | .CodeMirror-cursor {
303 | position: absolute;
304 | pointer-events: none;
305 | }
306 | .CodeMirror-measure pre { position: static; }
307 |
308 | div.CodeMirror-cursors {
309 | visibility: hidden;
310 | position: relative;
311 | z-index: 3;
312 | }
313 | div.CodeMirror-dragcursors {
314 | visibility: visible;
315 | }
316 |
317 | .CodeMirror-focused div.CodeMirror-cursors {
318 | visibility: visible;
319 | }
320 |
321 | .CodeMirror-selected { background: #d9d9d9; }
322 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
323 | .CodeMirror-crosshair { cursor: crosshair; }
324 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
325 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
326 |
327 | .cm-searching {
328 | background-color: #ffa;
329 | background-color: rgba(255, 255, 0, .4);
330 | }
331 |
332 | /* Used to force a border model for a node */
333 | .cm-force-border { padding-right: .1px; }
334 |
335 | @media print {
336 | /* Hide the cursor when printing */
337 | .CodeMirror div.CodeMirror-cursors {
338 | visibility: hidden;
339 | }
340 | }
341 |
342 | /* See issue #2901 */
343 | .cm-tab-wrap-hack:after { content: ''; }
344 |
345 | /* Help users use markselection to safely style text background */
346 | span.CodeMirror-selectedtext { background: none; }
347 |
--------------------------------------------------------------------------------
/src/compileSchema.js:
--------------------------------------------------------------------------------
1 | const oneline = require('oneline')
2 | const {parse, specifiedScalarTypes, valueFromASTUntyped, buildASTSchema} = require('graphql')
3 | const {words, snakeCase, camelCase, upperFirst, isPlainObject} = require('lodash')
4 | const coreSchema = require('./coreSchema')
5 | const schemaError = require('./schemaError')
6 | const stubExternalTypes = require('./stubExternalTypes')
7 | const withoutUndefined = require('./withoutUndefined')
8 |
9 | const core = parse(coreSchema)
10 | const coreTypeMap = core.definitions.reduce((map, def) => map.set(def.name.value, def), new Map())
11 |
12 | const unsupported = ['ScalarTypeDefinition', 'InputObjectTypeDefinition']
13 |
14 | module.exports = function compileSchema(userSchema, options) {
15 | const externalTypes = options.externalTypes || []
16 | const externalTypesSchema = stubExternalTypes(externalTypes)
17 | const merged = parse([coreSchema, externalTypesSchema, userSchema].join('\n\n'))
18 | const external = externalTypesSchema ? parse(externalTypesSchema) : {definitions: []}
19 | const parsed = parse(userSchema)
20 | if (merged.kind !== 'Document') {
21 | return [schemaError('Could not parse GraphQL schema, expected kind `Document` at root')]
22 | }
23 |
24 | // Build schema to validate it
25 | const schema = buildASTSchema(merged)
26 |
27 | const externalTypeMap = external.definitions.reduce(
28 | (map, def) => map.set(def.name.value, def),
29 | new Map()
30 | )
31 |
32 | const defs = parsed.definitions || []
33 | const userTypeMap = new Map()
34 | for (let i = 0; i < defs.length; i++) {
35 | const def = defs[i]
36 | if (unsupported.includes(def.kind)) {
37 | return [schemaError(`Unsupported node of kind ${def.kind} found`)]
38 | }
39 |
40 | userTypeMap.set(def.name.value, def)
41 | }
42 |
43 | const opts = {schema, icons: {}}
44 | const typeMap = new Map([...userTypeMap, ...externalTypeMap, ...coreTypeMap])
45 | const sanityTypes = []
46 | for (const def of userTypeMap.values()) {
47 | const sanityType = convertType(def, typeMap, opts)
48 | if (sanityType) {
49 | sanityTypes.push(sanityType)
50 | }
51 | }
52 |
53 | return externalTypes.concat(sanityTypes)
54 | }
55 |
56 | function titleCase(str) {
57 | const [first, ...rest] = words(snakeCase(str))
58 | return [upperFirst(first), ...rest].join(' ')
59 | }
60 |
61 | function isCoreScalar(typeName) {
62 | return specifiedScalarTypes.some(scalar => scalar.name === typeName)
63 | }
64 |
65 | function isScalar(typeName, map) {
66 | if (isCoreScalar(typeName)) {
67 | return true
68 | }
69 |
70 | const type = map.get(typeName)
71 | return type && isScalarTypeDef(type)
72 | }
73 |
74 | function isScalarTypeDef(def) {
75 | return def.kind === 'ScalarTypeDefinition'
76 | }
77 |
78 | function isInterface(def) {
79 | return def.kind === 'InterfaceTypeDefinition'
80 | }
81 |
82 | function isUnion(def) {
83 | return def && def.kind === 'UnionTypeDefinition'
84 | }
85 |
86 | function isDocument(def) {
87 | return def.interfaces && def.interfaces.some(iface => iface.name.value === 'Document')
88 | }
89 |
90 | function isArgument(node) {
91 | return node.kind === 'Argument'
92 | }
93 |
94 | function typeImplementsInterface(type, ifaceName) {
95 | if (!type) {
96 | return false
97 | }
98 |
99 | if (isInterface(type) && type.name.value === ifaceName) {
100 | return true
101 | }
102 |
103 | return type.interfaces && type.interfaces.some(iface => iface.name.value === ifaceName)
104 | }
105 |
106 | function getUnwrappedType(typeDef, map) {
107 | const unwrapFor = ['NonNullType', 'ListType']
108 | const type = unwrapFor.includes(typeDef.kind) ? typeDef.type : typeDef
109 | if (unwrapFor.includes(type.kind)) {
110 | return getUnwrappedType(type, map)
111 | }
112 |
113 | if (type.kind !== 'NamedType') {
114 | throw new Error(oneline`
115 | Expected unwrapped type to be of kind "NamedType", got "${type.kind}"
116 | `)
117 | }
118 |
119 | if (isCoreScalar(type.name.value)) {
120 | return {
121 | kind: 'NamedType',
122 | name: {kind: 'Name', value: type.name.value}
123 | }
124 | }
125 |
126 | return map.get(type.name.value)
127 | }
128 |
129 | function getDirectives(def) {
130 | return (def.directives || []).reduce(
131 | (acc, dir) => ({
132 | ...acc,
133 | [dir.name.value]: dir.arguments
134 | .filter(isArgument)
135 | .reduce((args, arg) => ({...args, [arg.name.value]: valueFromASTUntyped(arg.value)}), {})
136 | }),
137 | {display: {}}
138 | )
139 | }
140 |
141 | function convertType(def, ...args) {
142 | switch (def.kind) {
143 | case 'ObjectTypeDefinition':
144 | return convertObjectType(def, ...args)
145 | case 'UnionTypeDefinition':
146 | return null
147 | default:
148 | return schemaError(`Unhandled node kind "${def.kind}"`)
149 | }
150 | }
151 |
152 | function flattenUnionType(def, map) {
153 | return def.types.reduce((types, typeDef) => {
154 | const type = map.get(typeDef.name.value) || typeDef
155 | return isUnion(type) ? [...types, ...flattenUnionType(type, map)] : [...types, type]
156 | }, [])
157 | }
158 |
159 | function convertBlockType(def, map, options, directives) {
160 | const typeName = def.name.value
161 | const {display, block} = directives
162 | const {icons} = options
163 |
164 | const fields = def.fields.reduce((acc, field) => {
165 | acc[field.name.value] = field
166 | return acc
167 | }, {})
168 |
169 | const {of: inlineTypes, ...rest} = fields
170 | const unknown = Object.keys(rest)
171 | if (unknown.length > 0) {
172 | return schemaError(oneline`
173 | Unknown keys for block type:
174 | ${unknown.map(str => `"${str}"`).join(', ')}
175 | `)
176 | }
177 |
178 | let ofMembers
179 | if (inlineTypes) {
180 | const nonNull = inlineTypes.type.kind === 'NonNullType'
181 | const type = nonNull ? inlineTypes.type.type : inlineTypes.type
182 | const ofDef = convertArrayField(inlineTypes, map, {...options, parent: def}, type, inlineTypes)
183 | ofMembers = ofDef.of
184 | }
185 |
186 | let {styles, decorators, annotations} = block
187 | if (styles) {
188 | const invalid = validateNameTitleArray(styles, 'styles')
189 | if (invalid) {
190 | return invalid
191 | }
192 |
193 | styles = enforceTitlesForNameTitlePairs(styles)
194 | }
195 |
196 | if (decorators) {
197 | const invalid = validateNameTitleArray(decorators, 'decorators')
198 | if (invalid) {
199 | return invalid
200 | }
201 |
202 | decorators = enforceTitlesForNameTitlePairs(decorators)
203 | }
204 |
205 | if (annotations && typeof annotations !== 'string') {
206 | return schemaError('`annotations` needs to be a string referencing a type', 'annotations')
207 | } else if (annotations && !map.has(annotations)) {
208 | return schemaError(`'annotations' references missing type: "${annotations}"`)
209 | } else if (annotations) {
210 | annotations = getAnnotationMembers(def, annotations, map, options)
211 | }
212 |
213 | return withoutUndefined({
214 | name: camelCase(typeName),
215 | title: display.title || titleCase(def.name.value),
216 | type: 'block',
217 | description: def.description && def.description.value,
218 | icon: icons[display.icon],
219 | of: ofMembers
220 | })
221 | }
222 |
223 | function getAnnotationMembers(def, annotations, map, options) {
224 | return convertArrayField(
225 | {},
226 | map,
227 | {...options, parent: def},
228 | {
229 | kind: 'NamedType',
230 | name: {kind: 'Name', value: annotations}
231 | },
232 | {name: 'annotations'}
233 | ).of
234 | }
235 |
236 | function validateNameTitleArray(arr, name) {
237 | if (!Array.isArray(arr)) {
238 | return schemaError(`${name} must be an array of name/title pairs, got ${typeof arr}`, name)
239 | }
240 |
241 | for (let i = 0; i < arr.length; i++) {
242 | if (!isPlainObject(arr[i])) {
243 | return schemaError(`Expected name/title pair, unexpected value at index ${i}`, name)
244 | }
245 |
246 | const unknown = Object.keys(arr[i]).filter(key => !['name', 'title'].includes(key))
247 | if (unknown.length > 0) {
248 | return schemaError(
249 | `Name/title pair has unknown keys: ${unknown.map(key => `"${key}"`).join(', ')}`,
250 | name
251 | )
252 | }
253 |
254 | if (!arr[i].name) {
255 | return schemaError(`Name/title pair at index ${i} is missing "name" property`, name)
256 | }
257 | }
258 |
259 | return false
260 | }
261 |
262 | function enforceTitlesForNameTitlePairs(arr) {
263 | const getTitle = name => (/^h\d$/.test(name) ? `Heading ${name.slice(1)}` : titleCase(name))
264 | return arr.map(item => ({
265 | name: item.name,
266 | title: item.title || getTitle(item.name)
267 | }))
268 | }
269 |
270 | function convertObjectType(def, map, options) {
271 | const typeName = def.name.value
272 |
273 | if (coreTypeMap.has(typeName)) {
274 | return schemaError(
275 | oneline`
276 | "${typeName}" is a bundled type, please use a different name`,
277 | typeName
278 | )
279 | }
280 |
281 | if (def.interfaces.length > 1) {
282 | return schemaError(
283 | oneline`
284 | Tried to implement multiple interfaces, only a single interface is allowed
285 | `,
286 | typeName
287 | )
288 | }
289 |
290 | let type = 'object'
291 | if (def.interfaces[0]) {
292 | const iface = map.get(def.interfaces[0].name.value)
293 | if (!iface) {
294 | return schemaError(
295 | oneline`
296 | Tried to implement interface "${def.interfaces[0].name.value}",
297 | which is not defined`,
298 | typeName
299 | )
300 | }
301 |
302 | if (!isInterface(iface)) {
303 | return schemaError(
304 | `Tried to implement "${iface.name.value}", which is not an interface`,
305 | typeName
306 | )
307 | }
308 |
309 | type = camelCase(iface.name.value)
310 | }
311 |
312 | const directives = getDirectives(def)
313 | const {display, fieldsets, orderings, block} = directives
314 | const {icons} = options
315 |
316 | if (block) {
317 | return convertBlockType(def, map, options, directives)
318 | }
319 |
320 | return withoutUndefined({
321 | name: camelCase(typeName),
322 | title: display.title || titleCase(def.name.value),
323 | type,
324 | description: def.description && def.description.value,
325 | icon: icons[display.icon],
326 | fields: def.fields.map(field => convertField(field, map, {...options, parent: def})),
327 | orderings: orderings && orderings.from,
328 | fieldsets: fieldsets && fieldsets.from
329 | })
330 | }
331 |
332 | function optionsFromDirectives(type, directives, map, options) {
333 | const {parent} = options
334 | const typeName = type.name && type.name.value
335 | const typeDef = typeName && map.get(typeName)
336 | const {display, enum: enumDir, extract, hotspot, slug} = directives
337 | if (typeName === 'String') {
338 | return {
339 | list: enumDir && enumDir.values,
340 | layout: enumDir && enumDir.layout,
341 | direction: enumDir && enumDir.direction
342 | }
343 | }
344 |
345 | if (typeName === 'Slug') {
346 | return {
347 | source: (slug && slug.source) || getFirstStringField(parent.fields, map),
348 | maxLength: (slug && slug.maxLength) || 200
349 | }
350 | }
351 |
352 | if (typeImplementsInterface(typeDef, 'Image')) {
353 | return {
354 | metadata: extract && extract.metadata,
355 | storeOriginalFilename: extract && extract.originalFilename,
356 | hotspot: Boolean(hotspot) || undefined
357 | }
358 | }
359 |
360 | if (typeImplementsInterface(typeDef, 'File')) {
361 | return {
362 | storeOriginalFilename: extract && extract.originalFilename
363 | }
364 | }
365 |
366 | if (type.kind === 'ListType') {
367 | return {
368 | layout: display && display.layout
369 | }
370 | }
371 |
372 | return {}
373 | }
374 |
375 | function getFirstStringField(fields, map) {
376 | const first = fields
377 | .map(field => ({name: field.name.value, type: getUnwrappedType(field.type, map)}))
378 | .find(field => field.type && field.type.name.value === 'String')
379 |
380 | return first ? first.name : undefined
381 | }
382 |
383 | function convertArrayField(def, map, options, type, field) {
384 | const unwrapped = getUnwrappedType(type, map)
385 | const namedMembers = isUnion(unwrapped) ? flattenUnionType(unwrapped, map) : [unwrapped]
386 | const members = namedMembers.map(member => map.get(member.name.value) || member)
387 | const hasScalars = members.some(member => isScalar(member.name.value, map))
388 | const hasNonScalars = members.some(member => !isScalar(member.name.value, map))
389 | const hasDocuments = members.some(isDocument)
390 | const allDocuments = members.every(isDocument)
391 |
392 | if (hasScalars && hasNonScalars) {
393 | return schemaError(
394 | oneline`
395 | Field "${field.name}" on type "${options.parent.name.value}"
396 | cannot contain both scalar and object types`,
397 | field.name,
398 | field
399 | )
400 | }
401 |
402 | let ofDef
403 | const forcedInline = Boolean(getDirectives(def).inline)
404 |
405 | if (hasScalars || forcedInline || !hasDocuments) {
406 | // [String | Number]
407 | // [Author | Book] @inline
408 | // [Image | ImageWithCaption]
409 | ofDef = members.map(getFieldDefForNamedType)
410 | } else if (allDocuments) {
411 | // [Author | Book]
412 | ofDef = [{type: 'reference', to: members.map(getFieldDefForNamedType)}]
413 | } else {
414 | // [Author | Image]
415 | const docs = members.filter(isDocument)
416 | const inline = members.filter(member => !isDocument(member))
417 | ofDef = [
418 | {type: 'reference', to: docs.map(getFieldDefForNamedType)},
419 | ...inline.map(getFieldDefForNamedType)
420 | ]
421 | }
422 |
423 | return withoutUndefined({...field, type: 'array', of: ofDef})
424 | }
425 |
426 | function convertField(def, map, options) {
427 | const nonNull = def.type.kind === 'NonNullType'
428 | const type = nonNull ? def.type.type : def.type
429 | const name = def.name && def.name.value
430 |
431 | const directives = getDirectives(def)
432 | const {display, readOnly, hidden, fieldset, enum: enumDir} = directives
433 |
434 | const field = {
435 | name,
436 | type: undefined,
437 | title: display.title || (name && titleCase(name)),
438 | description: def.description && def.description.value,
439 | readOnly: Boolean(readOnly) || undefined,
440 | hidden: Boolean(hidden) || undefined,
441 | fieldset: fieldset && fieldset.set,
442 | options: withoutUndefined(optionsFromDirectives(type, directives, map, options))
443 | }
444 |
445 | if (def.arguments && def.arguments.length > 0) {
446 | return schemaError(`Field arguments are not supported`, name, field)
447 | }
448 |
449 | if (enumDir && type.name && type.name.value !== 'String') {
450 | return schemaError(`@enum directive only allowed for String fields`, name, field)
451 | }
452 |
453 | if (nonNull) {
454 | field.validation = field.validation
455 | ? [field.validation, Rule => Rule.required()]
456 | : Rule => Rule.required()
457 | }
458 |
459 | if (type.kind === 'ListType') {
460 | return convertArrayField(def, map, options, type, field)
461 | }
462 |
463 | if (type.kind === 'NamedType') {
464 | return convertNamedTypeField(def, map, options, type, field)
465 | }
466 |
467 | return field
468 | }
469 |
470 | function convertNamedTypeField(def, map, options, type, baseField) {
471 | const name = def.name && def.name.value
472 | const {parent} = options
473 | const typeName = type.name.value
474 | const referencedType = map.get(typeName)
475 |
476 | if (!referencedType && !isCoreScalar(typeName)) {
477 | return schemaError(
478 | oneline`
479 | Field "${name}" on type "${parent.name.value}" is of type "${typeName}",
480 | which is not a defined type`,
481 | name,
482 | baseField
483 | )
484 | } else if (!referencedType || isScalar(typeName, map)) {
485 | return withoutUndefined({...baseField, ...getFieldDefForNamedType(type)})
486 | }
487 |
488 | const members = isUnion(referencedType) ? flattenUnionType(referencedType, map) : [referencedType]
489 | const allMembersAreDocuments = members.every(isDocument)
490 | const shouldInline = Boolean(getDirectives(def).inline) || !allMembersAreDocuments
491 | const shouldReference = !shouldInline
492 |
493 | // Sanity doesn't support union fields, as such.
494 | // It does support references to one or more document type, but not "inline" unions.
495 | if (referencedType && !shouldReference && isUnion(referencedType)) {
496 | const targets = referencedType.types.map(member => getUnwrappedType(member, map))
497 | const nonDocTypes = targets
498 | .filter(member => !isDocument(member))
499 | .map(member => member.name.value)
500 |
501 | const plural = nonDocTypes.length > 1 ? 'are not document types' : 'is not a document type'
502 | const help =
503 | nonDocTypes.length > 0 &&
504 | `(${nonDocTypes.join(', ')} ${plural} and therefore must be inlined)`
505 |
506 | return schemaError(
507 | oneline`
508 | Field "${name}" on type "${parent.name.value}" cannot be
509 | an inline union ${help || ''}`,
510 | name,
511 | baseField
512 | )
513 | }
514 |
515 | const fieldDef = getFieldDefForNamedType(type)
516 | const refOverride = shouldReference
517 | ? {type: 'reference', to: members.map(getFieldDefForNamedType)}
518 | : {}
519 |
520 | return withoutUndefined({...baseField, ...fieldDef, ...refOverride})
521 | }
522 |
523 | function getFieldDefForNamedType(type) {
524 | switch (type.name.value) {
525 | case 'Int':
526 | return {type: 'number', validation: Rule => Rule.integer()}
527 | case 'Float':
528 | return {type: 'number'}
529 | case 'ID':
530 | return {type: 'string'}
531 | default:
532 | return {type: camelCase(type.name.value)}
533 | }
534 | }
535 |
--------------------------------------------------------------------------------
/test/__snapshots__/test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`01-simple 1`] = `
4 | Array [
5 | Object {
6 | "fields": Array [
7 | Object {
8 | "name": "name",
9 | "title": "Name",
10 | "type": "string",
11 | },
12 | ],
13 | "name": "author",
14 | "title": "Author",
15 | "type": "object",
16 | },
17 | ]
18 | `;
19 |
20 | exports[`02-basics 1`] = `
21 | Array [
22 | Object {
23 | "fields": Array [
24 | Object {
25 | "name": "int",
26 | "title": "Int",
27 | "type": "number",
28 | "validation": [Function],
29 | },
30 | Object {
31 | "name": "float",
32 | "title": "Float",
33 | "type": "number",
34 | },
35 | Object {
36 | "name": "string",
37 | "title": "String",
38 | "type": "string",
39 | },
40 | Object {
41 | "name": "bool",
42 | "title": "Bool",
43 | "type": "boolean",
44 | },
45 | Object {
46 | "name": "id",
47 | "title": "Id",
48 | "type": "string",
49 | },
50 | Object {
51 | "name": "number",
52 | "title": "Number",
53 | "type": "number",
54 | },
55 | Object {
56 | "name": "email",
57 | "title": "Email",
58 | "type": "email",
59 | },
60 | Object {
61 | "name": "text",
62 | "title": "Text",
63 | "type": "text",
64 | },
65 | Object {
66 | "name": "date",
67 | "title": "Date",
68 | "type": "date",
69 | },
70 | Object {
71 | "name": "dateTime",
72 | "title": "Date time",
73 | "type": "datetime",
74 | },
75 | Object {
76 | "name": "url",
77 | "title": "Url",
78 | "type": "url",
79 | },
80 | Object {
81 | "name": "list",
82 | "of": Array [
83 | Object {
84 | "type": "string",
85 | },
86 | ],
87 | "title": "List",
88 | "type": "array",
89 | },
90 | Object {
91 | "name": "textList",
92 | "of": Array [
93 | Object {
94 | "type": "text",
95 | },
96 | ],
97 | "title": "Text list",
98 | "type": "array",
99 | },
100 | ],
101 | "name": "author",
102 | "title": "Author",
103 | "type": "object",
104 | },
105 | ]
106 | `;
107 |
108 | exports[`03-multi-type 1`] = `
109 | Array [
110 | Object {
111 | "fields": Array [
112 | Object {
113 | "name": "name",
114 | "title": "Name",
115 | "type": "string",
116 | "validation": [Function],
117 | },
118 | ],
119 | "name": "author",
120 | "title": "Author",
121 | "type": "object",
122 | },
123 | Object {
124 | "fields": Array [
125 | Object {
126 | "name": "title",
127 | "title": "Title",
128 | "type": "string",
129 | },
130 | ],
131 | "name": "book",
132 | "title": "Book",
133 | "type": "document",
134 | },
135 | ]
136 | `;
137 |
138 | exports[`04-reference 1`] = `
139 | Array [
140 | Object {
141 | "fields": Array [
142 | Object {
143 | "name": "name",
144 | "title": "Name",
145 | "type": "string",
146 | "validation": [Function],
147 | },
148 | ],
149 | "name": "author",
150 | "title": "Author",
151 | "type": "document",
152 | },
153 | Object {
154 | "fields": Array [
155 | Object {
156 | "name": "title",
157 | "title": "Title",
158 | "type": "string",
159 | },
160 | Object {
161 | "name": "author",
162 | "title": "Author",
163 | "to": Array [
164 | Object {
165 | "type": "author",
166 | },
167 | ],
168 | "type": "reference",
169 | "validation": [Function],
170 | },
171 | ],
172 | "name": "book",
173 | "title": "Book",
174 | "type": "document",
175 | },
176 | ]
177 | `;
178 |
179 | exports[`05-reference-array 1`] = `
180 | Array [
181 | Object {
182 | "fields": Array [
183 | Object {
184 | "name": "name",
185 | "title": "Name",
186 | "type": "string",
187 | "validation": [Function],
188 | },
189 | ],
190 | "name": "author",
191 | "title": "Author",
192 | "type": "document",
193 | },
194 | Object {
195 | "fields": Array [
196 | Object {
197 | "name": "title",
198 | "title": "Title",
199 | "type": "string",
200 | },
201 | Object {
202 | "name": "authors",
203 | "of": Array [
204 | Object {
205 | "to": Array [
206 | Object {
207 | "type": "author",
208 | },
209 | ],
210 | "type": "reference",
211 | },
212 | ],
213 | "title": "Authors",
214 | "type": "array",
215 | },
216 | ],
217 | "name": "book",
218 | "title": "Book",
219 | "type": "document",
220 | },
221 | ]
222 | `;
223 |
224 | exports[`06-reference-array-non-nullable 1`] = `
225 | Array [
226 | Object {
227 | "fields": Array [
228 | Object {
229 | "name": "name",
230 | "title": "Name",
231 | "type": "string",
232 | "validation": [Function],
233 | },
234 | ],
235 | "name": "author",
236 | "title": "Author",
237 | "type": "document",
238 | },
239 | Object {
240 | "fields": Array [
241 | Object {
242 | "name": "title",
243 | "title": "Title",
244 | "type": "string",
245 | },
246 | Object {
247 | "name": "authors",
248 | "of": Array [
249 | Object {
250 | "to": Array [
251 | Object {
252 | "type": "author",
253 | },
254 | ],
255 | "type": "reference",
256 | },
257 | ],
258 | "title": "Authors",
259 | "type": "array",
260 | "validation": [Function],
261 | },
262 | ],
263 | "name": "book",
264 | "title": "Book",
265 | "type": "document",
266 | },
267 | ]
268 | `;
269 |
270 | exports[`07-directives 1`] = `
271 | Array [
272 | Object {
273 | "fields": Array [
274 | Object {
275 | "name": "name",
276 | "title": "Name",
277 | "type": "string",
278 | "validation": [Function],
279 | },
280 | Object {
281 | "hidden": true,
282 | "name": "birthDate",
283 | "title": "Birth date",
284 | "type": "date",
285 | },
286 | ],
287 | "name": "author",
288 | "title": "Author",
289 | "type": "document",
290 | },
291 | Object {
292 | "fields": Array [
293 | Object {
294 | "name": "title",
295 | "title": "Title",
296 | "type": "string",
297 | },
298 | Object {
299 | "name": "author",
300 | "title": "Author",
301 | "to": Array [
302 | Object {
303 | "type": "author",
304 | },
305 | ],
306 | "type": "reference",
307 | },
308 | Object {
309 | "name": "tags",
310 | "of": Array [
311 | Object {
312 | "type": "string",
313 | },
314 | ],
315 | "options": Object {
316 | "layout": "tags",
317 | },
318 | "title": "Tags",
319 | "type": "array",
320 | "validation": [Function],
321 | },
322 | ],
323 | "name": "book",
324 | "title": "Book",
325 | "type": "document",
326 | },
327 | ]
328 | `;
329 |
330 | exports[`08-inline-document 1`] = `
331 | Array [
332 | Object {
333 | "fields": Array [
334 | Object {
335 | "name": "name",
336 | "title": "Name",
337 | "type": "string",
338 | "validation": [Function],
339 | },
340 | ],
341 | "name": "author",
342 | "title": "Author",
343 | "type": "document",
344 | },
345 | Object {
346 | "fields": Array [
347 | Object {
348 | "name": "title",
349 | "title": "Title",
350 | "type": "string",
351 | },
352 | Object {
353 | "name": "author",
354 | "title": "Author",
355 | "type": "author",
356 | "validation": [Function],
357 | },
358 | ],
359 | "name": "book",
360 | "title": "Book",
361 | "type": "document",
362 | },
363 | ]
364 | `;
365 |
366 | exports[`09-inline-document-array 1`] = `
367 | Array [
368 | Object {
369 | "fields": Array [
370 | Object {
371 | "name": "name",
372 | "title": "Name",
373 | "type": "string",
374 | "validation": [Function],
375 | },
376 | ],
377 | "name": "author",
378 | "title": "Author",
379 | "type": "document",
380 | },
381 | Object {
382 | "fields": Array [
383 | Object {
384 | "name": "title",
385 | "title": "Title",
386 | "type": "string",
387 | },
388 | Object {
389 | "name": "authors",
390 | "of": Array [
391 | Object {
392 | "type": "author",
393 | },
394 | ],
395 | "title": "Authors",
396 | "type": "array",
397 | },
398 | ],
399 | "name": "book",
400 | "title": "Book",
401 | "type": "document",
402 | },
403 | ]
404 | `;
405 |
406 | exports[`10-unions 1`] = `
407 | Array [
408 | Object {
409 | "fields": Array [
410 | Object {
411 | "name": "name",
412 | "title": "Name",
413 | "type": "string",
414 | "validation": [Function],
415 | },
416 | Object {
417 | "name": "joined",
418 | "title": "Joined",
419 | "type": "date",
420 | },
421 | ],
422 | "name": "reviewer",
423 | "title": "Reviewer",
424 | "type": "document",
425 | },
426 | Object {
427 | "fields": Array [
428 | Object {
429 | "name": "name",
430 | "title": "Name",
431 | "type": "string",
432 | "validation": [Function],
433 | },
434 | Object {
435 | "name": "firstPublished",
436 | "title": "First published",
437 | "type": "date",
438 | },
439 | ],
440 | "name": "author",
441 | "title": "Author",
442 | "type": "document",
443 | },
444 | Object {
445 | "fields": Array [
446 | Object {
447 | "name": "title",
448 | "title": "Title",
449 | "type": "string",
450 | },
451 | Object {
452 | "name": "author",
453 | "title": "Author",
454 | "to": Array [
455 | Object {
456 | "type": "author",
457 | },
458 | ],
459 | "type": "reference",
460 | },
461 | Object {
462 | "name": "reviewer",
463 | "title": "Reviewer",
464 | "to": Array [
465 | Object {
466 | "type": "reviewer",
467 | },
468 | Object {
469 | "type": "author",
470 | },
471 | ],
472 | "type": "reference",
473 | },
474 | Object {
475 | "name": "descisionMaker",
476 | "title": "Descision maker",
477 | "to": Array [
478 | Object {
479 | "type": "reviewer",
480 | },
481 | Object {
482 | "type": "author",
483 | },
484 | ],
485 | "type": "reference",
486 | "validation": [Function],
487 | },
488 | ],
489 | "name": "book",
490 | "title": "Book",
491 | "type": "document",
492 | },
493 | ]
494 | `;
495 |
496 | exports[`11-union-array 1`] = `
497 | Array [
498 | Object {
499 | "fields": Array [
500 | Object {
501 | "name": "name",
502 | "title": "Name",
503 | "type": "string",
504 | "validation": [Function],
505 | },
506 | Object {
507 | "name": "joined",
508 | "title": "Joined",
509 | "type": "date",
510 | },
511 | ],
512 | "name": "reviewer",
513 | "title": "Reviewer",
514 | "type": "document",
515 | },
516 | Object {
517 | "fields": Array [
518 | Object {
519 | "name": "name",
520 | "title": "Name",
521 | "type": "string",
522 | "validation": [Function],
523 | },
524 | Object {
525 | "name": "firstPublished",
526 | "title": "First published",
527 | "type": "date",
528 | },
529 | ],
530 | "name": "author",
531 | "title": "Author",
532 | "type": "document",
533 | },
534 | Object {
535 | "fields": Array [
536 | Object {
537 | "name": "title",
538 | "title": "Title",
539 | "type": "string",
540 | },
541 | Object {
542 | "name": "author",
543 | "title": "Author",
544 | "to": Array [
545 | Object {
546 | "type": "author",
547 | },
548 | ],
549 | "type": "reference",
550 | },
551 | Object {
552 | "name": "reviewers",
553 | "of": Array [
554 | Object {
555 | "to": Array [
556 | Object {
557 | "type": "reviewer",
558 | },
559 | Object {
560 | "type": "author",
561 | },
562 | ],
563 | "type": "reference",
564 | },
565 | ],
566 | "title": "Reviewers",
567 | "type": "array",
568 | "validation": [Function],
569 | },
570 | ],
571 | "name": "book",
572 | "title": "Book",
573 | "type": "document",
574 | },
575 | ]
576 | `;
577 |
578 | exports[`12-union-of-unions 1`] = `
579 | Array [
580 | Object {
581 | "fields": Array [
582 | Object {
583 | "name": "name",
584 | "title": "Name",
585 | "type": "string",
586 | "validation": [Function],
587 | },
588 | Object {
589 | "name": "joined",
590 | "title": "Joined",
591 | "type": "date",
592 | },
593 | ],
594 | "name": "reviewer",
595 | "title": "Reviewer",
596 | "type": "document",
597 | },
598 | Object {
599 | "fields": Array [
600 | Object {
601 | "name": "name",
602 | "title": "Name",
603 | "type": "string",
604 | "validation": [Function],
605 | },
606 | Object {
607 | "name": "firstPublished",
608 | "title": "First published",
609 | "type": "date",
610 | },
611 | ],
612 | "name": "author",
613 | "title": "Author",
614 | "type": "document",
615 | },
616 | Object {
617 | "fields": Array [
618 | Object {
619 | "name": "title",
620 | "title": "Title",
621 | "type": "string",
622 | },
623 | Object {
624 | "name": "author",
625 | "title": "Author",
626 | "to": Array [
627 | Object {
628 | "type": "author",
629 | },
630 | ],
631 | "type": "reference",
632 | },
633 | Object {
634 | "name": "reviewer",
635 | "title": "Reviewer",
636 | "to": Array [
637 | Object {
638 | "type": "reviewer",
639 | },
640 | Object {
641 | "type": "author",
642 | },
643 | ],
644 | "type": "reference",
645 | },
646 | ],
647 | "name": "book",
648 | "title": "Book",
649 | "type": "document",
650 | },
651 | Object {
652 | "fields": Array [
653 | Object {
654 | "name": "title",
655 | "title": "Title",
656 | "type": "string",
657 | },
658 | Object {
659 | "name": "attachedTo",
660 | "title": "Attached to",
661 | "to": Array [
662 | Object {
663 | "type": "reviewer",
664 | },
665 | Object {
666 | "type": "author",
667 | },
668 | Object {
669 | "type": "book",
670 | },
671 | Object {
672 | "type": "magazine",
673 | },
674 | ],
675 | "type": "reference",
676 | },
677 | ],
678 | "name": "magazine",
679 | "title": "Magazine",
680 | "type": "document",
681 | },
682 | ]
683 | `;
684 |
685 | exports[`13-union-of-union-array 1`] = `
686 | Array [
687 | Object {
688 | "fields": Array [
689 | Object {
690 | "name": "name",
691 | "title": "Name",
692 | "type": "string",
693 | "validation": [Function],
694 | },
695 | Object {
696 | "name": "joined",
697 | "title": "Joined",
698 | "type": "date",
699 | },
700 | ],
701 | "name": "reviewer",
702 | "title": "Reviewer",
703 | "type": "document",
704 | },
705 | Object {
706 | "fields": Array [
707 | Object {
708 | "name": "name",
709 | "title": "Name",
710 | "type": "string",
711 | "validation": [Function],
712 | },
713 | Object {
714 | "name": "firstPublished",
715 | "title": "First published",
716 | "type": "date",
717 | },
718 | ],
719 | "name": "author",
720 | "title": "Author",
721 | "type": "document",
722 | },
723 | Object {
724 | "fields": Array [
725 | Object {
726 | "name": "title",
727 | "title": "Title",
728 | "type": "string",
729 | },
730 | Object {
731 | "name": "author",
732 | "title": "Author",
733 | "to": Array [
734 | Object {
735 | "type": "author",
736 | },
737 | ],
738 | "type": "reference",
739 | },
740 | Object {
741 | "name": "reviewer",
742 | "title": "Reviewer",
743 | "to": Array [
744 | Object {
745 | "type": "reviewer",
746 | },
747 | Object {
748 | "type": "author",
749 | },
750 | ],
751 | "type": "reference",
752 | },
753 | ],
754 | "name": "book",
755 | "title": "Book",
756 | "type": "document",
757 | },
758 | Object {
759 | "fields": Array [
760 | Object {
761 | "name": "title",
762 | "title": "Title",
763 | "type": "string",
764 | },
765 | Object {
766 | "name": "attachedTo",
767 | "of": Array [
768 | Object {
769 | "to": Array [
770 | Object {
771 | "type": "reviewer",
772 | },
773 | Object {
774 | "type": "author",
775 | },
776 | Object {
777 | "type": "book",
778 | },
779 | Object {
780 | "type": "magazine",
781 | },
782 | ],
783 | "type": "reference",
784 | },
785 | ],
786 | "title": "Attached to",
787 | "type": "array",
788 | },
789 | ],
790 | "name": "magazine",
791 | "title": "Magazine",
792 | "type": "document",
793 | },
794 | ]
795 | `;
796 |
797 | exports[`14-reference-and-inline-array 1`] = `
798 | Array [
799 | Object {
800 | "fields": Array [
801 | Object {
802 | "name": "name",
803 | "title": "Name",
804 | "type": "string",
805 | "validation": [Function],
806 | },
807 | ],
808 | "name": "author",
809 | "title": "Author",
810 | "type": "document",
811 | },
812 | Object {
813 | "fields": Array [
814 | Object {
815 | "name": "caption",
816 | "title": "Caption",
817 | "type": "string",
818 | },
819 | Object {
820 | "name": "url",
821 | "title": "Url",
822 | "type": "url",
823 | },
824 | ],
825 | "name": "externalImage",
826 | "title": "External image",
827 | "type": "object",
828 | },
829 | Object {
830 | "fields": Array [
831 | Object {
832 | "name": "title",
833 | "title": "Title",
834 | "type": "string",
835 | },
836 | Object {
837 | "name": "author",
838 | "title": "Author",
839 | "to": Array [
840 | Object {
841 | "type": "author",
842 | },
843 | ],
844 | "type": "reference",
845 | "validation": [Function],
846 | },
847 | Object {
848 | "name": "description",
849 | "of": Array [
850 | Object {
851 | "to": Array [
852 | Object {
853 | "type": "author",
854 | },
855 | ],
856 | "type": "reference",
857 | },
858 | Object {
859 | "type": "externalImage",
860 | },
861 | ],
862 | "title": "Description",
863 | "type": "array",
864 | },
865 | ],
866 | "name": "book",
867 | "title": "Book",
868 | "type": "document",
869 | },
870 | ]
871 | `;
872 |
873 | exports[`15-forced-inline-array 1`] = `
874 | Array [
875 | Object {
876 | "fields": Array [
877 | Object {
878 | "name": "name",
879 | "title": "Name",
880 | "type": "string",
881 | "validation": [Function],
882 | },
883 | ],
884 | "name": "author",
885 | "title": "Author",
886 | "type": "document",
887 | },
888 | Object {
889 | "fields": Array [
890 | Object {
891 | "name": "caption",
892 | "title": "Caption",
893 | "type": "string",
894 | },
895 | Object {
896 | "name": "url",
897 | "title": "Url",
898 | "type": "url",
899 | },
900 | ],
901 | "name": "externalImage",
902 | "title": "External image",
903 | "type": "object",
904 | },
905 | Object {
906 | "fields": Array [
907 | Object {
908 | "name": "title",
909 | "title": "Title",
910 | "type": "string",
911 | },
912 | Object {
913 | "name": "author",
914 | "title": "Author",
915 | "to": Array [
916 | Object {
917 | "type": "author",
918 | },
919 | ],
920 | "type": "reference",
921 | "validation": [Function],
922 | },
923 | Object {
924 | "name": "description",
925 | "of": Array [
926 | Object {
927 | "type": "author",
928 | },
929 | Object {
930 | "type": "externalImage",
931 | },
932 | ],
933 | "title": "Description",
934 | "type": "array",
935 | },
936 | ],
937 | "name": "book",
938 | "title": "Book",
939 | "type": "document",
940 | },
941 | ]
942 | `;
943 |
944 | exports[`16-error-inline-union 1`] = `
945 | Array [
946 | Object {
947 | "fields": Array [
948 | Object {
949 | "name": "name",
950 | "title": "Name",
951 | "type": "string",
952 | "validation": [Function],
953 | },
954 | ],
955 | "name": "author",
956 | "title": "Author",
957 | "type": "object",
958 | },
959 | Object {
960 | "fields": Array [
961 | Object {
962 | "name": "caption",
963 | "title": "Caption",
964 | "type": "string",
965 | },
966 | Object {
967 | "name": "url",
968 | "title": "Url",
969 | "type": "url",
970 | },
971 | ],
972 | "name": "externalImage",
973 | "title": "External image",
974 | "type": "object",
975 | },
976 | Object {
977 | "fields": Array [
978 | Object {
979 | "name": "title",
980 | "title": "Title",
981 | "type": "string",
982 | },
983 | Object {
984 | "name": "author",
985 | "title": "Author",
986 | "type": "author",
987 | "validation": [Function],
988 | },
989 | Object {
990 | "_problems": Array [
991 | Object {
992 | "message": "Field \\"description\\" on type \\"Book\\" cannot be an inline union (Author, ExternalImage are not document types and therefore must be inlined)",
993 | "severity": "error",
994 | },
995 | ],
996 | "name": "description",
997 | "title": "Description",
998 | },
999 | ],
1000 | "name": "book",
1001 | "title": "Book",
1002 | "type": "document",
1003 | },
1004 | ]
1005 | `;
1006 |
1007 | exports[`17-error-non-ref-union-field 1`] = `
1008 | Array [
1009 | Object {
1010 | "fields": Array [
1011 | Object {
1012 | "name": "name",
1013 | "title": "Name",
1014 | "type": "string",
1015 | "validation": [Function],
1016 | },
1017 | ],
1018 | "name": "author",
1019 | "title": "Author",
1020 | "type": "document",
1021 | },
1022 | Object {
1023 | "fields": Array [
1024 | Object {
1025 | "name": "caption",
1026 | "title": "Caption",
1027 | "type": "string",
1028 | },
1029 | Object {
1030 | "name": "url",
1031 | "title": "Url",
1032 | "type": "url",
1033 | },
1034 | ],
1035 | "name": "externalImage",
1036 | "title": "External image",
1037 | "type": "object",
1038 | },
1039 | Object {
1040 | "fields": Array [
1041 | Object {
1042 | "name": "title",
1043 | "title": "Title",
1044 | "type": "string",
1045 | },
1046 | Object {
1047 | "name": "author",
1048 | "title": "Author",
1049 | "to": Array [
1050 | Object {
1051 | "type": "author",
1052 | },
1053 | ],
1054 | "type": "reference",
1055 | "validation": [Function],
1056 | },
1057 | Object {
1058 | "_problems": Array [
1059 | Object {
1060 | "message": "Field \\"description\\" on type \\"Book\\" cannot be an inline union (ExternalImage is not a document type and therefore must be inlined)",
1061 | "severity": "error",
1062 | },
1063 | ],
1064 | "name": "description",
1065 | "title": "Description",
1066 | },
1067 | ],
1068 | "name": "book",
1069 | "title": "Book",
1070 | "type": "document",
1071 | },
1072 | ]
1073 | `;
1074 |
1075 | exports[`18-manual-slug 1`] = `
1076 | Array [
1077 | Object {
1078 | "fields": Array [
1079 | Object {
1080 | "name": "name",
1081 | "title": "Name",
1082 | "type": "string",
1083 | "validation": [Function],
1084 | },
1085 | Object {
1086 | "name": "description",
1087 | "title": "Description",
1088 | "type": "string",
1089 | },
1090 | Object {
1091 | "name": "slug",
1092 | "options": Object {
1093 | "maxLength": 200,
1094 | "source": "description",
1095 | },
1096 | "title": "Slug",
1097 | "type": "slug",
1098 | },
1099 | ],
1100 | "name": "author",
1101 | "title": "Author",
1102 | "type": "object",
1103 | },
1104 | ]
1105 | `;
1106 |
1107 | exports[`19-auto-slug 1`] = `
1108 | Array [
1109 | Object {
1110 | "fields": Array [
1111 | Object {
1112 | "name": "num",
1113 | "title": "Num",
1114 | "type": "number",
1115 | },
1116 | Object {
1117 | "name": "name",
1118 | "title": "Name",
1119 | "type": "string",
1120 | "validation": [Function],
1121 | },
1122 | Object {
1123 | "name": "slug",
1124 | "options": Object {
1125 | "maxLength": 200,
1126 | "source": "name",
1127 | },
1128 | "title": "Slug",
1129 | "type": "slug",
1130 | },
1131 | ],
1132 | "name": "author",
1133 | "title": "Author",
1134 | "type": "object",
1135 | },
1136 | ]
1137 | `;
1138 |
1139 | exports[`99-full 1`] = `
1140 | Array [
1141 | Object {
1142 | "description": "A blog post
1143 |
1144 | It can contain newlines",
1145 | "fields": Array [
1146 | Object {
1147 | "name": "title",
1148 | "title": "Title",
1149 | "type": "string",
1150 | },
1151 | Object {
1152 | "name": "leadImage",
1153 | "options": Object {
1154 | "hotspot": true,
1155 | },
1156 | "title": "Lead image",
1157 | "type": "captionedImage",
1158 | },
1159 | Object {
1160 | "name": "author",
1161 | "title": "Author",
1162 | "to": Array [
1163 | Object {
1164 | "type": "author",
1165 | },
1166 | ],
1167 | "type": "reference",
1168 | "validation": [Function],
1169 | },
1170 | Object {
1171 | "name": "body",
1172 | "title": "Body",
1173 | "type": "text",
1174 | },
1175 | Object {
1176 | "name": "tags",
1177 | "of": Array [
1178 | Object {
1179 | "type": "string",
1180 | },
1181 | ],
1182 | "options": Object {
1183 | "layout": "tags",
1184 | },
1185 | "title": "Tags",
1186 | "type": "array",
1187 | },
1188 | Object {
1189 | "name": "status",
1190 | "options": Object {
1191 | "layout": "radio",
1192 | "list": Array [
1193 | Object {
1194 | "name": "active",
1195 | },
1196 | Object {
1197 | "name": "disabled",
1198 | },
1199 | ],
1200 | },
1201 | "title": "Status",
1202 | "type": "string",
1203 | },
1204 | Object {
1205 | "hidden": true,
1206 | "name": "views",
1207 | "readOnly": true,
1208 | "title": "Views",
1209 | "type": "number",
1210 | "validation": [Function],
1211 | },
1212 | Object {
1213 | "description": "Comments are sent from the frontend",
1214 | "name": "comments",
1215 | "of": Array [
1216 | Object {
1217 | "to": Array [
1218 | Object {
1219 | "type": "comment",
1220 | },
1221 | ],
1222 | "type": "reference",
1223 | },
1224 | ],
1225 | "title": "User comments",
1226 | "type": "array",
1227 | },
1228 | ],
1229 | "name": "post",
1230 | "title": "Blog post",
1231 | "type": "document",
1232 | },
1233 | Object {
1234 | "fields": Array [
1235 | Object {
1236 | "name": "name",
1237 | "title": "Name",
1238 | "type": "string",
1239 | },
1240 | Object {
1241 | "name": "profileImage",
1242 | "title": "Profile image",
1243 | "type": "image",
1244 | },
1245 | ],
1246 | "name": "author",
1247 | "orderings": Array [
1248 | Object {
1249 | "by": Array [
1250 | Object {
1251 | "direction": "desc",
1252 | "field": "releaseDate.utc",
1253 | },
1254 | ],
1255 | "name": "releaseDateDesc",
1256 | "title": "Release Date",
1257 | },
1258 | ],
1259 | "title": "Author",
1260 | "type": "document",
1261 | },
1262 | Object {
1263 | "fields": Array [
1264 | Object {
1265 | "name": "name",
1266 | "title": "Name",
1267 | "type": "string",
1268 | },
1269 | ],
1270 | "name": "user",
1271 | "title": "User",
1272 | "type": "document",
1273 | },
1274 | Object {
1275 | "fields": Array [
1276 | Object {
1277 | "name": "caption",
1278 | "title": "Caption",
1279 | "type": "string",
1280 | },
1281 | Object {
1282 | "name": "uploadedBy",
1283 | "title": "Uploaded by",
1284 | "type": "author",
1285 | },
1286 | ],
1287 | "name": "captionedImage",
1288 | "title": "Captioned image",
1289 | "type": "image",
1290 | },
1291 | Object {
1292 | "fields": Array [
1293 | Object {
1294 | "description": "Author of this comment",
1295 | "name": "author",
1296 | "title": "Author",
1297 | "to": Array [
1298 | Object {
1299 | "type": "author",
1300 | },
1301 | ],
1302 | "type": "reference",
1303 | "validation": [Function],
1304 | },
1305 | Object {
1306 | "name": "text",
1307 | "title": "Text",
1308 | "type": "string",
1309 | },
1310 | Object {
1311 | "fieldset": "stats",
1312 | "name": "likes",
1313 | "title": "Likes",
1314 | "type": "number",
1315 | "validation": [Function],
1316 | },
1317 | Object {
1318 | "name": "likedBy",
1319 | "of": Array [
1320 | Object {
1321 | "to": Array [
1322 | Object {
1323 | "type": "user",
1324 | },
1325 | ],
1326 | "type": "reference",
1327 | },
1328 | ],
1329 | "title": "Liked by",
1330 | "type": "array",
1331 | },
1332 | ],
1333 | "fieldsets": Array [
1334 | Object {
1335 | "name": "stats",
1336 | "title": "Statistics",
1337 | },
1338 | ],
1339 | "name": "comment",
1340 | "title": "Comment",
1341 | "type": "document",
1342 | },
1343 | ]
1344 | `;
1345 |
--------------------------------------------------------------------------------