diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index d0961bfae8..83a78051c5 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -1,3 +1,4 @@ +import { AccumulatorMap } from '../jsutils/AccumulatorMap'; import type { ObjMap } from '../jsutils/ObjMap'; import type { @@ -37,7 +38,7 @@ export function collectFields( runtimeType: GraphQLObjectType, selectionSet: SelectionSetNode, ): Map> { - const fields = new Map(); + const fields = new AccumulatorMap(); collectFieldsImpl( schema, fragments, @@ -67,7 +68,7 @@ export function collectSubfields( returnType: GraphQLObjectType, fieldNodes: ReadonlyArray, ): Map> { - const subFieldNodes = new Map(); + const subFieldNodes = new AccumulatorMap(); const visitedFragmentNames = new Set(); for (const node of fieldNodes) { if (node.selectionSet) { @@ -91,7 +92,7 @@ function collectFieldsImpl( variableValues: { [variable: string]: unknown }, runtimeType: GraphQLObjectType, selectionSet: SelectionSetNode, - fields: Map>, + fields: AccumulatorMap, visitedFragmentNames: Set, ): void { for (const selection of selectionSet.selections) { @@ -100,13 +101,7 @@ function collectFieldsImpl( if (!shouldIncludeNode(variableValues, selection)) { continue; } - const name = getFieldEntryKey(selection); - const fieldList = fields.get(name); - if (fieldList !== undefined) { - fieldList.push(selection); - } else { - fields.set(name, [selection]); - } + fields.add(getFieldEntryKey(selection), selection); break; } case Kind.INLINE_FRAGMENT: { diff --git a/src/jsutils/AccumulatorMap.ts b/src/jsutils/AccumulatorMap.ts new file mode 100644 index 0000000000..156fe71c20 --- /dev/null +++ b/src/jsutils/AccumulatorMap.ts @@ -0,0 +1,17 @@ +/** + * ES6 Map with additional `add` method to accumulate items. + */ +export class AccumulatorMap extends Map> { + get [Symbol.toStringTag]() { + return 'AccumulatorMap'; + } + + add(key: K, item: T): void { + const group = this.get(key); + if (group === undefined) { + this.set(key, [item]); + } else { + group.push(item); + } + } +} diff --git a/src/jsutils/__tests__/AccumulatorMap-test.ts b/src/jsutils/__tests__/AccumulatorMap-test.ts new file mode 100644 index 0000000000..0fb34c6499 --- /dev/null +++ b/src/jsutils/__tests__/AccumulatorMap-test.ts @@ -0,0 +1,36 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { AccumulatorMap } from '../AccumulatorMap'; + +function expectMap(map: Map) { + return expect(Object.fromEntries(map.entries())); +} + +describe('AccumulatorMap', () => { + it('can be Object.toStringified', () => { + const accumulatorMap = new AccumulatorMap(); + + expect(Object.prototype.toString.call(accumulatorMap)).to.equal( + '[object AccumulatorMap]', + ); + }); + + it('accumulate items', () => { + const accumulatorMap = new AccumulatorMap(); + + expectMap(accumulatorMap).to.deep.equal({}); + + accumulatorMap.add('a', 1); + accumulatorMap.add('b', 2); + accumulatorMap.add('c', 3); + accumulatorMap.add('b', 4); + accumulatorMap.add('c', 5); + accumulatorMap.add('c', 6); + expectMap(accumulatorMap).to.deep.equal({ + a: [1], + b: [2, 4], + c: [3, 5, 6], + }); + }); +}); diff --git a/src/jsutils/groupBy.ts b/src/jsutils/groupBy.ts index f3b0c076d1..60e02ea304 100644 --- a/src/jsutils/groupBy.ts +++ b/src/jsutils/groupBy.ts @@ -1,3 +1,5 @@ +import { AccumulatorMap } from './AccumulatorMap'; + /** * Groups array items into a Map, given a function to produce grouping key. */ @@ -5,15 +7,9 @@ export function groupBy( list: ReadonlyArray, keyFn: (item: T) => K, ): Map> { - const result = new Map>(); + const result = new AccumulatorMap(); for (const item of list) { - const key = keyFn(item); - const group = result.get(key); - if (group === undefined) { - result.set(key, [item]); - } else { - group.push(item); - } + result.add(keyFn(item), item); } return result; }