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

Enable strict mode in tsconfig and fix type errors #11200

Merged
merged 33 commits into from Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
aca904f
Enable strict mode in tsconfig.json
jerelmiller Sep 7, 2023
0da3d3d
Fix type issues in utilities/graphql/transform
jerelmiller Sep 7, 2023
78ccf61
Use spread to push incoming objects in offsetLimitPagination
jerelmiller Sep 7, 2023
69d528a
Fix type issues in writeToStore test
jerelmiller Sep 7, 2023
e503cbb
Fix type issues in Concast
jerelmiller Sep 7, 2023
0fe49a0
Remove unused filterInPlace utility
jerelmiller Sep 7, 2023
e34d084
Add this type to itAsync
jerelmiller Sep 7, 2023
962bd47
Add ! annotation to mockLink
jerelmiller Sep 7, 2023
36129f8
Fix assignment from unknown in error link
jerelmiller Sep 7, 2023
cea054e
Fix type issue in readFromStore
jerelmiller Sep 7, 2023
bed3439
Fix type issues in useQuery
jerelmiller Sep 7, 2023
b568689
some progress
phryneas Sep 8, 2023
6498326
more type fixes
phryneas Sep 13, 2023
b8c6eb6
change `addExportedVariables` signature
phryneas Sep 13, 2023
c3a761d
better typing
phryneas Sep 13, 2023
9eb5e3e
undo deprecation
phryneas Sep 13, 2023
0966cc9
Merge branch 'main' into enable-strict-mode
phryneas Sep 13, 2023
a101977
test types
phryneas Sep 13, 2023
55a6351
add api extractor
phryneas Sep 14, 2023
f909921
add api reports
phryneas Sep 14, 2023
e1e2fbb
add workflow
phryneas Sep 14, 2023
0426c84
formatting
phryneas Sep 14, 2023
be151b0
package lock
phryneas Sep 14, 2023
c83b042
remove `ensureResult` for this PR, add `TODO` type
phryneas Sep 14, 2023
4aa485b
Merge branch 'pr/api-extractor' into enable-strict-mode
phryneas Sep 14, 2023
1e7c5c6
API updates for this PR
phryneas Sep 14, 2023
c080309
Merge remote-tracking branch 'origin/main' into enable-strict-mode
phryneas Nov 10, 2023
0b2cf25
size-limit
phryneas Nov 10, 2023
9303630
introduce IDE-only `tsconfig.json`
phryneas Nov 10, 2023
5e3fb70
formatting
phryneas Nov 10, 2023
04dcc0d
fix path
phryneas Nov 10, 2023
2cb5c83
Merge pull request #11359 from apollographql/pr/enable-test-ts
jerelmiller Nov 10, 2023
94b07f9
Add changeset
jerelmiller Nov 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cache/core/types/Cache.ts
Expand Up @@ -93,7 +93,7 @@ export namespace Cache {
this: TCache,
watch: Cache.WatchOptions,
diff: Cache.DiffResult<any>,
lastDiff: Cache.DiffResult<any> | undefined
lastDiff?: Cache.DiffResult<any> | undefined
) => any;
}

Expand Down
29 changes: 16 additions & 13 deletions src/cache/inmemory/__tests__/writeToStore.ts
Expand Up @@ -26,8 +26,13 @@ import { InMemoryCache } from "../inMemoryCache";
import { withErrorSpy, withWarningSpy } from "../../../testing";
import { TypedDocumentNode } from "../../../core";
import { extractFragmentContext } from "../helpers";
import { KeyFieldsFunction } from "../policies";
import { invariant } from "../../../utilities/globals";

const getIdField = ({ id }: { id: string }) => id;
const getIdField: KeyFieldsFunction = ({ id }) => {
invariant(typeof id === "string", "id is not a string");
return id;
};

