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 public method to retrieve all current observable queries #7813

Merged
12 changes: 12 additions & 0 deletions src/__tests__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2442,6 +2442,18 @@ describe('client', () => {
expect(spy).toHaveBeenCalledWith(options);
});

it('has a getObservableQueries method which calls QueryManager', async () => {
const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
});

// @ts-ignore
const spy = jest.spyOn(client.queryManager, 'getObservableQueries');
await client.getObservableQueries();
expect(spy).toHaveBeenCalled();
});

itAsync('should propagate errors from network interface to observers', (resolve, reject) => {
const link = ApolloLink.from([
() =>
Expand Down
272 changes: 270 additions & 2 deletions src/__tests__/refetchQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ describe("client.refetchQueries", () => {
operation.operationName.split("").forEach(letter => {
data[letter.toLowerCase()] = letter.toUpperCase();
});
observer.next({ data });
observer.complete();
function finish() {
observer.next({ data });
observer.complete();
}
if (typeof operation.variables.delay === "number") {
setTimeout(finish, operation.variables.delay);
} else finish();
})),
});
}
Expand Down Expand Up @@ -351,6 +356,269 @@ describe("client.refetchQueries", () => {
resolve();
});

itAsync('includes all queries when options.include === "all"', async (resolve, reject) => {
const client = makeClient();
const [
aObs,
bObs,
abObs,
] = await setup(client);

const ayyResults = await client.refetchQueries({
include: "all",

updateCache(cache) {
cache.writeQuery({
query: aQuery,
data: {
a: "Ayy",
},
});
},

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "Ayy" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "Ayy", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(ayyResults);

expect(ayyResults).toEqual([
{ a: "Ayy" },
{ a: "Ayy", b: "B" },
{ b: "B" },
]);

const beeResults = await client.refetchQueries({
include: "all",

updateCache(cache) {
cache.writeQuery({
query: bQuery,
data: {
b: "Bee",
},
});
},

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "Ayy" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "Bee" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "Ayy", b: "Bee" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return diff.result;
},
});

sortObjects(beeResults);

expect(beeResults).toEqual([
{ a: "Ayy" },
{ a: "Ayy", b: "Bee" },
{ b: "Bee" },
]);

unsubscribe();
resolve();
});

itAsync('includes all active queries when options.include === "active"', async (resolve, reject) => {
const client = makeClient();
const [
aObs,
bObs,
abObs,
] = await setup(client);

const extraObs = client.watchQuery({ query: abQuery });
expect(extraObs.hasObservers()).toBe(false);

const activeResults = await client.refetchQueries({
include: "active",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(activeResults);

expect(activeResults).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
{ b: "B" },
]);

subs.push(extraObs.subscribe({
next(result) {
expect(result).toEqual({ a: "A", b: "B" });
},
}));
expect(extraObs.hasObservers()).toBe(true);

const resultsAfterSubscribe = await client.refetchQueries({
include: "active",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else if (obs === extraObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(resultsAfterSubscribe);

expect(resultsAfterSubscribe).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
// Included thanks to extraObs this time.
{ a: "A", b: "B" },
// Sorted last by sortObjects.
{ b: "B" },
]);

unsubscribe();
resolve();
});

itAsync('should not include unwatched single queries', async (resolve, reject) => {
const client = makeClient();
const [
aObs,
bObs,
abObs,
] = await setup(client);

const delayedQuery = gql`query DELAYED { d e l a y e d }`;

client.query({
query: delayedQuery,
variables: {
// Delay this query by 10 seconds so it stays in-flight.
delay: 10000,
},
}).catch(reject);

const queries = client["queryManager"]["queries"];
expect(queries.size).toBe(4);

queries.forEach((queryInfo, queryId) => {
if (
queryId === "1" ||
queryId === "2" ||
queryId === "3"
) {
expect(queryInfo.observableQuery).toBeInstanceOf(ObservableQuery);
} else if (queryId === "4") {
// One-off client.query-style queries never get an ObservableQuery, so
// they should not be included by include: "active".
expect(queryInfo.observableQuery).toBe(null);
expect(queryInfo.document).toBe(delayedQuery);
}
});

const activeResults = await client.refetchQueries({
include: "active",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(activeResults);

expect(activeResults).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
{ b: "B" },
]);

const allResults = await client.refetchQueries({
include: "all",

onQueryUpdated(obs, diff) {
if (obs === aObs) {
expect(diff.result).toEqual({ a: "A" });
} else if (obs === bObs) {
expect(diff.result).toEqual({ b: "B" });
} else if (obs === abObs) {
expect(diff.result).toEqual({ a: "A", b: "B" });
} else {
reject(`unexpected ObservableQuery ${
obs.queryId
} with name ${obs.queryName}`);
}
return Promise.resolve(diff.result);
},
});

sortObjects(allResults);

expect(allResults).toEqual([
{ a: "A" },
{ a: "A", b: "B" },
{ b: "B" },
]);

unsubscribe();
client.stop();

expect(queries.size).toBe(0);

resolve();
});

itAsync("refetches watched queries if onQueryUpdated not provided", async (resolve, reject) => {
const client = makeClient();
const [
Expand Down
15 changes: 15 additions & 0 deletions src/core/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RefetchQueriesOptions,
RefetchQueriesResult,
InternalRefetchQueriesResult,
RefetchQueriesInclude,
} from './types';

import {
Expand Down Expand Up @@ -564,6 +565,20 @@ export class ApolloClient<TCacheShape> implements DataProxy {
return result;
}

/**
* Get all currently active `ObservableQuery` objects, in a `Map` keyed by
* query ID strings. An "active" query is one that has observers and a
* `fetchPolicy` other than "standby" or "cache-only". You can include all
* `ObservableQuery` objects (including the inactive ones) by passing "all"
* instead of "active", or you can include just a subset of active queries by
* passing an array of query names or DocumentNode objects.
*/
public getObservableQueries(
include: RefetchQueriesInclude = "active",
): Map<string, ObservableQuery<any>> {
return this.queryManager.getObservableQueries(include);
}
Comment on lines +576 to +580
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, all "active" queries will be included, but you can pass any RefetchQueriesInclude-typed value (in other words, anything you can pass as options.include to client.refetchQueries).


/**
* Exposes the cache's complete state, in a serializable format for later restoration.
*/
Expand Down
Loading