diff --git a/package.json b/package.json index 45dad464c2..faa2c8276b 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ "peerDependencies": { "react": "0.14.x || 15.* || ^15.0.0-rc", "redux": "^2.0.0 || ^3.0.0", - "apollo-client": "^0.1.0 || ^0.2.0 || ^0.3.0" + "apollo-client": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0" }, "devDependencies": { - "apollo-client": "^0.3.0", + "apollo-client": "^0.4.0", "browserify": "^13.0.0", "chai": "^3.5.0", "chai-as-promised": "^5.2.0", diff --git a/src/connect.tsx b/src/connect.tsx index 1de44b5a44..5b2b34f421 100644 --- a/src/connect.tsx +++ b/src/connect.tsx @@ -23,7 +23,17 @@ import { Store, } from 'redux'; -import ApolloClient, { readQueryFromStore } from 'apollo-client'; +import ApolloClient, { + readQueryFromStore, +} from 'apollo-client'; + +import { + ObservableQuery, +} from 'apollo-client/QueryManager'; + +import { + Subscription, +} from 'apollo-client/util/Observable'; import { GraphQLResult, @@ -131,7 +141,8 @@ export default function connect(opts?: ConnectOptions) { private previousQueries: Object; // request / action storage - private queryHandles: any; + private queryObservables: { [queryKey: string]: ObservableQuery }; + private querySubscriptions: { [queryKey: string]: Subscription }; private mutations: any; // calculated switches to control rerenders @@ -164,6 +175,8 @@ export default function connect(opts?: ConnectOptions) { this.data = {}; this.mutations = {}; + this.queryObservables = {}; + this.querySubscriptions = {}; } componentWillMount() { @@ -254,33 +267,31 @@ export default function connect(opts?: ConnectOptions) { const { watchQuery, reduxRootKey } = this.client; const { store } = this; - const queryHandles = mapQueriesToProps({ + const queryOptions = mapQueriesToProps({ state: store.getState(), ownProps: props, }); const oldQueries = assign({}, this.previousQueries); - this.previousQueries = assign({}, queryHandles); + this.previousQueries = assign({}, queryOptions); // don't re run queries if nothing has changed - if (isEqual(oldQueries, queryHandles)) { + if (isEqual(oldQueries, queryOptions)) { return false; } else if (oldQueries) { // unsubscribe from previous queries this.unsubcribeAllQueries(); } - if (isObject(queryHandles) && Object.keys(queryHandles).length) { - this.queryHandles = queryHandles; - - for (const key in queryHandles) { - if (!queryHandles.hasOwnProperty(key)) { + if (isObject(queryOptions) && Object.keys(queryOptions).length) { + for (const key in queryOptions) { + if (!queryOptions.hasOwnProperty(key)) { continue; } - const { query, variables, forceFetch } = queryHandles[key]; + const { query, variables, forceFetch } = queryOptions[key]; - const handle = watchQuery(queryHandles[key]); + const observableQuery = watchQuery(queryOptions[key]); // rudimentary way to manually check cache let queryData = defaultQueryData as any; @@ -303,24 +314,24 @@ export default function connect(opts?: ConnectOptions) { this.data[key] = queryData; - this.handleQueryData(handle, key); + this.handleQueryData(observableQuery, key); } } return true; } unsubcribeAllQueries() { - if (this.queryHandles) { - for (const key in this.queryHandles) { - if (!this.queryHandles.hasOwnProperty(key)) { + if (this.querySubscriptions) { + for (const key in this.querySubscriptions) { + if (!this.querySubscriptions.hasOwnProperty(key)) { continue; } - this.queryHandles[key].unsubscribe(); + this.querySubscriptions[key].unsubscribe(); } } } - handleQueryData(handle: any, key: string) { + handleQueryData(observableQuery: ObservableQuery, key: string) { // bind each handle to updating and rerendering when data // has been recieved let refetch, @@ -387,14 +398,15 @@ export default function connect(opts?: ConnectOptions) { } }; - this.queryHandles[key] = handle.subscribe({ + this.queryObservables[key] = observableQuery; + this.querySubscriptions[key] = observableQuery.subscribe({ next: forceRender, error(errors) { forceRender({ errors }); }, }); - refetch = createBoundRefetch(key, this.queryHandles[key].refetch); - startPolling = this.queryHandles[key].startPolling; - stopPolling = this.queryHandles[key].stopPolling; + refetch = createBoundRefetch(key, this.queryObservables[key].refetch); + startPolling = this.queryObservables[key].startPolling; + stopPolling = this.queryObservables[key].stopPolling; this.data[key] = assign(this.data[key], { refetch, diff --git a/typings/modules/apollo-client/index.d.ts b/typings/modules/apollo-client/index.d.ts index d1e26a2ab3..6b5f1b4411 100644 --- a/typings/modules/apollo-client/index.d.ts +++ b/typings/modules/apollo-client/index.d.ts @@ -145,6 +145,7 @@ export * from '~apollo-client/mutations/store'; declare module '~apollo-client/actions' { import { GraphQLResult } from 'graphql'; import { SelectionSetWithRoot } from '~apollo-client/queries/store'; +import { MutationBehavior } from '~apollo-client/data/mutationResults'; import { FragmentMap } from '~apollo-client/queries/getFromAST'; export interface QueryResultAction { type: 'APOLLO_QUERY_RESULT'; @@ -199,9 +200,22 @@ export interface MutationResultAction { type: 'APOLLO_MUTATION_RESULT'; result: GraphQLResult; mutationId: string; + resultBehaviors?: MutationBehavior[]; } export function isMutationResultAction(action: ApolloAction): action is MutationResultAction; -export type ApolloAction = QueryResultAction | QueryErrorAction | QueryInitAction | QueryResultClientAction | QueryStopAction | MutationInitAction | MutationResultAction; +export interface MutationErrorAction { + type: 'APOLLO_MUTATION_ERROR'; + error: Error; + mutationId: string; + resultBehaviors?: MutationBehavior[]; +} +export function isMutationErrorAction(action: ApolloAction): action is MutationErrorAction; +export interface StoreResetAction { + type: 'APOLLO_STORE_RESET'; + observableQueryIds: string[]; +} +export function isStoreResetAction(action: ApolloAction): action is StoreResetAction; +export type ApolloAction = QueryResultAction | QueryErrorAction | QueryInitAction | QueryResultClientAction | QueryStopAction | MutationInitAction | MutationResultAction | MutationErrorAction | StoreResetAction; } declare module 'apollo-client/actions' { export * from '~apollo-client/actions'; @@ -215,6 +229,7 @@ import { QueryStore } from '~apollo-client/queries/store'; import { MutationStore } from '~apollo-client/mutations/store'; import { ApolloAction } from '~apollo-client/actions'; import { IdGetter } from '~apollo-client/data/extensions'; +import { MutationBehaviorReducerMap } from '~apollo-client/data/mutationResults'; export interface Store { data: NormalizedCache; queries: QueryStore; @@ -233,6 +248,7 @@ export function createApolloStore({reduxRootKey, initialState, config, reportCra }): ApolloStore; export interface ApolloReducerConfig { dataIdFromObject?: IdGetter; + mutationBehaviorReducers?: MutationBehaviorReducerMap; } } declare module 'apollo-client/store' { @@ -269,16 +285,22 @@ import { NetworkInterface } from '~apollo-client/networkInterface'; import { ApolloStore, Store } from '~apollo-client/store'; import { QueryStoreValue } from '~apollo-client/queries/store'; import { QueryTransformer } from '~apollo-client/queries/queryTransform'; -import { GraphQLResult, Document } from 'graphql'; -import { Observable, Observer, Subscription } from '~apollo-client/util/Observable'; -export class ObservableQuery extends Observable { - subscribe(observer: Observer): QuerySubscription; - result(): Promise; -} -export interface QuerySubscription extends Subscription { - refetch(variables?: any): Promise; - stopPolling(): void; - startPolling(pollInterval: number): void; +import { Document, FragmentDefinition } from 'graphql'; +import { MutationBehavior } from '~apollo-client/data/mutationResults'; +import { ApolloQueryResult } from '~apollo-client/index'; +import { Observable, Observer, Subscription, SubscriberFunction } from '~apollo-client/util/Observable'; +export class ObservableQuery extends Observable { + refetch: (variables?: any) => Promise; + stopPolling: () => void; + startPolling: (p: number) => void; + constructor(options: { + subscriberFunction: SubscriberFunction; + refetch: (variables?: any) => Promise; + stopPolling: () => void; + startPolling: (p: number) => void; + }); + subscribe(observer: Observer): Subscription; + result(): Promise; } export interface WatchQueryOptions { query: Document; @@ -288,6 +310,7 @@ export interface WatchQueryOptions { forceFetch?: boolean; returnPartialData?: boolean; pollInterval?: number; + fragments?: FragmentDefinition[]; } export type QueryListener = (queryStoreValue: QueryStoreValue) => void; export class QueryManager { @@ -301,6 +324,8 @@ export class QueryManager { private scheduler; private batcher; private batcherPollInterval; + private fetchQueryPromises; + private observableQueries; constructor({networkInterface, store, reduxRootKey, queryTransformer, shouldBatch}: { networkInterface: NetworkInterface; store: ApolloStore; @@ -309,21 +334,29 @@ export class QueryManager { shouldBatch?: Boolean; }); broadcastNewStore(store: any): void; - mutate({mutation, variables}: { + mutate({mutation, variables, resultBehaviors, fragments}: { mutation: Document; variables?: Object; - }): Promise; - queryListenerForObserver(options: WatchQueryOptions, observer: Observer): QueryListener; - watchQuery(options: WatchQueryOptions): ObservableQuery; - query(options: WatchQueryOptions): Promise; - fetchQuery(queryId: string, options: WatchQueryOptions): Promise; + resultBehaviors?: MutationBehavior[]; + fragments?: FragmentDefinition[]; + }): Promise; + queryListenerForObserver(options: WatchQueryOptions, observer: Observer): QueryListener; + watchQuery(options: WatchQueryOptions, shouldSubscribe?: boolean): ObservableQuery; + query(options: WatchQueryOptions): Promise; + fetchQuery(queryId: string, options: WatchQueryOptions): Promise; generateQueryId(): string; stopQueryInStore(queryId: string): void; getApolloState(): Store; addQueryListener(queryId: string, listener: QueryListener): void; removeQueryListener(queryId: string): void; + addFetchQueryPromise(requestId: number, promise: Promise, resolve: (result: ApolloQueryResult) => void, reject: (error: Error) => void): void; + removeFetchQueryPromise(requestId: number): void; + addObservableQuery(queryId: string, observableQuery: ObservableQuery): void; + addQuerySubscription(queryId: string, querySubscription: Subscription): void; + removeObservableQuery(queryId: string): void; + resetStore(): void; private fetchQueryOverInterface(queryId, options, network); - private startQuery(options, listener); + private startQuery(queryId, options, listener); private stopQuery(queryId); private broadcastQueries(); private generateRequestId(); @@ -348,6 +381,7 @@ export interface FragmentMap { [fragmentName: string]: FragmentDefinition; } export function createFragmentMap(fragments: FragmentDefinition[]): FragmentMap; +export function addFragmentsToDocument(queryDoc: Document, fragments: FragmentDefinition[]): Document; } declare module 'apollo-client/queries/getFromAST' { export * from '~apollo-client/queries/getFromAST'; @@ -359,16 +393,18 @@ declare module '~apollo-client/data/readFromStore' { import { SelectionSet, Document } from 'graphql'; import { FragmentMap } from '~apollo-client/queries/getFromAST'; import { NormalizedCache } from '~apollo-client/data/store'; -export function readQueryFromStore({store, query, variables}: { +export function readQueryFromStore({store, query, variables, returnPartialData}: { store: NormalizedCache; query: Document; variables?: Object; + returnPartialData?: boolean; }): Object; -export function readFragmentFromStore({store, fragment, rootId, variables}: { +export function readFragmentFromStore({store, fragment, rootId, variables, returnPartialData}: { store: NormalizedCache; fragment: Document; rootId: string; variables?: Object; + returnPartialData?: boolean; }): Object; export function readSelectionSetFromStore({store, rootId, selectionSet, variables, returnPartialData, fragmentMap}: { store: NormalizedCache; @@ -383,6 +419,41 @@ declare module 'apollo-client/data/readFromStore' { export * from '~apollo-client/data/readFromStore'; } +// Generated by typings +// Source: node_modules/apollo-client/data/writeToStore.d.ts +declare module '~apollo-client/data/writeToStore' { +import { FragmentMap } from '~apollo-client/queries/getFromAST'; +import { SelectionSet, Document } from 'graphql'; +import { NormalizedCache } from '~apollo-client/data/store'; +import { IdGetter } from '~apollo-client/data/extensions'; +export function writeFragmentToStore({result, fragment, store, variables, dataIdFromObject}: { + result: Object; + fragment: Document; + store?: NormalizedCache; + variables?: Object; + dataIdFromObject?: IdGetter; +}): NormalizedCache; +export function writeQueryToStore({result, query, store, variables, dataIdFromObject}: { + result: Object; + query: Document; + store?: NormalizedCache; + variables?: Object; + dataIdFromObject?: IdGetter; +}): NormalizedCache; +export function writeSelectionSetToStore({result, dataId, selectionSet, store, variables, dataIdFromObject, fragmentMap}: { + dataId: string; + result: any; + selectionSet: SelectionSet; + store?: NormalizedCache; + variables: Object; + dataIdFromObject: IdGetter; + fragmentMap?: FragmentMap; +}): NormalizedCache; +} +declare module 'apollo-client/data/writeToStore' { +export * from '~apollo-client/data/writeToStore'; +} + // Generated by typings // Source: node_modules/apollo-client/data/extensions.d.ts declare module '~apollo-client/data/extensions' { @@ -399,9 +470,9 @@ export * from '~apollo-client/data/extensions'; // Source: node_modules/apollo-client/queries/queryTransform.d.ts declare module '~apollo-client/queries/queryTransform' { import { SelectionSet, OperationDefinition } from 'graphql'; -export type QueryTransformer = (queryPiece: SelectionSet) => void; -export function addFieldToSelectionSet(fieldName: string, queryPiece: SelectionSet): SelectionSet; -export function addTypenameToSelectionSet(queryPiece: SelectionSet): SelectionSet; +export type QueryTransformer = (selectionSet: SelectionSet) => void; +export function addFieldToSelectionSet(fieldName: string, selectionSet: SelectionSet): SelectionSet; +export function addTypenameToSelectionSet(selectionSet: SelectionSet): SelectionSet; export function addTypenameToQuery(queryDef: OperationDefinition): OperationDefinition; export function applyTransformerToOperation(queryDef: OperationDefinition, queryTransformer: QueryTransformer): OperationDefinition; } @@ -409,17 +480,96 @@ declare module 'apollo-client/queries/queryTransform' { export * from '~apollo-client/queries/queryTransform'; } +// Generated by typings +// Source: node_modules/apollo-client/data/scopeQuery.d.ts +declare module '~apollo-client/data/scopeQuery' { +import { FragmentMap } from '~apollo-client/queries/getFromAST'; +import { SelectionSet } from 'graphql'; +export type StorePath = (string | number)[]; +export function scopeJSONToResultPath({json, path}: { + json: any; + path: StorePath; +}): any; +export function scopeSelectionSetToResultPath({selectionSet, fragmentMap, path}: { + selectionSet: SelectionSet; + fragmentMap?: FragmentMap; + path: StorePath; +}): SelectionSet; +} +declare module 'apollo-client/data/scopeQuery' { +export * from '~apollo-client/data/scopeQuery'; +} + +// Generated by typings +// Source: node_modules/apollo-client/data/mutationResults.d.ts +declare module '~apollo-client/data/mutationResults' { +import { NormalizedCache } from '~apollo-client/data/store'; +import { GraphQLResult, SelectionSet } from 'graphql'; +import { FragmentMap } from '~apollo-client/queries/getFromAST'; +import { StorePath } from '~apollo-client/data/scopeQuery'; +import { ApolloReducerConfig } from '~apollo-client/store'; +export type MutationBehavior = MutationArrayInsertBehavior | MutationArrayDeleteBehavior | MutationDeleteBehavior; +export type MutationArrayInsertBehavior = { + type: 'ARRAY_INSERT'; + resultPath: StorePath; + storePath: StorePath; + where: ArrayInsertWhere; +}; +export type MutationDeleteBehavior = { + type: 'DELETE'; + dataId: string; +}; +export type MutationArrayDeleteBehavior = { + type: 'ARRAY_DELETE'; + storePath: StorePath; + dataId: string; +}; +export type ArrayInsertWhere = 'PREPEND' | 'APPEND'; +export type MutationBehaviorReducerArgs = { + behavior: MutationBehavior; + result: GraphQLResult; + variables: any; + fragmentMap: FragmentMap; + selectionSet: SelectionSet; + config: ApolloReducerConfig; +}; +export type MutationBehaviorReducerMap = { + [type: string]: MutationBehaviorReducer; +}; +export type MutationBehaviorReducer = (state: NormalizedCache, args: MutationBehaviorReducerArgs) => NormalizedCache; +export function cleanArray(originalArray: any, dataId: any): any; +export const defaultMutationBehaviorReducers: { + [type: string]: MutationBehaviorReducer; +}; +} +declare module 'apollo-client/data/mutationResults' { +export * from '~apollo-client/data/mutationResults'; +} + // Generated by typings // Source: node_modules/apollo-client/index.d.ts declare module '~apollo-client/index' { -import { NetworkInterface, createNetworkInterface } from '~apollo-client/networkInterface'; -import { GraphQLResult, Document } from 'graphql'; +import { NetworkInterface, createNetworkInterface, addQueryMerging } from '~apollo-client/networkInterface'; +import { Document, FragmentDefinition } from 'graphql'; +import { print } from 'graphql-tag/printer'; import { createApolloStore, ApolloStore, createApolloReducer, ApolloReducerConfig } from '~apollo-client/store'; import { QueryManager, WatchQueryOptions, ObservableQuery } from '~apollo-client/QueryManager'; import { readQueryFromStore, readFragmentFromStore } from '~apollo-client/data/readFromStore'; +import { writeQueryToStore, writeFragmentToStore } from '~apollo-client/data/writeToStore'; import { IdGetter } from '~apollo-client/data/extensions'; import { QueryTransformer, addTypenameToSelectionSet } from '~apollo-client/queries/queryTransform'; -export { createNetworkInterface, createApolloStore, createApolloReducer, readQueryFromStore, readFragmentFromStore, addTypenameToSelectionSet as addTypename }; +import { MutationBehaviorReducerMap } from '~apollo-client/data/mutationResults'; +export { createNetworkInterface, addQueryMerging, createApolloStore, createApolloReducer, readQueryFromStore, readFragmentFromStore, addTypenameToSelectionSet as addTypename, writeQueryToStore, writeFragmentToStore, print as printAST }; +export type ApolloQueryResult = { + data: any; +}; +export let fragmentDefinitionsMap: { + [fragmentName: string]: FragmentDefinition[]; +}; +export function createFragment(doc: Document, fragments?: FragmentDefinition[]): FragmentDefinition[]; +export function disableFragmentWarnings(): void; +export function enableFragmentWarnings(): void; +export function clearFragmentDefinitions(): void; export default class ApolloClient { networkInterface: NetworkInterface; store: ApolloStore; @@ -429,20 +579,44 @@ export default class ApolloClient { reducerConfig: ApolloReducerConfig; queryTransformer: QueryTransformer; shouldBatch: boolean; - constructor({networkInterface, reduxRootKey, initialState, dataIdFromObject, queryTransformer, shouldBatch}?: { + shouldForceFetch: boolean; + dataId: IdGetter; + fieldWithArgs: (fieldName: string, args?: Object) => string; + constructor({networkInterface, reduxRootKey, initialState, dataIdFromObject, queryTransformer, shouldBatch, ssrMode, ssrForceFetchDelay, mutationBehaviorReducers}?: { networkInterface?: NetworkInterface; reduxRootKey?: string; initialState?: any; dataIdFromObject?: IdGetter; queryTransformer?: QueryTransformer; shouldBatch?: boolean; + ssrMode?: boolean; + ssrForceFetchDelay?: number; + mutationBehaviorReducers?: MutationBehaviorReducerMap; }); watchQuery: (options: WatchQueryOptions) => ObservableQuery; - query: (options: WatchQueryOptions) => Promise; + query: (options: WatchQueryOptions) => Promise<{ + data: any; + }>; mutate: (options: { mutation: Document; + resultBehaviors?: ({ + type: "ARRAY_INSERT"; + resultPath: (string | number)[]; + storePath: (string | number)[]; + where: "PREPEND" | "APPEND"; + } | { + type: "ARRAY_DELETE"; + storePath: (string | number)[]; + dataId: string; + } | { + type: "DELETE"; + dataId: string; + })[]; variables?: Object; - }) => Promise; + fragments?: FragmentDefinition[]; + }) => Promise<{ + data: any; + }>; reducer(): Function; middleware: () => (store: ApolloStore) => (next: any) => (action: any) => any; initStore(): void; diff --git a/typings/modules/apollo-client/typings.json b/typings/modules/apollo-client/typings.json index c4827034c3..1a0fb6525e 100644 --- a/typings/modules/apollo-client/typings.json +++ b/typings/modules/apollo-client/typings.json @@ -1,8 +1,8 @@ { "resolution": "main", "tree": { - "src": "/Users/james.baxley/Products/apollo/react-apollo/node_modules/apollo-client/index.d.ts", + "src": "/Users/sashko/git/react-apollo/node_modules/apollo-client/index.d.ts", "raw": "file:node_modules/apollo-client/index.d.ts", - "typings": "/Users/james.baxley/Products/apollo/react-apollo/node_modules/apollo-client/index.d.ts" + "typings": "/Users/sashko/git/react-apollo/node_modules/apollo-client/index.d.ts" } }