Skip to content

Commit

Permalink
Make it possible to extend ApolloCache<NormalizedCacheObject> in a In…
Browse files Browse the repository at this point in the history
…MemoryCache compatible way
  • Loading branch information
msand committed Aug 28, 2024
1 parent 82d8cb4 commit 0510eab
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 57 deletions.
3 changes: 0 additions & 3 deletions src/__tests__/__snapshots__/exports.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Array [
"ApolloError",
"ApolloLink",
"ApolloProvider",
"Cache",
"DocumentTransform",
"DocumentType",
"HttpLink",
Expand Down Expand Up @@ -74,7 +73,6 @@ Array [
exports[`exports of public entry points @apollo/client/cache 1`] = `
Array [
"ApolloCache",
"Cache",
"EntityStore",
"InMemoryCache",
"MissingFieldError",
Expand All @@ -96,7 +94,6 @@ Array [
"ApolloClient",
"ApolloError",
"ApolloLink",
"Cache",
"DocumentTransform",
"HttpLink",
"InMemoryCache",
Expand Down
37 changes: 25 additions & 12 deletions src/cache/core/types/Cache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataProxy } from "./DataProxy.js";
import type { DataProxy } from "./DataProxy.js";
import type { AllFieldsModifier, Modifiers } from "./common.js";
import type { ApolloCache } from "../cache.js";

Expand All @@ -8,8 +8,10 @@ export namespace Cache {
lastDiff?: Cache.DiffResult<TData>
) => void;

export interface ReadOptions<TVariables = any, TData = any>
extends DataProxy.Query<TVariables, TData> {
export type ReadOptions<TVariables = any, TData = any> = DataProxy.Query<
TVariables,
TData
> & {
rootId?: string;
previousResult?: any;
optimistic: boolean;
Expand All @@ -22,7 +24,7 @@ export namespace Cache {
* the risk of memory leaks.
*/
canonizeResults?: boolean;
}
};

export interface WriteOptions<TResult = any, TVariables = any>
extends Omit<DataProxy.Query<TVariables, TResult>, "id">,
Expand Down Expand Up @@ -104,12 +106,23 @@ export namespace Cache {
) => any;
}

export import DiffResult = DataProxy.DiffResult;
export import ReadQueryOptions = DataProxy.ReadQueryOptions;
export import ReadFragmentOptions = DataProxy.ReadFragmentOptions;
export import WriteQueryOptions = DataProxy.WriteQueryOptions;
export import WriteFragmentOptions = DataProxy.WriteFragmentOptions;
export import UpdateQueryOptions = DataProxy.UpdateQueryOptions;
export import UpdateFragmentOptions = DataProxy.UpdateFragmentOptions;
export import Fragment = DataProxy.Fragment;
export type DiffResult<T> = DataProxy.DiffResult<T>;
export type ReadQueryOptions<TData, TVariables> = DataProxy.ReadQueryOptions<
TData,
TVariables
>;
export type ReadFragmentOptions<TData, TVariables> =
DataProxy.ReadFragmentOptions<TData, TVariables>;
export type WriteQueryOptions<TData, TVariables> =
DataProxy.WriteQueryOptions<TData, TVariables>;
export type WriteFragmentOptions<TData, TVariables> =
DataProxy.WriteFragmentOptions<TData, TVariables>;
export type UpdateQueryOptions<TData, TVariables> =
DataProxy.UpdateQueryOptions<TData, TVariables>;
export type UpdateFragmentOptions<TData, TVariables> =
DataProxy.UpdateFragmentOptions<TData, TVariables>;
export type Fragment<TVariables, TData> = DataProxy.Fragment<
TVariables,
TData
>;
}
2 changes: 1 addition & 1 deletion src/cache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type {
WatchFragmentResult,
} from "./core/cache.js";
export { ApolloCache } from "./core/cache.js";
export { Cache } from "./core/types/Cache.js";
export type { Cache } from "./core/types/Cache.js";
export type { DataProxy } from "./core/types/DataProxy.js";
export type {
MissingTree,
Expand Down
4 changes: 3 additions & 1 deletion src/cache/inmemory/__tests__/optimistic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import gql from "graphql-tag";

import { InMemoryCache } from "../inMemoryCache";
import { ApolloCache } from "../../core/cache";
import type { NormalizedCacheObject } from "../types";

describe("optimistic cache layers", () => {
it("return === results for repeated reads", () => {
Expand Down Expand Up @@ -28,7 +30,7 @@ describe("optimistic cache layers", () => {
}
`;

function readOptimistic(cache: InMemoryCache) {
function readOptimistic(cache: ApolloCache<NormalizedCacheObject>) {
return cache.readQuery<{ book: any }>({ query }, true);
}

Expand Down
6 changes: 3 additions & 3 deletions src/cache/inmemory/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import type {
} from "../core/types/common.js";
import type { DocumentNode, FieldNode, SelectionSetNode } from "graphql";

const DELETE: DeleteModifier = Object.create(null);
const delModifier: Modifier<any> = () => DELETE;
const INVALIDATE: InvalidateModifier = Object.create(null);
export const DELETE: DeleteModifier = Object.create(null);
export const delModifier: Modifier<any> = () => DELETE;
export const INVALIDATE: InvalidateModifier = Object.create(null);

export abstract class EntityStore implements NormalizedCache {
protected data: NormalizedCacheObject = Object.create(null);
Expand Down
43 changes: 24 additions & 19 deletions src/cache/inmemory/inMemoryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,25 @@ import type { OperationVariables } from "../../core/index.js";
import { getInMemoryCacheMemoryInternals } from "../../utilities/caching/getMemoryInternals.js";

type BroadcastOptions = Pick<
Cache.BatchOptions<InMemoryCache>,
Cache.BatchOptions<ApolloCache<NormalizedCacheObject>>,
"optimistic" | "onWatchUpdated"
>;

export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
private data!: EntityStore;
private optimisticData!: EntityStore;
public data!: EntityStore;
public optimisticData!: EntityStore;

protected config: InMemoryCacheConfig;
private watches = new Set<Cache.WatchOptions>();
private addTypename: boolean;
public readonly config: InMemoryCacheConfig;
public readonly watches = new Set<Cache.WatchOptions>();
public readonly addTypename: boolean;

private storeReader!: StoreReader;
private storeWriter!: StoreWriter;
private addTypenameTransform = new DocumentTransform(addTypenameToDocument);
public storeReader!: StoreReader;
public storeWriter!: StoreWriter;
public readonly addTypenameTransform = new DocumentTransform(
addTypenameToDocument
);

private maybeBroadcastWatch!: OptimisticWrapperFunction<
public maybeBroadcastWatch!: OptimisticWrapperFunction<
[Cache.WatchOptions, BroadcastOptions?],
any,
[Cache.WatchOptions]
Expand Down Expand Up @@ -80,7 +82,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
this.init();
}

private init() {
init() {
// Passing { resultCaching: false } in the InMemoryCache constructor options
// will completely disable dependency tracking, which will improve memory
// usage but worsen the performance of repeated reads.
Expand All @@ -99,7 +101,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
this.resetResultCache();
}

private resetResultCache(resetResultIdentities?: boolean) {
resetResultCache(resetResultIdentities?: boolean) {
const previousReader = this.storeReader;
const { fragments } = this.config;

Expand Down Expand Up @@ -411,10 +413,13 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
}
}

private txCount = 0;
public txCount = 0;

public batch<TUpdateResult>(
options: Cache.BatchOptions<InMemoryCache, TUpdateResult>
options: Cache.BatchOptions<
ApolloCache<NormalizedCacheObject>,
TUpdateResult
>
): TUpdateResult {
const {
update,
Expand Down Expand Up @@ -515,7 +520,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
}

public performTransaction(
update: (cache: InMemoryCache) => any,
update: (cache: ApolloCache<NormalizedCacheObject>) => any,
optimisticId?: string | null
) {
return this.batch({
Expand All @@ -528,18 +533,18 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
return this.addTypenameToDocument(this.addFragmentsToDocument(document));
}

protected broadcastWatches(options?: BroadcastOptions) {
public broadcastWatches(options?: BroadcastOptions) {
if (!this.txCount) {
this.watches.forEach((c) => this.maybeBroadcastWatch(c, options));
}
}

private addFragmentsToDocument(document: DocumentNode) {
addFragmentsToDocument(document: DocumentNode) {
const { fragments } = this.config;
return fragments ? fragments.transform(document) : document;
}

private addTypenameToDocument(document: DocumentNode) {
addTypenameToDocument(document: DocumentNode) {
if (this.addTypename) {
return this.addTypenameTransform.transformDocument(document);
}
Expand All @@ -552,7 +557,7 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
// simpler to check for changes after recomputing a result but before
// broadcasting it, but this wrapping approach allows us to skip both
// the recomputation and the broadcast, in most cases.
private broadcastWatch(c: Cache.WatchOptions, options?: BroadcastOptions) {
broadcastWatch(c: Cache.WatchOptions, options?: BroadcastOptions) {
const { lastDiff } = c;

// Both WatchOptions and DiffOptions extend ReadOptions, and DiffOptions
Expand Down
4 changes: 2 additions & 2 deletions src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1705,9 +1705,9 @@ interface FetchConcastInfo {
// Metadata properties that can be returned in addition to the Concast.
fromLink: boolean;
}
interface SourcesAndInfo<TData> extends FetchConcastInfo {
export interface SourcesAndInfo<TData> extends FetchConcastInfo {
sources: ConcastSourcesArray<ApolloQueryResult<TData>>;
}
interface ConcastAndInfo<TData> extends FetchConcastInfo {
export interface ConcastAndInfo<TData> extends FetchConcastInfo {
concast: Concast<ApolloQueryResult<TData>>;
}
2 changes: 1 addition & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export { isApolloError, ApolloError } from "../errors/index.js";
export type {
// All the exports (types) from ../cache, minus cacheSlot,
// which we want to keep semi-private.
Cache,
Transaction,
DataProxy,
InMemoryCacheConfig,
Expand All @@ -44,7 +45,6 @@ export type {
WatchFragmentResult,
} from "../cache/index.js";
export {
Cache,
ApolloCache,
InMemoryCache,
MissingFieldError,
Expand Down
2 changes: 1 addition & 1 deletion src/link/persisted-queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export const createPersistedQueryLink = (
(networkError.result.errors as GraphQLFormattedError[]);
}
if (isNonEmptyArray(networkErrors)) {
graphQLErrors.push(...networkErrors);
graphQLErrors.push(...(networkErrors as GraphQLFormattedError[]));
}

const disablePayload: ErrorResponse = {
Expand Down
2 changes: 1 addition & 1 deletion src/react/hoc/__tests__/queries/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ describe("queries", () => {
});
const Container = graphql<Vars, Data>(query)(() => null);

let errorCaught = null;
let errorCaught: unknown = null;
try {
const { unmount } = render(
<ApolloProvider client={client}>
Expand Down
4 changes: 2 additions & 2 deletions src/react/ssr/__tests__/useLazyQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @jest-environment node */
import React from "react";
import React, { ReactElement } from "react";
import { DocumentNode } from "graphql";
import gql from "graphql-tag";
import { mockSingleLink } from "../../../testing";
Expand Down Expand Up @@ -44,7 +44,7 @@ describe("useLazyQuery Hook SSR", () => {
});

const Component = () => {
let html = null;
let html: ReactElement | null = null;
const [execute, { loading, called, data }] = useLazyQuery(CAR_QUERY);

if (!loading && !called) {
Expand Down
42 changes: 31 additions & 11 deletions src/utilities/common/__tests__/mergeDeep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { mergeDeep, mergeDeepArray, DeepMerger } from "../mergeDeep";

type Value = {
value: number;
};

type Nested = {
[p: `nested${number}`]: Value;
value?: number;
};

type FromNested = {
[p: `from${number}`]: Value | Nested;
nested: Nested;
value?: number;
};

type UniqueConflict = {
[p: `unique${number}`]: Value | FromNested;
conflict: FromNested;
};

describe("mergeDeep", function () {
it("should return an object if first argument falsy", function () {
expect(mergeDeep()).toEqual({});
Expand Down Expand Up @@ -41,15 +61,15 @@ describe("mergeDeep", function () {
});

it("should resolve conflicts among more than two objects", function () {
const sources = [];
const sources: UniqueConflict[] = [];

for (let i = 0; i < 100; ++i) {
sources.push({
["unique" + i]: { value: i },
[`unique${i}`]: { value: i },
conflict: {
["from" + i]: { value: i },
[`from${i}`]: { value: i },
nested: {
["nested" + i]: { value: i },
[`nested${i}`]: { value: i },
},
},
});
Expand All @@ -58,17 +78,17 @@ describe("mergeDeep", function () {
const merged = mergeDeep(...sources);

sources.forEach((source, i) => {
expect(merged["unique" + i].value).toBe(i);
expect(source["unique" + i]).toBe(merged["unique" + i]);
expect(merged[`unique${i}`].value).toBe(i);
expect(source[`unique${i}`]).toBe(merged[`unique${i}`]);

expect(merged.conflict).not.toBe(source.conflict);
expect(merged.conflict["from" + i].value).toBe(i);
expect(merged.conflict["from" + i]).toBe(source.conflict["from" + i]);
expect(merged.conflict[`from${i}`].value).toBe(i);
expect(merged.conflict[`from${i}`]).toBe(source.conflict[`from${i}`]);

expect(merged.conflict.nested).not.toBe(source.conflict.nested);
expect(merged.conflict.nested["nested" + i].value).toBe(i);
expect(merged.conflict.nested["nested" + i]).toBe(
source.conflict.nested["nested" + i]
expect(merged.conflict.nested[`nested${i}`].value).toBe(i);
expect(merged.conflict.nested[`nested${i}`]).toBe(
source.conflict.nested[`nested${i}`]
);
});
});
Expand Down

0 comments on commit 0510eab

Please sign in to comment.