Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add client awareness support #4154

Merged
merged 14 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Apollo Client (vNext)

### Apollo Client (vNext)

- The `ApolloClient` constructor has been updated to accept `name` and
`version` params, that can be used to support Apollo Server [Client Awareness](https://www.apollographql.com/docs/apollo-server/v2/features/metrics.html#Client-Awareness)
functionality. These client awareness properties are passed into the
defined Apollo Link chain, and are then ultimately sent out as custom
headers with outgoing requests. <br/>
[@hwillson](https://github.com/hwillson) in [#4154](https://github.com/apollographql/apollo-client/pull/4154)

## Apollo Client (2.4.6)

### Apollo Cache In-Memory (1.3.10)
Expand Down
2 changes: 2 additions & 0 deletions docs/source/api/apollo-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The Apollo Client constructor takes a small number of options, of which two are
- `ssrForceFetchDelay`: determines the time interval before Apollo Client force fetchs queries after a server side render.
- `connectToDevTools`: This argument allows the [Apollo Client Devtools](../features/developer-tooling.html) to connect to your application's Apollo Client. You can set this to be `true` to use the tools in production (they are on by default in dev mode).
- `queryDeduplication`: If set to false, this argument will force a query to still be sent to the server even if a query with identical parameters (query, variables, operationName) is already in flight.
- `name`: A custom name that can be used to identify this client, e.g. "iOS". Apollo Server leverages this property as part of its [Client Awareness](/docs/apollo-server/v2/features/metrics.html#Client-Awareness) functionality.
- `version`: A custom version that can be used to identify this client, when using Apollo client awareness features. This is the version of your client, which you may want to increment on new builds. This is NOT the version of Apollo Client that you are using. Apollo Server leverages this property as part of its [Client Awareness](/docs/apollo-server/v2/features/metrics.html#Client-Awareness) functionality.
- `defaultOptions`: If you want to set application wide defaults for the options supplied to `watchQuery`, `query`, or `mutate`, you can pass them as a `defaultOptions` object. An example object looks like this:

```js
Expand Down
34 changes: 30 additions & 4 deletions packages/apollo-client/src/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type ApolloClientOptions<TCacheShape> = {
connectToDevTools?: boolean;
queryDeduplication?: boolean;
defaultOptions?: DefaultOptions;
name?: string;
version?: string;
};

/**
Expand All @@ -71,6 +73,7 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
private proxy: ApolloCache<TCacheShape> | undefined;
private ssrMode: boolean;
private resetStoreCallbacks: Array<() => Promise<any>> = [];
private clientAwareness: Record<string, string> = {};

/**
* Constructs an instance of {@link ApolloClient}.
Expand All @@ -87,8 +90,19 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
* @param queryDeduplication If set to false, a query will still be sent to the server even if a query
* with identical parameters (query, variables, operationName) is already in flight.
*
* @param defaultOptions Used to set application wide defaults for the
* options supplied to `watchQuery`, `query`, or
* `mutate`.
*
* @param name A custom name that can be used to identify this client, when
* using Apollo client awareness features. E.g. "iOS".
*
* @param version A custom version that can be used to identify this client,
* when using Apollo client awareness features. This is the
* version of your client, which you may want to increment on
* new builds. This is NOT the version of Apollo Client that
* you are using.
*/

constructor(options: ApolloClientOptions<TCacheShape>) {
const {
link,
Expand All @@ -98,6 +112,8 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
connectToDevTools,
queryDeduplication = true,
defaultOptions,
name: clientAwarenessName,
version: clientAwarenessVersion,
} = options;

if (!link || !cache) {
Expand All @@ -114,7 +130,7 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
const supportedDirectives = new ApolloLink(
(operation: Operation, forward: NextLink) => {
let result = supportedCache.get(operation.query);
if (! result) {
if (!result) {
result = removeConnectionDirectiveFromDocument(operation.query);
supportedCache.set(operation.query, result);
supportedCache.set(result, result);
Expand Down Expand Up @@ -191,7 +207,16 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
}
}
}

this.version = version;

if (clientAwarenessName) {
this.clientAwareness.name = clientAwarenessName;
}

if (clientAwarenessVersion) {
this.clientAwareness.version = clientAwarenessVersion;
}
}
/**
* This watches the cache store of the query according to the options specified and
Expand Down Expand Up @@ -402,6 +427,7 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
store: this.store,
queryDeduplication: this.queryDeduplication,
ssrMode: this.ssrMode,
clientAwareness: this.clientAwareness,
onBroadcast: () => {
if (this.devToolsHookCb) {
this.devToolsHookCb({
Expand Down Expand Up @@ -460,8 +486,8 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
*/
public clearStore(): Promise<void | null> {
const { queryManager } = this;
return Promise.resolve().then(
() => (queryManager ? queryManager.clearStore() : Promise.resolve(null)),
return Promise.resolve().then(() =>
queryManager ? queryManager.clearStore() : Promise.resolve(null),
);
}

Expand Down
34 changes: 21 additions & 13 deletions packages/apollo-client/src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class QueryManager<TStore> {

private deduplicator: ApolloLink;
private queryDeduplication: boolean;
private clientAwareness: Record<string, string> = {};

private onBroadcast: () => void;

Expand All @@ -94,19 +95,21 @@ export class QueryManager<TStore> {
store,
onBroadcast = () => undefined,
ssrMode = false,
clientAwareness = {},
}: {
link: ApolloLink;
queryDeduplication?: boolean;
store: DataStore<TStore>;
onBroadcast?: () => void;
ssrMode?: boolean;
clientAwareness?: Record<string, string>;
}) {
this.link = link;
this.deduplicator = ApolloLink.from([new Deduplicator(), link]);
this.queryDeduplication = queryDeduplication;
this.dataStore = store;
this.onBroadcast = onBroadcast;

this.clientAwareness = clientAwareness;
this.scheduler = new QueryScheduler({ queryManager: this, ssrMode });
}

Expand Down Expand Up @@ -604,9 +607,11 @@ export class QueryManager<TStore> {
}

if (observer.next) {
if (previouslyHadError ||
!observableQuery ||
observableQuery.isDifferentFromLastResult(resultFromStore)) {
if (
previouslyHadError ||
!observableQuery ||
observableQuery.isDifferentFromLastResult(resultFromStore)
) {
try {
observer.next(resultFromStore);
} catch (e) {
Expand Down Expand Up @@ -1212,15 +1217,17 @@ export class QueryManager<TStore> {
}

private getQuery(queryId: string) {
return this.queries.get(queryId) || {
listeners: [],
invalidated: false,
document: null,
newData: null,
lastRequestId: null,
observableQuery: null,
subscriptions: [],
};
return (
this.queries.get(queryId) || {
listeners: [],
invalidated: false,
document: null,
newData: null,
lastRequestId: null,
observableQuery: null,
subscriptions: [],
}
);
}

private setQuery(queryId: string, updater: (prev: QueryInfo) => any) {
Expand Down Expand Up @@ -1268,6 +1275,7 @@ export class QueryManager<TStore> {
);
}
},
clientAwareness: this.clientAwareness,
},
};
}
Expand Down
50 changes: 50 additions & 0 deletions packages/apollo-client/src/core/__tests__/QueryManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,18 @@ describe('QueryManager', () => {
const createQueryManager = ({
link,
config = {},
clientAwareness = {},
}: {
link?: ApolloLink;
config?: ApolloReducerConfig;
clientAwareness?: { [key: string]: string };
}) => {
return new QueryManager({
link: link || mockSingleLink(),
store: new DataStore(
new InMemoryCache({ addTypename: false, ...config }),
),
clientAwareness,
});
};

Expand Down Expand Up @@ -4771,4 +4774,51 @@ describe('QueryManager', () => {
},
);
});

describe('client awareness', () => {
it('should pass client awareness settings into the link chain via context', done => {
const query = gql`
query {
author {
firstName
lastName
}
}
`;

const data = {
author: {
firstName: 'John',
lastName: 'Smith',
},
};

const link = mockSingleLink({
request: { query },
result: { data },
});

const clientAwareness = {
name: 'Test',
version: '1.0.0',
};

const queryManager = createQueryManager({
link,
clientAwareness,
});

const observable = queryManager.watchQuery<any>({
query,
fetchPolicy: 'no-cache',
});

observableToPromise({ observable }, result => {
const context = link.operation.getContext();
expect(context.clientAwareness).toBeDefined();
expect(context.clientAwareness).toEqual(clientAwareness);
done();
});
});
});
});