Repository: FormidableLabs/urql
Pull request #130 (2018-11-26 15:24:16)

isolate components, library functions and exchanges


Patch:
diff --git a/src/components/client.tsx b/src/components/client.tsx
index 570bf1a4..71c96832 100755
--- a/src/components/client.tsx
+++ b/src/components/client.tsx
@@ -1,5 +1,5 @@
 import { Component, ReactNode } from 'react';
-import { CombinedError } from '../modules/error';
+import { CombinedError } from '../lib';
 import { zipObservables } from '../utils/zip-observables';
 
 import {
@@ -36,7 +36,7 @@ export interface IClientState {
   data: object | object[] | IClientState[]; // Data
 }
 
-export default class UrqlClient extends Component<IClientProps, IClientState> {
+export class UrqlClient extends Component<IClientProps, IClientState> {
   static defaultProps = {
     cacheInvalidation: true,
     cache: true,
diff --git a/src/components/connect-hoc.tsx b/src/components/connect-hoc.tsx
index a1871598..bfb42f1e 100755
--- a/src/components/connect-hoc.tsx
+++ b/src/components/connect-hoc.tsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
 
 import hoistStatics from 'hoist-non-react-statics';
 
-import Connect from '../components/connect';
+import { Connect } from '../components/connect';
 import { IMutation, IQuery } from '../interfaces/index';
 
 export interface IHOCProps {
diff --git a/src/components/connect.tsx b/src/components/connect.tsx
index 037ac9a2..a2898466 100755
--- a/src/components/connect.tsx
+++ b/src/components/connect.tsx
@@ -1,7 +1,7 @@
 import React, { Component, ReactNode } from 'react';
 import { ICache, IClient, IMutation, IQuery } from '../interfaces/index';
-import ClientWrapper from './client';
-import { Consumer } from './context';
+import { UrqlClient } from './client';
+import { ContextConsumer } from './context';
 
 export interface IConnectProps<Data, Mutations> {
   children: (props: UrqlProps<Data, Mutations>) => ReactNode; // Render prop
@@ -59,15 +59,15 @@ export type UrqlProps<Data, Mutations = {}> = {
   refreshAllFromCache: () => void;
 } & Mutations;
 
-export default class Connect<Data = {}, Mutations = {}> extends Component<
+export class Connect<Data = {}, Mutations = {}> extends Component<
   IConnectProps<Data, Mutations>
 > {
   render() {
-    // Use react-create-context to provide context to ClientWrapper
+    // Use react-create-context to provide context to UrqlClient
     return (
-      <Consumer>
+      <ContextConsumer>
         {(client: IClient) => (
-          <ClientWrapper
+          <UrqlClient
             client={client}
             children={this.props.children}
             subscription={this.props.subscription}
@@ -78,7 +78,7 @@ export default class Connect<Data = {}, Mutations = {}> extends Component<
             cache={this.props.cache}
           />
         )}
-      </Consumer>
+      </ContextConsumer>
     );
   }
 }
diff --git a/src/components/context.tsx b/src/components/context.tsx
index 53c48e64..2fd2f766 100644
--- a/src/components/context.tsx
+++ b/src/components/context.tsx
@@ -15,4 +15,7 @@ interface IContext {
   Consumer: ComponentClass<ConsumerProps<{}>>;
 }
 
-export const { Provider, Consumer }: IContext = context;
+export const {
+  Provider: ContextProvider,
+  Consumer: ContextConsumer,
+}: IContext = context;
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 00000000..3c9bd84e
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1,5 @@
+export * from './client';
+export * from './connect-hoc';
+export * from './connect';
+export * from './context';
+export * from './provider';
diff --git a/src/components/provider.tsx b/src/components/provider.tsx
index 0c46e230..326c098a 100644
--- a/src/components/provider.tsx
+++ b/src/components/provider.tsx
@@ -1,13 +1,13 @@
 import React, { Component, ReactNode } from 'react';
 import { IClient } from '../interfaces/index';
-import { Provider as ContextProvider } from './context';
+import { ContextProvider } from './context';
 
 export interface IProviderProps {
   children: ReactNode;
   client: IClient;
 }
 
-export default class Provider extends Component<IProviderProps> {
+export class Provider extends Component<IProviderProps> {
   render() {
     // Use react-create-context to provide client over context
     return (
diff --git a/src/modules/cache-exchange.ts b/src/exchanges/cache.ts
similarity index 97%
rename from src/modules/cache-exchange.ts
rename to src/exchanges/cache.ts
index ce4f081b..3aa6d04a 100755
--- a/src/modules/cache-exchange.ts
+++ b/src/exchanges/cache.ts
@@ -1,7 +1,7 @@
 import Observable from 'zen-observable-ts';
 
 import { IClient, IExchange, IExchangeResult } from '../interfaces/index';
-import { gankTypeNamesFromResponse, formatTypeNames } from './typenames';
+import { gankTypeNamesFromResponse, formatTypeNames } from '../lib';
 
 interface ITypenameInvalidate {
   [typeName: string]: string[];
diff --git a/src/modules/dedup-exchange.ts b/src/exchanges/dedup.ts
similarity index 100%
rename from src/modules/dedup-exchange.ts
rename to src/exchanges/dedup.ts
diff --git a/src/modules/http-exchange.ts b/src/exchanges/http.ts
similarity index 98%
rename from src/modules/http-exchange.ts
rename to src/exchanges/http.ts
index ed989df1..fc9bc28a 100755
--- a/src/modules/http-exchange.ts
+++ b/src/exchanges/http.ts
@@ -1,7 +1,7 @@
 import Observable from 'zen-observable-ts';
 
 import { IExchange } from '../interfaces/index';
-import { CombinedError } from './error';
+import { CombinedError } from '../lib';
 
 const checkStatus = (redirectMode: string = 'follow') => (
   response: Response
diff --git a/src/exchanges/index.ts b/src/exchanges/index.ts
new file mode 100644
index 00000000..a7d3e3ed
--- /dev/null
+++ b/src/exchanges/index.ts
@@ -0,0 +1,4 @@
+export * from './cache';
+export * from './dedup';
+export * from './http';
+export * from './subscription';
diff --git a/src/modules/subscription-exchange.ts b/src/exchanges/subscription.ts
similarity index 96%
rename from src/modules/subscription-exchange.ts
rename to src/exchanges/subscription.ts
index 747b2657..05b601fd 100755
--- a/src/modules/subscription-exchange.ts
+++ b/src/exchanges/subscription.ts
@@ -7,7 +7,7 @@ import {
   ISubscriptionObserver,
 } from '../interfaces/index';
 
-import { CombinedError } from './error';
+import { CombinedError } from '../lib';
 
 export const subscriptionExchange = (
   createSubscription: (IOperation, ISubscriptionObserver) => ISubscription,
diff --git a/src/index.ts b/src/index.ts
index 17317c1e..6ae38612 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,5 @@
-export { default as Client } from './modules/client';
-export * from './modules/client';
+export { Client } from './lib/client';
+export * from './lib/client';
 
 export { default as Provider } from './components/provider';
 export * from './components/provider';
@@ -10,11 +10,11 @@ export * from './components/connect';
 export { default as ConnectHOC } from './components/connect-hoc';
 export * from './components/connect-hoc';
 
-export * from './modules/query';
-export * from './modules/cache-exchange';
-export * from './modules/dedup-exchange';
-export * from './modules/error';
-export * from './modules/http-exchange';
-export * from './modules/subscription-exchange';
+export * from './lib/query';
+export * from './exchanges/cache';
+export * from './exchanges/dedup';
+export * from './lib/error';
+export * from './exchanges/http';
+export * from './exchanges/subscription';
 
 export * from './interfaces/index';
diff --git a/src/interfaces/exchange.ts b/src/interfaces/exchange.ts
index 60408409..1bf4b6f0 100755
--- a/src/interfaces/exchange.ts
+++ b/src/interfaces/exchange.ts
@@ -1,6 +1,6 @@
 import Observable from 'zen-observable-ts';
 
-import { CombinedError } from '../modules/error';
+import { CombinedError } from '../lib';
 import { IOperation } from './operation';
 
 // Adapted from: https://github.com/graphql/graphql-js/blob/ae5b163d2e6c124107fa0971f6d838c8a7d29f51/src/execution/execute.js#L105-L114<Paste>
diff --git a/src/modules/client.ts b/src/modules/client.ts
deleted file mode 100755
index 65fc23f2..00000000
--- a/src/modules/client.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-import Observable from 'zen-observable-ts';
-
-import {
-  ClientEventType,
-  IEventFn,
-  ICache,
-  IClient,
-  IClientOptions,
-  IExchange,
-  IExchangeResult,
-  IQuery,
-} from '../interfaces/index';
-
-import { cacheExchange } from './cache-exchange';
-import { dedupExchange } from './dedup-exchange';
-import { defaultCache } from './default-cache';
-import { hashString } from './hash';
-import { httpExchange } from './http-exchange';
-
-const getQueryKey = (q: IQuery) => {
-  const { query, variables } = q;
-  return hashString(JSON.stringify({ query, variables }));
-};
-
-export default class Client implements IClient {
-  url: string;
-  store: object; // Internal store
-  subscriptionSize: number; // Used to generate IDs for subscriptions
-  cache: ICache; // Cache object
-  exchange: IExchange; // Exchange to communicate with GraphQL APIs
-  fetchOptions: RequestInit | (() => RequestInit); // Options for fetch call
-  subscriptions: { [id: string]: IEventFn }; // Map of subscribed Connect components
-
-  constructor(opts?: IClientOptions) {
-    if (!opts) {
-      throw new Error('Please provide configuration object');
-    } else if (!opts.url) {
-      throw new Error('Please provide a URL for your GraphQL API');
-    }
-
-    this.url = opts.url;
-    this.store = opts.initialCache || {};
-    this.subscriptions = Object.create(null);
-    this.subscriptionSize = 0;
-    this.cache = opts.cache || defaultCache(this.store);
-    this.fetchOptions = opts.fetchOptions || {};
-
-    const exchange = cacheExchange(this, dedupExchange(httpExchange()));
-    this.exchange = opts.transformExchange
-      ? opts.transformExchange(exchange, this)
-      : exchange;
-  }
-
-  /* Event handler methods: */
-
-  dispatch: IEventFn = (type, payload) => {
-    /* tslint:disable-next-line forin */
-    for (const sub in this.subscriptions) {
-      this.subscriptions[sub](type, payload);
-    }
-  };
-
-  subscribe(callback: IEventFn): () => void {
-    // Create an identifier, add callback to subscriptions
-    const id = this.subscriptionSize++;
-    this.subscriptions[id] = callback;
-
-    // Return unsubscription function
-    return () => {
-      delete this.subscriptions[id];
-    };
-  }
-
-  /* Cache methods: */
-
-  // Receives keys and invalidates them on the cache
-  // Dispatches a CacheKeysInvalidated event after
-  deleteCacheKeys = (keys: string[]): Promise<void> => {
-    const batchedInvalidate = keys.map(key => this.cache.invalidate(key));
-
-    return Promise.all(batchedInvalidate).then(() => {
-      this.dispatch(ClientEventType.CacheKeysInvalidated, keys);
-    });
-  };
-
-  // Receives [key, value] entry and writes it to the cache
-  // Dispatches a CacheEntryUpdated event after
-  updateCacheEntry = (key: string, value: any): Promise<void> => {
-    return this.cache.write(key, value).then(() => {
-      this.dispatch(ClientEventType.CacheKeysInvalidated, [key]);
-    });
-  };
-
-  refreshAllFromCache = () => {
-    this.dispatch(ClientEventType.RefreshAll, undefined);
-  };
-
-  invalidateQuery = (queryObject: IQuery) => {
-    const key = getQueryKey(queryObject);
-    return this.cache.invalidate(key).then(() => {
-      this.dispatch(ClientEventType.CacheKeysInvalidated, [key]);
-    });
-  };
-
-  // Cache methods that are exposed on the component and will dispatch their
-  // changes
-  cacheWithEvents: ICache = {
-    write: this.updateCacheEntry,
-    read: (key: string) => this.cache.read(key),
-    invalidate: (key: string) => this.deleteCacheKeys([key]),
-    invalidateAll: () =>
-      this.cache.invalidateAll().then(() => {
-        this.dispatch(ClientEventType.RefreshAll, undefined);
-      }),
-    update: (callback: (...args: any[]) => void) =>
-      this.cache.update(callback).then(() => {
-        this.dispatch(ClientEventType.RefreshAll, undefined);
-      }),
-  };
-
-  /* Execute methods: */
-
-  makeContext({ skipCache }: { skipCache?: boolean }): Record<string, any> {
-    return {
-      fetchOptions:
-        typeof this.fetchOptions === 'function'
-          ? this.fetchOptions()
-          : this.fetchOptions,
-      skipCache: !!skipCache,
-      url: this.url,
-    };
-  }
-
-  executeSubscription$(
-    subscriptionObject: IQuery
-  ): Observable<IExchangeResult> {
-    const { query, variables } = subscriptionObject;
-
-    const operation = {
-      context: this.makeContext({}),
-      key: getQueryKey(subscriptionObject),
-      operationName: 'subscription',
-      query,
-      variables,
-    };
-
-    return this.exchange(operation);
-  }
-
-  executeQuery$(
-    queryObject: IQuery,
-    skipCache: boolean
-  ): Observable<IExchangeResult> {
-    const { query, variables } = queryObject;
-
-    const operation = {
-      context: this.makeContext({ skipCache }),
-      key: getQueryKey(queryObject),
-      operationName: 'query',
-      query,
-      variables,
-    };
-
-    return this.exchange(operation);
-  }
-
-  executeQuery = (
-    queryObject: IQuery,
-    skipCache: boolean
-  ): Promise<IExchangeResult> => {
-    return new Promise<IExchangeResult>((resolve, reject) => {
-      this.executeQuery$(queryObject, skipCache).subscribe({
-        error: reject,
-        next: resolve,
-      });
-    });
-  };
-
-  executeMutation$(
-    mutationObject: IQuery
-  ): Observable<IExchangeResult['data']> {
-    const { query, variables } = mutationObject;
-
-    const operation = {
-      context: this.makeContext({}),
-      key: getQueryKey(mutationObject),
-      operationName: 'mutation',
-      query,
-      variables,
-    };
-
-    return this.exchange(operation).map(x => x.data);
-  }
-
-  executeMutation = (
-    mutationObject: IQuery
-  ): Promise<IExchangeResult['data']> => {
-    return new Promise<IExchangeResult>((resolve, reject) => {
-      this.executeMutation$(mutationObject).subscribe({
-        error: reject,
-        next: resolve,
-      });
-    });
-  };
-}
diff --git a/src/modules/default-cache.ts b/src/modules/default-cache.ts
deleted file mode 100644
index 9d3696b2..00000000
--- a/src/modules/default-cache.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { ICache } from '../interfaces/index';
-
-export const defaultCache = (store: object): ICache => ({
-  invalidate: hash =>
-    new Promise(resolve => {
-      delete store[hash];
-      resolve(hash);
-    }),
-  invalidateAll: () =>
-    new Promise(resolve => {
-      store = {};
-      resolve();
-    }),
-  read: hash =>
-    new Promise(resolve => {
-      resolve(store[hash] || null);
-    }),
-  update: callback =>
-    new Promise(resolve => {
-      if (typeof callback === 'function') {
-        Object.keys(store).forEach(key => {
-          callback(store, key, store[key]);
-        });
-      }
-      resolve();
-    }),
-  write: (hash, data) =>
-    new Promise(resolve => {
-      store[hash] = data;
-      resolve(hash);
-    }),
-});
diff --git a/src/modules/error.ts b/src/modules/error.ts
deleted file mode 100644
index 7998373d..00000000
--- a/src/modules/error.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { IGraphQLError } from '../interfaces/index';
-
-const generateErrorMessage = (
-  networkErr?: Error,
-  graphQlErrs?: IGraphQLError[]
-) => {
-  let error = '';
-  if (networkErr) {
-    error = `[Network] ${networkErr.message}`;
-    return error;
-  }
-
-  graphQlErrs.forEach(err => {
-    error += `[GraphQL] ${err.message}\n`;
-  });
-
-  return error.trim();
-};
-
-const rehydrateGraphQlError = (error: string | IGraphQLError): Error => {
-  if (typeof error === 'string') {
-    return new Error(error);
-  } else if (error.message) {
-    return new Error(error.message);
-  } else {
-    return error as any;
-  }
-};
-
-export class CombinedError implements Error {
-  public name: string;
-  public message: string;
-  public graphQLErrors: Error[];
-  public networkError?: Error;
-  public response?: any;
-
-  constructor({
-    networkError,
-    graphQLErrors,
-    response,
-  }: {
-    networkError?: Error;
-    graphQLErrors?: Array<string | IGraphQLError>;
-    response?: any;
-  }) {
-    this.name = 'CombinedError';
-    this.graphQLErrors = (graphQLErrors || []).map(rehydrateGraphQlError);
-    this.message = generateErrorMessage(networkError, this.graphQLErrors);
-    this.networkError = networkError;
-    this.response = response;
-  }
-}
diff --git a/src/modules/hash.ts b/src/modules/hash.ts
deleted file mode 100644
index 3c28f897..00000000
--- a/src/modules/hash.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/* tslint:disable */
-
-export function hashString(str: string) {
-  return hash(str).toString(36);
-}
-
-// Source: https://github.com/garycourt/murmurhash-js/blob/master/murmurhash2_gc.js
-function hash(str: string) {
-  var l = str.length,
-    h = l ^ l,
-    i = 0,
-    k;
-
-  while (l >= 4) {
-    k =
-      (str.charCodeAt(i) & 0xff) |
-      ((str.charCodeAt(++i) & 0xff) << 8) |
-      ((str.charCodeAt(++i) & 0xff) << 16) |
-      ((str.charCodeAt(++i) & 0xff) << 24);
-
-    k =
-      (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16);
-    k ^= k >>> 24;
-    k =
-      (k & 0xffff) * 0x5bd1e995 + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16);
-
-    h =
-      ((h & 0xffff) * 0x5bd1e995 +
-        ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^
-      k;
-
-    l -= 4;
-    ++i;
-  }
-
-  switch (l) {
-    case 3:
-      h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
-    case 2:
-      h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
-    case 1:
-      h ^= str.charCodeAt(i) & 0xff;
-      h =
-        (h & 0xffff) * 0x5bd1e995 +
-        ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16);
-  }
-
-  h ^= h >>> 13;
-  h = (h & 0xffff) * 0x5bd1e995 + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16);
-  h ^= h >>> 15;
-
-  return h >>> 0;
-}
diff --git a/src/modules/query.ts b/src/modules/query.ts
deleted file mode 100644
index 28b1e8d7..00000000
--- a/src/modules/query.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-const createQuery = (q: string, vars?: object) => {
-  return {
-    query: q,
-    variables: vars || {},
-  };
-};
-
-export const query = createQuery;
-export const mutation = createQuery;
-export const subscription = createQuery;
diff --git a/src/modules/typenames.ts b/src/modules/typenames.ts
deleted file mode 100755
index b76235f4..00000000
--- a/src/modules/typenames.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import {
-  DefinitionNode,
-  DocumentNode,
-  FieldNode,
-  OperationDefinitionNode,
-  SelectionSetNode,
-} from 'graphql';
-
-import { parse } from 'graphql/language/parser';
-import { print } from 'graphql/language/printer';
-
-const TYPENAME_FIELD: FieldNode = {
-  kind: 'Field',
-  name: {
-    kind: 'Name',
-    value: '__typename',
-  },
-};
-
-function addTypename(selectionSet: SelectionSetNode, isRoot = false) {
-  if (selectionSet.selections) {
-    if (!isRoot) {
-      const exists = selectionSet.selections.some(selection => {
-        return (
-          selection.kind === 'Field' &&
-          (selection as FieldNode).name.value === '__typename'
-        );
-      });
-
-      if (!exists) {
-        selectionSet.selections.push(TYPENAME_FIELD);
-      }
-    }
-
-    selectionSet.selections.forEach(selection => {
-      if (selection.kind === 'Field') {
-        if (
-          selection.name.value.lastIndexOf('__', 0) !== 0 &&
-          selection.selectionSet
-        ) {
-          addTypename(selection.selectionSet);
-        }
-      } else if (selection.kind === 'InlineFragment') {
-        if (selection.selectionSet) {
-          addTypename(selection.selectionSet);
-        }
-      }
-    });
-  }
-}
-
-function addTypenameToDocument(doc: DocumentNode) {
-  doc.definitions.forEach((definition: DefinitionNode) => {
-    const isRoot = definition.kind === 'OperationDefinition';
-    addTypename((definition as OperationDefinitionNode).selectionSet, isRoot);
-  });
-
-  return doc;
-}
-
-// Adds __typename fields to a GraphQL query string
-export function formatTypeNames(query: string): string {
-  const doc = parse(query);
-  return print(addTypenameToDocument(doc));
-}
-
-function getTypeNameFromField(obj: object, typenames: string[]) {
-  Object.keys(obj).map(item => {
-    if (typeof obj[item] === 'object') {
-      if (obj[item] && '__typename' in obj[item]) {
-        typenames.push(obj[item].__typename);
-      }
-      if (obj[item]) {
-        getTypeNameFromField(obj[item], typenames);
-      }
-    }
-  });
-}
-
-export function gankTypeNamesFromResponse(response: object) {
-  const typeNames = [];
-  getTypeNameFromField(response, typeNames);
-  return typeNames.filter((v, i, a) => a.indexOf(v) === i);
-}


Test patch:
diff --git a/src/tests/components/client.test.tsx b/src/tests/components/client.test.tsx
index 6ce6e1f5..df310b36 100755
--- a/src/tests/components/client.test.tsx
+++ b/src/tests/components/client.test.tsx
@@ -1,10 +1,9 @@
 /* tslint:disable */
 
 import React from 'react';
-import Client from '../../components/client';
-import { CombinedError } from '../../modules/error';
-import { default as ClientModule } from '../../modules/client';
-import { subscriptionExchange } from '../../modules/subscription-exchange';
+import { UrqlClient } from '../../components';
+import { Client, CombinedError } from '../../lib';
+import { subscriptionExchange } from '../../exchanges';
 import { ClientEventType } from '../../interfaces/index';
 import renderer from 'react-test-renderer';
 
@@ -17,13 +16,12 @@ describe('Client Component', () => {
 
   it('should return null with no render function', () => {
     // @ts-ignore
-    const component = renderer.create(<Client />);
+    const component = renderer.create(<UrqlClient />);
     let tree = component.toJSON();
     expect(tree).toBeNull();
     component.update(
       // @ts-ignore
-      <Client
-        // @ts-ignore
+      <UrqlClient
         children={() => {
           return <div>hey</div>;
         }}
@@ -36,7 +34,7 @@ describe('Client Component', () => {
   it('should use the render prop when supplied and render the defaults', done => {
     renderer.create(
       // @ts-ignore
-      <Client
+      <UrqlClient
         // @ts-ignore
         children={({ data, error, fetching, loaded, refetch }) => {
           expect(data).toBeNull();
@@ -58,11 +56,11 @@ describe('Client Component', () => {
         json: () => ({ data: { todos: [{ id: 1 }] } }),
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         // @ts-ignore
         query={{ query: `{ todos { id } }` }}
@@ -99,11 +97,11 @@ describe('Client Component', () => {
         json: () => ({ data: { todos: [{ id: 1 }] } }),
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
 
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         key="test"
         client={clientModule}
         // @ts-ignore
@@ -115,7 +113,7 @@ describe('Client Component', () => {
     const newQuery = { query: `{ posts { id } }` };
 
     client.update(
-      <Client
+      <UrqlClient
         key="test"
         client={clientModule}
         // @ts-ignore
@@ -137,12 +135,12 @@ describe('Client Component', () => {
         json: () => ({ data: { todos: [{ id: 1 }] } }),
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     // @ts-ignore
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         key="test"
         client={clientModule}
@@ -163,7 +161,7 @@ describe('Client Component', () => {
     ]);
 
     client.update(
-      <Client
+      <UrqlClient
         // @ts-ignore
         key="test"
         client={clientModule}
@@ -186,11 +184,11 @@ describe('Client Component', () => {
 
   it('should return an error thrown by fetch', done => {
     (global as any).fetch.mockReturnValue(Promise.reject(new Error('oh no!')));
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         // @ts-ignore
         query={{ query: `{ todos { id } }` }}
@@ -227,11 +225,11 @@ describe('Client Component', () => {
         json: () => ({ data: { todos: [{ id: 1, __typename: 'Todo' }] } }),
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         // @ts-ignore
         query={[{ query: `{ todos { id } }` }, { query: `{ todos { id } }` }]}
@@ -266,11 +264,11 @@ describe('Client Component', () => {
 
   it('should return the proper render prop arguments with multiple queries supplied and an error', done => {
     (global as any).fetch.mockReturnValue(Promise.reject(new Error('lol')));
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         // @ts-ignore
         query={[{ query: `{ todos { id } }` }, { query: `{ todos { id } }` }]}
@@ -299,11 +297,11 @@ describe('Client Component', () => {
         data: { todos: [{ id: 1 }] },
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -349,13 +347,13 @@ describe('Client Component', () => {
         json: () => ({ data: { todos: [{ id: 1, __typename: 'Todo' }] } }),
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const spy = jest.spyOn(global, 'fetch');
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -406,11 +404,11 @@ describe('Client Component', () => {
         }),
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -452,12 +450,12 @@ describe('Client Component', () => {
         data: { todos: [{ id: 1, __typename: 'Todo' }] },
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     // @ts-ignore
     const spy = jest.spyOn(global, 'fetch');
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -488,12 +486,12 @@ describe('Client Component', () => {
         data: { todos: [{ id: 1, __typename: 'Todo' }] },
       })
     );
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     // @ts-ignore
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -535,11 +533,11 @@ describe('Client Component', () => {
   it('should not update in response to mutations that throw', done => {
     (global as any).fetch.mockReturnValue(Promise.reject(new Error('Yoinks!')));
 
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -572,11 +570,11 @@ describe('Client Component', () => {
   });
 
   it('shouldnt return data or mutations if neither is provided', () => {
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         // @ts-ignore
         client={clientModule}
         // @ts-ignore
@@ -839,7 +837,7 @@ describe('Client Component', () => {
       return { unsubscribe };
     };
 
-    const clientModule = new ClientModule({
+    const clientModule = new Client({
       url: 'test',
       transformExchange: x => subscriptionExchange(createSubscription, x),
     });
@@ -847,7 +845,7 @@ describe('Client Component', () => {
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         subscription={{ query: `subscription { todos { id } }` }}
         children={args => {
@@ -894,7 +892,7 @@ describe('Client Component', () => {
       };
 
       // @ts-ignore
-      const c = new Client(p as any);
+      const c = new UrqlClient(p as any);
 
       c.formatProps(p);
     }).toThrowErrorMatchingSnapshot();
@@ -918,7 +916,7 @@ describe('Client Component', () => {
       return { unsubscribe };
     };
 
-    const clientModule = new ClientModule({
+    const clientModule = new Client({
       url: 'test',
       transformExchange: x => subscriptionExchange(createSubscription, x),
     });
@@ -929,7 +927,7 @@ describe('Client Component', () => {
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         query={{ query }}
         subscription={{ query: `subscription { todos { id } }` }}
@@ -979,7 +977,7 @@ describe('Client Component', () => {
       return { unsubscribe };
     };
 
-    const clientModule = new ClientModule({
+    const clientModule = new Client({
       url: 'test',
       transformExchange: x => subscriptionExchange(createSubscription, x),
     });
@@ -990,7 +988,7 @@ describe('Client Component', () => {
     let result;
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         query={[{ query }, { query }]}
         subscription={{ query: `subscription { todos { id } }` }}
@@ -1024,12 +1022,16 @@ describe('Client Component', () => {
 
   it('should unsubscribe before starting a new query', () => {
     (global as any).fetch.mockReturnValue(new Promise(() => {}));
-    const clientModule = new ClientModule({ url: 'test' });
+    const clientModule = new Client({ url: 'test' });
     const query = `{ todos { id } }`;
 
     // @ts-ignore
     const client = renderer.create(
-      <Client client={clientModule} query={{ query }} children={() => null} />
+      <UrqlClient
+        client={clientModule}
+        query={{ query }}
+        children={() => null}
+      />
     );
 
     expect(client.getInstance().querySub).not.toBeNull();
@@ -1043,14 +1045,14 @@ describe('Client Component', () => {
 
   it('should unsubscribe before starting a new subscription', () => {
     const createSubscription = (_, _observer) => ({ unsubscribe: () => {} });
-    const clientModule = new ClientModule({
+    const clientModule = new Client({
       url: 'test',
       transformExchange: x => subscriptionExchange(createSubscription, x),
     });
 
     // @ts-ignore
     const client = renderer.create(
-      <Client
+      <UrqlClient
         client={clientModule}
         subscription={{ query: `subscription { ideas { id } }` }}
         children={() => null}
diff --git a/src/tests/components/connect.test.tsx b/src/tests/components/connect.test.tsx
index 0ab66f07..f27a6da5 100644
--- a/src/tests/components/connect.test.tsx
+++ b/src/tests/components/connect.test.tsx
@@ -1,7 +1,7 @@
 /* tslint:disable */
 
 import React from 'react';
-import Connect from '../../components/connect';
+import { Connect } from '../../components/connect';
 import renderer from 'react-test-renderer';
 
 describe('Client Component', () => {
diff --git a/src/tests/components/context.test.tsx b/src/tests/components/context.test.tsx
index ff185abc..533724f5 100644
--- a/src/tests/components/context.test.tsx
+++ b/src/tests/components/context.test.tsx
@@ -4,7 +4,7 @@ import * as exp from '../../components/context';
 
 describe('Context', () => {
   it('should export a new react context', () => {
-    expect(exp.Consumer).toBeTruthy();
-    expect(exp.Provider).toBeTruthy();
+    expect(exp.ContextConsumer).toBeTruthy();
+    expect(exp.ContextProvider).toBeTruthy();
   });
 });
diff --git a/src/tests/components/provider.test.tsx b/src/tests/components/provider.test.tsx
index c8d25591..c732ffaf 100644
--- a/src/tests/components/provider.test.tsx
+++ b/src/tests/components/provider.test.tsx
@@ -1,7 +1,7 @@
 /* tslint:disable */
 
 import React from 'react';
-import Provider from '../../components/provider';
+import { Provider } from '../../components/provider';
 import renderer from 'react-test-renderer';
 
 describe('Provider Component', () => {
diff --git a/src/tests/modules/cache-exchange.test.ts b/src/tests/modules/cache-exchange.test.ts
index 0baf870e..4c256f09 100755
--- a/src/tests/modules/cache-exchange.test.ts
+++ b/src/tests/modules/cache-exchange.test.ts
@@ -1,7 +1,7 @@
 import Observable from 'zen-observable-ts';
 import { IClient, IExchange, IOperation } from '../../interfaces/index';
-import { cacheExchange } from '../../modules/cache-exchange';
-import { defaultCache } from '../../modules/default-cache';
+import { cacheExchange } from '../../exchanges';
+import { defaultCache } from '../../lib';
 
 const result = {
   data: {
diff --git a/src/tests/modules/client.test.ts b/src/tests/modules/client.test.ts
index 5500553e..9c9bdd39 100755
--- a/src/tests/modules/client.test.ts
+++ b/src/tests/modules/client.test.ts
@@ -1,5 +1,4 @@
-import Client from '../../modules/client';
-import { defaultCache } from '../../modules/default-cache';
+import { Client, defaultCache } from '../../lib';
 import { ClientEventType } from '../../interfaces/events';
 
 describe('Client', () => {
diff --git a/src/tests/modules/dedup-exchange.test.ts b/src/tests/modules/dedup-exchange.test.ts
index a9fd670e..90b5dae2 100644
--- a/src/tests/modules/dedup-exchange.test.ts
+++ b/src/tests/modules/dedup-exchange.test.ts
@@ -1,6 +1,6 @@
 import Observable from 'zen-observable-ts';
 import { IExchange } from '../../interfaces/exchange';
-import { dedupExchange } from '../../modules/dedup-exchange';
+import { dedupExchange } from '../../exchanges/dedup';
 
 describe('dedupExchange', () => {
   beforeEach(() => {
diff --git a/src/tests/modules/default-cache.test.ts b/src/tests/modules/default-cache.test.ts
index 81b49b72..7a7be197 100644
--- a/src/tests/modules/default-cache.test.ts
+++ b/src/tests/modules/default-cache.test.ts
@@ -1,4 +1,4 @@
-import { defaultCache } from '../../modules/default-cache';
+import { defaultCache } from '../../lib/default-cache';
 
 describe('defaultCache', () => {
   beforeEach(() => {
diff --git a/src/tests/modules/error.test.ts b/src/tests/modules/error.test.ts
index 3678448e..b0f80cff 100644
--- a/src/tests/modules/error.test.ts
+++ b/src/tests/modules/error.test.ts
@@ -1,4 +1,4 @@
-import { CombinedError } from '../../modules/error';
+import { CombinedError } from '../../lib/error';
 
 describe('CombinedError', () => {
   it('can be instantiated with graphQLErrors', () => {
diff --git a/src/tests/modules/hash.test.ts b/src/tests/modules/hash.test.ts
index 18e3c89d..46550a0d 100644
--- a/src/tests/modules/hash.test.ts
+++ b/src/tests/modules/hash.test.ts
@@ -1,4 +1,4 @@
-import { hashString } from '../../modules/hash';
+import { hashString } from '../../lib/hash';
 
 describe('hash', () => {
   it('should returned a murmur hashed string from a query string', () => {
diff --git a/src/tests/modules/http-exchange.test.ts b/src/tests/modules/http-exchange.test.ts
index 2bcb4439..3612276d 100755
--- a/src/tests/modules/http-exchange.test.ts
+++ b/src/tests/modules/http-exchange.test.ts
@@ -1,6 +1,6 @@
 import { IOperation } from '../../interfaces/index';
-import { CombinedError } from '../../modules/error';
-import { httpExchange } from '../../modules/http-exchange';
+import { CombinedError } from '../../lib/error';
+import { httpExchange } from '../../exchanges/http';
 
 describe('httpExchange', () => {
   beforeEach(() => {
diff --git a/src/tests/modules/query.test.ts b/src/tests/modules/query.test.ts
index 7558032d..91a90dbb 100644
--- a/src/tests/modules/query.test.ts
+++ b/src/tests/modules/query.test.ts
@@ -1,4 +1,4 @@
-import { query } from '../../modules/query';
+import { query } from '../../lib/query';
 
 describe('query / mutation / subscription', () => {
   it('should return a valid query object', () => {
diff --git a/src/tests/modules/subscription-exchange.test.ts b/src/tests/modules/subscription-exchange.test.ts
index 3d9e7aeb..d66bac18 100755
--- a/src/tests/modules/subscription-exchange.test.ts
+++ b/src/tests/modules/subscription-exchange.test.ts
@@ -1,6 +1,6 @@
 import { IOperation } from '../../interfaces/index';
-import { subscriptionExchange } from '../../modules/subscription-exchange';
-import { CombinedError } from '../../modules/error';
+import { subscriptionExchange } from '../../exchanges/subscription';
+import { CombinedError } from '../../lib/error';
 
 describe('subscriptionExchange', () => {
   beforeEach(() => {
diff --git a/src/tests/modules/typenames.test.ts b/src/tests/modules/typenames.test.ts
index b59fc12a..d7061b5e 100755
--- a/src/tests/modules/typenames.test.ts
+++ b/src/tests/modules/typenames.test.ts
@@ -1,7 +1,7 @@
 import {
   formatTypeNames,
   gankTypeNamesFromResponse,
-} from '../../modules/typenames';
+} from '../../lib/typenames';
 
 describe('formatTypeNames', () => {
   it('should add typenames to a query string', () => {