├── .gitignore ├── README.md ├── example.js ├── examples ├── spotify-album.gql ├── spotify-artist.gql └── spotify-track.gql ├── package.json └── src ├── executor.js ├── models ├── album.js ├── artist.js └── track.js ├── parser ├── graphql-parser.js └── graphql.pegjs └── spotify-graphql.js /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spotify Web API GraphQL 2 | 3 | A proof of concept to create a [GraphQL](https://facebook.github.io/react/blog/2015/02/20/introducing-relay-and-graphql.html) compatible proxy to talk with the [Spotify Web API](https://developer.spotify.com/web-api/). It is based on [@cobbweb's GraphQL parser](https://github.com/cobbweb/graphqljs). 4 | 5 | ## Usage 6 | 7 | ### Install 8 | 9 | ``` 10 | $ npm install 11 | ``` 12 | 13 | ### Example 14 | 15 | Suppose we would like to fetch some information from the Web API, defined by the following GraphQL query: 16 | 17 | ``` 18 | album(5CHfJNBnVafJEyYNiavoUi) { 19 | artists.first(1) { 20 | id, 21 | name 22 | }, 23 | name, 24 | tracks.first(1) { id } 25 | } 26 | ``` 27 | 28 | This will retrieve the album with id `5CHfJNBnVafJEyYNiavoUi` using the [Get an Album endpoint](https://developer.spotify.com/web-api/get-album/) and filter the returned fields to match the query. 29 | 30 | ```js 31 | { 32 | "artists": [ 33 | { 34 | "id": "6J6yx1t3nwIDyPXk5xa7O8", 35 | "name": "Vetusta Morla" 36 | } 37 | ], 38 | "name": "La Deriva", 39 | "tracks": [ 40 | { 41 | "id": "1spx7pPpe3AGG2gIrsMbVm" 42 | } 43 | ] 44 | } 45 | ``` 46 | 47 | There is basic support for queries that result in several requests. For instance, take this query: 48 | 49 | ``` 50 | artist(0OdUWJ0sBjDrqHygGUXeCF) { 51 | name, 52 | followers { total }, 53 | toptracks.first(5) { 54 | id, 55 | name, 56 | popularity 57 | } 58 | } 59 | ``` 60 | 61 | Fetching the data involves a request to the endpoints [Get an Artist](https://developer.spotify.com/web-api/get-artist/) and [Get Artist's Top Tracks](https://developer.spotify.com/web-api/get-artists-top-tracks/). The adaptor will take care of it and will return this object: 62 | 63 | ```js 64 | { 65 | "name": "Band of Horses", 66 | "followers": { 67 | "total": 347707 68 | }, 69 | "toptracks": [ 70 | { 71 | "id": "4o0NjemqhmsYLIMwlcosvW", 72 | "name": "The Funeral", 73 | "popularity": 74 74 | }, 75 | { 76 | "id": "3LeNQIGi0zwmQm8WShZB95", 77 | "name": "No One's Gonna Love You", 78 | "popularity": 70 79 | }, 80 | { 81 | "id": "5MYfpFJYm8WNFGssR6H2Oz", 82 | "name": "No One's Gonna Love You - Live from Spotify Sweden", 83 | "popularity": 69 84 | }, 85 | { 86 | "id": "5qWgGPylB0Al9IVq2HKTHE", 87 | "name": "Is There A Ghost", 88 | "popularity": 61 89 | }, 90 | { 91 | "id": "3MNTXYdBLLeBJjbihvTjOJ", 92 | "name": "The General Specific", 93 | "popularity": 58 94 | } 95 | ] 96 | } 97 | ``` 98 | 99 | ### Demo 100 | 101 | After cloning, install deps with `npm install`. You can see a demo running `npm run demo` which executes the example queries from the `examples` folder and dumps the parsed object to the console. 102 | 103 | You can re-build the parser by running `npm run build` 104 | 105 | #### TODO 106 | 107 | * Support all endpoints from the Web API 108 | * Issue several requests to fetch missing fields. This is partially supported, but should cover cases like: 109 | - hydrating simple objects with full objects 110 | - using multi-get to resolve multiple requests for full tracks, full albums or full artists 111 | * Write tests 112 | * Implement an example using Relay -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var parser = require('./src/parser/graphql-parser'); 3 | var SpotifyGraphQL = require('./src/spotify-graphql'); 4 | 5 | var spotifyGraphQL = new SpotifyGraphQL(); 6 | spotifyGraphQL.init(); 7 | 8 | function dump(object) { 9 | console.log(JSON.stringify(object, null, 2)); 10 | } 11 | 12 | var examples = fs.readdirSync('examples'); 13 | 14 | function executeExample(example, cb) { 15 | console.log('Processing ' + example); 16 | var input = fs.readFileSync('examples/' + example, 'utf8'); 17 | spotifyGraphQL.execute(input, function(output) { 18 | dump(output); 19 | cb(); 20 | }); 21 | } 22 | 23 | var i = 0; 24 | var ex = function(i) { 25 | executeExample(examples[i], function() { 26 | if (i < examples.length - 1) { 27 | ex(++i); 28 | } 29 | }); 30 | }; 31 | 32 | ex(i); -------------------------------------------------------------------------------- /examples/spotify-album.gql: -------------------------------------------------------------------------------- 1 | album(5CHfJNBnVafJEyYNiavoUi) { 2 | artists.first(1) { 3 | id, 4 | name 5 | }, 6 | name, 7 | tracks.first(1) { id } 8 | } -------------------------------------------------------------------------------- /examples/spotify-artist.gql: -------------------------------------------------------------------------------- 1 | artist(0OdUWJ0sBjDrqHygGUXeCF) { 2 | name, 3 | followers { total }, 4 | toptracks.first(5) { 5 | id, 6 | name, 7 | popularity 8 | } 9 | } -------------------------------------------------------------------------------- /examples/spotify-track.gql: -------------------------------------------------------------------------------- 1 | track(4oqoEYKWtIcH2ajYm2qQZt) { 2 | name, 3 | artists.first(1) { 4 | id, 5 | name 6 | }, 7 | preview_url 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spotify-web-api-graphql", 3 | "version": "0.0.1", 4 | "description": "A proof-of-concept to create a graphql proxy for the Spotify Web API in javascript", 5 | "main": "src/spotify-graphql.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "dependencies": { 10 | "promise": "^6.1.0", 11 | "spotify-web-api-node": "^2.0.1" 12 | }, 13 | "devDependencies": { 14 | "graphql": "0.0.2", 15 | "pegjs": "^0.8.0" 16 | }, 17 | "scripts": { 18 | "build": "./node_modules/.bin/pegjs src/parser/graphql.pegjs src/parser/graphql-parser.js", 19 | "demo": "npm run build && node ./example.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/jmperez/spotify-web-api-graphql" 24 | }, 25 | "keywords": [ 26 | "spotify", 27 | "graphql" 28 | ], 29 | "author": "José M. Pérez", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/jmperez/spotify-web-api-graphql/issues" 33 | }, 34 | "homepage": "https://github.com/jmperez/spotify-web-api-graphql" 35 | } 36 | -------------------------------------------------------------------------------- /src/executor.js: -------------------------------------------------------------------------------- 1 | var Promise = require('promise'); 2 | 3 | var Executor = function(initial, schema, edges, context) { 4 | this._initial = initial; 5 | this._schema = schema; 6 | this._promises = []; 7 | this._context = context; // used to find out against what we need to apply a deep request 8 | this._edges = edges; // used as a mapping 'extra -> deep request' 9 | }; 10 | 11 | /** 12 | * [execute description] 13 | * @return {[type]} A promise! 14 | */ 15 | Executor.prototype.execute = function() { 16 | return this._processObject(this._initial, this._schema); 17 | }; 18 | 19 | /** 20 | * 21 | * It takes an object and applies a certain schema 22 | * 23 | * Object { 24 | * name: 'My name', 25 | * followers: { 'total': 10, 'href': null } 26 | * artist: 'AN ARTIST' 27 | * } 28 | * Schema { 29 | * name: 'name', 30 | * followers: { 31 | * properties: { 32 | * properties: { 33 | * name: 'total' 34 | * } 35 | * } 36 | * } 37 | * } 38 | * 39 | * will output { 40 | * name: 'My name', 41 | * followers: { 42 | * total: 10 43 | * } 44 | * } 45 | * 46 | */ 47 | Executor.prototype._processObject = function(object, schema) { 48 | var self = this; 49 | var promises = []; 50 | var props = schema; 51 | 52 | var result = {}; 53 | 54 | props = Array.isArray(props) ? props : [props]; 55 | 56 | props.forEach(function(prop) { 57 | var name = prop.name; 58 | if (name in object) { 59 | // we have property i in object, go on 60 | var partial = object[name]; 61 | var called = self._executeCalls(object[name], prop.calls); 62 | if (Array.isArray(called)) { 63 | // create promise 64 | var _promise = new Promise(function(resolve, reject) { 65 | var _promises = []; 66 | 67 | var p = called.forEach(function(c) { 68 | return _promises.push(self._processObject(c, prop.properties.properties)); 69 | }); 70 | 71 | return Promise.all(_promises).then(function(results) { 72 | result[name] = results; 73 | resolve(); 74 | }); 75 | }); 76 | promises.push(_promise); 77 | } else if (typeof called === 'object') { 78 | var promise = self._processObject(called, prop.properties.properties, self._edges, self._context).then(function(r) { 79 | result[name] = r; 80 | }); 81 | promises.push(promise); 82 | } else { 83 | result[name] = called; 84 | } 85 | } else { 86 | if (name in self._edges) { 87 | promises.push(self._edges[name].call(null, self._context).then(function(r) { 88 | if (prop.properties) { 89 | result[name] = r; 90 | return self._processObject(result, prop, self._edges, self._context); 91 | } else { 92 | return r; 93 | } 94 | }).then(function(res) { 95 | result[name] = res[name]; 96 | })); 97 | } 98 | } 99 | }); 100 | 101 | return Promise.all(promises).then(function() { 102 | return result; 103 | }); 104 | }; 105 | 106 | Executor.prototype._executeCalls = function (object, calls) { 107 | if (!calls) { 108 | return object; 109 | } else { 110 | var array = object; 111 | if (!Array.isArray(array)) { 112 | array = array.items; 113 | } 114 | if (calls[0].call === 'first') { 115 | return array.slice(0, +calls[0].parameters[0]); 116 | } 117 | } 118 | return {}; 119 | }; 120 | 121 | 122 | module.exports = Executor; -------------------------------------------------------------------------------- /src/models/album.js: -------------------------------------------------------------------------------- 1 | var Executor = require('../executor'); 2 | 3 | module.exports = function(apiWrapper) { 4 | 5 | var my_properties = [ 6 | 'album_type', 7 | 'artists', 8 | 'available_markets', 9 | 'copyrights', 10 | 'external_ids', 11 | 'external_urls', 12 | 'genres', 13 | 'href', 14 | 'id', 15 | 'images', 16 | 'name', 17 | 'popularity', 18 | 'release_date', 19 | 'release_date_precision', 20 | 'tracks', 21 | 'type', 22 | 'uri' 23 | ]; 24 | 25 | var edges = {}; 26 | 27 | function isCompatible(schema) { 28 | var compatible = true, 29 | props = schema.properties; 30 | for (var i = 0; i < props.length; i++) { 31 | var name = props[i].name; 32 | if (my_properties.indexOf(name) === -1) { 33 | compatible = false; 34 | } 35 | } 36 | return compatible; 37 | } 38 | 39 | function execute(schema, callback) { 40 | var albumId = schema.parameters; 41 | return apiWrapper.getAlbum(albumId).then(function(track) { 42 | var executor = new Executor(track.body, schema.properties, edges, {albumId: albumId}); 43 | return executor.execute(); 44 | }).catch(function(e) {console.error(e);}); 45 | } 46 | 47 | return { 48 | matches: function(schema) { 49 | return isCompatible(schema); 50 | }, 51 | execute: execute 52 | }; 53 | }; -------------------------------------------------------------------------------- /src/models/artist.js: -------------------------------------------------------------------------------- 1 | var Executor = require('../executor'); 2 | 3 | module.exports = function(apiWrapper) { 4 | 5 | var my_properties = [ 6 | 'external_urls', 7 | 'id', 8 | 'followers', 9 | 'genres', 10 | 'href', 11 | 'id', 12 | 'images', 13 | 'name', 14 | 'popularity', 15 | 'type', 16 | 'uri', 17 | 'toptracks' 18 | ]; 19 | 20 | var edges = { 21 | 'toptracks': function(context) { 22 | return apiWrapper.getArtistTopTracks(context.artistId, 'SE') 23 | .then(function(toptracks) { 24 | // console.log(toptracks.body); 25 | return toptracks.body.tracks; 26 | }); 27 | } 28 | }; 29 | 30 | function isCompatible(schema) { 31 | var compatible = true, 32 | props = schema.properties; 33 | for (var i = 0; i < props.length; i++) { 34 | var name = props[i].name; 35 | if (my_properties.indexOf(name) === -1) { 36 | compatible = false; 37 | } 38 | } 39 | return compatible; 40 | } 41 | 42 | function execute(schema, callback) { 43 | var artistId = schema.parameters; 44 | return apiWrapper.getArtist(artistId).then(function(artist) { 45 | var executor = new Executor(artist.body, schema.properties, edges, {artistId: artistId}); 46 | return executor.execute(); 47 | }).catch(function(e) {console.error(e);}); 48 | } 49 | 50 | return { 51 | matches: function(schema) { 52 | return isCompatible(schema); 53 | }, 54 | execute: execute 55 | }; 56 | }; -------------------------------------------------------------------------------- /src/models/track.js: -------------------------------------------------------------------------------- 1 | var Executor = require('../executor'); 2 | 3 | module.exports = function(apiWrapper) { 4 | 5 | var my_properties = [ 6 | 'artists', 7 | 'available_markets', 8 | 'disc_number', 9 | 'duration_ms', 10 | 'explicit', 11 | 'external_urls', 12 | 'href', 13 | 'id', 14 | 'is_playable', 15 | 'linked_from', 16 | 'name', 17 | 'preview_url', 18 | 'track_number', 19 | 'type', 20 | 'uri' 21 | ]; 22 | 23 | var edges = {}; 24 | 25 | function isCompatible(schema) { 26 | var compatible = true, 27 | props = schema.properties; 28 | for (var i = 0; i < props.length; i++) { 29 | var name = props[i].name; 30 | if (my_properties.indexOf(name) === -1) { 31 | compatible = false; 32 | } 33 | } 34 | return compatible; 35 | } 36 | 37 | function execute(schema, callback) { 38 | var trackId = schema.parameters; 39 | return apiWrapper.getTrack(trackId).then(function(track) { 40 | var executor = new Executor(track.body, schema.properties, edges, {trackId: trackId}); 41 | return executor.execute(); 42 | }).catch(function(e) {console.error(e);}); 43 | } 44 | 45 | return { 46 | matches: function(schema) { 47 | return isCompatible(schema); 48 | }, 49 | execute: execute 50 | }; 51 | }; -------------------------------------------------------------------------------- /src/parser/graphql-parser.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | /* 3 | * Generated by PEG.js 0.8.0. 4 | * 5 | * http://pegjs.majda.cz/ 6 | */ 7 | 8 | function peg$subclass(child, parent) { 9 | function ctor() { this.constructor = child; } 10 | ctor.prototype = parent.prototype; 11 | child.prototype = new ctor(); 12 | } 13 | 14 | function SyntaxError(message, expected, found, offset, line, column) { 15 | this.message = message; 16 | this.expected = expected; 17 | this.found = found; 18 | this.offset = offset; 19 | this.line = line; 20 | this.column = column; 21 | 22 | this.name = "SyntaxError"; 23 | } 24 | 25 | peg$subclass(SyntaxError, Error); 26 | 27 | function parse(input) { 28 | var options = arguments.length > 1 ? arguments[1] : {}, 29 | 30 | peg$FAILED = {}, 31 | 32 | peg$startRuleFunctions = { start: peg$parsestart }, 33 | peg$startRuleFunction = peg$parsestart, 34 | 35 | peg$c0 = peg$FAILED, 36 | peg$c1 = function(call, properties) { call.properties = properties.properties; return call; }, 37 | peg$c2 = null, 38 | peg$c3 = function(call) { call.root = true; return call; }, 39 | peg$c4 = [], 40 | peg$c5 = ".", 41 | peg$c6 = { type: "literal", value: ".", description: "\".\"" }, 42 | peg$c7 = function(call) { return call }, 43 | peg$c8 = function(calls) { return Array.isArray(calls) ? calls : [calls]; }, 44 | peg$c9 = function(name, parameters) { return { call: name, parameters: parameters }}, 45 | peg$c10 = "(", 46 | peg$c11 = { type: "literal", value: "(", description: "\"(\"" }, 47 | peg$c12 = ")", 48 | peg$c13 = { type: "literal", value: ")", description: "\")\"" }, 49 | peg$c14 = function(call_parameters) { return call_parameters; }, 50 | peg$c15 = function(p) { return p }, 51 | peg$c16 = function(first, rest) { return [first].concat(rest); }, 52 | peg$c17 = function(parameter_list) { return parameter_list; }, 53 | peg$c18 = /^[a-zA-Z0-9]/, 54 | peg$c19 = { type: "class", value: "[a-zA-Z0-9]", description: "[a-zA-Z0-9]" }, 55 | peg$c20 = function(parameter) { return parameter.join('') }, 56 | peg$c21 = "{", 57 | peg$c22 = { type: "literal", value: "{", description: "\"{\"" }, 58 | peg$c23 = "}", 59 | peg$c24 = { type: "literal", value: "}", description: "\"}\"" }, 60 | peg$c25 = function(properties) { return { properties: properties } }, 61 | peg$c26 = function(properties) { return properties; }, 62 | peg$c27 = function(name) { return { name: name }; }, 63 | peg$c28 = function(name, properties) { return { name: name, properties: properties } }, 64 | peg$c29 = function(name, calls, properties) { return { name: name, calls: calls, properties: properties }; }, 65 | peg$c30 = ",", 66 | peg$c31 = { type: "literal", value: ",", description: "\",\"" }, 67 | peg$c32 = /^[a-z0-9A-Z_$]/, 68 | peg$c33 = { type: "class", value: "[a-z0-9A-Z_$]", description: "[a-z0-9A-Z_$]" }, 69 | peg$c34 = function(identifier) { return identifier.join(''); }, 70 | peg$c35 = { type: "other", description: "whitespace" }, 71 | peg$c36 = /^[ \t\n\r]/, 72 | peg$c37 = { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" }, 73 | 74 | peg$currPos = 0, 75 | peg$reportedPos = 0, 76 | peg$cachedPos = 0, 77 | peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, 78 | peg$maxFailPos = 0, 79 | peg$maxFailExpected = [], 80 | peg$silentFails = 0, 81 | 82 | peg$result; 83 | 84 | if ("startRule" in options) { 85 | if (!(options.startRule in peg$startRuleFunctions)) { 86 | throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); 87 | } 88 | 89 | peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; 90 | } 91 | 92 | function text() { 93 | return input.substring(peg$reportedPos, peg$currPos); 94 | } 95 | 96 | function offset() { 97 | return peg$reportedPos; 98 | } 99 | 100 | function line() { 101 | return peg$computePosDetails(peg$reportedPos).line; 102 | } 103 | 104 | function column() { 105 | return peg$computePosDetails(peg$reportedPos).column; 106 | } 107 | 108 | function expected(description) { 109 | throw peg$buildException( 110 | null, 111 | [{ type: "other", description: description }], 112 | peg$reportedPos 113 | ); 114 | } 115 | 116 | function error(message) { 117 | throw peg$buildException(message, null, peg$reportedPos); 118 | } 119 | 120 | function peg$computePosDetails(pos) { 121 | function advance(details, startPos, endPos) { 122 | var p, ch; 123 | 124 | for (p = startPos; p < endPos; p++) { 125 | ch = input.charAt(p); 126 | if (ch === "\n") { 127 | if (!details.seenCR) { details.line++; } 128 | details.column = 1; 129 | details.seenCR = false; 130 | } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { 131 | details.line++; 132 | details.column = 1; 133 | details.seenCR = true; 134 | } else { 135 | details.column++; 136 | details.seenCR = false; 137 | } 138 | } 139 | } 140 | 141 | if (peg$cachedPos !== pos) { 142 | if (peg$cachedPos > pos) { 143 | peg$cachedPos = 0; 144 | peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; 145 | } 146 | advance(peg$cachedPosDetails, peg$cachedPos, pos); 147 | peg$cachedPos = pos; 148 | } 149 | 150 | return peg$cachedPosDetails; 151 | } 152 | 153 | function peg$fail(expected) { 154 | if (peg$currPos < peg$maxFailPos) { return; } 155 | 156 | if (peg$currPos > peg$maxFailPos) { 157 | peg$maxFailPos = peg$currPos; 158 | peg$maxFailExpected = []; 159 | } 160 | 161 | peg$maxFailExpected.push(expected); 162 | } 163 | 164 | function peg$buildException(message, expected, pos) { 165 | function cleanupExpected(expected) { 166 | var i = 1; 167 | 168 | expected.sort(function(a, b) { 169 | if (a.description < b.description) { 170 | return -1; 171 | } else if (a.description > b.description) { 172 | return 1; 173 | } else { 174 | return 0; 175 | } 176 | }); 177 | 178 | while (i < expected.length) { 179 | if (expected[i - 1] === expected[i]) { 180 | expected.splice(i, 1); 181 | } else { 182 | i++; 183 | } 184 | } 185 | } 186 | 187 | function buildMessage(expected, found) { 188 | function stringEscape(s) { 189 | function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } 190 | 191 | return s 192 | .replace(/\\/g, '\\\\') 193 | .replace(/"/g, '\\"') 194 | .replace(/\x08/g, '\\b') 195 | .replace(/\t/g, '\\t') 196 | .replace(/\n/g, '\\n') 197 | .replace(/\f/g, '\\f') 198 | .replace(/\r/g, '\\r') 199 | .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 200 | .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) 201 | .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) 202 | .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); 203 | } 204 | 205 | var expectedDescs = new Array(expected.length), 206 | expectedDesc, foundDesc, i; 207 | 208 | for (i = 0; i < expected.length; i++) { 209 | expectedDescs[i] = expected[i].description; 210 | } 211 | 212 | expectedDesc = expected.length > 1 213 | ? expectedDescs.slice(0, -1).join(", ") 214 | + " or " 215 | + expectedDescs[expected.length - 1] 216 | : expectedDescs[0]; 217 | 218 | foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; 219 | 220 | return "Expected " + expectedDesc + " but " + foundDesc + " found."; 221 | } 222 | 223 | var posDetails = peg$computePosDetails(pos), 224 | found = pos < input.length ? input.charAt(pos) : null; 225 | 226 | if (expected !== null) { 227 | cleanupExpected(expected); 228 | } 229 | 230 | return new SyntaxError( 231 | message !== null ? message : buildMessage(expected, found), 232 | expected, 233 | found, 234 | pos, 235 | posDetails.line, 236 | posDetails.column 237 | ); 238 | } 239 | 240 | function peg$parsestart() { 241 | var s0, s1, s2; 242 | 243 | s0 = peg$currPos; 244 | s1 = peg$parseroot_call(); 245 | if (s1 !== peg$FAILED) { 246 | s2 = peg$parseblock(); 247 | if (s2 !== peg$FAILED) { 248 | peg$reportedPos = s0; 249 | s1 = peg$c1(s1, s2); 250 | s0 = s1; 251 | } else { 252 | peg$currPos = s0; 253 | s0 = peg$c0; 254 | } 255 | } else { 256 | peg$currPos = s0; 257 | s0 = peg$c0; 258 | } 259 | 260 | return s0; 261 | } 262 | 263 | function peg$parseroot_call() { 264 | var s0, s1, s2; 265 | 266 | s0 = peg$currPos; 267 | s1 = peg$parsews(); 268 | if (s1 === peg$FAILED) { 269 | s1 = peg$c2; 270 | } 271 | if (s1 !== peg$FAILED) { 272 | s2 = peg$parsecall(); 273 | if (s2 !== peg$FAILED) { 274 | peg$reportedPos = s0; 275 | s1 = peg$c3(s2); 276 | s0 = s1; 277 | } else { 278 | peg$currPos = s0; 279 | s0 = peg$c0; 280 | } 281 | } else { 282 | peg$currPos = s0; 283 | s0 = peg$c0; 284 | } 285 | 286 | return s0; 287 | } 288 | 289 | function peg$parsecalls() { 290 | var s0, s1, s2, s3, s4; 291 | 292 | s0 = peg$currPos; 293 | s1 = []; 294 | s2 = peg$currPos; 295 | if (input.charCodeAt(peg$currPos) === 46) { 296 | s3 = peg$c5; 297 | peg$currPos++; 298 | } else { 299 | s3 = peg$FAILED; 300 | if (peg$silentFails === 0) { peg$fail(peg$c6); } 301 | } 302 | if (s3 !== peg$FAILED) { 303 | s4 = peg$parsecall(); 304 | if (s4 !== peg$FAILED) { 305 | peg$reportedPos = s2; 306 | s3 = peg$c7(s4); 307 | s2 = s3; 308 | } else { 309 | peg$currPos = s2; 310 | s2 = peg$c0; 311 | } 312 | } else { 313 | peg$currPos = s2; 314 | s2 = peg$c0; 315 | } 316 | if (s2 !== peg$FAILED) { 317 | while (s2 !== peg$FAILED) { 318 | s1.push(s2); 319 | s2 = peg$currPos; 320 | if (input.charCodeAt(peg$currPos) === 46) { 321 | s3 = peg$c5; 322 | peg$currPos++; 323 | } else { 324 | s3 = peg$FAILED; 325 | if (peg$silentFails === 0) { peg$fail(peg$c6); } 326 | } 327 | if (s3 !== peg$FAILED) { 328 | s4 = peg$parsecall(); 329 | if (s4 !== peg$FAILED) { 330 | peg$reportedPos = s2; 331 | s3 = peg$c7(s4); 332 | s2 = s3; 333 | } else { 334 | peg$currPos = s2; 335 | s2 = peg$c0; 336 | } 337 | } else { 338 | peg$currPos = s2; 339 | s2 = peg$c0; 340 | } 341 | } 342 | } else { 343 | s1 = peg$c0; 344 | } 345 | if (s1 !== peg$FAILED) { 346 | peg$reportedPos = s0; 347 | s1 = peg$c8(s1); 348 | } 349 | s0 = s1; 350 | 351 | return s0; 352 | } 353 | 354 | function peg$parsecall() { 355 | var s0, s1, s2; 356 | 357 | s0 = peg$currPos; 358 | s1 = peg$parseidentifier(); 359 | if (s1 !== peg$FAILED) { 360 | s2 = peg$parsecall_parameters(); 361 | if (s2 !== peg$FAILED) { 362 | peg$reportedPos = s0; 363 | s1 = peg$c9(s1, s2); 364 | s0 = s1; 365 | } else { 366 | peg$currPos = s0; 367 | s0 = peg$c0; 368 | } 369 | } else { 370 | peg$currPos = s0; 371 | s0 = peg$c0; 372 | } 373 | 374 | return s0; 375 | } 376 | 377 | function peg$parsecall_parameters() { 378 | var s0, s1, s2, s3, s4, s5; 379 | 380 | s0 = peg$currPos; 381 | s1 = peg$parsews(); 382 | if (s1 === peg$FAILED) { 383 | s1 = peg$c2; 384 | } 385 | if (s1 !== peg$FAILED) { 386 | if (input.charCodeAt(peg$currPos) === 40) { 387 | s2 = peg$c10; 388 | peg$currPos++; 389 | } else { 390 | s2 = peg$FAILED; 391 | if (peg$silentFails === 0) { peg$fail(peg$c11); } 392 | } 393 | if (s2 !== peg$FAILED) { 394 | s3 = peg$parsews(); 395 | if (s3 === peg$FAILED) { 396 | s3 = peg$c2; 397 | } 398 | if (s3 !== peg$FAILED) { 399 | s4 = peg$parseparameter_list(); 400 | if (s4 === peg$FAILED) { 401 | s4 = peg$c2; 402 | } 403 | if (s4 !== peg$FAILED) { 404 | if (input.charCodeAt(peg$currPos) === 41) { 405 | s5 = peg$c12; 406 | peg$currPos++; 407 | } else { 408 | s5 = peg$FAILED; 409 | if (peg$silentFails === 0) { peg$fail(peg$c13); } 410 | } 411 | if (s5 !== peg$FAILED) { 412 | peg$reportedPos = s0; 413 | s1 = peg$c14(s4); 414 | s0 = s1; 415 | } else { 416 | peg$currPos = s0; 417 | s0 = peg$c0; 418 | } 419 | } else { 420 | peg$currPos = s0; 421 | s0 = peg$c0; 422 | } 423 | } else { 424 | peg$currPos = s0; 425 | s0 = peg$c0; 426 | } 427 | } else { 428 | peg$currPos = s0; 429 | s0 = peg$c0; 430 | } 431 | } else { 432 | peg$currPos = s0; 433 | s0 = peg$c0; 434 | } 435 | 436 | return s0; 437 | } 438 | 439 | function peg$parseparameter_list() { 440 | var s0, s1, s2, s3, s4, s5, s6, s7, s8; 441 | 442 | s0 = peg$currPos; 443 | s1 = peg$currPos; 444 | s2 = peg$parseparameter(); 445 | if (s2 !== peg$FAILED) { 446 | s3 = []; 447 | s4 = peg$currPos; 448 | s5 = peg$parsews(); 449 | if (s5 === peg$FAILED) { 450 | s5 = peg$c2; 451 | } 452 | if (s5 !== peg$FAILED) { 453 | s6 = peg$parseproperty_separator(); 454 | if (s6 !== peg$FAILED) { 455 | s7 = peg$parsews(); 456 | if (s7 === peg$FAILED) { 457 | s7 = peg$c2; 458 | } 459 | if (s7 !== peg$FAILED) { 460 | s8 = peg$parseparameter(); 461 | if (s8 !== peg$FAILED) { 462 | peg$reportedPos = s4; 463 | s5 = peg$c15(s8); 464 | s4 = s5; 465 | } else { 466 | peg$currPos = s4; 467 | s4 = peg$c0; 468 | } 469 | } else { 470 | peg$currPos = s4; 471 | s4 = peg$c0; 472 | } 473 | } else { 474 | peg$currPos = s4; 475 | s4 = peg$c0; 476 | } 477 | } else { 478 | peg$currPos = s4; 479 | s4 = peg$c0; 480 | } 481 | while (s4 !== peg$FAILED) { 482 | s3.push(s4); 483 | s4 = peg$currPos; 484 | s5 = peg$parsews(); 485 | if (s5 === peg$FAILED) { 486 | s5 = peg$c2; 487 | } 488 | if (s5 !== peg$FAILED) { 489 | s6 = peg$parseproperty_separator(); 490 | if (s6 !== peg$FAILED) { 491 | s7 = peg$parsews(); 492 | if (s7 === peg$FAILED) { 493 | s7 = peg$c2; 494 | } 495 | if (s7 !== peg$FAILED) { 496 | s8 = peg$parseparameter(); 497 | if (s8 !== peg$FAILED) { 498 | peg$reportedPos = s4; 499 | s5 = peg$c15(s8); 500 | s4 = s5; 501 | } else { 502 | peg$currPos = s4; 503 | s4 = peg$c0; 504 | } 505 | } else { 506 | peg$currPos = s4; 507 | s4 = peg$c0; 508 | } 509 | } else { 510 | peg$currPos = s4; 511 | s4 = peg$c0; 512 | } 513 | } else { 514 | peg$currPos = s4; 515 | s4 = peg$c0; 516 | } 517 | } 518 | if (s3 !== peg$FAILED) { 519 | s4 = peg$parsews(); 520 | if (s4 === peg$FAILED) { 521 | s4 = peg$c2; 522 | } 523 | if (s4 !== peg$FAILED) { 524 | peg$reportedPos = s1; 525 | s2 = peg$c16(s2, s3); 526 | s1 = s2; 527 | } else { 528 | peg$currPos = s1; 529 | s1 = peg$c0; 530 | } 531 | } else { 532 | peg$currPos = s1; 533 | s1 = peg$c0; 534 | } 535 | } else { 536 | peg$currPos = s1; 537 | s1 = peg$c0; 538 | } 539 | if (s1 !== peg$FAILED) { 540 | peg$reportedPos = s0; 541 | s1 = peg$c17(s1); 542 | } 543 | s0 = s1; 544 | 545 | return s0; 546 | } 547 | 548 | function peg$parseparameter() { 549 | var s0, s1, s2; 550 | 551 | s0 = peg$currPos; 552 | s1 = []; 553 | if (peg$c18.test(input.charAt(peg$currPos))) { 554 | s2 = input.charAt(peg$currPos); 555 | peg$currPos++; 556 | } else { 557 | s2 = peg$FAILED; 558 | if (peg$silentFails === 0) { peg$fail(peg$c19); } 559 | } 560 | if (s2 !== peg$FAILED) { 561 | while (s2 !== peg$FAILED) { 562 | s1.push(s2); 563 | if (peg$c18.test(input.charAt(peg$currPos))) { 564 | s2 = input.charAt(peg$currPos); 565 | peg$currPos++; 566 | } else { 567 | s2 = peg$FAILED; 568 | if (peg$silentFails === 0) { peg$fail(peg$c19); } 569 | } 570 | } 571 | } else { 572 | s1 = peg$c0; 573 | } 574 | if (s1 !== peg$FAILED) { 575 | peg$reportedPos = s0; 576 | s1 = peg$c20(s1); 577 | } 578 | s0 = s1; 579 | 580 | return s0; 581 | } 582 | 583 | function peg$parseblock() { 584 | var s0, s1, s2, s3, s4, s5, s6, s7; 585 | 586 | s0 = peg$currPos; 587 | s1 = peg$parsews(); 588 | if (s1 === peg$FAILED) { 589 | s1 = peg$c2; 590 | } 591 | if (s1 !== peg$FAILED) { 592 | if (input.charCodeAt(peg$currPos) === 123) { 593 | s2 = peg$c21; 594 | peg$currPos++; 595 | } else { 596 | s2 = peg$FAILED; 597 | if (peg$silentFails === 0) { peg$fail(peg$c22); } 598 | } 599 | if (s2 !== peg$FAILED) { 600 | s3 = peg$parsews(); 601 | if (s3 === peg$FAILED) { 602 | s3 = peg$c2; 603 | } 604 | if (s3 !== peg$FAILED) { 605 | s4 = peg$parseproperties(); 606 | if (s4 !== peg$FAILED) { 607 | s5 = peg$parsews(); 608 | if (s5 === peg$FAILED) { 609 | s5 = peg$c2; 610 | } 611 | if (s5 !== peg$FAILED) { 612 | if (input.charCodeAt(peg$currPos) === 125) { 613 | s6 = peg$c23; 614 | peg$currPos++; 615 | } else { 616 | s6 = peg$FAILED; 617 | if (peg$silentFails === 0) { peg$fail(peg$c24); } 618 | } 619 | if (s6 !== peg$FAILED) { 620 | s7 = peg$parsews(); 621 | if (s7 === peg$FAILED) { 622 | s7 = peg$c2; 623 | } 624 | if (s7 !== peg$FAILED) { 625 | peg$reportedPos = s0; 626 | s1 = peg$c25(s4); 627 | s0 = s1; 628 | } else { 629 | peg$currPos = s0; 630 | s0 = peg$c0; 631 | } 632 | } else { 633 | peg$currPos = s0; 634 | s0 = peg$c0; 635 | } 636 | } else { 637 | peg$currPos = s0; 638 | s0 = peg$c0; 639 | } 640 | } else { 641 | peg$currPos = s0; 642 | s0 = peg$c0; 643 | } 644 | } else { 645 | peg$currPos = s0; 646 | s0 = peg$c0; 647 | } 648 | } else { 649 | peg$currPos = s0; 650 | s0 = peg$c0; 651 | } 652 | } else { 653 | peg$currPos = s0; 654 | s0 = peg$c0; 655 | } 656 | 657 | return s0; 658 | } 659 | 660 | function peg$parseproperties() { 661 | var s0, s1, s2, s3, s4, s5, s6, s7; 662 | 663 | s0 = peg$currPos; 664 | s1 = peg$currPos; 665 | s2 = peg$parseproperty(); 666 | if (s2 !== peg$FAILED) { 667 | s3 = []; 668 | s4 = peg$currPos; 669 | s5 = peg$parseproperty_separator(); 670 | if (s5 !== peg$FAILED) { 671 | s6 = peg$parsews(); 672 | if (s6 === peg$FAILED) { 673 | s6 = peg$c2; 674 | } 675 | if (s6 !== peg$FAILED) { 676 | s7 = peg$parseproperty(); 677 | if (s7 !== peg$FAILED) { 678 | peg$reportedPos = s4; 679 | s5 = peg$c15(s7); 680 | s4 = s5; 681 | } else { 682 | peg$currPos = s4; 683 | s4 = peg$c0; 684 | } 685 | } else { 686 | peg$currPos = s4; 687 | s4 = peg$c0; 688 | } 689 | } else { 690 | peg$currPos = s4; 691 | s4 = peg$c0; 692 | } 693 | while (s4 !== peg$FAILED) { 694 | s3.push(s4); 695 | s4 = peg$currPos; 696 | s5 = peg$parseproperty_separator(); 697 | if (s5 !== peg$FAILED) { 698 | s6 = peg$parsews(); 699 | if (s6 === peg$FAILED) { 700 | s6 = peg$c2; 701 | } 702 | if (s6 !== peg$FAILED) { 703 | s7 = peg$parseproperty(); 704 | if (s7 !== peg$FAILED) { 705 | peg$reportedPos = s4; 706 | s5 = peg$c15(s7); 707 | s4 = s5; 708 | } else { 709 | peg$currPos = s4; 710 | s4 = peg$c0; 711 | } 712 | } else { 713 | peg$currPos = s4; 714 | s4 = peg$c0; 715 | } 716 | } else { 717 | peg$currPos = s4; 718 | s4 = peg$c0; 719 | } 720 | } 721 | if (s3 !== peg$FAILED) { 722 | peg$reportedPos = s1; 723 | s2 = peg$c16(s2, s3); 724 | s1 = s2; 725 | } else { 726 | peg$currPos = s1; 727 | s1 = peg$c0; 728 | } 729 | } else { 730 | peg$currPos = s1; 731 | s1 = peg$c0; 732 | } 733 | if (s1 !== peg$FAILED) { 734 | peg$reportedPos = s0; 735 | s1 = peg$c26(s1); 736 | } 737 | s0 = s1; 738 | 739 | return s0; 740 | } 741 | 742 | function peg$parseproperty() { 743 | var s0; 744 | 745 | s0 = peg$parsecall_property(); 746 | if (s0 === peg$FAILED) { 747 | s0 = peg$parseobject_property(); 748 | if (s0 === peg$FAILED) { 749 | s0 = peg$parsesimple_property(); 750 | } 751 | } 752 | 753 | return s0; 754 | } 755 | 756 | function peg$parsesimple_property() { 757 | var s0, s1, s2; 758 | 759 | s0 = peg$currPos; 760 | s1 = peg$parseidentifier(); 761 | if (s1 !== peg$FAILED) { 762 | s2 = peg$parsews(); 763 | if (s2 === peg$FAILED) { 764 | s2 = peg$c2; 765 | } 766 | if (s2 !== peg$FAILED) { 767 | peg$reportedPos = s0; 768 | s1 = peg$c27(s1); 769 | s0 = s1; 770 | } else { 771 | peg$currPos = s0; 772 | s0 = peg$c0; 773 | } 774 | } else { 775 | peg$currPos = s0; 776 | s0 = peg$c0; 777 | } 778 | 779 | return s0; 780 | } 781 | 782 | function peg$parseobject_property() { 783 | var s0, s1, s2; 784 | 785 | s0 = peg$currPos; 786 | s1 = peg$parseidentifier(); 787 | if (s1 !== peg$FAILED) { 788 | s2 = peg$parseblock(); 789 | if (s2 !== peg$FAILED) { 790 | peg$reportedPos = s0; 791 | s1 = peg$c28(s1, s2); 792 | s0 = s1; 793 | } else { 794 | peg$currPos = s0; 795 | s0 = peg$c0; 796 | } 797 | } else { 798 | peg$currPos = s0; 799 | s0 = peg$c0; 800 | } 801 | 802 | return s0; 803 | } 804 | 805 | function peg$parsecall_property() { 806 | var s0, s1, s2, s3; 807 | 808 | s0 = peg$currPos; 809 | s1 = peg$parseidentifier(); 810 | if (s1 !== peg$FAILED) { 811 | s2 = peg$parsecalls(); 812 | if (s2 !== peg$FAILED) { 813 | s3 = peg$parseblock(); 814 | if (s3 !== peg$FAILED) { 815 | peg$reportedPos = s0; 816 | s1 = peg$c29(s1, s2, s3); 817 | s0 = s1; 818 | } else { 819 | peg$currPos = s0; 820 | s0 = peg$c0; 821 | } 822 | } else { 823 | peg$currPos = s0; 824 | s0 = peg$c0; 825 | } 826 | } else { 827 | peg$currPos = s0; 828 | s0 = peg$c0; 829 | } 830 | 831 | return s0; 832 | } 833 | 834 | function peg$parseproperty_separator() { 835 | var s0; 836 | 837 | if (input.charCodeAt(peg$currPos) === 44) { 838 | s0 = peg$c30; 839 | peg$currPos++; 840 | } else { 841 | s0 = peg$FAILED; 842 | if (peg$silentFails === 0) { peg$fail(peg$c31); } 843 | } 844 | 845 | return s0; 846 | } 847 | 848 | function peg$parseidentifier() { 849 | var s0, s1, s2; 850 | 851 | s0 = peg$currPos; 852 | s1 = []; 853 | if (peg$c32.test(input.charAt(peg$currPos))) { 854 | s2 = input.charAt(peg$currPos); 855 | peg$currPos++; 856 | } else { 857 | s2 = peg$FAILED; 858 | if (peg$silentFails === 0) { peg$fail(peg$c33); } 859 | } 860 | if (s2 !== peg$FAILED) { 861 | while (s2 !== peg$FAILED) { 862 | s1.push(s2); 863 | if (peg$c32.test(input.charAt(peg$currPos))) { 864 | s2 = input.charAt(peg$currPos); 865 | peg$currPos++; 866 | } else { 867 | s2 = peg$FAILED; 868 | if (peg$silentFails === 0) { peg$fail(peg$c33); } 869 | } 870 | } 871 | } else { 872 | s1 = peg$c0; 873 | } 874 | if (s1 !== peg$FAILED) { 875 | peg$reportedPos = s0; 876 | s1 = peg$c34(s1); 877 | } 878 | s0 = s1; 879 | 880 | return s0; 881 | } 882 | 883 | function peg$parsews() { 884 | var s0, s1; 885 | 886 | peg$silentFails++; 887 | s0 = []; 888 | if (peg$c36.test(input.charAt(peg$currPos))) { 889 | s1 = input.charAt(peg$currPos); 890 | peg$currPos++; 891 | } else { 892 | s1 = peg$FAILED; 893 | if (peg$silentFails === 0) { peg$fail(peg$c37); } 894 | } 895 | while (s1 !== peg$FAILED) { 896 | s0.push(s1); 897 | if (peg$c36.test(input.charAt(peg$currPos))) { 898 | s1 = input.charAt(peg$currPos); 899 | peg$currPos++; 900 | } else { 901 | s1 = peg$FAILED; 902 | if (peg$silentFails === 0) { peg$fail(peg$c37); } 903 | } 904 | } 905 | peg$silentFails--; 906 | if (s0 === peg$FAILED) { 907 | s1 = peg$FAILED; 908 | if (peg$silentFails === 0) { peg$fail(peg$c35); } 909 | } 910 | 911 | return s0; 912 | } 913 | 914 | peg$result = peg$startRuleFunction(); 915 | 916 | if (peg$result !== peg$FAILED && peg$currPos === input.length) { 917 | return peg$result; 918 | } else { 919 | if (peg$result !== peg$FAILED && peg$currPos < input.length) { 920 | peg$fail({ type: "end", description: "end of input" }); 921 | } 922 | 923 | throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos); 924 | } 925 | } 926 | 927 | return { 928 | SyntaxError: SyntaxError, 929 | parse: parse 930 | }; 931 | })(); 932 | -------------------------------------------------------------------------------- /src/parser/graphql.pegjs: -------------------------------------------------------------------------------- 1 | start 2 | = call:root_call properties:block 3 | { call.properties = properties.properties; return call; } 4 | 5 | root_call 6 | = ws? call:call 7 | { call.root = true; return call; } 8 | 9 | calls 10 | = calls:("." call:call { return call })+ 11 | { return Array.isArray(calls) ? calls : [calls]; } 12 | 13 | call 14 | = name:call_name parameters:call_parameters 15 | { return { call: name, parameters: parameters }} 16 | 17 | call_name 18 | = identifier 19 | 20 | call_parameters 21 | = ws? '(' ws? call_parameters:parameter_list? ')' 22 | { return call_parameters; } 23 | 24 | parameter_list 25 | = parameter_list:( 26 | first:parameter 27 | rest:(ws? property_separator ws? p:parameter { return p })* 28 | ws? 29 | { return [first].concat(rest); } 30 | ) 31 | { return parameter_list; } 32 | 33 | parameter 34 | = parameter:[a-zA-Z0-9]+ { return parameter.join('') } 35 | 36 | block 37 | = ws? '{' ws? properties:properties ws? '}' ws? 38 | { return { properties: properties } } 39 | 40 | properties 41 | = properties:( 42 | first:property 43 | rest:(property_separator ws? p:property { return p })* 44 | { return [first].concat(rest); } 45 | ) 46 | { return properties; } 47 | 48 | property 49 | = call_property 50 | / object_property 51 | / simple_property 52 | 53 | simple_property 54 | = name:identifier ws? 55 | { return { name: name }; } 56 | 57 | object_property 58 | = name:identifier properties:block 59 | { return { name: name, properties: properties } } 60 | 61 | call_property 62 | = name:identifier calls:calls properties:block 63 | { return { name: name, calls: calls, properties: properties }; } 64 | 65 | property_separator 66 | = ',' 67 | 68 | identifier 69 | = identifier:[a-z0-9A-Z_\$]+ { return identifier.join(''); } 70 | 71 | ws 'whitespace' 72 | = [ \t\n\r]* 73 | -------------------------------------------------------------------------------- /src/spotify-graphql.js: -------------------------------------------------------------------------------- 1 | var parser = require('./parser/graphql-parser'); 2 | var SpotifyWebApi = require('spotify-web-api-node'); 3 | var AlbumModel = require('./models/album'); 4 | var TrackModel = require('./models/track'); 5 | var ArtistModel = require('./models/artist'); 6 | 7 | var SpotifyWebApiGraphQL = function() { 8 | this._models = []; 9 | this._apiWrapper = null; 10 | }; 11 | 12 | SpotifyWebApiGraphQL.prototype.init = function() { 13 | this._apiWrapper = new SpotifyWebApi(); 14 | this._models.push(new AlbumModel(this._apiWrapper)); 15 | this._models.push(new TrackModel(this._apiWrapper)); 16 | this._models.push(new ArtistModel(this._apiWrapper)); 17 | }; 18 | 19 | SpotifyWebApiGraphQL.prototype.execute = function(query, callback) { 20 | var parsed = parser.parse(query); 21 | for (var i = 0; i < this._models.length; i++) { 22 | if (this._models[i].matches(parsed)) { 23 | return this._models[i].execute(parsed).then(function(result) { 24 | callback(result); 25 | }); 26 | } 27 | } 28 | callback(null); 29 | }; 30 | 31 | module.exports = SpotifyWebApiGraphQL; --------------------------------------------------------------------------------