Repository: FormidableLabs/urql (ts)

Issue:
(graphcache) - cache.resolve(parent, ...) doesn't seem to work
Hi guys,

After upgrade to `@urql/exchange-graphcache@3.3.1` `cache.resolve` doesn't seem to work always return `null` value.

```js
export const cache = cacheExchange({
  resolvers: {
    City: {
      name(parent, args, cache) {
        const id = cache.resolve(parent, "id");
        const name = cache.resolve(parent, "name");

        console.log({ id, name }); // null , null

        return `ID: ${id} - ` + name;
      }
    }
  }
});
```
`urql: 1.11.4`
`@urql/exchange-graphcache: 3.3.1`

**Steps to reproduce**
Check log tab
https://codesandbox.io/s/headless-wildflower-ynjsk

**The repo older version still work**
https://codesandbox.io/s/prod-monad-gmvyk


Pull request description:
(graphcache) - Enable more cache.keyOfEntity parent cases and deprecate cache.resolveFieldByKey
Resolve #1207 
Follow up to #1208 

## Summary

This enables a case that the previous patch was meant to enable, the usage of `cache.resolve(parent, ...)` where `parent` does not have a `__typename` field (or other keyable field) set yet.

This also deprecated `cache.resolveFieldByKey` since I coincidentally noticed that it's not necessary anymore.

## Set of changes

- Move up `ctx.parent` check in `cache.keyOfEntity`
- Refactor `cache.keyOfEntity` slightly
- Deprecate `cache.resolveFieldByKey` in favour of `cache.resolve` and update docs
- Replace `cache.resolveFieldByKey` in extras sources



Patch:
diff --git a/.changeset/tough-keys-visit.md b/.changeset/tough-keys-visit.md
new file mode 100644
index 00000000..8dafa098
--- /dev/null
+++ b/.changeset/tough-keys-visit.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+Update `cache.resolve(parent, ...)` case to enable _even more_ cases, for instance where `parent.__typename` isn't set yet. This was intended to be enabled in the previous patch but has been forgotten.
diff --git a/.changeset/wild-pandas-vanish.md b/.changeset/wild-pandas-vanish.md
new file mode 100644
index 00000000..a95219ee
--- /dev/null
+++ b/.changeset/wild-pandas-vanish.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+Deprecate `cache.resolveFieldByKey` in favour of `cache.resolve`, which functionally was already able to do the same.
diff --git a/docs/api/graphcache.md b/docs/api/graphcache.md
index cd270a81..be6329ab 100644
--- a/docs/api/graphcache.md
+++ b/docs/api/graphcache.md
@@ -250,15 +250,16 @@ an entity key, which makes it possible to pass a key from `cache.keyOfEntity` or
 > be dangerous to use in some cases. It's a good idea to make sure first whether the field you're
 > reading will be a key or a value.
 
-There's an alternative method, `cache.resolveFieldByKey` which accepts a field key that may be
-generated using `cache.keyOfField`.
+The `cache.resolve` method may also be called with a field key as generated by `cache.keyOfField`.
 
 ```js
-cache.resolveFieldByKey({ __typename: 'Query' }, cache.keyOfField('todo', { id: 1 })); // 'Todo:1'
+cache.resolve({ __typename: 'Query' }, cache.keyOfField('todo', { id: 1 })); // 'Todo:1'
 ```
 
-This specialized method is likely only going to be useful in combination with
-[`cache.inspectFields`](#inspectfields).
+This specialized case is likely only going to be useful in combination with
+[`cache.inspectFields`](#inspectfields). Previously a specialised method existed for this
+case specifically and was called `cache.resolveFieldByKey`, which is now deprecated, since
+`cache.resolve` may be called with a field key and no extra arguments.
 
 ### inspectFields
 
diff --git a/exchanges/graphcache/default-storage/package.json b/exchanges/graphcache/default-storage/package.json
index 645fc708..e7d59cd5 100644
--- a/exchanges/graphcache/default-storage/package.json
+++ b/exchanges/graphcache/default-storage/package.json
@@ -15,7 +15,7 @@
     "./package.json": "./package.json"
   },
   "dependencies": {
-    "@urql/core": ">=1.15.2",
+    "@urql/core": ">=1.16.0",
     "wonka": "^4.0.14"
   }
 }
