Skip to content
Snippets Groups Projects
Commit 3d561f9b authored by Jure's avatar Jure
Browse files

Merge branch 'graphql-types' into 'master'

GraphQL endpoint with basic queries

Closes #317

See merge request pubsweet/pubsweet!51
parents c2e03208 71383e39
No related branches found
No related tags found
No related merge requests found
Showing
with 852 additions and 2 deletions
...@@ -4,6 +4,7 @@ const winston = require('winston') ...@@ -4,6 +4,7 @@ const winston = require('winston')
module.exports = { module.exports = {
'pubsweet-server': { 'pubsweet-server': {
logger: new winston.Logger({ level: 'warn' }), logger: new winston.Logger({ level: 'warn' }),
port: 4000,
secret: 'test', secret: 'test',
sse: false, sse: false,
}, },
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
"main": "src/index.js", "main": "src/index.js",
"dependencies": { "dependencies": {
"@pubsweet/logger": "^0.2.1", "@pubsweet/logger": "^0.2.1",
"apollo-server-express": "^1.3.2",
"authsome": "0.0.9", "authsome": "0.0.9",
"bcrypt": "^1.0.2", "bcrypt": "^1.0.2",
"bluebird": "^3.5.1", "bluebird": "^3.5.1",
...@@ -24,6 +25,8 @@ ...@@ -24,6 +25,8 @@
"cookie-parser": "^1.4.3", "cookie-parser": "^1.4.3",
"dotenv": "^4.0.0", "dotenv": "^4.0.0",
"express": "^4.16.1", "express": "^4.16.1",
"graphql": "^0.12.3",
"graphql-tools": "^2.18.0",
"helmet": "^3.8.1", "helmet": "^3.8.1",
"http-status-codes": "^1.0.6", "http-status-codes": "^1.0.6",
"joi": "^13.1.0", "joi": "^13.1.0",
......
const authsome = require('../helpers/authsome')
const AuthorizationError = require('../errors/AuthorizationError')
const NotFoundError = require('../errors/NotFoundError')
// check permissions or throw authroization error
async function can(userId, verb, entity) {
const permission = await authsome.can(userId, verb, entity)
if (!permission) {
throw new AuthorizationError(
`Operation not permitted: ${verb} ${entity.type || entity}`,
)
}
// return identity if no filter function
return permission.filter || (id => id)
}
// check 'read' permissions or throw not found error (to avoid leaking the existence of data)
async function canKnowAbout(userId, entity) {
const permission = await authsome.can(userId, 'read', entity)
if (!permission) {
throw new NotFoundError(
`Object not found: ${entity.type} with id ${entity.id}`,
)
}
// return identity if no filter function
return permission.filter || (id => id)
}
// create a function which creates a new entity and performs authorization checks
function createCreator(entityName, EntityModel) {
return async (input, ctx) => {
await can(ctx.user, 'create', entityName)
const entity = new EntityModel(input)
const outputFilter = await canKnowAbout(ctx.user, entity)
await can(ctx.user, 'create', entity)
return outputFilter(await entity.save())
}
}
// create a function which deletes an entity and performs authorization checks
function deleteCreator(entityName, EntityModel) {
return async (id, ctx) => {
await can(ctx.user, 'delete', entityName)
const entity = await EntityModel.find(id)
const outputFilter = await canKnowAbout(ctx.user, entity)
await can(ctx.user, 'delete', entity)
return outputFilter(await entity.delete())
}
}
// create a function which updates a new entity and performs authorization checks
function updateCreator(entityName, EntityModel) {
return async (id, input, ctx) => {
await can(ctx.user, 'update', entityName)
const entity = await EntityModel.find(id)
const outputFilter = await canKnowAbout(ctx.user, entity)
const inputFilter = await can(ctx.user, 'update', entity)
await entity.updateProperties(inputFilter(input))
return outputFilter(await entity.save())
}
}
// create a function which fetches all entities of the
// given model and performs authorization checks
function fetchAllCreator(entityName, EntityModel) {
return async ctx => {
await can(ctx.user, 'read', entityName)
const entities = await EntityModel.all()
// check permissions (in parallel) and swallow exceptions
const permissions = await Promise.all(
entities.map(entity => can(ctx.user, 'read', entity).catch(() => false)),
)
// apply permissions
return entities.reduce((filtered, entity, index) => {
const permissionOrFilter = permissions[index]
if (permissionOrFilter) {
filtered.push(permissionOrFilter(entity))
}
return filtered
}, [])
}
}
// create a function which fetches by ID a single entity
// of the given model and performs authorization checks
function fetchOneCreator(entityName, EntityModel) {
return async (id, ctx) => {
await can(ctx.user, 'read', entityName)
const entity = await EntityModel.find(id)
const outputFilter = await canKnowAbout(ctx.user, entity)
return outputFilter(entity)
}
}
// create a function which fetches a number of entities by ID
// and delegates authorization checks
function fetchSomeCreator(fetchOne) {
return (ids, ctx) => Promise.all(ids.map(id => fetchOne(id, ctx)))
}
// create a connector object with fetchers for all, one and some
function connectorCreator(entityName, EntityModel) {
const create = createCreator(entityName, EntityModel)
const del = deleteCreator(entityName, EntityModel)
const update = updateCreator(entityName, EntityModel)
const fetchAll = fetchAllCreator(entityName, EntityModel)
const fetchOne = fetchOneCreator(entityName, EntityModel)
const fetchSome = fetchSomeCreator(fetchOne)
return { create, delete: del, update, fetchAll, fetchOne, fetchSome }
}
module.exports = { connectorCreator }
const { connectorCreator } = require('./creators')
const Collection = require('../models/Collection')
const Fragment = require('../models/Fragment')
const Team = require('../models/Team')
const User = require('../models/User')
module.exports = {
collection: connectorCreator('collections', Collection),
fragment: connectorCreator('fragments', Fragment),
team: connectorCreator('teams', Team),
user: connectorCreator('users', User),
}
const logger = require('@pubsweet/logger')
const User = require('../../models/User')
const authentication = require('../../authentication')
const resolvers = {
Query: {
async currentUser(_, vars, ctx) {
const user = await User.find(ctx.user)
return {
user,
token: authentication.token.create(user),
}
},
},
Mutation: {
async loginUser(_, { input }) {
let isValid = false
let user
try {
user = await User.findByUsername(input.username)
isValid = await user.validPassword(input.password)
} catch (err) {
logger.debug(err)
}
if (!isValid) {
throw new Error('Wrong username or password.')
}
return {
user,
token: authentication.token.create(user),
}
},
},
}
const typeDefs = `
extend type Query {
# Get the currently authenticated user based on the JWT in the HTTP headers
currentUser: LoginResult
}
extend type Mutation {
# Authenticate a user using username and password
loginUser(input: LoginUserInput): LoginResult
}
# User details and bearer token
type LoginResult {
user: User!
token: String!
}
input LoginUserInput {
username: String!
password: String!
}
`
module.exports = { resolvers, typeDefs }
const resolvers = {
Query: {
collection(_, { id }, ctx) {
return ctx.connectors.collection.fetchOne(id, ctx)
},
collections(ctx) {
return ctx.connectors.collection.fetchAll(ctx)
},
},
Mutation: {
deleteCollection(_, { id }, ctx) {
return ctx.connectors.collection.delete(id, ctx)
},
createCollection(_, { input }, ctx) {
return ctx.connectors.collection.create(input, ctx)
},
},
Collection: {
owners(collection, vars, ctx) {
return collection.owners
? ctx.connectors.user.fetchSome(collection.owners, ctx)
: []
},
},
}
const typeDefs = `
extend type Query {
collection(id: ID): Collection
collections: [Collection]
}
extend type Mutation {
createCollection(input: CollectionInput): Collection
deleteCollection(id: ID): Collection
}
type Collection {
id: ID!
type: String!
owners: [User!]!
fragments: [Fragment!]!
}
input CollectionInput {
owners: [ID!]
fragments: [ID!]
}
`
module.exports = { resolvers, typeDefs }
const resolvers = {
Query: {
fragment(_, { id }, ctx) {
return ctx.connectors.fragment.fetchOne(id, ctx)
},
fragments(ctx) {
return ctx.connectors.fragment.fetchAll(ctx)
},
},
Mutation: {
deleteFragment(_, { id }, ctx) {
return ctx.connectors.fragment.delete(id, ctx)
},
createFragment(_, { input }, ctx) {
return ctx.connectors.fragment.create(input, ctx)
},
},
Fragment: {
owners(fragment, vars, ctx) {
return fragment.owners
? ctx.connectors.user.fetchSome(fragment.owners, ctx)
: []
},
},
}
const typeDefs = `
extend type Query {
fragment(id: ID): Fragment
fragments: [Fragment]
}
extend type Mutation {
createFragment(input: FragmentInput): Fragment
deleteFragment(id: ID): Fragment
}
type Fragment {
id: ID!
type: String!
fragmentType: String
fragments: [Fragment!]!
owners: [User!]!
}
input FragmentInput {
owners: [ID!]
fragments: [ID!]
}
`
module.exports = { resolvers, typeDefs }
const resolvers = {
Query: {
team(_, { id }, ctx) {
return ctx.connectors.team.fetchOne(id, ctx)
},
teams(_, vars, ctx) {
return ctx.connectors.team.fetchAll(ctx)
},
},
Mutation: {
deleteTeam(_, { id }, ctx) {
return ctx.connectors.team.delete(id, ctx)
},
createTeam(_, { input }, ctx) {
return ctx.connectors.team.create(input, ctx)
},
},
Team: {
members(team, vars, ctx) {
return team.members
? ctx.connectors.user.fetchSome(team.members, ctx)
: []
},
},
}
const typeDefs = `
extend type Query {
team(id: ID): Team
teams: [Team]
}
extend type Mutation {
createTeam(input: TeamInput): Team
deleteTeam(id: ID): Team
}
type Team {
id: ID!
type: String!
teamType: String!
name: String!
object: ID!
members: [User!]!
}
input TeamInput {
owners: [ID!]
fragments: [ID!]
}
`
module.exports = { resolvers, typeDefs }
const resolvers = {
Query: {
user(_, { id }, ctx) {
return ctx.connectors.user.fetchOne(id, ctx)
},
users(_, vars, ctx) {
return ctx.connectors.user.fetchAll(ctx)
},
},
Mutation: {
createUser(_, { input }, ctx) {
return ctx.connectors.user.create(input, ctx)
},
deleteUser(_, { id }, ctx) {
return ctx.connectors.user.delete(id, ctx)
},
updateUser(_, { id, input }, ctx) {
return ctx.connectors.user.update(id, input, ctx)
},
},
User: {
collections(user, vars, ctx) {
return user.collections
? ctx.connectors.collection.fetchSome(user.collections, ctx)
: []
},
teams(user, vars, ctx) {
return user.teams ? ctx.connectors.team.fetchSome(user.teams, ctx) : []
},
fragments(user, vars, ctx) {
return user.fragments
? ctx.connectors.fragment.fetchSome(user.fragments, ctx)
: []
},
},
}
const typeDefs = `
extend type Query {
user(id: ID): User
users: [User]
}
extend type Mutation {
createUser(input: UserInput): User
deleteUser(id: ID): User
updateUser(id: ID, input: UserInput): User
}
type User {
id: ID!
type: String
username: String!
email: String!
admin: Boolean
teams: [Team!]!
fragments: [Fragment!]!
collections: [Collection!]!
}
input UserInput {
username: String!
email: String!
password: String
rev: String
}
`
module.exports = { resolvers, typeDefs }
const express = require('express')
const passport = require('passport')
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express')
const config = require('config')
const graphqlSchema = require('./schema')
const connectors = require('../connectors')
const authBearerAndPublic = passport.authenticate(['bearer', 'anonymous'], {
session: false,
})
const router = new express.Router()
router.use(
'/graphql',
authBearerAndPublic,
graphqlExpress(req => ({
schema: graphqlSchema,
context: { user: req.user, connectors },
})),
)
if (
config.has('pubsweet-server.graphiql') &&
config.get('pubsweet-server.graphiql')
) {
router.get(
'/graphiql',
authBearerAndPublic,
graphiqlExpress({ endpointURL: '/graphql' }),
)
}
module.exports = router
const config = require('config')
const requireRelative = require('require-relative')
const { merge } = require('lodash')
const { makeExecutableSchema } = require('graphql-tools')
const collection = require('./definitions/collection')
const fragment = require('./definitions/fragment')
const team = require('./definitions/team')
const user = require('./definitions/user')
const authentication = require('./definitions/authentication')
// load base types and resolvers
const typeDefs = [
`type Query, type Mutation`,
collection.typeDefs,
fragment.typeDefs,
team.typeDefs,
user.typeDefs,
authentication.typeDefs,
]
const resolvers = merge(
{},
collection.resolvers,
fragment.resolvers,
team.resolvers,
user.resolvers,
authentication.resolvers,
)
// merge in component types and resolvers
if (config.has('pubsweet.components')) {
config.get('pubsweet.components').forEach(name => {
const component = requireRelative(name)
if (component.typeDefs) {
typeDefs.push(component.typeDefs)
}
if (component.resolvers) {
merge(resolvers, component.resolvers)
}
})
}
// merge in app-specific types and resolvers from config
if (config.has('pubsweet-server.typeDefs')) {
typeDefs.push(config.get('pubsweet-server.typeDefs'))
}
if (config.has('pubsweet-server.resolvers')) {
merge(resolvers, config.get('pubsweet-server.resolvers'))
}
module.exports = makeExecutableSchema({ typeDefs, resolvers })
...@@ -10,6 +10,7 @@ const helmet = require('helmet') ...@@ -10,6 +10,7 @@ const helmet = require('helmet')
const cookieParser = require('cookie-parser') const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const passport = require('passport') const passport = require('passport')
const graphqlApi = require('./graphql/routes')
const index = require('./routes/index') const index = require('./routes/index')
const api = require('./routes/api') const api = require('./routes/api')
const authsome = require('./helpers/authsome') const authsome = require('./helpers/authsome')
...@@ -47,9 +48,15 @@ const configureApp = app => { ...@@ -47,9 +48,15 @@ const configureApp = app => {
registerComponents(app) registerComponents(app)
// Main API // REST API
app.use('/api', api) app.use('/api', api)
// GraphQL API
// temporary environment check while this stuff is in beta
if (['development', 'test'].includes(config.util.getEnv('NODE_ENV'))) {
app.use(graphqlApi)
}
// SSE update stream // SSE update stream
if (_.get('pubsweet-server.sse', config)) { if (_.get('pubsweet-server.sse', config)) {
app.get( app.get(
......
const { omit } = require('lodash')
const authsome = require('../src/helpers/authsome')
const User = require('../src/models/User')
const Team = require('../src/models/Team')
const cleanDB = require('./helpers/db_cleaner')
const fixtures = require('./fixtures/fixtures')
const api = require('./helpers/api')
const authentication = require('../src/authentication')
describe('GraphQL endpoint', () => {
let token
let user
beforeEach(async () => {
await cleanDB()
user = await new User(fixtures.adminUser).save()
token = authentication.token.create(user)
})
describe('queries', () => {
it('can resolve all users', async () => {
const { body } = await api.graphql.query(
`{ users { username, admin } }`,
{},
token,
)
expect(body).toEqual({
data: { users: [{ username: 'admin', admin: true }] },
})
})
it('can resolve user by ID', async () => {
const { body } = await api.graphql.query(
`query($id: ID) {
user(id: $id) {
username
admin
}
}`,
{ id: user.id },
token,
)
expect(body).toEqual({
data: { user: { username: 'admin', admin: true } },
})
})
it('can resolve nested query', async () => {
await new Team({ ...fixtures.contributorTeam, members: [user.id] }).save()
const { body } = await api.graphql.query(
`{ users { username, teams { name } } }`,
{},
token,
)
expect(body).toEqual({
data: {
users: [{ username: 'admin', teams: [{ name: 'My contributors' }] }],
},
})
})
})
describe('mutations', () => {
it('can create a user', async () => {
const { body } = await api.graphql.query(
`mutation($input: UserInput) {
createUser(input: $input) { username }
}`,
{
input: {
username: 'floobs',
email: 'nobody@example.com',
password: 'password',
},
},
token,
)
expect(body).toEqual({
data: {
createUser: { username: 'floobs' },
},
})
})
it('can update a user', async () => {
const { body } = await api.graphql.query(
`mutation($id: ID, $input: UserInput) {
updateUser(id: $id, input: $input) { username, email }
}`,
{
id: user.id,
input: {
username: 'floobs',
email: 'nobody@example.com',
rev: user.rev,
},
},
token,
)
expect(body).toEqual({
data: {
updateUser: { username: 'floobs', email: 'nobody@example.com' },
},
})
})
it('can delete a user', async () => {
const { body } = await api.graphql.query(
`mutation($id: ID) {
deleteUser(id: $id) { username }
}`,
{ id: user.id },
token,
)
expect(body).toEqual({
data: { deleteUser: { username: 'admin' } },
})
})
})
describe('auth', () => {
it('can log in', async () => {
const { body } = await api.graphql.query(
`mutation($input: LoginUserInput) {
loginUser(input: $input) {
user { username }
token
}
}`,
{ input: { username: 'admin', password: 'admin' } },
)
expect(body).toMatchObject({
data: {
loginUser: { token: expect.any(String), user: { username: 'admin' } },
},
})
})
it('blocks invalid login', async () => {
const { body } = await api.graphql.query(
`mutation($input: LoginUserInput) {
loginUser(input: $input) {
token
}
}`,
{ input: { username: 'admin', password: 'not correct' } },
)
expect(body).toMatchObject({
data: { loginUser: null },
errors: [{ message: 'Wrong username or password.' }],
})
})
it('fetches current user from token', async () => {
const { body } = await api.graphql.query(
`{ currentUser { user { username, email } token} }`,
{},
token,
)
expect(body).toMatchObject({
data: {
currentUser: {
user: { username: 'admin', email: 'admin@admins.example.org' },
token: expect.any(String),
},
},
})
})
it('errors when unauthenticated', async () => {
const { body } = await api.graphql.query(`{ users { username } }`)
expect(body).toMatchObject({
data: { users: null },
errors: [{ message: 'Operation not permitted: read users' }],
})
})
it('filters the returned data', async () => {
jest
.spyOn(authsome, 'can')
.mockReturnValue({ filter: user => omit(user, 'admin') })
const { body } = await api.graphql.query(
`{ users { username, admin } }`,
{},
token,
)
expect(body).toEqual({
data: { users: [{ username: 'admin', admin: null }] },
})
})
it('returns not found if not authorized', async () => {
jest
.spyOn(authsome, 'can')
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
const { body } = await api.graphql.query(
`query($id: ID) {
user(id: $id) {
username
admin
}
}`,
{ id: user.id },
token,
)
expect(body).toMatchObject({
data: { user: null },
errors: [{ message: `Object not found: user with id ${user.id}` }],
})
})
})
})
...@@ -285,11 +285,22 @@ const upload = { ...@@ -285,11 +285,22 @@ const upload = {
}, },
} }
const graphql = {
query: (query, variables, token) => {
const req = request(api)
.post('/graphql')
.send({ query, variables })
if (token) req.set('Authorization', `Bearer ${token}`)
return req
},
}
module.exports = { module.exports = {
fragments, fragments,
users, users,
collections, collections,
teams, teams,
upload, upload,
graphql,
api, api,
} }
...@@ -6,6 +6,8 @@ const mockComponent = { ...@@ -6,6 +6,8 @@ const mockComponent = {
res.status(STATUS.OK).json({ ok: '!' }), res.status(STATUS.OK).json({ ok: '!' }),
) )
}, },
typeDefs: `extend type Query { test: String }`,
resolvers: { Query: { test: () => 'OK' } },
} }
module.exports = mockComponent module.exports = mockComponent
...@@ -13,4 +13,9 @@ describe('App startup', async () => { ...@@ -13,4 +13,9 @@ describe('App startup', async () => {
const res = await request(api.api).get('/mock-component') const res = await request(api.api).get('/mock-component')
expect(res.status).toBe(STATUS.OK) expect(res.status).toBe(STATUS.OK)
}) })
it('loads graphql types and resolvers', async () => {
const res = await api.graphql.query('{ test }')
expect(res.body).toEqual({ data: { test: 'OK' } })
})
}) })
...@@ -221,6 +221,10 @@ ...@@ -221,6 +221,10 @@
version "8.0.58" version "8.0.58"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.58.tgz#5b3881c0be3a646874803fee3197ea7f1ed6df90" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.58.tgz#5b3881c0be3a646874803fee3197ea7f1ed6df90"
"@types/zen-observable@0.5.3":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.3.tgz#91b728599544efbb7386d8b6633693a3c2e7ade5"
JSONStream@^1.0.4: JSONStream@^1.0.4:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
...@@ -415,6 +419,49 @@ anymatch@^1.3.0: ...@@ -415,6 +419,49 @@ anymatch@^1.3.0:
micromatch "^2.1.5" micromatch "^2.1.5"
normalize-path "^2.0.0" normalize-path "^2.0.0"
apollo-cache-control@^0.0.x:
version "0.0.9"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.0.9.tgz#77100f456fb19526d33b7f595c8ab1a2980dcbb4"
dependencies:
graphql-extensions "^0.0.x"
apollo-link@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.0.7.tgz#42cd38a7378332fc3e41a214ff6a6e5e703a556f"
dependencies:
"@types/zen-observable" "0.5.3"
apollo-utilities "^1.0.0"
zen-observable "^0.6.0"
apollo-server-core@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.2.tgz#f36855a3ebdc2d77b8b9c454380bf1d706105ffc"
dependencies:
apollo-cache-control "^0.0.x"
apollo-tracing "^0.1.0"
graphql-extensions "^0.0.x"
apollo-server-express@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-1.3.2.tgz#0ff8201c0bf362804a151e1399767dae6ab7e309"
dependencies:
apollo-server-core "^1.3.2"
apollo-server-module-graphiql "^1.3.0"
apollo-server-module-graphiql@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.3.2.tgz#0a9e4c48dece3af904fee333f95f7b9817335ca7"
apollo-tracing@^0.1.0:
version "0.1.3"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.1.3.tgz#6820c066bf20f9d9a4eddfc023f7c83ee2601f0b"
dependencies:
graphql-extensions "^0.0.x"
apollo-utilities@^1.0.0, apollo-utilities@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.4.tgz#560009ea5541b9fdc4ee07ebb1714ee319a76c15"
app-root-path@^2.0.0: app-root-path@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46"
...@@ -2635,7 +2682,7 @@ core-js@^1.0.0: ...@@ -2635,7 +2682,7 @@ core-js@^1.0.0:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0, core-js@^2.5.0: core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.3:
version "2.5.3" version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
...@@ -3085,6 +3132,10 @@ depd@1.1.1, depd@~1.1.1: ...@@ -3085,6 +3132,10 @@ depd@1.1.1, depd@~1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
deprecated-decorator@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37"
des.js@^1.0.0: des.js@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
...@@ -4872,6 +4923,36 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, ...@@ -4872,6 +4923,36 @@ graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3,
version "4.1.11" version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
graphql-extensions@^0.0.x:
version "0.0.7"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.0.7.tgz#807e7c3493da45e8f8fd02c0da771a9b3f1f2d1a"
dependencies:
core-js "^2.5.3"
source-map-support "^0.5.1"
graphql-subscriptions@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.5.6.tgz#0d8e960fbaaf9ecbe7900366e86da2fc143fc5b2"
dependencies:
es6-promise "^4.1.1"
iterall "^1.1.3"
graphql-tools@^2.18.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-2.18.0.tgz#8e2d6436f9adba1d579c1a1710ae95e7f5e7248b"
dependencies:
apollo-link "^1.0.0"
apollo-utilities "^1.0.1"
deprecated-decorator "^0.1.6"
graphql-subscriptions "^0.5.6"
uuid "^3.1.0"
graphql@^0.12.3:
version "0.12.3"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.12.3.tgz#11668458bbe28261c0dcb6e265f515ba79f6ce07"
dependencies:
iterall "1.1.3"
growly@^1.3.0: growly@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
...@@ -5982,6 +6063,10 @@ istanbul-reports@^1.1.3: ...@@ -5982,6 +6063,10 @@ istanbul-reports@^1.1.3:
dependencies: dependencies:
handlebars "^4.0.3" handlebars "^4.0.3"
iterall@1.1.3, iterall@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9"
javascript-stringify@^1.6.0: javascript-stringify@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-1.6.0.tgz#142d111f3a6e3dae8f4a9afd77d45855b5a9cce3" resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-1.6.0.tgz#142d111f3a6e3dae8f4a9afd77d45855b5a9cce3"
...@@ -10553,6 +10638,12 @@ source-map-support@^0.5.0: ...@@ -10553,6 +10638,12 @@ source-map-support@^0.5.0:
dependencies: dependencies:
source-map "^0.6.0" source-map "^0.6.0"
source-map-support@^0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.2.tgz#1a6297fd5b2e762b39688c7fc91233b60984f0a5"
dependencies:
source-map "^0.6.0"
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6: source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
version "0.5.7" version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
...@@ -12326,6 +12417,10 @@ yargs@~3.10.0: ...@@ -12326,6 +12417,10 @@ yargs@~3.10.0:
decamelize "^1.0.0" decamelize "^1.0.0"
window-size "0.1.0" window-size "0.1.0"
zen-observable@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.6.1.tgz#01dbed3bc8d02cbe9ee1112c83e04c807f647244"
zip-stream@^1.2.0: zip-stream@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04"
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment