apollo-link-state
Advanced tools
Comparing version 0.4.1 to 0.4.2
# Change log | ||
### vNext | ||
### vNEXT | ||
### 0.4.2 | ||
- Allow providing resolvers via function [#293](https://github.com/apollographql/apollo-link-state/pull/293) | ||
- Add support for DocumentNode input to `typeDefs` [#284](https://github.com/apollographql/apollo-link-state/pull/284) | ||
- Remove dependency on zen-observable's flatMap [#284](https://github.com/apollographql/apollo-link-state/pull/284) | ||
### 0.4.1 | ||
- Return defaults when no Queries resolver is found and check for `resolverMap` [#202](https://github.com/apollographql/apollo-link-state/pull/202) | ||
@@ -5,0 +12,0 @@ - Fix for merging local & remote data not part of same selection set [#193](https://github.com/apollographql/apollo-link-state/pull/193) |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('apollo-utilities'), require('apollo-link'), require('graphql-anywhere/lib/async')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'apollo-utilities', 'apollo-link', 'graphql-anywhere/lib/async'], factory) : | ||
(factory((global.apolloLink = global.apolloLink || {}, global.apolloLink.state = {}),global.apollo.utilities,global.apolloLink.core,global.graphqlAnywhere.async)); | ||
}(this, (function (exports,apolloUtilities,apolloLink,async) { 'use strict'; | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('graphql'), require('apollo-utilities'), require('apollo-link'), require('graphql-anywhere/lib/async')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'graphql', 'apollo-utilities', 'apollo-link', 'graphql-anywhere/lib/async'], factory) : | ||
(factory((global.apolloLink = global.apolloLink || {}, global.apolloLink.state = {}),global.graphql,global.apollo.utilities,global.apolloLink.core,global.graphqlAnywhere.async)); | ||
}(this, (function (exports,graphql,apolloUtilities,apolloLink,Async) { 'use strict'; | ||
@@ -21,2 +21,9 @@ var connectionRemoveConfig = { | ||
} | ||
function normalizeTypeDefs(typeDefs) { | ||
var defs = Array.isArray(typeDefs) ? typeDefs : [typeDefs]; | ||
return defs | ||
.map(function (typeDef) { return (typeof typeDef === 'string' ? typeDef : graphql.print(typeDef)); }) | ||
.map(function (str) { return str.trim(); }) | ||
.join('\n'); | ||
} | ||
@@ -33,6 +40,7 @@ var __extends = (undefined && undefined.__extends) || (function () { | ||
})(); | ||
var graphql$1 = Async.graphql; | ||
var capitalizeFirstLetter = function (str) { return str.charAt(0).toUpperCase() + str.slice(1); }; | ||
var withClientState = function (clientStateConfig) { | ||
if (clientStateConfig === void 0) { clientStateConfig = { resolvers: {}, defaults: {} }; } | ||
var resolvers = clientStateConfig.resolvers, defaults = clientStateConfig.defaults, cache = clientStateConfig.cache, typeDefs = clientStateConfig.typeDefs; | ||
var defaults = clientStateConfig.defaults, cache = clientStateConfig.cache, typeDefs = clientStateConfig.typeDefs, fragmentMatcher = clientStateConfig.fragmentMatcher; | ||
if (cache && defaults) { | ||
@@ -55,5 +63,3 @@ cache.writeData({ data: defaults }); | ||
var directives_1 = 'directive @client on FIELD'; | ||
var definition_1 = typeof typeDefs === 'string' | ||
? typeDefs | ||
: typeDefs.map(function (typeDef) { return typeDef.trim(); }).join('\n'); | ||
var definition_1 = normalizeTypeDefs(typeDefs); | ||
operation.setContext(function (_a) { | ||
@@ -69,2 +75,5 @@ var _b = _a.schemas, schemas = _b === void 0 ? [] : _b; | ||
return forward(operation); | ||
var resolvers = typeof clientStateConfig.resolvers === 'function' | ||
? clientStateConfig.resolvers() | ||
: clientStateConfig.resolvers; | ||
var server = removeClientSetsFromDocument(operation.query); | ||
@@ -75,5 +84,9 @@ var query = operation.query; | ||
if (rootValue === void 0) { rootValue = {}; } | ||
var fieldValue = rootValue[info.resultKey]; | ||
if (fieldValue !== undefined) | ||
return fieldValue; | ||
var resultKey = info.resultKey; | ||
var aliasedNode = rootValue[resultKey]; | ||
var preAliasingNode = rootValue[fieldName]; | ||
var aliasNeeded = resultKey !== fieldName; | ||
if (aliasedNode !== undefined || preAliasingNode !== undefined) { | ||
return aliasedNode || preAliasingNode; | ||
} | ||
var resolverMap = resolvers[rootValue.__typename || type]; | ||
@@ -85,18 +98,24 @@ if (resolverMap) { | ||
} | ||
return defaults[fieldName]; | ||
return ((aliasNeeded ? aliasedNode : preAliasingNode) || | ||
(defaults || {})[fieldName]); | ||
}; | ||
if (server) | ||
operation.query = server; | ||
var obs = server && forward | ||
? forward(operation) | ||
: apolloLink.Observable.of({ | ||
data: {}, | ||
}); | ||
return new apolloLink.Observable(function (observer) { | ||
if (server) | ||
operation.query = server; | ||
var obs = server && forward | ||
? forward(operation) | ||
: apolloLink.Observable.of({ | ||
data: {}, | ||
}); | ||
var observerErrorHandler = observer.error.bind(observer); | ||
var sub = obs.subscribe({ | ||
var complete = false; | ||
var handlingNext = false; | ||
obs.subscribe({ | ||
next: function (_a) { | ||
var data = _a.data, errors = _a.errors; | ||
var observerErrorHandler = observer.error.bind(observer); | ||
var context = operation.getContext(); | ||
async.graphql(resolver, query, data, context, operation.variables) | ||
handlingNext = true; | ||
graphql$1(resolver, query, data, context, operation.variables, { | ||
fragmentMatcher: fragmentMatcher, | ||
}) | ||
.then(function (nextData) { | ||
@@ -107,12 +126,17 @@ observer.next({ | ||
}); | ||
observer.complete(); | ||
if (complete) { | ||
observer.complete(); | ||
} | ||
handlingNext = false; | ||
}) | ||
.catch(observerErrorHandler); | ||
}, | ||
error: observerErrorHandler, | ||
error: observer.error.bind(observer), | ||
complete: function () { | ||
if (!handlingNext) { | ||
observer.complete(); | ||
} | ||
complete = true; | ||
}, | ||
}); | ||
return function () { | ||
if (sub) | ||
sub.unsubscribe(); | ||
}; | ||
}); | ||
@@ -119,0 +143,0 @@ }; |
/// <reference types="zen-observable" /> | ||
import { ApolloLink, Observable, Operation, NextLink, FetchResult } from 'apollo-link'; | ||
import { ApolloCache } from 'apollo-cache'; | ||
import { DocumentNode } from 'graphql'; | ||
import { FragmentMatcher } from 'graphql-anywhere'; | ||
export declare type ClientStateConfig = { | ||
cache?: ApolloCache<any>; | ||
resolvers: any; | ||
resolvers: any | (() => any); | ||
defaults?: any; | ||
typeDefs?: string | string[]; | ||
typeDefs?: string | string[] | DocumentNode | DocumentNode[]; | ||
fragmentMatcher?: FragmentMatcher; | ||
}; | ||
@@ -10,0 +13,0 @@ export declare const withClientState: (clientStateConfig?: ClientStateConfig) => { |
@@ -13,8 +13,9 @@ var __extends = (this && this.__extends) || (function () { | ||
import { hasDirectives, getMainDefinition } from 'apollo-utilities'; | ||
import { graphql } from 'graphql-anywhere/lib/async'; | ||
import { removeClientSetsFromDocument } from './utils'; | ||
import * as Async from 'graphql-anywhere/lib/async'; | ||
var graphql = Async.graphql; | ||
import { removeClientSetsFromDocument, normalizeTypeDefs } from './utils'; | ||
var capitalizeFirstLetter = function (str) { return str.charAt(0).toUpperCase() + str.slice(1); }; | ||
export var withClientState = function (clientStateConfig) { | ||
if (clientStateConfig === void 0) { clientStateConfig = { resolvers: {}, defaults: {} }; } | ||
var resolvers = clientStateConfig.resolvers, defaults = clientStateConfig.defaults, cache = clientStateConfig.cache, typeDefs = clientStateConfig.typeDefs; | ||
var defaults = clientStateConfig.defaults, cache = clientStateConfig.cache, typeDefs = clientStateConfig.typeDefs, fragmentMatcher = clientStateConfig.fragmentMatcher; | ||
if (cache && defaults) { | ||
@@ -37,5 +38,3 @@ cache.writeData({ data: defaults }); | ||
var directives_1 = 'directive @client on FIELD'; | ||
var definition_1 = typeof typeDefs === 'string' | ||
? typeDefs | ||
: typeDefs.map(function (typeDef) { return typeDef.trim(); }).join('\n'); | ||
var definition_1 = normalizeTypeDefs(typeDefs); | ||
operation.setContext(function (_a) { | ||
@@ -51,2 +50,5 @@ var _b = _a.schemas, schemas = _b === void 0 ? [] : _b; | ||
return forward(operation); | ||
var resolvers = typeof clientStateConfig.resolvers === 'function' | ||
? clientStateConfig.resolvers() | ||
: clientStateConfig.resolvers; | ||
var server = removeClientSetsFromDocument(operation.query); | ||
@@ -57,5 +59,9 @@ var query = operation.query; | ||
if (rootValue === void 0) { rootValue = {}; } | ||
var fieldValue = rootValue[info.resultKey]; | ||
if (fieldValue !== undefined) | ||
return fieldValue; | ||
var resultKey = info.resultKey; | ||
var aliasedNode = rootValue[resultKey]; | ||
var preAliasingNode = rootValue[fieldName]; | ||
var aliasNeeded = resultKey !== fieldName; | ||
if (aliasedNode !== undefined || preAliasingNode !== undefined) { | ||
return aliasedNode || preAliasingNode; | ||
} | ||
var resolverMap = resolvers[rootValue.__typename || type]; | ||
@@ -67,18 +73,24 @@ if (resolverMap) { | ||
} | ||
return defaults[fieldName]; | ||
return ((aliasNeeded ? aliasedNode : preAliasingNode) || | ||
(defaults || {})[fieldName]); | ||
}; | ||
if (server) | ||
operation.query = server; | ||
var obs = server && forward | ||
? forward(operation) | ||
: Observable.of({ | ||
data: {}, | ||
}); | ||
return new Observable(function (observer) { | ||
if (server) | ||
operation.query = server; | ||
var obs = server && forward | ||
? forward(operation) | ||
: Observable.of({ | ||
data: {}, | ||
}); | ||
var observerErrorHandler = observer.error.bind(observer); | ||
var sub = obs.subscribe({ | ||
var complete = false; | ||
var handlingNext = false; | ||
obs.subscribe({ | ||
next: function (_a) { | ||
var data = _a.data, errors = _a.errors; | ||
var observerErrorHandler = observer.error.bind(observer); | ||
var context = operation.getContext(); | ||
graphql(resolver, query, data, context, operation.variables) | ||
handlingNext = true; | ||
graphql(resolver, query, data, context, operation.variables, { | ||
fragmentMatcher: fragmentMatcher, | ||
}) | ||
.then(function (nextData) { | ||
@@ -89,12 +101,17 @@ observer.next({ | ||
}); | ||
observer.complete(); | ||
if (complete) { | ||
observer.complete(); | ||
} | ||
handlingNext = false; | ||
}) | ||
.catch(observerErrorHandler); | ||
}, | ||
error: observerErrorHandler, | ||
error: observer.error.bind(observer), | ||
complete: function () { | ||
if (!handlingNext) { | ||
observer.complete(); | ||
} | ||
complete = true; | ||
}, | ||
}); | ||
return function () { | ||
if (sub) | ||
sub.unsubscribe(); | ||
}; | ||
}); | ||
@@ -101,0 +118,0 @@ }; |
import { DocumentNode } from 'graphql'; | ||
export declare function removeClientSetsFromDocument(query: DocumentNode): DocumentNode; | ||
export declare function normalizeTypeDefs(typeDefs: string | string[] | DocumentNode | DocumentNode[]): string; |
@@ -1,2 +0,3 @@ | ||
import { checkDocument, removeDirectivesFromDocument, } from 'apollo-utilities'; | ||
import { print } from 'graphql'; | ||
import { checkDocument, removeDirectivesFromDocument } from 'apollo-utilities'; | ||
var connectionRemoveConfig = { | ||
@@ -16,2 +17,9 @@ test: function (directive) { return directive.name.value === 'client'; }, | ||
} | ||
export function normalizeTypeDefs(typeDefs) { | ||
var defs = Array.isArray(typeDefs) ? typeDefs : [typeDefs]; | ||
return defs | ||
.map(function (typeDef) { return (typeof typeDef === 'string' ? typeDef : print(typeDef)); }) | ||
.map(function (str) { return str.trim(); }) | ||
.join('\n'); | ||
} | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "apollo-link-state", | ||
"version": "0.4.1", | ||
"version": "0.4.2", | ||
"description": "An easy way to manage local state with Apollo Link", | ||
@@ -20,4 +20,3 @@ "author": "James Baxley <james@meteor.com>", | ||
"scripts": { | ||
"build:browser": | ||
"browserify ./lib/bundle.umd.js -o=./lib/bundle.js --i apollo-link --i apollo-utilities --i graphql-anywhere && npm run minify:browser", | ||
"build:browser": "browserify ./lib/bundle.umd.js -o=./lib/bundle.js --i apollo-link --i apollo-utilities --i graphql-anywhere -i graphql && npm run minify:browser", | ||
"build": "tsc -p .", | ||
@@ -28,8 +27,6 @@ "bundle": "rollup -c", | ||
"prelint": "npm run lint-fix", | ||
"lint-fix": | ||
"prettier --trailing-comma all --single-quote --write \"src/**/*.{j,t}s*\"", | ||
"lint-fix": "prettier --trailing-comma all --single-quote --write \"src/**/*.{j,t}s*\"", | ||
"lint": "tslint --type-check -p tsconfig.json -c tslint.json src/*.ts", | ||
"lint-staged": "lint-staged", | ||
"minify:browser": | ||
"uglifyjs -c -m -o ./lib/bundle.min.js -- ./lib/bundle.js", | ||
"minify:browser": "uglifyjs -c -m -o ./lib/bundle.min.js -- ./lib/bundle.js", | ||
"postbuild": "npm run bundle", | ||
@@ -53,3 +50,3 @@ "prebuild": "npm run clean", | ||
"devDependencies": { | ||
"@types/graphql": "0.11.5", | ||
"@types/graphql": "0.12.7", | ||
"@types/jest": "22.1.x", | ||
@@ -63,3 +60,3 @@ "apollo-cache-inmemory": "^1.1.5", | ||
"danger": "1.2.0", | ||
"graphql": "0.11.7", | ||
"graphql": "0.12.3", | ||
"graphql-tag": "2.5.0", | ||
@@ -85,3 +82,8 @@ "jest": "21.2.1", | ||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", | ||
"moduleFileExtensions": ["ts", "tsx", "js", "json"] | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js", | ||
"json" | ||
] | ||
}, | ||
@@ -93,13 +95,12 @@ "dependencies": { | ||
"lint-staged": { | ||
"*.ts*": [ | ||
"*.{ts, tsx, js, jsx}": [ | ||
"prettier --trailing-comma all --single-quote --write", | ||
"git add" | ||
], | ||
"*.js*": [ | ||
"prettier --trailing-comma all --single-quote --write", | ||
"!(package).json": [ | ||
"prettier --write", | ||
"git add" | ||
], | ||
"*.json*": ["prettier --write", "git add"] | ||
] | ||
}, | ||
"pre-commit": "lint-staged" | ||
} |
@@ -208,3 +208,3 @@ import gql from 'graphql-tag'; | ||
next: ({ data }) => { | ||
expect({ ...data }).toEqual({ count: 0, lastCount: 1 }); | ||
expect({ ...data }).toMatchObject({ count: 0, lastCount: 1 }); | ||
done(); | ||
@@ -251,3 +251,3 @@ }, | ||
try { | ||
expect({ ...data.user }).toEqual({ | ||
expect({ ...data.user }).toMatchObject({ | ||
firstName: 'John', | ||
@@ -337,3 +337,3 @@ lastName: 'Doe', | ||
expect(data.count).toEqual(0); | ||
expect({ ...data.user }).toEqual({ | ||
expect({ ...data.user }).toMatchObject({ | ||
__typename: 'User', | ||
@@ -356,3 +356,3 @@ firstName: 'John', | ||
expect(data.count).toEqual(1); | ||
expect({ ...data.user }).toEqual({ | ||
expect({ ...data.user }).toMatchObject({ | ||
__typename: 'User', | ||
@@ -359,0 +359,0 @@ firstName: 'Harry', |
import gql from 'graphql-tag'; | ||
import { ApolloLink, execute, Observable } from 'apollo-link'; | ||
import { ApolloClient } from 'apollo-client'; | ||
import { InMemoryCache } from 'apollo-cache-inmemory'; | ||
import { | ||
InMemoryCache, | ||
IntrospectionFragmentMatcher, | ||
} from 'apollo-cache-inmemory'; | ||
@@ -40,3 +43,3 @@ import { print } from 'graphql/language/printer'; | ||
return client.query({ query }).then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
}); | ||
@@ -85,3 +88,3 @@ }); | ||
return client.query({ query }).then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
}); | ||
@@ -116,3 +119,3 @@ }); | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
expect(count).toBe(1); | ||
@@ -122,3 +125,3 @@ }) | ||
client.query({ query }).then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
expect(count).toBe(1); | ||
@@ -155,3 +158,3 @@ }), | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
expect(count).toBe(1); | ||
@@ -163,3 +166,3 @@ }) | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
expect(count).toBe(2); | ||
@@ -169,2 +172,83 @@ }), | ||
}); | ||
it('supports subscriptions', done => { | ||
const query = gql` | ||
subscription { | ||
field | ||
} | ||
`; | ||
const link = new ApolloLink(() => | ||
Observable.of({ data: { field: 1 } }, { data: { field: 2 } }), | ||
); | ||
const local = withClientState(); | ||
const client = new ApolloClient({ | ||
cache: new InMemoryCache(), | ||
link: local.concat(link), | ||
}); | ||
let counter = 0; | ||
expect.assertions(2); | ||
return client.subscribe({ query }).forEach(item => { | ||
expect(item).toMatchObject({ data: { field: ++counter } }); | ||
if (counter === 2) { | ||
done(); | ||
} | ||
}); | ||
}); | ||
it('uses fragment matcher', () => { | ||
const query = gql` | ||
{ | ||
foo { | ||
... on Bar { | ||
bar @client | ||
} | ||
... on Baz { | ||
baz @client | ||
} | ||
} | ||
} | ||
`; | ||
const link = new ApolloLink(() => | ||
Observable.of({ | ||
data: { foo: [{ __typename: 'Bar' }, { __typename: 'Baz' }] }, | ||
}), | ||
); | ||
const local = withClientState({ | ||
resolvers: { | ||
Bar: { | ||
bar: () => 'Bar', | ||
}, | ||
Baz: { | ||
baz: () => 'Baz', | ||
}, | ||
}, | ||
fragmentMatcher: ({ __typename }, typeCondition) => | ||
__typename === typeCondition, | ||
}); | ||
const client = new ApolloClient({ | ||
cache: new InMemoryCache({ | ||
fragmentMatcher: new IntrospectionFragmentMatcher({ | ||
introspectionQueryResultData: { | ||
__schema: { | ||
types: [ | ||
{ | ||
kind: 'UnionTypeDefinition', | ||
name: 'Foo', | ||
possibleTypes: [{ name: 'Bar' }, { name: 'Baz' }], | ||
}, | ||
], | ||
}, | ||
}, | ||
}), | ||
}), | ||
link: local.concat(link), | ||
}); | ||
return client.query({ query }).then(({ data }) => { | ||
expect(data).toMatchObject({ foo: [{ bar: 'Bar' }, { baz: 'Baz' }] }); | ||
}); | ||
}); | ||
}); | ||
@@ -190,3 +274,3 @@ | ||
.query({ query }) | ||
.then(({ data }) => expect({ ...data }).toEqual({ field: 'yo' })); | ||
.then(({ data }) => expect({ ...data }).toMatchObject({ field: 'yo' })); | ||
}); | ||
@@ -226,3 +310,3 @@ it('lets you write to the cache with a mutation', () => { | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
}); | ||
@@ -267,3 +351,3 @@ }); | ||
if (count === 1) { | ||
expect({ ...data }).toEqual({ field: 0 }); | ||
expect({ ...data }).toMatchObject({ field: 0 }); | ||
client.mutate({ mutation }); | ||
@@ -273,3 +357,3 @@ } | ||
if (count === 2) { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
done(); | ||
@@ -323,38 +407,6 @@ } | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: '1234' }); | ||
expect({ ...data }).toMatchObject({ field: '1234' }); | ||
}); | ||
}); | ||
it('runs default resolvers for aliased fields tagged with @client', () => { | ||
const query = gql` | ||
{ | ||
fie: foo @client { | ||
bar | ||
} | ||
} | ||
`; | ||
const cache = new InMemoryCache(); | ||
const client = new ApolloClient({ | ||
cache, | ||
link: withClientState({ | ||
cache, | ||
resolvers: {}, | ||
defaults: { | ||
foo: { | ||
bar: 'yo', | ||
__typename: 'Foo', | ||
}, | ||
}, | ||
}), | ||
}); | ||
return client.query({ query }).then(({ data }) => { | ||
expect({ ...data }).toMatchObject({ | ||
fie: { bar: 'yo', __typename: 'Foo' }, | ||
}); | ||
}); | ||
}); | ||
it('writeDefaults lets you write defaults to the cache after the store is reset', done => { | ||
@@ -400,3 +452,3 @@ const mutation = gql` | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ foo: 'bar' }); | ||
expect({ ...data }).toMatchObject({ foo: 'bar' }); | ||
}) | ||
@@ -409,3 +461,3 @@ .catch(done.fail); | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ foo: 'woo' }); | ||
expect({ ...data }).toMatchObject({ foo: 'woo' }); | ||
}) | ||
@@ -416,3 +468,3 @@ //should be default after this reset call | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ foo: 'bar' }); | ||
expect({ ...data }).toMatchObject({ foo: 'bar' }); | ||
done(); | ||
@@ -530,3 +582,3 @@ }) | ||
it('returns the Query result after resetStore', done => { | ||
it('returns the Query result after resetStore', async done => { | ||
const stateLink = withClientState({ | ||
@@ -555,3 +607,3 @@ cache, | ||
const client = createClient(stateLink); | ||
client.mutate({ mutation: plusMutation }); | ||
await client.mutate({ mutation: plusMutation }); | ||
expect(cache.readQuery({ query: counterQuery })).toMatchObject({ | ||
@@ -561,7 +613,9 @@ counter: 11, | ||
client.mutate({ mutation: plusMutation }); | ||
await client.mutate({ mutation: plusMutation }); | ||
expect(cache.readQuery({ query: counterQuery })).toMatchObject({ | ||
counter: 12, | ||
}); | ||
expect(client.query({ query: counterQuery })).resolves.toMatchObject({ | ||
await expect( | ||
client.query({ query: counterQuery }), | ||
).resolves.toMatchObject({ | ||
data: { counter: 12 }, | ||
@@ -742,3 +796,3 @@ }); | ||
client.resetStore() as Promise<null>; | ||
client.resetStore(); | ||
}); | ||
@@ -810,3 +864,3 @@ }); | ||
try { | ||
expect({ ...data }).toEqual({ count: 0, lastCount: 1 }); | ||
expect({ ...data }).toMatchObject({ count: 0, lastCount: 1 }); | ||
} catch (e) { | ||
@@ -820,3 +874,3 @@ done.fail(e); | ||
try { | ||
expect({ ...data }).toEqual({ count: 2, lastCount: 1 }); | ||
expect({ ...data }).toMatchObject({ count: 2, lastCount: 1 }); | ||
} catch (e) { | ||
@@ -829,3 +883,3 @@ done.fail(e); | ||
try { | ||
expect({ ...data }).toEqual({ count: 1, lastCount: 1 }); | ||
expect({ ...data }).toMatchObject({ count: 1, lastCount: 1 }); | ||
} catch (e) { | ||
@@ -886,3 +940,3 @@ done.fail(e); | ||
if (count === 1) { | ||
expect({ ...data }).toEqual({ todos: [] }); | ||
expect({ ...data }).toMatchObject({ todos: [] }); | ||
client.mutate({ | ||
@@ -896,3 +950,3 @@ mutation, | ||
} else if (count === 2) { | ||
expect(data.todos.map(x => ({ ...x }))).toEqual([ | ||
expect(data.todos.map(x => ({ ...x }))).toMatchObject([ | ||
{ | ||
@@ -899,0 +953,0 @@ title: 'Apollo Client 2.0', |
@@ -5,3 +5,2 @@ import gql from 'graphql-tag'; | ||
import { print } from 'graphql/language/printer'; | ||
import { parse } from 'graphql/language/parser'; | ||
@@ -19,21 +18,2 @@ import { withClientState } from '../'; | ||
const aliasedQuery = gql` | ||
query Test { | ||
fie: foo @client { | ||
bar | ||
} | ||
} | ||
`; | ||
const doubleQuery = gql` | ||
query Double { | ||
foo @client { | ||
bar | ||
} | ||
bar @client { | ||
foo | ||
} | ||
} | ||
`; | ||
const mixedQuery = gql` | ||
@@ -50,11 +30,2 @@ query Mixed { | ||
const data = { | ||
foo: { bar: true }, | ||
}; | ||
const doubleData = { | ||
foo: { bar: true }, | ||
bar: { foo: false }, | ||
}; | ||
const resolvers = { | ||
@@ -74,3 +45,7 @@ Query: { | ||
execute(client.concat(nextLink), { query }).subscribe(result => { | ||
expect(result.data).toEqual({ foo: { bar: true } }); | ||
try { | ||
expect(result.data).toEqual({ foo: { bar: true } }); | ||
} catch (error) { | ||
done.fail(error); | ||
} | ||
done(); | ||
@@ -82,12 +57,16 @@ }, done.fail); | ||
const nextLink = new ApolloLink(operation => { | ||
expect(operation.getContext()).toMatchSnapshot(); | ||
expect(print(operation.query)).toEqual( | ||
print(gql` | ||
query Mixed { | ||
bar { | ||
foo | ||
try { | ||
expect(operation.getContext()).toMatchSnapshot(); | ||
expect(print(operation.query)).toEqual( | ||
print(gql` | ||
query Mixed { | ||
bar { | ||
foo | ||
} | ||
} | ||
} | ||
`), | ||
); | ||
`), | ||
); | ||
} catch (error) { | ||
done.fail(error); | ||
} | ||
return Observable.of({ data: { bar: { foo: true } } }); | ||
@@ -113,3 +92,7 @@ }); | ||
execute(client, { query }).subscribe(({ data }) => { | ||
expect(data).toEqual({ foo: { bar: true } }); | ||
try { | ||
expect(data).toEqual({ foo: { bar: true } }); | ||
} catch (error) { | ||
done.fail(error); | ||
} | ||
done(); | ||
@@ -135,3 +118,7 @@ }, done.fail); | ||
execute(client.concat(sample), { query }).subscribe(({ data }) => { | ||
expect(data).toEqual({ foo: { bar: true }, bar: { baz: true } }); | ||
try { | ||
expect(data).toEqual({ foo: { bar: true }, bar: { baz: true } }); | ||
} catch (error) { | ||
done.fail(error); | ||
} | ||
done(); | ||
@@ -189,3 +176,7 @@ }, done.fail); | ||
execute(client, { query, variables: { id: 1 } }).subscribe(({ data }) => { | ||
expect(data).toEqual({ foo: { bar: 1 } }); | ||
try { | ||
expect(data).toEqual({ foo: { bar: 1 } }); | ||
} catch (error) { | ||
done.fail(error); | ||
} | ||
done(); | ||
@@ -195,45 +186,26 @@ }, done.fail); | ||
it('runs resolvers for missing client queries with aliased field', done => { | ||
it('passes context to client resolvers', done => { | ||
const query = gql` | ||
query Aliased { | ||
query WithContext { | ||
foo @client { | ||
bar | ||
} | ||
baz: bar { | ||
foo | ||
} | ||
} | ||
`; | ||
const sample = new ApolloLink(() => | ||
//The server takes care of the aliasing, so returns baz, not bar | ||
Observable.of({ data: { baz: { foo: true } } }), | ||
); | ||
const client = withClientState({ | ||
resolvers, | ||
}); | ||
execute(client.concat(sample), { query }).subscribe(({ data }) => { | ||
try { | ||
expect(data).toEqual({ foo: { bar: true }, baz: { foo: true } }); | ||
} catch (e) { | ||
done.fail(e); | ||
} | ||
done(); | ||
}, done.fail); | ||
}); | ||
it('runs resolvers for client queries when aliases are in use on the @client-tagged node', done => { | ||
const client = withClientState({ | ||
resolvers: { | ||
Query: { | ||
foo: () => ({ bar: true }), | ||
fie: () => { | ||
done.fail( | ||
"Called the resolver using the alias' name, instead of the correct resolver name.", | ||
); | ||
}, | ||
foo: () => ({ __typename: 'Foo' }), | ||
}, | ||
Foo: { | ||
bar: (data, _, { id }) => id, | ||
}, | ||
}, | ||
}); | ||
execute(client, { query: aliasedQuery }).subscribe(({ data }) => { | ||
expect(data).toEqual({ fie: { bar: true } }); | ||
execute(client, { query, context: { id: 1 } }).subscribe(({ data }) => { | ||
try { | ||
expect(data).toEqual({ foo: { bar: 1 } }); | ||
} catch (error) { | ||
done.fail(error); | ||
} | ||
done(); | ||
@@ -243,3 +215,3 @@ }, done.fail); | ||
it('passes context to client resolvers', done => { | ||
it('calls resolvers on each request if the prop is a function', done => { | ||
const query = gql` | ||
@@ -252,4 +224,6 @@ query WithContext { | ||
`; | ||
const client = withClientState({ | ||
resolvers: { | ||
const resolversSpy = jest.fn(); | ||
const resolvers = () => { | ||
resolversSpy(); | ||
return { | ||
Query: { | ||
@@ -259,10 +233,22 @@ foo: () => ({ __typename: 'Foo' }), | ||
Foo: { | ||
bar: (data, _, { id }) => id, | ||
bar: () => 1, | ||
}, | ||
}, | ||
}; | ||
}; | ||
const client = withClientState({ | ||
resolvers, | ||
}); | ||
execute(client, { query, context: { id: 1 } }).subscribe(({ data }) => { | ||
// once | ||
execute(client, { query }).subscribe(({ data }) => { | ||
expect(data).toEqual({ foo: { bar: 1 } }); | ||
done(); | ||
// twice | ||
execute(client, { query }).subscribe(({ data }) => { | ||
expect(data).toEqual({ foo: { bar: 1 } }); | ||
expect(resolversSpy).toHaveBeenCalledTimes(2); | ||
done(); | ||
}, done.fail); | ||
}, done.fail); | ||
}); |
@@ -46,3 +46,3 @@ import gql from 'graphql-tag'; | ||
.then(({ data }) => { | ||
expect({ ...data }).toEqual({ field: 1 }); | ||
expect({ ...data }).toMatchObject({ field: 1 }); | ||
}); | ||
@@ -49,0 +49,0 @@ }); |
112
src/index.ts
@@ -9,8 +9,13 @@ import { | ||
import { ApolloCache } from 'apollo-cache'; | ||
import { DocumentNode } from 'graphql'; | ||
import { hasDirectives, getMainDefinition } from 'apollo-utilities'; | ||
import { graphql } from 'graphql-anywhere/lib/async'; | ||
import { removeClientSetsFromDocument } from './utils'; | ||
import * as Async from 'graphql-anywhere/lib/async'; | ||
const { graphql } = Async; | ||
import { FragmentMatcher } from 'graphql-anywhere'; | ||
import { removeClientSetsFromDocument, normalizeTypeDefs } from './utils'; | ||
const capitalizeFirstLetter = str => str.charAt(0).toUpperCase() + str.slice(1); | ||
@@ -20,5 +25,6 @@ | ||
cache?: ApolloCache<any>; | ||
resolvers: any; | ||
resolvers: any | (() => any); | ||
defaults?: any; | ||
typeDefs?: string | string[]; | ||
typeDefs?: string | string[] | DocumentNode | DocumentNode[]; | ||
fragmentMatcher?: FragmentMatcher; | ||
}; | ||
@@ -29,3 +35,3 @@ | ||
) => { | ||
const { resolvers, defaults, cache, typeDefs } = clientStateConfig; | ||
const { defaults, cache, typeDefs, fragmentMatcher } = clientStateConfig; | ||
if (cache && defaults) { | ||
@@ -48,7 +54,5 @@ cache.writeData({ data: defaults }); | ||
const directives = 'directive @client on FIELD'; | ||
const definition = | ||
typeof typeDefs === 'string' | ||
? typeDefs | ||
: typeDefs.map(typeDef => typeDef.trim()).join('\n'); | ||
const definition = normalizeTypeDefs(typeDefs); | ||
operation.setContext(({ schemas = [] }) => ({ | ||
@@ -63,2 +67,6 @@ schemas: schemas.concat([{ definition, directives }]), | ||
const resolvers = | ||
typeof clientStateConfig.resolvers === 'function' | ||
? clientStateConfig.resolvers() | ||
: clientStateConfig.resolvers; | ||
const server = removeClientSetsFromDocument(operation.query); | ||
@@ -72,9 +80,21 @@ const { query } = operation; | ||
const resolver = (fieldName, rootValue = {}, args, context, info) => { | ||
//resultKey is where data under the field name is ultimately returned by the server | ||
//https://github.com/apollographql/apollo-client/tree/master/packages/graphql-anywhere#resolver-info | ||
const fieldValue = rootValue[info.resultKey]; | ||
const { resultKey } = info; | ||
//If fieldValue is defined, server returned a value | ||
if (fieldValue !== undefined) return fieldValue; | ||
// rootValue[fieldName] is where the data is stored in the "canonical model" | ||
// rootValue[info.resultKey] is where the user wants the data to be. | ||
// If fieldName != info.resultKey -- then GraphQL Aliases are in play | ||
// See also: | ||
// - https://github.com/apollographql/apollo-client/tree/master/packages/graphql-anywhere#resolver-info | ||
// - https://github.com/apollographql/apollo-link-rest/pull/113 | ||
// Support GraphQL Aliases! | ||
const aliasedNode = rootValue[resultKey]; | ||
const preAliasingNode = rootValue[fieldName]; | ||
const aliasNeeded = resultKey !== fieldName; | ||
// If aliasedValue is defined, some other link or server already returned a value | ||
if (aliasedNode !== undefined || preAliasingNode !== undefined) { | ||
return aliasedNode || preAliasingNode; | ||
} | ||
// Look for the field in the custom resolver map | ||
@@ -86,26 +106,39 @@ const resolverMap = resolvers[(rootValue as any).__typename || type]; | ||
} | ||
//TODO: the proper thing to do here is throw an error saying to | ||
//add `client.onResetStore(link.writeDefaults);` | ||
//waiting on https://github.com/apollographql/apollo-client/pull/3010 | ||
//Currently with nested fields, this sort of return does not work | ||
return defaults[fieldName]; | ||
// TODO: the proper thing to do here is throw an error saying to | ||
// add `client.onResetStore(link.writeDefaults);` | ||
// waiting on https://github.com/apollographql/apollo-client/pull/3010 | ||
return ( | ||
// Support nested fields | ||
(aliasNeeded ? aliasedNode : preAliasingNode) || | ||
(defaults || {})[fieldName] | ||
); | ||
}; | ||
if (server) operation.query = server; | ||
const obs = | ||
server && forward | ||
? forward(operation) | ||
: Observable.of({ | ||
data: {}, | ||
}); | ||
return new Observable(observer => { | ||
if (server) operation.query = server; | ||
const obs = | ||
server && forward | ||
? forward(operation) | ||
: Observable.of({ | ||
data: {}, | ||
}); | ||
const observerErrorHandler = observer.error.bind(observer); | ||
const sub = obs.subscribe({ | ||
// Works around race condition between completion and graphql execution | ||
// finishing. If complete is called during the graphql call, we will | ||
// miss out on the result, since the observer will have completed | ||
let complete = false; | ||
let handlingNext = false; | ||
obs.subscribe({ | ||
next: ({ data, errors }) => { | ||
const observerErrorHandler = observer.error.bind(observer); | ||
const context = operation.getContext(); | ||
handlingNext = true; | ||
//data is from the server and provides the root value to this GraphQL resolution | ||
//when there is no resolver, the data is taken from the context | ||
graphql(resolver, query, data, context, operation.variables) | ||
graphql(resolver, query, data, context, operation.variables, { | ||
fragmentMatcher, | ||
}) | ||
.then(nextData => { | ||
@@ -116,12 +149,17 @@ observer.next({ | ||
}); | ||
observer.complete(); | ||
if (complete) { | ||
observer.complete(); | ||
} | ||
handlingNext = false; | ||
}) | ||
.catch(observerErrorHandler); | ||
}, | ||
error: observerErrorHandler, | ||
error: observer.error.bind(observer), | ||
complete: () => { | ||
if (!handlingNext) { | ||
observer.complete(); | ||
} | ||
complete = true; | ||
}, | ||
}); | ||
return () => { | ||
if (sub) sub.unsubscribe(); | ||
}; | ||
}); | ||
@@ -128,0 +166,0 @@ } |
@@ -1,7 +0,6 @@ | ||
import { DocumentNode, DirectiveNode } from 'graphql'; | ||
// importing print is a reasonable thing to do, since Apollo Link Http requires | ||
// it to be present | ||
import { DocumentNode, DirectiveNode, print } from 'graphql'; | ||
import { | ||
checkDocument, | ||
removeDirectivesFromDocument, | ||
} from 'apollo-utilities'; | ||
import { checkDocument, removeDirectivesFromDocument } from 'apollo-utilities'; | ||
@@ -32,1 +31,12 @@ const connectionRemoveConfig = { | ||
} | ||
export function normalizeTypeDefs( | ||
typeDefs: string | string[] | DocumentNode | DocumentNode[], | ||
) { | ||
const defs = Array.isArray(typeDefs) ? typeDefs : [typeDefs]; | ||
return defs | ||
.map(typeDef => (typeof typeDef === 'string' ? typeDef : print(typeDef))) | ||
.map(str => str.trim()) | ||
.join('\n'); | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
96819
25
2398
1