diff --git a/exchanges/graphcache/src/extras/relayPagination.ts b/exchanges/graphcache/src/extras/relayPagination.ts
index 86fbe7f6..534223e5 100644
--- a/exchanges/graphcache/src/extras/relayPagination.ts
+++ b/exchanges/graphcache/src/extras/relayPagination.ts
@@ -128,7 +128,7 @@ const getPage = (
   entityKey: string,
   fieldKey: string
 ): Page | null => {
-  const link = ensureKey(cache.resolveFieldByKey(entityKey, fieldKey));
+  const link = ensureKey(cache.resolve(entityKey, fieldKey));
   if (!link) return null;
 
   const typename = cache.resolve(link, '__typename') as string;
diff --git a/exchanges/graphcache/src/extras/simplePagination.ts b/exchanges/graphcache/src/extras/simplePagination.ts
index b228ac82..110c20c0 100644
--- a/exchanges/graphcache/src/extras/simplePagination.ts
+++ b/exchanges/graphcache/src/extras/simplePagination.ts
@@ -67,7 +67,7 @@ export const simplePagination = ({
         continue;
       }
 
-      const links = cache.resolveFieldByKey(entityKey, fieldKey) as string[];
+      const links = cache.resolve(entityKey, fieldKey) as string[];
       const currentOffset = args[offsetArgument];
 
       if (
diff --git a/exchanges/graphcache/src/store/store.ts b/exchanges/graphcache/src/store/store.ts
index 81e576a2..2330cb83 100644
--- a/exchanges/graphcache/src/store/store.ts
+++ b/exchanges/graphcache/src/store/store.ts
@@ -105,45 +105,43 @@ export class Store implements Cache {
   keyOfField = keyOfField;
 
   keyOfEntity(data: Data | null | string) {
-    if (data == null || typeof data === 'string') return data || null;
-    const { __typename: typename, id, _id } = data;
-    if (!typename) return null;
-    if (this.rootNames[typename]) return typename;
-
     // In resolvers and updaters we may have a specific parent
     // object available that can be used to skip to a specific parent
     // key directly without looking at its incomplete properties
     if (contextRef.current && data === contextRef.current.parent)
       return contextRef.current!.parentKey;
 
+    if (data == null || typeof data === 'string') return data || null;
+    if (!data.__typename) return null;
+    if (this.rootNames[data.__typename]) return data.__typename;
+
     let key: string | null | void;
-    if (this.keys[typename]) {
-      key = this.keys[typename](data);
-    } else if (id !== undefined && id !== null) {
-      key = `${id}`;
-    } else if (_id !== undefined && _id !== null) {
-      key = `${_id}`;
+    if (this.keys[data.__typename]) {
+      key = this.keys[data.__typename](data);
+    } else if (data.id != null) {
+      key = `${data.id}`;
+    } else if (data._id != null) {
+      key = `${data._id}`;
     }
 
-    return key ? `${typename}:${key}` : null;
+    return key ? `${data.__typename}:${key}` : null;
   }
 
-  resolveFieldByKey(entity: Data | string | null, fieldKey: string): DataField {
+  resolve(
+    entity: Data | string | null,
+    field: string,
+    args?: Variables
+  ): DataField {
+    const fieldKey = keyOfField(field, args);
     const entityKey = this.keyOfEntity(entity);
     if (!entityKey) return null;
     const fieldValue = InMemoryData.readRecord(entityKey, fieldKey);
     if (fieldValue !== undefined) return fieldValue;
     const link = InMemoryData.readLink(entityKey, fieldKey);
-    return link ? link : null;
+    return link || null;
   }
 
-  resolve(
-    entity: Data | string | null,
-    field: string,
-    args?: Variables
-  ): DataField {
-    return this.resolveFieldByKey(entity, keyOfField(field, args));
-  }
+  resolveFieldByKey = this.resolve;
 
   invalidate(entity: Data | string | null, field?: string, args?: Variables) {
     const entityKey = this.keyOfEntity(entity);
diff --git a/exchanges/graphcache/src/types.ts b/exchanges/graphcache/src/types.ts
index 6f025cce..1dec403b 100644
--- a/exchanges/graphcache/src/types.ts
+++ b/exchanges/graphcache/src/types.ts
@@ -91,7 +91,7 @@ export interface Cache {
     args?: Variables
   ): DataField;
 
-  /** resolveFieldByKey() returns a field's value on an entity, given that field's key */
+  /** @deprecated use resolve() instead */
   resolveFieldByKey(entity: Data | string | null, fieldKey: string): DataField;
 
   /** inspectFields() retrieves all known fields for a given entity */


Test patch:
diff --git a/exchanges/graphcache/src/store/store.test.ts b/exchanges/graphcache/src/store/store.test.ts
index cb08f51c..9e682a3e 100644
--- a/exchanges/graphcache/src/store/store.test.ts
+++ b/exchanges/graphcache/src/store/store.test.ts
@@ -393,6 +393,10 @@ describe('Store with OptimisticMutationConfig', () => {
 
     expect(store.keyOfEntity(randomData)).toBe(context.parentKey);
     expect(store.keyOfEntity({})).not.toBe(context.parentKey);
+
+    // Should work without a __typename field
+    delete (randomData as any).__typename;
+    expect(store.keyOfEntity(randomData)).toBe(context.parentKey);
   });
 
   it('should resolve with a key as first argument', () => {