Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further fixes on pull #2 #3

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
MOCHA_TARGET=test/specs.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove Makefile
use npm scripts
https://www.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/


test:
make testonly && make lint

testonly:
mocha $(MOCHA_TARGET)

testonly-watch:
mocha -w $(MOCHA_TARGET)

lint:
standard .

.PHONY: test testonly testonly-watch lint
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,25 @@ npm install graphql-client -S

## How To
```javascript
var client = require('graphql-client')({url: 'http://your-host/graphql'})
var client = require('graphql-client')({
url: 'http://your-host/graphql'
})
// Before request hook
.on('request', (req) => {
// Do whatever you want with `Request` instance, e.g. add JWT auth header
if (authenticated) {
req.headers.set('Authentication', 'Bearer ' + token)
}
})
// On response hook. Access `Response` instance before parsing it's body
.on('response', (res) => {
...
})
// After response is parsed as JSON
.on('data', (data) => {
console.log('GraphQL response:', data)
})


var query = `
query search ($query: String, $from: Int, $limit: Int) {
Expand Down
162 changes: 115 additions & 47 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,129 @@
function highlightQuery (query, errors) {
var locations = errors.map(function (e) { return e.locations })
.reduce(function (a, b) {
return a.concat(b)
}, [])
/* global fetch, Headers */
require('isomorphic-fetch')

var queryHighlight = ''
function Client (options) {
var self = this

query.split('\n').forEach(function (row, index) {
var line = index + 1
var lineErrors = locations.filter(function (loc) { return loc.line === line })
if (!options.url) throw new Error('Missing url parameter')

queryHighlight += row + '\n'
self.options = options
self.url = options.url

if (lineErrors.length) {
var errorHighlight = []
// A stack of registered listeners
self.listeners = []
}

lineErrors.forEach(function (line) {
for (var i = 0; i < 8; i++) {
errorHighlight[line.column + i] = '~'
}
})
// to reduce file size
var proto = Client.prototype

for (var i = 0; i < errorHighlight.length; i++) {
queryHighlight += errorHighlight[i] || ' '
}
queryHighlight += '\n'
/**
* Send a query and get a Promise
* @param {String} query
* @param {Object} variables
* @param {Function} beforeRequest hook
* @returns {Promise}
*/
proto.query = function (query, variables, beforeRequest) {
var self = this

var req = self.options.request || {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make this to an actual request object Fetch API Request.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! Fixed this as well

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to switch back to plain req object since there were many problems with Request. For example, I couldn't pass headers to it when initializing the client because they could only be modified after the Request object is initialized. Also, I couldn't pass "credentials" to it, because of a bug in isomorphic-fetch. There were some other issues like a difference in behaviour between browser and Node, so it is just not the way to go

req.method || (req.method = 'POST')
if (!req.headers) {
req.headers = new Headers()
req.headers.set('content-type', 'application/json')
}
req.body = JSON.stringify({
query: query,
variables: variables
})

// 'beforeRequest' is a top priority per-query hook, it should forcibly
// override response even from other hooks.
var result = beforeRequest && beforeRequest(req)

if (typeof result === 'undefined') {
result = self.emit('request', req)

// No 'response' hook here, reserve it for real responses only.

// 'data' hook is only triggered if there are any data
if (typeof result !== 'undefined') {
var data = self.emit('data', result, true) // `true` for fake data
if (typeof data !== 'undefined') result = data
}
}

if (typeof result !== 'undefined') {
result = Promise.resolve(result)
}
return result || self.fetch(req)
}

/**
* For making requests
* @param {Object} req
* @returns Promise
*/
proto.fetch = function (req) {
var self = this

return fetch(self.url, req).then(function (res) {
// 'response' hook can redefine `res`
var _res = self.emit('response', res)
if (typeof _res !== 'undefined') res = _res

return res.json()
}).then(function (data) {
// 'data' hook can redefine `data`
var _data = self.emit('data', data)
if (typeof _data !== 'undefined') data = _data

return data
})
}

/**
* Register a listener.
* @param {String} eventName - 'request', 'response', 'data'
* @param {Function} callback
* @returns Client instance
*/
proto.on = function (eventName, callback) {
var allowedNames = ['request', 'response', 'data']

if (~allowedNames.indexOf(eventName)) {
this.listeners.push([ eventName, callback ])
}

return queryHighlight
return this
}

module.exports = function (params) {
require('isomorphic-fetch')
if (!params.url) throw new Error('Missing url parameter')

return {
query: function (query, variables) {
var headers = new Headers()
headers.append('Content-Type', 'application/json')

return fetch(params.url, {
method: 'POST',
body: JSON.stringify({
query: query,
variables: variables
}),
headers: headers,
credentials: params.credentials
}).then(function (res) {
return res.json()
}).then(function (data) {
if (data.errors && data.errors.length) {
throw new Error(data.errors.map(function (e) { return e.message }).join('\n') + '\n' + highlightQuery(query, data.errors))
}
return data
})
/**
* Emit an event.
* @param {String} eventName - 'request', 'response', 'data'
* @param {mixed} ...args
* @returns {Array} array of results received from each listener respectively
*/
proto.emit = function (eventName) {
var args = Array.prototype.slice.call(arguments, 1)
var listeners = this.listeners
var result

// Triggering listeners and gettings latest result
for (var i = 0; i < listeners.length; i++) {
if (listeners[i][0] === eventName) {
var r = listeners[i][1].apply(this, args)
if (typeof r !== 'undefined') {
result = r
}
}
}

return result
}

module.exports = function (options) {
return new Client(options)
}

module.exports.Client = Client
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "make test"
},
"files": [
"index.js",
"README.md"
],
"repository": {
"type": "git",
"url": "https://github.com/nordsimon/graphql-client"
Expand All @@ -15,5 +19,9 @@
"license": "ISC",
"dependencies": {
"isomorphic-fetch": "^2.2.1"
},
"devDependencies": {
"chai": "^3.5.0",
"graphql": "^0.6.2"
}
}
36 changes: 36 additions & 0 deletions test/lib/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString
} = require('graphql')

const data = [
{ id: '1', name: 'Dan' },
{ id: '2', name: 'Marie' },
{ id: '3', name: 'Jessie' }
]

const userType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString }
}
})

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
args: {
id: { type: GraphQLString }
},
resolve: (_, args) => data.find((u) => u.id === args.id)
}
}
})
})

module.exports = schema
54 changes: 54 additions & 0 deletions test/lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const http = require('http')
const schema = require('./schema')
const { graphql } = require('graphql')

module.exports = http.createServer((req, res) => {
if (req.url === '/graphql') {
let body = ''

req.on('data', function (data) {
body += data
})

req.on('end', function () {
let query = body
let variables
let operationName

if (~req.headers['content-type'].indexOf('application/json')) {
try {
const obj = JSON.parse(query)
if (obj.query && typeof obj.query === 'string') {
query = obj.query
}
if (obj.variables !== undefined) {
variables = obj.variables
}
// Name of GraphQL operation to execute.
if (typeof obj.operationName === 'string') {
operationName = obj.operationName
}
} catch (err) {
// do nothing
}
}

res.writeHead(200, {'content-type': 'text/json'})

graphql(schema, query, null, variables, operationName).then((result) => {
let response = result

if (result.errors) {
res.statusCode = 400
response = {
errors: result.errors.map(String)
}
}

res.end(JSON.stringify(response))
}).catch((e) => {
res.end(JSON.stringify(e))
})
})
}
})
Loading