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

(graphcache) - Refactor SelectionIterator and clean up exports #1060

Merged
merged 4 commits into from
Oct 14, 2020
Merged
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
5 changes: 5 additions & 0 deletions .changeset/kind-radios-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/exchange-graphcache': patch
---

Changes some internals of how selections are iterated over and remove some private exports. This will have no effect or fixes on how Graphcache functions, but may improve some minor performance characteristics of large queries.
21 changes: 11 additions & 10 deletions exchanges/graphcache/src/ast/traversal.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
SelectionNode,
DefinitionNode,
DocumentNode,
FragmentDefinitionNode,
OperationDefinitionNode,
valueFromASTUntyped,
Kind,
Expand All @@ -13,9 +11,6 @@ import { getName } from './node';
import { invariant } from '../helpers/help';
import { Fragments, Variables } from '../types';

const isFragmentNode = (node: DefinitionNode): node is FragmentDefinitionNode =>
node.kind === Kind.FRAGMENT_DEFINITION;

/** Returns the main operation's definition */
export const getMainOperation = (
doc: DocumentNode
Expand All @@ -35,11 +30,17 @@ export const getMainOperation = (
};

/** Returns a mapping from fragment names to their selections */
export const getFragments = (doc: DocumentNode): Fragments =>
doc.definitions.filter(isFragmentNode).reduce((map: Fragments, node) => {
map[getName(node)] = node;
return map;
}, {});
export const getFragments = (doc: DocumentNode): Fragments => {
const fragments: Fragments = {};
for (let i = 0; i < doc.definitions.length; i++) {
const node = doc.definitions[i];
if (node.kind === Kind.FRAGMENT_DEFINITION) {
fragments[getName(node)] = node;
}
}

return fragments;
};

export const shouldInclude = (
node: SelectionNode,
Expand Down
4 changes: 2 additions & 2 deletions exchanges/graphcache/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './types';
export { query, write, writeOptimistic } from './operations';
export { Store, noopDataState, reserveLayer } from './store';
export { query, write } from './operations';
export { Store } from './store';
export { cacheExchange } from './cacheExchange';
export { offlineExchange } from './offlineExchange';
8 changes: 4 additions & 4 deletions exchanges/graphcache/src/operations/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ const readRoot = (
return originalData;
}

const iter = makeSelectionIterator(entityKey, entityKey, select, ctx);
const iterate = makeSelectionIterator(entityKey, entityKey, select, ctx);
const data = {} as Data;
data.__typename = originalData.__typename;

let node: FieldNode | void;
while ((node = iter.next()) !== undefined) {
while ((node = iterate()) !== undefined) {
const fieldAlias = getFieldAlias(node);
const fieldValue = originalData[fieldAlias];
if (node.selectionSet !== undefined && fieldValue !== null) {
Expand Down Expand Up @@ -274,12 +274,12 @@ const readSelection = (
// The following closely mirrors readSelection, but differs only slightly for the
// sake of resolving from an existing resolver result
data.__typename = typename;
const iter = makeSelectionIterator(typename, entityKey, select, ctx);
const iterate = makeSelectionIterator(typename, entityKey, select, ctx);

let node: FieldNode | void;
let hasFields = false;
let hasPartials = false;
while ((node = iter.next()) !== undefined) {
while ((node = iterate()) !== undefined) {
// Derive the needed data from our node.
const fieldName = getName(node);
const fieldArgs = getFieldArguments(node, ctx.variables);
Expand Down
107 changes: 54 additions & 53 deletions exchanges/graphcache/src/operations/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InlineFragmentNode, FragmentDefinitionNode } from 'graphql';
import { FieldNode, InlineFragmentNode, FragmentDefinitionNode } from 'graphql';

import {
isInlineFragment,
Expand Down Expand Up @@ -90,68 +90,69 @@ const isFragmentHeuristicallyMatching = (
});
};

interface SelectionIterator {
(): FieldNode | undefined;
}

export const makeSelectionIterator = (
typename: void | string,
entityKey: string,
select: SelectionSet,
ctx: Context
) => {
const indexStack: number[] = [0];
const selectionStack: SelectionSet[] = [select];

return {
next() {
while (indexStack.length !== 0) {
const index = indexStack[indexStack.length - 1]++;
const select = selectionStack[selectionStack.length - 1];
if (index >= select.length) {
indexStack.pop();
selectionStack.pop();
if (process.env.NODE_ENV !== 'production') {
popDebugNode();
}
continue;
} else {
const node = select[index];
if (!shouldInclude(node, ctx.variables)) {
continue;
} else if (!isFieldNode(node)) {
// A fragment is either referred to by FragmentSpread or inline
const fragmentNode = !isInlineFragment(node)
? ctx.fragments[getName(node)]
: node;

if (fragmentNode !== undefined) {
if (process.env.NODE_ENV !== 'production') {
pushDebugNode(typename, fragmentNode);
}

const isMatching = ctx.store.schema
? isInterfaceOfType(ctx.store.schema, fragmentNode, typename)
: isFragmentHeuristicallyMatching(
fragmentNode,
typename,
entityKey,
ctx.variables
);

if (isMatching) {
indexStack.push(0);
selectionStack.push(getSelectionSet(fragmentNode));
}
): SelectionIterator => {
let childIterator: SelectionIterator | void;
let index = 0;

return function next() {
if (childIterator !== undefined) {
const node = childIterator();
if (node !== undefined) {
return node;
}

childIterator = undefined;
if (process.env.NODE_ENV !== 'production') {
popDebugNode();
}
}

while (index < select.length) {
const node = select[index++];
if (!shouldInclude(node, ctx.variables)) {
continue;
} else if (!isFieldNode(node)) {
// A fragment is either referred to by FragmentSpread or inline
const fragmentNode = !isInlineFragment(node)
? ctx.fragments[getName(node)]
: node;

if (fragmentNode !== undefined) {
const isMatching = ctx.store.schema
? isInterfaceOfType(ctx.store.schema, fragmentNode, typename)
: isFragmentHeuristicallyMatching(
fragmentNode,
typename,
entityKey,
ctx.variables
);

if (isMatching) {
if (process.env.NODE_ENV !== 'production') {
pushDebugNode(typename, fragmentNode);
}

continue;
} else if (getName(node) === '__typename') {
continue;
} else {
return node;
return (childIterator = makeSelectionIterator(
typename,
entityKey,
getSelectionSet(fragmentNode),
ctx
))();
}
}
} else if (getName(node) !== '__typename') {
return node;
}

return undefined;
},
}
};
};

Expand Down
4 changes: 2 additions & 2 deletions exchanges/graphcache/src/operations/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,15 @@ const writeSelection = (
InMemoryData.writeRecord(entityKey, '__typename', typename);
}

const iter = makeSelectionIterator(
const iterate = makeSelectionIterator(
typename,
entityKey || typename,
select,
ctx
);

let node: FieldNode | void;
while ((node = iter.next())) {
while ((node = iterate())) {
const fieldName = getName(node);
const fieldArgs = getFieldArguments(node, ctx.variables);
const fieldKey = keyOfField(fieldName, fieldArgs);
Expand Down