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

Added custom objectCache root in inMemoryCache #5601

Closed
Closed
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
4 changes: 2 additions & 2 deletions src/__tests__/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2290,15 +2290,15 @@ describe('ApolloClient', () => {
`,
});

expect((client.cache as any).data.data).toEqual({
expect((client.cache as any).data.cache.toObject()).toEqual({
ROOT_QUERY: {
__typename: "Query",
a: 1,
},
});

await client.clearStore();
expect((client.cache as any).data.data).toEqual({});
expect((client.cache as any).data.cache.toObject()).toEqual({});
});
});

Expand Down
35 changes: 19 additions & 16 deletions src/cache/inmemory/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { NormalizedCache, NormalizedCacheObject } from './types';
import { hasOwn, fieldNameFromStoreName } from './helpers';
import { Policies } from './policies';
import { ObjectCache } from './objectCache';
import { Cache } from '../core/types/Cache';
import {
SafeReadonly,
Expand All @@ -29,11 +30,11 @@ const DELETE: any = Object.create(null);
const delModifier: Modifier<any> = () => DELETE;

export abstract class EntityStore implements NormalizedCache {
protected data: NormalizedCacheObject = Object.create(null);

constructor(
public readonly policies: Policies,
public readonly group: CacheGroup,
protected cache: ObjectCache = new ObjectCache()
) {}

public abstract addLayer(
Expand All @@ -48,7 +49,7 @@ export abstract class EntityStore implements NormalizedCache {
// are inherited by the Root and Layer subclasses.

public toObject(): NormalizedCacheObject {
return { ...this.data };
return { ...this.cache.toObject() };
}

public has(dataId: string): boolean {
Expand All @@ -57,8 +58,8 @@ export abstract class EntityStore implements NormalizedCache {

public get(dataId: string, fieldName: string): StoreValue {
this.group.depend(dataId, fieldName);
if (hasOwn.call(this.data, dataId)) {
const storeObject = this.data[dataId];
if (this.cache.has(dataId)) {
const storeObject = this.cache.get(dataId);
if (storeObject && hasOwn.call(storeObject, fieldName)) {
return storeObject[fieldName];
}
Expand All @@ -79,7 +80,7 @@ export abstract class EntityStore implements NormalizedCache {
// should not rely on this dependency, since the contents could change
// without the object being added or removed.
if (dependOnExistence) this.group.depend(dataId, "__exists");
return hasOwn.call(this.data, dataId) ? this.data[dataId] :
return this.cache.has(dataId) ? this.cache.get(dataId) :
this instanceof Layer ? this.parent.lookup(dataId, dependOnExistence) : void 0;
}

Expand All @@ -88,7 +89,7 @@ export abstract class EntityStore implements NormalizedCache {
const merged = new DeepMerger(storeObjectReconciler).merge(existing, incoming);
// Even if merged === existing, existing may have come from a lower
// layer, so we always need to set this.data[dataId] on this level.
this.data[dataId] = merged;
this.cache.set(dataId, merged)
if (merged !== existing) {
delete this.refs[dataId];
if (this.group.caching) {
Expand Down Expand Up @@ -173,9 +174,9 @@ export abstract class EntityStore implements NormalizedCache {

if (allDeleted) {
if (this instanceof Layer) {
this.data[dataId] = void 0;
this.cache.set(dataId, void 0);
} else {
delete this.data[dataId];
this.cache.delete(dataId);
}
this.group.dirty(dataId, "__exists");
}
Expand Down Expand Up @@ -214,7 +215,7 @@ export abstract class EntityStore implements NormalizedCache {
public evict(options: Cache.EvictOptions): boolean {
let evicted = false;
if (options.id) {
if (hasOwn.call(this.data, options.id)) {
if (this.cache.has(options.id)) {
evicted = this.delete(options.id, options.fieldName, options.args);
}
if (this instanceof Layer) {
Expand All @@ -236,7 +237,7 @@ export abstract class EntityStore implements NormalizedCache {
}

public replace(newData: NormalizedCacheObject | null): void {
Object.keys(this.data).forEach(dataId => {
this.cache.getAllKeys().forEach(dataId => {
if (!(newData && hasOwn.call(newData, dataId))) {
this.delete(dataId);
}
Expand Down Expand Up @@ -313,7 +314,7 @@ export abstract class EntityStore implements NormalizedCache {
public findChildRefIds(dataId: string): Record<string, true> {
if (!hasOwn.call(this.refs, dataId)) {
const found = this.refs[dataId] = Object.create(null);
const workSet = new Set([this.data[dataId]]);
const workSet = new Set([this.cache.get(dataId)]);
// Within the store, only arrays and objects can contain child entity
// references, so we can prune the traversal using this predicate:
const canTraverse = (obj: any) => obj !== null && typeof obj === 'object';
Expand Down Expand Up @@ -445,12 +446,14 @@ export namespace EntityStore {
policies,
resultCaching = true,
seed,
objectCache
}: {
policies: Policies;
resultCaching?: boolean;
seed?: NormalizedCacheObject;
objectCache?: ObjectCache;
}) {
super(policies, new CacheGroup(resultCaching));
super(policies, new CacheGroup(resultCaching), objectCache);
this.sharedLayerGroup = new CacheGroup(resultCaching);
if (seed) this.replace(seed);
}
Expand Down Expand Up @@ -497,12 +500,12 @@ class Layer extends EntityStore {
if (layerId === this.id) {
// Dirty every ID we're removing.
if (this.group.caching) {
Object.keys(this.data).forEach(dataId => {
this.cache.getAllKeys().forEach(dataId => {
// If this.data[dataId] contains nothing different from what
// lies beneath, we can avoid dirtying this dataId and all of
// its fields, and simply discard this Layer. The only reason we
// call this.delete here is to dirty the removed fields.
if (this.data[dataId] !== (parent as Layer).lookup(dataId)) {
if (this.cache.get(dataId) !== (parent as Layer).lookup(dataId)) {
this.delete(dataId);
}
});
Expand All @@ -520,13 +523,13 @@ class Layer extends EntityStore {
public toObject(): NormalizedCacheObject {
return {
...this.parent.toObject(),
...this.data,
...this.cache.toObject()
};
}

public findChildRefIds(dataId: string): Record<string, true> {
const fromParent = this.parent.findChildRefIds(dataId);
return hasOwn.call(this.data, dataId) ? {
return this.cache.has(dataId) ? {
...fromParent,
...super.findChildRefIds(dataId),
} : fromParent;
Expand Down
3 changes: 3 additions & 0 deletions src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import {
Policies,
TypePolicies,
} from './policies';
import { ObjectCache } from "./objectCache";
import { hasOwn } from './helpers';

export interface InMemoryCacheConfig extends ApolloReducerConfig {
resultCaching?: boolean;
possibleTypes?: PossibleTypesMap;
typePolicies?: TypePolicies;
objectCache?: ObjectCache;
}

const defaultConfig: InMemoryCacheConfig = {
Expand Down Expand Up @@ -78,6 +80,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
this.data = new EntityStore.Root({
policies: this.policies,
resultCaching: this.config.resultCaching,
objectCache: this.config.objectCache
});

// When no optimistic writes are currently active, cache.optimisticData ===
Expand Down
31 changes: 31 additions & 0 deletions src/cache/inmemory/objectCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NormalizedCacheObject, StoreObject } from "./types";

const hasOwn = Object.prototype.hasOwnProperty;

export class ObjectCache {
constructor(protected data: NormalizedCacheObject = Object.create(null)) {}

public toObject() {
return this.data;
}

public get(dataId: string) {
return this.data[dataId]!;
}

public set(dataId: string, value: StoreObject | undefined) {
this.data[dataId] = value;
}

public has(dataId: string): boolean {
return hasOwn.call(this.data, dataId);
}

public delete(dataId: string) {
delete this.data[dataId];
}

public getAllKeys(): string[] {
return Object.keys(this.data);
}
}