diff --git a/docs/package.json b/docs/package.json index 989c9084d18..5ea98d09fe3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -21,8 +21,7 @@ }, "scripts": { "start": "npm run build && chexo apollo-hexo-config -- server", - "types": - "cd ../packages/apollo-client && typedoc --json ../../docs/docs.json --ignoreCompilerErrors ./src/index.ts", + "types": "cd ../packages/apollo-client && typedoc --json ../../docs/docs.json --ignoreCompilerErrors ./src/index.ts", "build": "npm run types && chexo apollo-hexo-config -- generate", "clean": "hexo clean", "test": "npm run clean; npm run build" diff --git a/packages/apollo-client/CHANGELOG.md b/packages/apollo-client/CHANGELOG.md index 3b397090062..d6e4993bc31 100644 --- a/packages/apollo-client/CHANGELOG.md +++ b/packages/apollo-client/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +### vNext + +- Typescript improvements. Made observable query parameterized on data and + variables: `ObservableQuery` + [PR#3140](https://github.com/apollographql/apollo-client/pull/3140) + ### 2.3.2 - Fix SSR and `cache-and-network` fetch policy @@ -30,7 +36,8 @@ ### 2.3.0 - fixed edge case bug of changing fetchPolicies right after resetStore with no variables present -- Various optimizations for cache read performance [#3300](https://github.com/apollographql/apollo-client/pull/3300) +- Various optimizations for cache read performance + [#3300](https://github.com/apollographql/apollo-client/pull/3300) ### 2.2.8 - Added the graphQLResultHasError in QueryManager.ts to check not only if result.errors is null, but also empty. diff --git a/packages/apollo-client/src/core/ObservableQuery.ts b/packages/apollo-client/src/core/ObservableQuery.ts index 4e9d07c3ffe..3e1e2f0b263 100644 --- a/packages/apollo-client/src/core/ObservableQuery.ts +++ b/packages/apollo-client/src/core/ObservableQuery.ts @@ -12,7 +12,7 @@ import { QueryScheduler } from '../scheduler/scheduler'; import { ApolloError } from '../errors/ApolloError'; import { QueryManager } from './QueryManager'; -import { ApolloQueryResult, FetchType } from './types'; +import { ApolloQueryResult, FetchType, OperationVariables } from './types'; import { ModifiableWatchQueryOptions, WatchQueryOptions, @@ -33,18 +33,21 @@ export type ApolloCurrentResult = { partial?: boolean; }; -export interface FetchMoreOptions { +export interface FetchMoreOptions< + TData = any, + TVariables = OperationVariables +> { updateQuery: ( - previousQueryResult: { [key: string]: any }, + previousQueryResult: TData, options: { - fetchMoreResult?: { [key: string]: any }; - variables: { [key: string]: any }; + fetchMoreResult?: TData; + variables?: TVariables; }, - ) => Object; + ) => TData; } -export interface UpdateQueryOptions { - variables?: Object; +export interface UpdateQueryOptions { + variables?: TVariables; } export const hasError = ( @@ -57,24 +60,27 @@ export const hasError = ( policy === 'none') || storeValue.networkError); -export class ObservableQuery extends Observable> { - public options: WatchQueryOptions; +export class ObservableQuery< + TData = any, + TVariables = OperationVariables +> extends Observable> { + public options: WatchQueryOptions; public queryId: string; /** * * The current value of the variables for this query. Can change. */ - public variables: { [key: string]: any }; + public variables: TVariables; private isCurrentlyPolling: boolean; private shouldSubscribe: boolean; private isTornDown: boolean; private scheduler: QueryScheduler; private queryManager: QueryManager; - private observers: Observer>[]; + private observers: Observer>[]; private subscriptionHandles: Subscription[]; - private lastResult: ApolloQueryResult; + private lastResult: ApolloQueryResult; private lastError: ApolloError; constructor({ @@ -83,10 +89,10 @@ export class ObservableQuery extends Observable> { shouldSubscribe = true, }: { scheduler: QueryScheduler; - options: WatchQueryOptions; + options: WatchQueryOptions; shouldSubscribe?: boolean; }) { - super((observer: Observer>) => + super((observer: Observer>) => this.onSubscribe(observer), ); @@ -96,7 +102,7 @@ export class ObservableQuery extends Observable> { // query information this.options = options; - this.variables = options.variables || {}; + this.variables = options.variables || ({} as TVariables); this.queryId = scheduler.queryManager.generateQueryId(); this.shouldSubscribe = shouldSubscribe; @@ -109,11 +115,11 @@ export class ObservableQuery extends Observable> { this.subscriptionHandles = []; } - public result(): Promise> { + public result(): Promise> { const that = this; return new Promise((resolve, reject) => { let subscription: Subscription; - const observer: Observer> = { + const observer: Observer> = { next(result) { resolve(result); @@ -149,7 +155,7 @@ export class ObservableQuery extends Observable> { * `partial` lets you know if the result from the local cache is complete or partial * @return {result: Object, loading: boolean, networkStatus: number, partial: boolean} */ - public currentResult(): ApolloCurrentResult { + public currentResult(): ApolloCurrentResult { if (this.isTornDown) { return { data: this.lastError ? {} : this.lastResult ? this.lastResult.data : {}, @@ -202,7 +208,7 @@ export class ObservableQuery extends Observable> { data, loading: isNetworkRequestInFlight(networkStatus), networkStatus, - } as ApolloQueryResult; + } as ApolloQueryResult; if ( queryStoreValue && @@ -217,12 +223,12 @@ export class ObservableQuery extends Observable> { this.lastResult = { ...result, stale }; } - return { ...result, partial } as ApolloCurrentResult; + return { ...result, partial } as ApolloCurrentResult; } // Returns the last result that observer.next was called with. This is not the same as // currentResult! If you're not sure which you need, then you probably need currentResult. - public getLastResult(): ApolloQueryResult { + public getLastResult(): ApolloQueryResult { return this.lastResult; } @@ -236,7 +242,7 @@ export class ObservableQuery extends Observable> { this.isTornDown = false; } - public refetch(variables?: any): Promise> { + public refetch(variables?: TVariables): Promise> { const { fetchPolicy } = this.options; // early return if trying to read from cache during refetch if (fetchPolicy === 'cache-only') { @@ -249,18 +255,16 @@ export class ObservableQuery extends Observable> { if (!isEqual(this.variables, variables)) { // update observable variables - this.variables = { - ...this.variables, - ...variables, - }; + this.variables = Object.assign({}, this.variables, variables); } if (!isEqual(this.options.variables, this.variables)) { // Update the existing options with new variables - this.options.variables = { - ...this.options.variables, - ...this.variables, - }; + this.options.variables = Object.assign( + {}, + this.options.variables, + this.variables, + ); } // Override fetchPolicy for this call only @@ -278,9 +282,10 @@ export class ObservableQuery extends Observable> { .then(result => maybeDeepFreeze(result)); } - public fetchMore( - fetchMoreOptions: FetchMoreQueryOptions & FetchMoreOptions, - ): Promise> { + public fetchMore( + fetchMoreOptions: FetchMoreQueryOptions & + FetchMoreOptions, + ): Promise> { // early return if no update Query if (!fetchMoreOptions.updateQuery) { throw new Error( @@ -302,10 +307,11 @@ export class ObservableQuery extends Observable> { combinedOptions = { ...this.options, ...fetchMoreOptions, - variables: { - ...this.variables, - ...fetchMoreOptions.variables, - }, + variables: Object.assign( + {}, + this.variables, + fetchMoreOptions.variables, + ), }; } @@ -321,19 +327,21 @@ export class ObservableQuery extends Observable> { .then(fetchMoreResult => { this.updateQuery((previousResult: any) => fetchMoreOptions.updateQuery(previousResult, { - fetchMoreResult: fetchMoreResult.data, + fetchMoreResult: fetchMoreResult.data as TData, variables: combinedOptions.variables, }), ); - return fetchMoreResult as ApolloQueryResult; + return fetchMoreResult as ApolloQueryResult; }); } // XXX the subscription variables are separate from the query variables. // if you want to update subscription variables, right now you have to do that separately, // and you can only do it by stopping the subscription and then subscribing again with new variables. - public subscribeToMore(options: SubscribeToMoreOptions): () => void { + public subscribeToMore( + options: SubscribeToMoreOptions, + ): () => void { const subscription = this.queryManager .startGraphQLSubscription({ query: options.document, @@ -342,11 +350,14 @@ export class ObservableQuery extends Observable> { .subscribe({ next: data => { if (options.updateQuery) { - this.updateQuery((previous: Object, { variables }) => - (options.updateQuery as UpdateQueryFn)(previous, { - subscriptionData: data, - variables, - }), + this.updateQuery((previous, { variables }) => + (options.updateQuery as UpdateQueryFn)( + previous, + { + subscriptionData: data as { data: TData }, + variables, + }, + ), ); } }, @@ -374,12 +385,11 @@ export class ObservableQuery extends Observable> { // will return null immediately. public setOptions( opts: ModifiableWatchQueryOptions, - ): Promise> { + ): Promise> { const oldOptions = this.options; - this.options = { - ...this.options, - ...opts, - } as WatchQueryOptions; + this.options = Object.assign({}, this.options, opts) as WatchQueryOptions< + TVariables + >; if (opts.pollInterval) { this.startPolling(opts.pollInterval); @@ -398,7 +408,7 @@ export class ObservableQuery extends Observable> { false; return this.setVariables( - this.options.variables, + this.options.variables as TVariables, tryFetch, opts.fetchResults, ); @@ -425,10 +435,10 @@ export class ObservableQuery extends Observable> { * */ public setVariables( - variables: any, + variables: TVariables, tryFetch: boolean = false, fetchResults = true, - ): Promise> { + ): Promise> { // since setVariables restarts the subscription, we reset the tornDown status this.isTornDown = false; @@ -462,7 +472,10 @@ export class ObservableQuery extends Observable> { } public updateQuery( - mapFn: (previousQueryResult: any, options: UpdateQueryOptions) => any, + mapFn: ( + previousQueryResult: TData, + options: UpdateQueryOptions, + ) => TData, ): void { const { previousResult, @@ -471,7 +484,7 @@ export class ObservableQuery extends Observable> { } = this.queryManager.getQueryWithPreviousResult(this.queryId); const newResult = tryFunctionOrLogError(() => - mapFn(previousResult, { variables }), + mapFn(previousResult, { variables: variables as TVariables }), ); if (newResult) { @@ -511,7 +524,7 @@ export class ObservableQuery extends Observable> { this.scheduler.startPollingQuery(this.options, this.queryId); } - private onSubscribe(observer: Observer>) { + private onSubscribe(observer: Observer>) { // Zen Observable has its own error function, in order to log correctly // we need to declare a custom error if nothing is passed if ( @@ -546,7 +559,7 @@ export class ObservableQuery extends Observable> { private setUpQuery() { if (this.shouldSubscribe) { - this.queryManager.addObservableQuery(this.queryId, this); + this.queryManager.addObservableQuery(this.queryId, this); } if (!!this.options.pollInterval) { @@ -560,11 +573,11 @@ export class ObservableQuery extends Observable> { } this.isCurrentlyPolling = true; - this.scheduler.startPollingQuery(this.options, this.queryId); + this.scheduler.startPollingQuery(this.options, this.queryId); } - const observer: Observer> = { - next: (result: ApolloQueryResult) => { + const observer: Observer> = { + next: (result: ApolloQueryResult) => { this.lastResult = result; this.observers.forEach(obs => obs.next && obs.next(result)); }, @@ -574,7 +587,7 @@ export class ObservableQuery extends Observable> { }, }; - this.queryManager.startQuery( + this.queryManager.startQuery( this.queryId, this.options, this.queryManager.queryListenerForObserver( diff --git a/packages/apollo-client/src/core/types.ts b/packages/apollo-client/src/core/types.ts index 596708bd4c1..2d44d8b5a5f 100644 --- a/packages/apollo-client/src/core/types.ts +++ b/packages/apollo-client/src/core/types.ts @@ -8,6 +8,8 @@ export type QueryListener = ( newData?: any, ) => void; +export type OperationVariables = { [key: string]: any }; + export type PureQueryOptions = { query: DocumentNode; variables?: { [key: string]: any }; diff --git a/packages/apollo-client/src/core/watchQueryOptions.ts b/packages/apollo-client/src/core/watchQueryOptions.ts index 7600b75083e..44db098db0e 100644 --- a/packages/apollo-client/src/core/watchQueryOptions.ts +++ b/packages/apollo-client/src/core/watchQueryOptions.ts @@ -4,7 +4,7 @@ import { DataProxy } from 'apollo-cache'; import { MutationQueryReducersMap } from './types'; -import { PureQueryOptions } from './types'; +import { PureQueryOptions, OperationVariables } from './types'; /** * fetchPolicy determines where the client may return a result from. The options are: @@ -36,12 +36,12 @@ export type ErrorPolicy = 'none' | 'ignore' | 'all'; /** * We can change these options to an ObservableQuery */ -export interface ModifiableWatchQueryOptions { +export interface ModifiableWatchQueryOptions { /** * A map going from variable name to variable value, where the variables are used * within the GraphQL query. */ - variables?: { [key: string]: any }; + variables?: TVariables; /** * The time interval (in milliseconds) on which this query should be @@ -73,7 +73,8 @@ export interface ModifiableWatchQueryOptions { /** * The argument to a query */ -export interface WatchQueryOptions extends ModifiableWatchQueryOptions { +export interface WatchQueryOptions + extends ModifiableWatchQueryOptions { /** * A GraphQL document that consists of a single query to be sent down to the * server. @@ -93,23 +94,26 @@ export interface WatchQueryOptions extends ModifiableWatchQueryOptions { context?: any; } -export interface FetchMoreQueryOptions { +export interface FetchMoreQueryOptions { query?: DocumentNode; - variables?: { [key: string]: any }; + variables?: Pick; } -export type UpdateQueryFn = ( - previousQueryResult: Object, +export type UpdateQueryFn = ( + previousQueryResult: TData, options: { - subscriptionData: { data: any }; - variables?: { [key: string]: any }; + subscriptionData: { data: TData }; + variables?: TVariables; }, -) => Object; +) => TData; -export type SubscribeToMoreOptions = { +export type SubscribeToMoreOptions< + TData = any, + TVariables = OperationVariables +> = { document: DocumentNode; - variables?: { [key: string]: any }; - updateQuery?: UpdateQueryFn; + variables?: TVariables; + updateQuery?: UpdateQueryFn; onError?: (error: Error) => void; };