apollo-link
Advanced tools
Comparing version 0.6.1-beta.1 to 0.6.1-beta.3
{ | ||
"name": "apollo-link", | ||
"version": "0.6.1-beta.1", | ||
"version": "0.6.1-beta.3", | ||
"description": "Flexible, lightweight transport layer for GraphQL", | ||
@@ -13,6 +13,6 @@ "author": "Evans Hauser <evanshauser@gmail.com>", | ||
"license": "MIT", | ||
"main": "./dist/src/bundle.umd.js", | ||
"module": "./dist/src/index.js", | ||
"jsnext:main": "./dist/src/index.js", | ||
"typings": "./dist/src/index.d.ts", | ||
"main": "./lib/bundle.umd.js", | ||
"module": "./lib/index.js", | ||
"jsnext:main": "./lib/index.js", | ||
"typings": "./lib/index.d.ts", | ||
"repository": { | ||
@@ -27,51 +27,43 @@ "type": "git", | ||
"scripts": { | ||
"pretest": "npm run build-test", | ||
"test": "npm run test-only --", | ||
"posttest": "npm run lint", | ||
"test-only": "mocha --reporter spec --full-trace dist/tests/tests.js", | ||
"test-watch": | ||
"mocha --reporter spec --full-trace dist/tests/tests.js --watch", | ||
"coverage": | ||
"istanbul cover ./node_modules/mocha/bin/_mocha -- --reporter dot --full-trace dist/tests/tests.js", | ||
"postcoverage": | ||
"remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info", | ||
"build:browser": | ||
"browserify ./lib/bundle.umd.js -o=./lib/bundle.js --i apollo-utilities --i graphql --i zen-observable-ts && npm run minify:browser", | ||
"build": "tsc -p .", | ||
"bundle": "rollup -c", | ||
"clean": "rimraf lib/* && rimraf coverage/*", | ||
"filesize": "npm run build && npm run build:browser", | ||
"lint": | ||
"tslint --type-check -p tsconfig.test.json src/*.ts && tslint --type-check -p tsconfig.test.json tests/*.ts", | ||
"prebuild": "npm run clean:dist", | ||
"build": "tsc -p .", | ||
"build-test": "tsc -p tsconfig.test.json", | ||
"tslint --type-check -p tsconfig.json -c ../../tslint.json src/*.ts", | ||
"minify:browser": | ||
"uglifyjs -c -m -o ./lib/bundle.min.js -- ./lib/bundle.js", | ||
"postbuild": "npm run bundle", | ||
"postbuild-test": "npm run bundle", | ||
"bundle": "rollup -c", | ||
"watch": "tsc -w -p .", | ||
"clean": "npm run clean:dist && npm run clean:coverage", | ||
"clean:dist": "rimraf dist/*", | ||
"clean:coverage": "rimraf coverage/*", | ||
"prepublishOnly": "npm run clean && npm run build" | ||
"prebuild": "npm run clean", | ||
"prepublishOnly": "npm run clean && npm run build", | ||
"test": "jest", | ||
"watch": "tsc -w -p ." | ||
}, | ||
"dependencies": { | ||
"graphql": "^0.10.3", | ||
"graphql-tag": "^2.4.2", | ||
"zen-observable-ts": "^0.4.3-beta.0" | ||
"apollo-utilities": "^0.2.0-beta.0", | ||
"graphql": "^0.11.3", | ||
"zen-observable-ts": "^0.4.3-beta.2" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "4.0.4", | ||
"@types/chai-as-promised": "0.0.31", | ||
"@types/graphql": "0.10.2", | ||
"@types/mocha": "2.2.42", | ||
"@types/sinon": "2.3.3", | ||
"chai": "4.1.1", | ||
"chai-as-promised": "7.1.1", | ||
"fetch-mock": "5.12.2", | ||
"istanbul": "0.4.5", | ||
"lodash": "4.17.4", | ||
"mocha": "3.5.0", | ||
"remap-istanbul": "0.9.5", | ||
"@types/jest": "^20.0.8", | ||
"browserify": "^14.4.0", | ||
"graphql-tag": "^2.4.2", | ||
"jest": "^21.1.0", | ||
"rimraf": "2.6.1", | ||
"rollup": "^0.45.2", | ||
"sinon": "3.2.0", | ||
"source-map-support": "0.4.16", | ||
"ts-jest": "^21.0.1", | ||
"tslint": "5.7.0", | ||
"typescript": "2.5.1" | ||
"typescript": "2.5.1", | ||
"uglify-js": "^3.1.1" | ||
}, | ||
"jest": { | ||
"transform": { | ||
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js" | ||
}, | ||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", | ||
"moduleFileExtensions": ["ts", "tsx", "js", "json"] | ||
} | ||
} |
export default { | ||
entry: 'dist/src/index.js', | ||
dest: 'dist/src/bundle.umd.js', | ||
entry: './lib/index.js', | ||
dest: './lib/bundle.umd.js', | ||
format: 'umd', | ||
@@ -8,10 +8,7 @@ sourceMap: true, | ||
exports: 'named', | ||
onwarn | ||
onwarn, | ||
}; | ||
function onwarn(message) { | ||
const suppressed = [ | ||
'UNRESOLVED_IMPORT', | ||
'THIS_IS_UNDEFINED' | ||
]; | ||
const suppressed = ['UNRESOLVED_IMPORT', 'THIS_IS_UNDEFINED']; | ||
@@ -18,0 +15,0 @@ if (!suppressed.find(code => message.code === code)) { |
@@ -1,2 +0,2 @@ | ||
import { execute, ApolloLink } from './link'; | ||
export * from './link'; | ||
export { makePromise } from './linkUtils'; | ||
@@ -8,3 +8,2 @@ export * from './types'; | ||
export default ApolloLink; | ||
export { Observable, ApolloLink, execute }; | ||
export { Observable }; |
231
src/link.ts
@@ -0,1 +1,3 @@ | ||
import Observable from 'zen-observable-ts'; | ||
import { | ||
@@ -11,106 +13,108 @@ GraphQLRequest, | ||
validateOperation, | ||
toLink, | ||
isTerminating, | ||
LinkError, | ||
transformOperation, | ||
createOperation, | ||
} from './linkUtils'; | ||
import gql from 'graphql-tag'; | ||
const passthrough = (op, forward) => (forward ? forward(op) : Observable.of()); | ||
import Observable from 'zen-observable-ts'; | ||
import { | ||
DocumentNode, | ||
DefinitionNode, | ||
OperationDefinitionNode, | ||
} from 'graphql/language/ast'; | ||
const toLink = (handler: RequestHandler | ApolloLink) => | ||
typeof handler === 'function' ? new ApolloLink(handler) : handler; | ||
export abstract class ApolloLink { | ||
public static from(links: (ApolloLink | RequestHandler)[]) { | ||
if (links.length === 0) { | ||
return ApolloLink.empty(); | ||
} | ||
export const empty = (): ApolloLink => | ||
new ApolloLink((op, forward) => Observable.of()); | ||
return links.map(toLink).reduce((x, y) => x.concat(y)); | ||
export const from = (links: ApolloLink[]): ApolloLink => { | ||
if (links.length === 0) return empty(); | ||
return links.map(toLink).reduce((x, y) => x.concat(y)); | ||
}; | ||
export const split = ( | ||
test: (op: Operation) => boolean, | ||
left: ApolloLink | RequestHandler, | ||
right: ApolloLink | RequestHandler = new ApolloLink(passthrough), | ||
): ApolloLink => { | ||
const leftLink = toLink(left); | ||
const rightLink = toLink(right); | ||
if (isTerminating(leftLink) && isTerminating(rightLink)) { | ||
return new ApolloLink(operation => { | ||
return test(operation) | ||
? leftLink.request(operation) || Observable.of() | ||
: rightLink.request(operation) || Observable.of(); | ||
}); | ||
} else { | ||
return new ApolloLink((operation, forward) => { | ||
return test(operation) | ||
? leftLink.request(operation, forward) || Observable.of() | ||
: rightLink.request(operation, forward) || Observable.of(); | ||
}); | ||
} | ||
}; | ||
public static empty(): ApolloLink { | ||
return new FunctionLink((op, forward) => Observable.of()); | ||
// join two Links together | ||
export const concat = ( | ||
first: ApolloLink | RequestHandler, | ||
second: ApolloLink | RequestHandler, | ||
) => { | ||
const firstLink = toLink(first); | ||
if (isTerminating(firstLink)) { | ||
console.warn( | ||
new LinkError( | ||
`You are calling concat on a terminating link, which will have no effect`, | ||
firstLink, | ||
), | ||
); | ||
return firstLink; | ||
} | ||
const nextLink = toLink(second); | ||
public static passthrough(): ApolloLink { | ||
return new FunctionLink( | ||
(op, forward) => (forward ? forward(op) : Observable.of()), | ||
if (isTerminating(nextLink)) { | ||
return new ApolloLink( | ||
operation => | ||
firstLink.request( | ||
operation, | ||
op => nextLink.request(op) || Observable.of(), | ||
) || Observable.of(), | ||
); | ||
} else { | ||
return new ApolloLink((operation, forward) => { | ||
return ( | ||
firstLink.request(operation, op => { | ||
return nextLink.request(op, forward) || Observable.of(); | ||
}) || Observable.of() | ||
); | ||
}); | ||
} | ||
}; | ||
// split allows for creating a split point in an execution chain | ||
// like filter, it can be used to direct operations based | ||
// on request information. Instead of dead ending an execution, | ||
// split allows for new chains to be formed. | ||
public static split( | ||
test: (op: Operation) => boolean, | ||
left: ApolloLink | RequestHandler, | ||
right: ApolloLink | RequestHandler = ApolloLink.passthrough(), | ||
): ApolloLink { | ||
const leftLink = toLink(left); | ||
const rightLink = toLink(right); | ||
if (isTerminating(leftLink) && isTerminating(rightLink)) { | ||
return new FunctionLink(operation => { | ||
return test(operation) | ||
? leftLink.request(operation) || Observable.of() | ||
: rightLink.request(operation) || Observable.of(); | ||
}); | ||
} else { | ||
return new FunctionLink((operation, forward) => { | ||
return test(operation) | ||
? leftLink.request(operation, forward) || Observable.of() | ||
: rightLink.request(operation, forward) || Observable.of(); | ||
}); | ||
} | ||
export class ApolloLink { | ||
constructor(request?: RequestHandler) { | ||
if (request) this.request = request; | ||
} | ||
public static empty = empty; | ||
public static from = from; | ||
public static split = split; | ||
public split( | ||
test: (op: Operation) => boolean, | ||
left: ApolloLink | RequestHandler, | ||
right: ApolloLink | RequestHandler = ApolloLink.passthrough(), | ||
right: ApolloLink | RequestHandler = new ApolloLink(passthrough), | ||
): ApolloLink { | ||
return this.concat(ApolloLink.split(test, left, right)); | ||
return this.concat(split(test, left, right)); | ||
} | ||
// join two Links together | ||
public concat(next: ApolloLink | RequestHandler): ApolloLink { | ||
if (isTerminating(this)) { | ||
console.warn( | ||
new LinkError( | ||
`You are calling concat on a terminating link, which will have no effect`, | ||
this, | ||
), | ||
); | ||
return this; | ||
} | ||
const nextLink = toLink(next); | ||
if (isTerminating(nextLink)) { | ||
return new FunctionLink( | ||
operation => | ||
this.request( | ||
operation, | ||
op => nextLink.request(op) || Observable.of(), | ||
) || Observable.of(), | ||
); | ||
} else { | ||
return new FunctionLink((operation, forward) => { | ||
return ( | ||
this.request(operation, op => { | ||
return nextLink.request(op, forward) || Observable.of(); | ||
}) || Observable.of() | ||
); | ||
}); | ||
} | ||
return concat(this, next); | ||
} | ||
public abstract request( | ||
public request( | ||
operation: Operation, | ||
forward?: NextLink, | ||
): Observable<FetchResult> | null; | ||
): Observable<FetchResult> | null { | ||
throw new Error('request is not implemented'); | ||
} | ||
} | ||
@@ -122,67 +126,10 @@ | ||
): Observable<FetchResult> { | ||
const copy = { ...operation }; | ||
validateOperation(copy); | ||
if (!copy.context) { | ||
copy.context = {}; | ||
} | ||
if (!copy.variables) { | ||
copy.variables = {}; | ||
} | ||
if (!copy.query) { | ||
console.warn(`query should either be a string or GraphQL AST`); | ||
copy.query = <DocumentNode>{}; | ||
} | ||
return link.request(transformOperation(copy)) || Observable.of(); | ||
return ( | ||
link.request( | ||
createOperation( | ||
operation.context, | ||
transformOperation(validateOperation(operation)), | ||
), | ||
) || Observable.of() | ||
); | ||
} | ||
function getName(node: OperationDefinitionNode) { | ||
return node && node.name && node.name.kind === 'Name' && node.name.value; | ||
} | ||
function transformOperation(operation: GraphQLRequest): Operation { | ||
let transformedOperation: Operation; | ||
if (typeof operation.query === 'string') { | ||
transformedOperation = { | ||
...operation, | ||
query: gql(operation.query), | ||
}; | ||
} else { | ||
transformedOperation = { | ||
...operation, | ||
} as Operation; | ||
} | ||
if (transformedOperation.query && transformedOperation.query.definitions) { | ||
if (!transformedOperation.operationName) { | ||
const operationTypes = ['query', 'mutation', 'subscription']; | ||
const definitions = <OperationDefinitionNode[]>transformedOperation.query.definitions.filter( | ||
(x: DefinitionNode) => | ||
x.kind === 'OperationDefinition' && | ||
operationTypes.indexOf(x.operation) >= 0, | ||
); | ||
transformedOperation.operationName = getName(definitions[0]) || ''; | ||
} | ||
} else if (!transformedOperation.operationName) { | ||
transformedOperation.operationName = ''; | ||
} | ||
return transformedOperation; | ||
} | ||
export class FunctionLink extends ApolloLink { | ||
constructor(public f: RequestHandler) { | ||
super(); | ||
this.request = f; | ||
} | ||
public request( | ||
operation: Operation, | ||
forward: NextLink, | ||
): Observable<FetchResult> { | ||
throw Error('should be overridden'); | ||
} | ||
} |
@@ -1,9 +0,17 @@ | ||
import { GraphQLRequest, RequestHandler } from './types'; | ||
import { getOperationName } from 'apollo-utilities'; | ||
import Observable from 'zen-observable-ts'; | ||
import { print } from 'graphql/language/printer'; | ||
import { ApolloLink, FunctionLink } from './link'; | ||
import { GraphQLRequest, Operation } from './types'; | ||
import { ApolloLink } from './link'; | ||
import Observable from 'zen-observable-ts'; | ||
export function validateOperation(operation: GraphQLRequest): GraphQLRequest { | ||
const OPERATION_FIELDS = ['query', 'operationName', 'variables', 'context']; | ||
const OPERATION_FIELDS = [ | ||
'query', | ||
'operationName', | ||
'variables', | ||
'extensions', | ||
'context', | ||
]; | ||
if (!operation.query) throw new Error('ApolloLink requires a query'); | ||
for (let key of Object.keys(operation)) { | ||
@@ -26,10 +34,2 @@ if (OPERATION_FIELDS.indexOf(key) < 0) { | ||
export function toLink(link: ApolloLink | RequestHandler): ApolloLink { | ||
if (typeof link === 'function') { | ||
return new FunctionLink(link); | ||
} else { | ||
return link as ApolloLink; | ||
} | ||
} | ||
export function isTerminating(link: ApolloLink): boolean { | ||
@@ -57,1 +57,60 @@ return link.request.length <= 1; | ||
} | ||
export function transformOperation(operation: GraphQLRequest): GraphQLRequest { | ||
const transformedOperation: GraphQLRequest = { | ||
variables: operation.variables || {}, | ||
extensions: operation.extensions || {}, | ||
operationName: operation.operationName, | ||
query: operation.query, | ||
}; | ||
// best guess at an operation name | ||
if (!transformedOperation.operationName) { | ||
transformedOperation.operationName = | ||
typeof transformedOperation.query !== 'string' | ||
? getOperationName(transformedOperation.query) | ||
: ''; | ||
} | ||
return transformedOperation as Operation; | ||
} | ||
export function createOperation( | ||
starting: any, | ||
operation: GraphQLRequest, | ||
): Operation { | ||
let context = { ...starting }; | ||
const setContext = next => { | ||
if (typeof next === 'function') { | ||
context = next(context); | ||
} else { | ||
context = { ...next }; | ||
} | ||
}; | ||
const getContext = () => ({ ...context }); | ||
Object.defineProperty(operation, 'setContext', { | ||
enumerable: false, | ||
value: setContext, | ||
}); | ||
Object.defineProperty(operation, 'getContext', { | ||
enumerable: false, | ||
value: getContext, | ||
}); | ||
Object.defineProperty(operation, 'toKey', { | ||
enumerable: false, | ||
value: () => getKey(operation), | ||
}); | ||
return operation as Operation; | ||
} | ||
export function getKey(operation: GraphQLRequest) { | ||
// XXX we're assuming here that variables will be serialized in the same order. | ||
// that might not always be true | ||
return `${print(operation.query)}|${JSON.stringify( | ||
operation.variables, | ||
)}|${operation.operationName}`; | ||
} |
@@ -20,8 +20,5 @@ import { Operation, NextLink, FetchResult } from '../types'; | ||
): Observable<FetchResult> { | ||
if (!operation.context) { | ||
operation.context = {}; | ||
} | ||
operation.context = this.setContext(operation.context); | ||
operation.setContext(this.setContext(operation.getContext())); | ||
return forward(operation); | ||
} | ||
} |
@@ -1,23 +0,19 @@ | ||
import { assert } from 'chai'; | ||
import * as sinon from 'sinon'; | ||
import * as Links from '../link'; | ||
import gql from 'graphql-tag'; | ||
import { execute, ApolloLink } from '../link'; | ||
const sampleQuery = ` | ||
query SampleQuery { | ||
stub{ | ||
id | ||
const sampleQuery = gql` | ||
query SampleQuery { | ||
stub { | ||
id | ||
} | ||
} | ||
} | ||
`; | ||
export function checkCalls<T>( | ||
calls: Array<sinon.SinonSpyCall>, | ||
results: Array<T>, | ||
) { | ||
assert.deepEqual(calls.length, results.length); | ||
calls.map((call, i) => assert.deepEqual(call.args[0].data, results[i])); | ||
export function checkCalls<T>(calls: any[] = [], results: Array<T>) { | ||
expect(calls.length).toBe(results.length); | ||
calls.map((call, i) => expect(call.data).toEqual(results[i])); | ||
} | ||
export interface TestResultType { | ||
link: Links.ApolloLink; | ||
link: ApolloLink; | ||
results?: any[]; | ||
@@ -36,8 +32,8 @@ query?: string; | ||
const spy = sinon.spy(); | ||
Links.execute(link, { query, context, variables }).subscribe({ | ||
const spy = jest.fn(); | ||
execute(link, { query, context, variables }).subscribe({ | ||
next: spy, | ||
error: error => { | ||
assert(error, results.pop()); | ||
checkCalls(spy.getCalls(), results); | ||
expect(error).toEqual(results.pop()); | ||
checkCalls(spy.mock.calls[0], results); | ||
if (done) { | ||
@@ -48,3 +44,3 @@ done(); | ||
complete: () => { | ||
checkCalls(spy.getCalls(), results); | ||
checkCalls(spy.mock.calls[0], results); | ||
if (done) { | ||
@@ -51,0 +47,0 @@ done(); |
@@ -5,5 +5,7 @@ import Observable from 'zen-observable-ts'; | ||
export interface GraphQLRequest { | ||
query?: string | DocumentNode; | ||
query: DocumentNode; | ||
variables?: Record<string, any>; | ||
operationName?: string; | ||
context?: Record<string, any>; | ||
extensions?: Record<string, any>; | ||
} | ||
@@ -13,5 +15,8 @@ | ||
query: DocumentNode; | ||
variables?: Record<string, any>; | ||
operationName?: string; | ||
context?: Record<string, any>; | ||
variables: Record<string, any>; | ||
operationName: string; | ||
extensions: Record<string, any>; | ||
setContext: (context: Record<string, any>) => Record<string, any>; | ||
getContext: () => Record<string, any>; | ||
toKey: () => string; | ||
} | ||
@@ -18,0 +23,0 @@ |
{ | ||
"extends": "../../tsconfig", | ||
"compilerOptions": { | ||
"outDir": "dist", | ||
"rootDir": "." | ||
"rootDir": "./src", | ||
"outDir": "lib" | ||
}, | ||
"include": ["src/**/*.ts"] | ||
"include": ["src/**/*.ts"], | ||
"exclude": ["src/**/__tests__/*.ts"] | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
86515
11
41
1829
0
21
1
+ Addedapollo-utilities@0.2.0-rc.3(transitive)
+ Addedgraphql@0.11.7(transitive)
+ Addediterall@1.1.3(transitive)
- Removedgraphql-tag@^2.4.2
- Removedgraphql@0.10.5(transitive)
- Removedgraphql-tag@2.12.6(transitive)
- Removediterall@1.3.0(transitive)
- Removedtslib@2.8.1(transitive)
Updatedgraphql@^0.11.3