diff --git a/src/type/__tests__/enumType-test.ts b/src/type/__tests__/enumType-test.ts index f2786947c6..b42bee2f5b 100644 --- a/src/type/__tests__/enumType-test.ts +++ b/src/type/__tests__/enumType-test.ts @@ -31,6 +31,14 @@ const ComplexEnum = new GraphQLEnumType({ }, }); +const ThunkValuesEnum = new GraphQLEnumType({ + name: 'ThunkValues', + values: () => ({ + A: { value: 'a' }, + B: { value: 'b' }, + }), +}); + const QueryType = new GraphQLObjectType({ name: 'Query', fields: { @@ -84,6 +92,15 @@ const QueryType = new GraphQLObjectType({ return fromEnum; }, }, + thunkValuesString: { + type: GraphQLString, + args: { + fromEnum: { type: ThunkValuesEnum }, + }, + resolve(_source, { fromEnum }) { + return fromEnum; + }, + }, }, }); @@ -400,6 +417,14 @@ describe('Type System: Enum Values', () => { }); }); + it('may have values specified via a callback', () => { + const result = executeQuery('{ thunkValuesString(fromEnum: B) }'); + + expect(result).to.deep.equal({ + data: { thunkValuesString: 'b' }, + }); + }); + it('can be introspected without error', () => { expect(() => introspectionFromSchema(schema)).to.not.throw(); }); diff --git a/src/type/definition.ts b/src/type/definition.ts index 0ca4152bd2..cef415c90d 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -1237,6 +1237,17 @@ export interface GraphQLEnumTypeExtensions { [attributeName: string]: unknown; } +function enumValuesFromConfig(values: GraphQLEnumValueConfigMap) { + return Object.entries(values).map(([valueName, valueConfig]) => ({ + name: assertEnumValueName(valueName), + description: valueConfig.description, + value: valueConfig.value !== undefined ? valueConfig.value : valueName, + deprecationReason: valueConfig.deprecationReason, + extensions: toObjMap(valueConfig.extensions), + astNode: valueConfig.astNode, + })); +} + /** * Enum Type Definition * @@ -1267,9 +1278,12 @@ export class GraphQLEnumType /* */ { astNode: Maybe; extensionASTNodes: ReadonlyArray; - private _values: ReadonlyArray */>; - private _valueLookup: ReadonlyMap; - private _nameLookup: ObjMap; + private _values: + | ReadonlyArray */> + | (() => GraphQLEnumValueConfigMap); + + private _valueLookup: ReadonlyMap | null; + private _nameLookup: ObjMap | null; constructor(config: Readonly */>) { this.name = assertName(config.name); @@ -1278,20 +1292,12 @@ export class GraphQLEnumType /* */ { this.astNode = config.astNode; this.extensionASTNodes = config.extensionASTNodes ?? []; - this._values = Object.entries(config.values).map( - ([valueName, valueConfig]) => ({ - name: assertEnumValueName(valueName), - description: valueConfig.description, - value: valueConfig.value !== undefined ? valueConfig.value : valueName, - deprecationReason: valueConfig.deprecationReason, - extensions: toObjMap(valueConfig.extensions), - astNode: valueConfig.astNode, - }), - ); - this._valueLookup = new Map( - this._values.map((enumValue) => [enumValue.value, enumValue]), - ); - this._nameLookup = keyMap(this._values, (value) => value.name); + this._values = + typeof config.values === 'function' + ? config.values + : enumValuesFromConfig(config.values); + this._valueLookup = null; + this._nameLookup = null; } get [Symbol.toStringTag]() { @@ -1299,14 +1305,25 @@ export class GraphQLEnumType /* */ { } getValues(): ReadonlyArray */> { + if (typeof this._values === 'function') { + this._values = enumValuesFromConfig(this._values()); + } return this._values; } getValue(name: string): Maybe { + if (this._nameLookup === null) { + this._nameLookup = keyMap(this.getValues(), (value) => value.name); + } return this._nameLookup[name]; } serialize(outputValue: unknown /* T */): Maybe { + if (this._valueLookup === null) { + this._valueLookup = new Map( + this.getValues().map((enumValue) => [enumValue.value, enumValue]), + ); + } const enumValue = this._valueLookup.get(outputValue); if (enumValue === undefined) { throw new GraphQLError( @@ -1406,13 +1423,14 @@ function didYouMeanEnumValue( export interface GraphQLEnumTypeConfig { name: string; description?: Maybe; - values: GraphQLEnumValueConfigMap /* */; + values: ThunkObjMap */>; extensions?: Maybe>; astNode?: Maybe; extensionASTNodes?: Maybe>; } interface GraphQLEnumTypeNormalizedConfig extends GraphQLEnumTypeConfig { + values: ObjMap */>; extensions: Readonly; extensionASTNodes: ReadonlyArray; }