diff --git a/CHANGELOG.md b/CHANGELOG.md index f2160d1fa9b..27b179a7f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,17 @@ - Removed `apollo-boost` since Apollo Client 3.0 provides a boost like getting started experience out of the box.
[@hwillson](https://github.com/hwillson) in [#5217](https://github.com/apollographql/apollo-client/pull/5217) +- `InMemoryCache` provides a new API for storing local state that can be easily updated by external code: + ```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 + ``` + These local variables are _reactive_ in the sense that updating their values invalidates any previously cached query results that depended on the old values.
+ [@benjamn](https://github.com/benjamn) in [#5799](https://github.com/apollographql/apollo-client/pull/5799) + - The `queryManager` property of `ApolloClient` instances is now marked as `private`, paving the way for a more aggressive redesign of its API. diff --git a/src/cache/inmemory/__tests__/cache.ts b/src/cache/inmemory/__tests__/cache.ts index d4a59790acf..e35469d9960 100644 --- a/src/cache/inmemory/__tests__/cache.ts +++ b/src/cache/inmemory/__tests__/cache.ts @@ -1324,3 +1324,110 @@ describe("InMemoryCache#broadcastWatches", function () { ]); }); }); + +describe("cache.makeLocalVar", () => { + function makeCacheAndVar(resultCaching: boolean) { + const cache: InMemoryCache = new InMemoryCache({ + resultCaching, + typePolicies: { + Person: { + fields: { + name() { + return nameVar(); + }, + }, + }, + }, + }); + + const nameVar = cache.makeLocalVar("Ben"); + + const query = gql` + query { + onCall { + name + } + } + `; + + cache.writeQuery({ + query, + data: { + onCall: { + __typename: "Person", + }, + }, + }); + + return { + cache, + nameVar, + query, + }; + } + + it("should work with resultCaching enabled (default)", () => { + const { cache, nameVar, query } = makeCacheAndVar(true); + + const result1 = cache.readQuery({ query }); + expect(result1).toEqual({ + onCall: { + __typename: "Person", + name: "Ben", + }, + }); + + // No change before updating the nameVar. + expect(cache.readQuery({ query })).toBe(result1); + + expect(nameVar()).toBe("Ben"); + expect(nameVar("Hugh")).toBe("Hugh"); + + const result2 = cache.readQuery({ query }); + expect(result2).not.toBe(result1); + expect(result2).toEqual({ + onCall: { + __typename: "Person", + name: "Hugh", + }, + }); + + expect(nameVar()).toBe("Hugh"); + expect(nameVar("James")).toBe("James"); + + expect(cache.readQuery({ query })).toEqual({ + onCall: { + __typename: "Person", + name: "James", + }, + }); + }); + + it("should work with resultCaching disabled (unusual)", () => { + const { cache, nameVar, query } = makeCacheAndVar(false); + + const result1 = cache.readQuery({ query }); + expect(result1).toEqual({ + onCall: { + __typename: "Person", + name: "Ben", + }, + }); + + const result2 = cache.readQuery({ query }); + // Without resultCaching, equivalent results will not be ===. + expect(result2).not.toBe(result1); + expect(result2).toEqual(result1); + + expect(nameVar()).toBe("Ben"); + expect(nameVar("Hugh")).toBe("Hugh"); + + const result3 = cache.readQuery({ query }); + expect(result3).toEqual({ + onCall: { + __typename: "Person", + name: "Hugh", + }, + }); + }); +}); diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index c139e43e5ad..82d8c96a9e6 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -2,7 +2,7 @@ import './fixPolyfills'; import { DocumentNode } from 'graphql'; -import { wrap } from 'optimism'; +import { dep, wrap } from 'optimism'; import { ApolloCache, Transaction } from '../core/cache'; import { Cache } from '../core/types/Cache'; @@ -298,4 +298,21 @@ export class InMemoryCache extends ApolloCache { }), ); } + + public makeLocalVar(value: T): LocalVar { + return function LocalVar(newValue) { + if (arguments.length > 0) { + if (value !== newValue) { + value = newValue; + localVarDep.dirty(LocalVar); + } + } else { + localVarDep(LocalVar); + } + return value; + }; + } } + +const localVarDep = dep>(); +export type LocalVar = (newValue?: T) => T;