Skip to content

Commit

Permalink
Merge pull request #5976 from apollographql/reactive-cache.makeVar-br…
Browse files Browse the repository at this point in the history
…oadcast

Make updating cache.makeVar variables broadcast watches.
  • Loading branch information
benjamn committed Feb 21, 2020
2 parents af2c69c + 80d1fad commit 03f00a1
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 42 deletions.
14 changes: 7 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@
This API gracefully handles cases where multiple field values are associated with a single field name, and also removes the need for updating the cache by reading a query or fragment, modifying the result, and writing the modified result back into the cache. Behind the scenes, the `cache.evict` method is now implemented in terms of `cache.modify`. <br/>
[@benjamn](https://github.com/benjamn) in [#5909](https://github.com/apollographql/apollo-client/pull/5909)

- `InMemoryCache` provides a new API for storing local state that can be easily updated by external code:
- `InMemoryCache` provides a new API for storing client state that can be updated from anywhere:
```ts
const lv = cache.makeLocalVar(123)
console.log(lv()) // 123
console.log(lv(lv() + 1)) // 124
console.log(lv()) // 124
lv("asdf") // TS type error
const v = cache.makeVar(123)
console.log(v()) // 123
console.log(v(v() + 1)) // 124
console.log(v()) // 124
v("asdf") // TS type error
```
These local variables are _reactive_ in the sense that updating their values invalidates any previously cached query results that depended on the old values. <br/>
These variables are _reactive_ in the sense that updating their values invalidates any previously cached query results that depended on the old values. <br/>
[@benjamn](https://github.com/benjamn) in [#5799](https://github.com/apollographql/apollo-client/pull/5799)

- Various cache read and write performance optimizations, cutting read and write times by more than 50% in larger benchmarks. <br/>
Expand Down
2 changes: 1 addition & 1 deletion src/cache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export {
export {
InMemoryCache,
InMemoryCacheConfig,
LocalVar,
ReactiveVar,
} from './inmemory/inMemoryCache';

export {
Expand Down
4 changes: 2 additions & 2 deletions src/cache/inmemory/__tests__/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2293,7 +2293,7 @@ describe("InMemoryCache#modify", () => {
});
});

describe("cache.makeLocalVar", () => {
describe("cache.makeVar", () => {
function makeCacheAndVar(resultCaching: boolean) {
const cache: InMemoryCache = new InMemoryCache({
resultCaching,
Expand All @@ -2308,7 +2308,7 @@ describe("cache.makeLocalVar", () => {
},
});

const nameVar = cache.makeLocalVar("Ben");
const nameVar = cache.makeVar("Ben");

const query = gql`
query {
Expand Down
20 changes: 10 additions & 10 deletions src/cache/inmemory/__tests__/policies.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import gql from "graphql-tag";
import { InMemoryCache, LocalVar } from "../inMemoryCache";
import { StoreValue } from "../../../utilities";
import { FieldPolicy, Policies } from "../policies";

import { InMemoryCache } from "../inMemoryCache";
import { Policies } from "../policies";
import { Reference, StoreObject } from "../../../core";

function reverse(s: string) {
Expand Down Expand Up @@ -925,15 +925,15 @@ describe("type policies", function () {
result: {
read(_, { storage }) {
if (!storage.jobName) {
storage.jobName = cache.makeLocalVar<string>();
storage.jobName = cache.makeVar<string>();
}
return storage.jobName();
},
merge(_, incoming: string, { storage }) {
if (storage.jobName) {
storage.jobName(incoming);
} else {
storage.jobName = cache.makeLocalVar(incoming);
storage.jobName = cache.makeVar(incoming);
}
},
},
Expand Down Expand Up @@ -1439,11 +1439,11 @@ describe("type policies", function () {
// Rather than writing ownTime data into the cache, we maintain it
// externally in this object:
const ownTimes = {
"parent task": cache.makeLocalVar(2),
"child task 1": cache.makeLocalVar(3),
"child task 2": cache.makeLocalVar(4),
"grandchild task": cache.makeLocalVar(5),
"independent task": cache.makeLocalVar(11),
"parent task": cache.makeVar(2),
"child task 1": cache.makeVar(3),
"child task 2": cache.makeVar(4),
"grandchild task": cache.makeVar(5),
"independent task": cache.makeVar(11),
};

cache.writeQuery({
Expand Down
48 changes: 26 additions & 22 deletions src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
// cache.policies.addPossibletypes.
public readonly policies: Policies;

// Set this while in a transaction to prevent broadcasts...
// don't forget to turn it back on!
private silenceBroadcast: boolean = false;

constructor(config: InMemoryCacheConfig = {}) {
super();
this.config = { ...defaultConfig, ...config };
Expand Down Expand Up @@ -243,25 +239,28 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
}
}

private txCount = 0;

public performTransaction(
transaction: (proxy: InMemoryCache) => any,
transaction: (cache: InMemoryCache) => any,
// This parameter is not part of the performTransaction signature inherited
// from the ApolloCache abstract class, but it's useful because it saves us
// from duplicating this implementation in recordOptimisticTransaction.
optimisticId?: string,
) {
const perform = (layer?: EntityStore) => {
const proxy: InMemoryCache = Object.create(this);
proxy.silenceBroadcast = true;
const { data, optimisticData } = this;
++this.txCount;
if (layer) {
// The proxy object is just like this except that silenceBroadcast
// is set to true, and proxy.data and proxy.optimisticData both
// point to the same layer.
proxy.data = proxy.optimisticData = layer;
this.data = this.optimisticData = layer;
}
try {
transaction(this);
} finally {
--this.txCount;
this.data = data;
this.optimisticData = optimisticData;
}
// Because the proxy object can simply be forgotten, we do not need
// to wrap this call with a try-finally block.
return transaction(proxy);
};

if (typeof optimisticId === 'string') {
Expand All @@ -275,7 +274,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
perform();
}

// This broadcast does nothing if this.silenceBroadcast is true.
// This broadcast does nothing if this.txCount > 0.
this.broadcastWatches();
}

Expand Down Expand Up @@ -303,7 +302,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
}

protected broadcastWatches() {
if (!this.silenceBroadcast) {
if (!this.txCount) {
this.watches.forEach(c => this.maybeBroadcastWatch(c));
}
}
Expand All @@ -320,20 +319,25 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
);
}

public makeLocalVar<T>(value?: T): LocalVar<T> {
return function LocalVar(newValue) {
private varDep = dep<ReactiveVar<any>>();

public makeVar<T>(value?: T): ReactiveVar<T> {
const cache = this;
return function rv(newValue) {
if (arguments.length > 0) {
if (value !== newValue) {
value = newValue;
localVarDep.dirty(LocalVar);
cache.varDep.dirty(rv);
// In order to perform several ReactiveVar updates without
// broadcasting each time, use cache.performTransaction.
cache.broadcastWatches();
}
} else {
localVarDep(LocalVar);
cache.varDep(rv);
}
return value;
};
}
}

const localVarDep = dep<LocalVar<any>>();
export type LocalVar<T> = (newValue?: T) => T;
export type ReactiveVar<T> = (newValue?: T) => T;

0 comments on commit 03f00a1

Please sign in to comment.