describe("writing to the store", () => {
const cache = new InMemoryCache({
Expand Down Expand Up @@ -1293,19 +1298,17 @@ describe("writing to the store", () => {
}

testData.forEach((data) => {
data.mutation.definitions.forEach(
(definition: OperationDefinitionNode) => {
if (isOperationDefinition(definition)) {
definition.selectionSet.selections.forEach((selection) => {
if (isField(selection)) {
expect(
storeKeyNameFromField(selection, data.variables)
).toEqual(data.expected);
}
});
}
data.mutation.definitions.forEach((definition) => {
if (isOperationDefinition(definition)) {
definition.selectionSet.selections.forEach((selection) => {
if (isField(selection)) {
expect(storeKeyNameFromField(selection, data.variables)).toEqual(
data.expected
);
}
});
}
);
});
});
});

Expand Down
14 changes: 9 additions & 5 deletions src/cache/inmemory/entityStore.ts
Expand Up @@ -548,7 +548,7 @@ class CacheGroup {

// Used by the EntityStore#makeCacheKey method to compute cache keys
// specific to this CacheGroup.
public keyMaker: Trie<object>;
public keyMaker!: Trie<object>;

constructor(
public readonly caching: boolean,
Expand Down Expand Up @@ -755,7 +755,11 @@ class Layer extends EntityStore {
public getStorage(): StorageType {
let p: EntityStore = this.parent;
while ((p as Layer).parent) p = (p as Layer).parent;
return p.getStorage.apply(p, arguments);
return p.getStorage.apply(
p,
// @ts-expect-error
arguments
);
}
}

Expand All @@ -778,20 +782,20 @@ class Stump extends Layer {
return this;
}

public merge() {
public merge(older: string | StoreObject, newer: string | StoreObject) {
Copy link
Member

Choose a reason for hiding this comment

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

This minifies even shorter than the old code.

// We never want to write any data into the Stump, so we forward any merge
// calls to the Root instead. Another option here would be to throw an
// exception, but the toReference(object, true) function can sometimes
// trigger Stump writes (which used to be Root writes, before the Stump
// concept was introduced).
return this.parent.merge.apply(this.parent, arguments);
return this.parent.merge(older, newer);
}
}

function storeObjectReconciler(
existingObject: StoreObject,
incomingObject: StoreObject,
property: string
property: string | number
): StoreValue {
const existingValue = existingObject[property];
const incomingValue = incomingObject[property];
Expand Down
6 changes: 3 additions & 3 deletions src/cache/inmemory/fixPolyfills.native.ts
Expand Up @@ -33,10 +33,10 @@ try {
// https://github.com/apollographql/react-apollo/issues/2442#issuecomment-426489517
testMap.set(frozen, frozen).delete(frozen);
} catch {
const wrap = (method: <T>(obj: T) => T): typeof method => {
const wrap = <M extends <T>(obj: T) => T>(method: M): M => {
return (
method &&
((obj) => {
(((obj) => {
try {
// If .set succeeds, also call .delete to avoid leaking memory.
testMap.set(obj, obj).delete(obj);
Expand All @@ -45,7 +45,7 @@ try {
// by this return-from-finally statement:
return method.call(Object, obj);
}
})
}) as M)
);
};
Object.freeze = wrap(Object.freeze);
Expand Down
37 changes: 12 additions & 25 deletions src/cache/inmemory/fragmentRegistry.ts
Expand Up @@ -6,6 +6,7 @@ import type {
} from "graphql";
import { visit } from "graphql";

import type { OptimisticWrapperFunction } from "optimism";
import { wrap } from "optimism";

import type { FragmentMap } from "../../utilities/index.js";
Expand All @@ -29,24 +30,22 @@ export function createFragmentRegistry(
return new FragmentRegistry(...fragments);
}

const { forEach: arrayLikeForEach } = Array.prototype;

class FragmentRegistry implements FragmentRegistryAPI {
private registry: FragmentMap = Object.create(null);

// Call static method FragmentRegistry.from(...) instead of invoking the
// Call `createFragmentRegistry` instead of invoking the
// FragmentRegistry constructor directly. This reserves the constructor for
// future configuration of the FragmentRegistry.
constructor(...fragments: DocumentNode[]) {
this.resetCaches();
if (fragments.length) {
this.register.apply(this, fragments);
this.register(...fragments);
Copy link
Member

Choose a reason for hiding this comment

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

This transpiles to the old code, but if we switch to a newer target we save a few bytes.

}
}

public register(): this {
public register(...fragments: DocumentNode[]): this {
const definitions = new Map<string, FragmentDefinitionNode>();
arrayLikeForEach.call(arguments, (doc: DocumentNode) => {
fragments.forEach((doc: DocumentNode) => {
Comment on lines +46 to +48
Copy link
Member

Choose a reason for hiding this comment

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

2384d2383
< var arrayLikeForEach = Array.prototype.forEach;
2398a2398,2401
>         var fragments = [];
>         for (var _i = 0; _i < arguments.length; _i++) {
>             fragments[_i] = arguments[_i];
>         }
2400c2403
<         arrayLikeForEach.call(arguments, function (doc) {
---
>         fragments.forEach(function (doc) {

This transpiles slightly longer, are we okay with that?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm ok with it.

getFragmentDefinitions(doc).forEach((node) => {
definitions.set(node.name.value, node);
});
Expand All @@ -66,27 +65,15 @@ class FragmentRegistry implements FragmentRegistryAPI {
private invalidate(name: string) {}

public resetCaches() {
this.invalidate = (this.lookup = this.cacheUnaryMethod("lookup")).dirty; // This dirty function is bound to the wrapped lookup method.
this.transform = this.cacheUnaryMethod("transform");
this.findFragmentSpreads = this.cacheUnaryMethod("findFragmentSpreads");
this.invalidate = (this.lookup = this.cacheUnaryMethod(this.lookup)).dirty; // This dirty function is bound to the wrapped lookup method.
this.transform = this.cacheUnaryMethod(this.transform);
this.findFragmentSpreads = this.cacheUnaryMethod(this.findFragmentSpreads);
}

private cacheUnaryMethod<
TName extends keyof Pick<
FragmentRegistry,
"lookup" | "transform" | "findFragmentSpreads"
>,
>(name: TName) {
const registry = this;
const originalMethod = FragmentRegistry.prototype[name];
return wrap(
function () {
return originalMethod.apply(registry, arguments);
},
{
makeCacheKey: (arg) => arg,
}
);
private cacheUnaryMethod<F extends (arg: any) => any>(originalMethod: F) {
return wrap<Parameters<F>, ReturnType<F>>(originalMethod.bind(this), {
makeCacheKey: (arg) => arg,
}) as OptimisticWrapperFunction<Parameters<F>, ReturnType<F>> & F;
}

public lookup(fragmentName: string): FragmentDefinitionNode | null {
Expand Down
10 changes: 5 additions & 5 deletions src/cache/inmemory/inMemoryCache.ts
Expand Up @@ -33,18 +33,18 @@ type BroadcastOptions = Pick<
>;

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

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

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

private maybeBroadcastWatch: OptimisticWrapperFunction<
private maybeBroadcastWatch!: OptimisticWrapperFunction<
Comment on lines +36 to +47
Copy link
Member

Choose a reason for hiding this comment

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

All of these are set in functions that are directly called from the constructor.

[Cache.WatchOptions, BroadcastOptions?],
any,
[Cache.WatchOptions]
Expand Down
2 changes: 1 addition & 1 deletion src/cache/inmemory/readFromStore.ts
Expand Up @@ -535,7 +535,7 @@ function firstMissing(tree: MissingTree): string | undefined {
return value;
});
} catch (result) {
return result;
return result as string;
}
}

Expand Down
28 changes: 25 additions & 3 deletions src/core/ApolloClient.ts
Expand Up @@ -86,7 +86,7 @@ export class ApolloClient<TCacheShape> implements DataProxy {
public readonly typeDefs: ApolloClientOptions<TCacheShape>["typeDefs"];

private queryManager: QueryManager<TCacheShape>;
private devToolsHookCb: Function;
private devToolsHookCb?: Function;
private resetStoreCallbacks: Array<() => Promise<any>> = [];
private clearStoreCallbacks: Array<() => Promise<any>> = [];
private localState: LocalState<TCacheShape>;
Expand Down Expand Up @@ -586,11 +586,33 @@ export class ApolloClient<TCacheShape> implements DataProxy {
* re-execute any queries then you should make sure to stop watching any
* active queries.
*/
public refetchQueries<TResult = Promise<ApolloQueryResult<any>>>(
options: RefetchQueriesOptions<ApolloCache<TCacheShape>, TResult>
): RefetchQueriesResult<TResult>;

/**
* @deprecated The two-generics version of refetchQueries is deprecated
* and will be removed in the next major version.
*
* Please use the single-generic version only specifying the return type instead.
*
* The first generic argument of this overload is essentially an
* `cache as any as TCache`
* assertion and does not give you any type safety.
*
* If you rely on a specific cache implementation, please do an `instanceof` check
* in your runtime code to narrow down the type in a type-safe manner.
*/
public refetchQueries<
TCache extends ApolloCache<any> = ApolloCache<TCacheShape>,
TResult = Promise<ApolloQueryResult<any>>,
/** @deprecated */
TCache extends ApolloCache<any>,
TResult,
>(
options: RefetchQueriesOptions<TCache, TResult>
): RefetchQueriesResult<TResult>;

public refetchQueries<TResult>(
options: RefetchQueriesOptions<ApolloCache<TCacheShape>, TResult>
phryneas marked this conversation as resolved.
Show resolved Hide resolved
): RefetchQueriesResult<TResult> {
const map = this.queryManager.refetchQueries(options);
const queries: ObservableQuery<any>[] = [];
Expand Down
12 changes: 6 additions & 6 deletions src/core/LocalState.ts
Expand Up @@ -75,9 +75,9 @@ export type LocalStateOptions<TCacheShape> = {

export class LocalState<TCacheShape> {
private cache: ApolloCache<TCacheShape>;
private client: ApolloClient<TCacheShape>;
private client?: ApolloClient<TCacheShape>;
private resolvers?: Resolvers;
private fragmentMatcher: FragmentMatcher;
private fragmentMatcher?: FragmentMatcher;
private selectionsToResolveCache = new WeakMap<
ExecutableDefinitionNode,
Set<SelectionNode>
Expand Down Expand Up @@ -162,7 +162,7 @@ export class LocalState<TCacheShape> {
this.fragmentMatcher = fragmentMatcher;
}

public getFragmentMatcher(): FragmentMatcher {
public getFragmentMatcher(): FragmentMatcher | undefined {
return this.fragmentMatcher;
}

Expand Down Expand Up @@ -197,11 +197,11 @@ export class LocalState<TCacheShape> {
// To support `@client @export(as: "someVar")` syntax, we'll first resolve
// @client @export fields locally, then pass the resolved values back to be
// used alongside the original operation variables.
public async addExportedVariables(
public async addExportedVariables<TVars extends OperationVariables>(
document: DocumentNode,
variables: OperationVariables = {},
variables: TVars = {} as TVars,
context = {}
) {
): /* returns at least the variables that were passed in */ Promise<TVars> {
if (document) {
return this.resolveDocument(
document,
Expand Down
24 changes: 20 additions & 4 deletions src/core/ObservableQuery.ts
Expand Up @@ -928,7 +928,9 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
newOptions?: Partial<WatchQueryOptions<TVariables, TData>>,
newNetworkStatus?: NetworkStatus
) {
return this.reobserveAsConcast(newOptions, newNetworkStatus).promise;
return this.reobserveAsConcast(newOptions, newNetworkStatus).promise.then(
ensureResult
);
phryneas marked this conversation as resolved.
Show resolved Hide resolved
}

public resubscribeAfterError(
Expand Down Expand Up @@ -1051,14 +1053,18 @@ export function reobserveCacheFirst<TData, TVars extends OperationVariables>(
fetchPolicy: "cache-first",
// Use a temporary nextFetchPolicy function that replaces itself with the
// previous nextFetchPolicy value and returns the original fetchPolicy.
nextFetchPolicy(this: WatchQueryOptions<TVars, TData>) {
nextFetchPolicy(
this: WatchQueryOptions<TVars, TData>,
currentFetchPolicy: WatchQueryFetchPolicy,
context: NextFetchPolicyContext<TData, TVars>
) {
// Replace this nextFetchPolicy function in the options object with the
// original this.options.nextFetchPolicy value.
this.nextFetchPolicy = nextFetchPolicy;
// If the original nextFetchPolicy value was a function, give it a
// chance to decide what happens here.
if (typeof nextFetchPolicy === "function") {
return nextFetchPolicy.apply(this, arguments);
if (typeof this.nextFetchPolicy === "function") {
return this.nextFetchPolicy(currentFetchPolicy, context);
}
// Otherwise go back to the original this.options.fetchPolicy.
return fetchPolicy!;
Expand Down Expand Up @@ -1090,3 +1096,13 @@ function skipCacheDataFor(
fetchPolicy === "standby"
);
}

export function ensureResult<TData = any>(
value: ApolloQueryResult<TData> | undefined
): ApolloQueryResult<TData> {
invariant(
value,
"A Concast finished without a result. This in an Apollo Client bug, please file a bug report."
);
return value;
}
7 changes: 4 additions & 3 deletions src/core/QueryInfo.ts
Expand Up @@ -50,6 +50,7 @@ function wrapDestructiveCacheMethod(
// that matters in any conceivable practical scenario.
(destructiveMethodCounts.get(cache)! + 1) % 1e15
);
// @ts-expect-error this is just too generic to be typed correctly
return original.apply(this, arguments);
};
}
Expand Down Expand Up @@ -113,7 +114,7 @@ export class QueryInfo {
// NetworkStatus.loading, but also possibly fetchMore, poll, refetch,
// or setVariables.
networkStatus?: NetworkStatus;
observableQuery?: ObservableQuery<any>;
observableQuery?: ObservableQuery<any, any>;
Copy link
Member

Choose a reason for hiding this comment

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

We are generally very inconsistent with the extends OperationVariables on TVariables, and I have to say I'm not too happy with OperationVariables, as it works with type-declared types, but not with interfaces (as those don't have an implicit index signature).
We might want to look into that - but not in this PR.

lastRequestId?: number;
}): this {
let networkStatus = query.networkStatus || NetworkStatus.loading;
Expand Down Expand Up @@ -214,10 +215,10 @@ export class QueryInfo {
}
}

public readonly observableQuery: ObservableQuery<any> | null = null;
public readonly observableQuery: ObservableQuery<any, any> | null = null;
private oqListener?: QueryListener;

setObservableQuery(oq: ObservableQuery<any> | null) {
setObservableQuery(oq: ObservableQuery<any, any> | null) {
if (oq === this.observableQuery) return;

if (this.oqListener) {
Expand Down