diff --git a/.changeset/lovely-hounds-tap.md b/.changeset/lovely-hounds-tap.md new file mode 100644 index 0000000000..a69d9e81ed --- /dev/null +++ b/.changeset/lovely-hounds-tap.md @@ -0,0 +1,7 @@ +--- +'@urql/introspection': minor +--- + +Add options to `@urql/introspection`'s `minifyIntrospectionQuery` allowing the inclusion of more +information into the minified schema as needed, namely `includeScalars`, `includeEnums`, +`includeInputs`, and `includeDirectives`. diff --git a/packages/introspection/src/minifyIntrospectionQuery.ts b/packages/introspection/src/minifyIntrospectionQuery.ts index e915007a7b..0bf77bdf66 100644 --- a/packages/introspection/src/minifyIntrospectionQuery.ts +++ b/packages/introspection/src/minifyIntrospectionQuery.ts @@ -2,37 +2,44 @@ import { IntrospectionQuery, IntrospectionType, IntrospectionTypeRef, + IntrospectionInputValue, } from 'graphql'; +let _includeScalars = false; +let _includeEnums = false; +let _includeInputs = false; +let _hasAnyType = false; + const anyType: IntrospectionTypeRef = { kind: 'SCALAR', name: 'Any', }; -const mapType = ( - fromType: any, - toType: IntrospectionTypeRef -): IntrospectionTypeRef => { +const mapType = (fromType: any): IntrospectionTypeRef => { switch (fromType.kind) { case 'NON_NULL': case 'LIST': return { kind: fromType.kind, - ofType: mapType(fromType.ofType, toType), + ofType: mapType(fromType.ofType), }; case 'SCALAR': + _hasAnyType = _hasAnyType || _includeScalars; + return _includeScalars ? fromType : anyType; + case 'INPUT_OBJECT': + _hasAnyType = _hasAnyType || _includeInputs; + return _includeInputs ? fromType : anyType; + case 'ENUM': - return toType; + _hasAnyType = _hasAnyType || _includeEnums; + return _includeEnums ? fromType : anyType; case 'OBJECT': case 'INTERFACE': case 'UNION': - return { - kind: fromType.kind, - name: fromType.name, - }; + return fromType; default: throw new TypeError( @@ -45,7 +52,40 @@ const minifyIntrospectionType = ( type: IntrospectionType ): IntrospectionType => { switch (type.kind) { - case 'OBJECT': { + case 'SCALAR': + return { + kind: 'SCALAR', + name: type.name, + }; + + case 'ENUM': + return { + kind: 'ENUM', + name: type.name, + enumValues: type.enumValues.map( + value => + ({ + name: value.name, + } as any) + ), + }; + + case 'INPUT_OBJECT': { + return { + kind: 'INPUT_OBJECT', + name: type.name, + inputFields: type.inputFields.map( + field => + ({ + name: field.name, + type: mapType(field.type), + defaultValue: field.defaultValue || undefined, + } as IntrospectionInputValue) + ), + }; + } + + case 'OBJECT': return { kind: 'OBJECT', name: type.name, @@ -53,12 +93,12 @@ const minifyIntrospectionType = ( field => ({ name: field.name, - type: field.type && mapType(field.type, anyType), + type: field.type && mapType(field.type), args: field.args && field.args.map(arg => ({ name: arg.name, - type: mapType(arg.type, anyType), + type: mapType(arg.type), })), } as any) ), @@ -69,9 +109,8 @@ const minifyIntrospectionType = ( name: int.name, })), }; - } - case 'INTERFACE': { + case 'INTERFACE': return { kind: 'INTERFACE', name: type.name, @@ -79,12 +118,12 @@ const minifyIntrospectionType = ( field => ({ name: field.name, - type: field.type && mapType(field.type, anyType), + type: field.type && mapType(field.type), args: field.args && field.args.map(arg => ({ name: arg.name, - type: mapType(arg.type, anyType), + type: mapType(arg.type), })), } as any) ), @@ -101,9 +140,8 @@ const minifyIntrospectionType = ( name: type.name, })), }; - } - case 'UNION': { + case 'UNION': return { kind: 'UNION', name: type.name, @@ -112,22 +150,39 @@ const minifyIntrospectionType = ( name: type.name, })), }; - } default: return type; } }; +export interface MinifySchemaOptions { + /** Includes scalar names (instead of an `Any` replacement) in the output when enabled. */ + includeScalars?: boolean; + /** Includes enums (instead of an `Any` replacement) in the output when enabled. */ + includeEnums?: boolean; + /** Includes all input objects (instead of an `Any` replacement) in the output when enabled. */ + includeInputs?: boolean; + /** Includes all directives in the output when enabled. */ + includeDirectives?: boolean; +} + +/** Removes extraneous information from introspected schema data to minify it and prepare it for use on the client-side. */ export const minifyIntrospectionQuery = ( - schema: IntrospectionQuery + schema: IntrospectionQuery, + opts: MinifySchemaOptions = {} ): IntrospectionQuery => { if (!schema || !('__schema' in schema)) { throw new TypeError('Expected to receive an IntrospectionQuery.'); } + _hasAnyType = false; + _includeScalars = !!opts.includeScalars; + _includeEnums = !!opts.includeEnums; + _includeInputs = !!opts.includeInputs; + const { - __schema: { queryType, mutationType, subscriptionType, types }, + __schema: { queryType, mutationType, subscriptionType, types, directives }, } = schema; const minifiedTypes = types @@ -144,6 +199,9 @@ export const minifyIntrospectionQuery = ( return false; default: return ( + (_includeScalars && type.kind === 'SCALAR') || + (_includeEnums && type.kind === 'ENUM') || + (_includeInputs && type.kind === 'INPUT_OBJECT') || type.kind === 'OBJECT' || type.kind === 'INTERFACE' || type.kind === 'UNION' @@ -152,7 +210,23 @@ export const minifyIntrospectionQuery = ( }) .map(minifyIntrospectionType); - minifiedTypes.push({ kind: 'SCALAR', name: anyType.name }); + const minifiedDirectives = (directives || []).map(directive => ({ + name: directive.name, + isRepeatable: directive.isRepeatable ? true : undefined, + locations: directive.locations, + args: directive.args.map( + arg => + ({ + name: arg.name, + type: mapType(arg.type), + defaultValue: arg.defaultValue || undefined, + } as IntrospectionInputValue) + ), + })); + + if (!_includeScalars || !_includeEnums || !_includeInputs || _hasAnyType) { + minifiedTypes.push({ kind: 'SCALAR', name: anyType.name }); + } return { __schema: { @@ -160,7 +234,7 @@ export const minifyIntrospectionQuery = ( mutationType, subscriptionType, types: minifiedTypes, - directives: [], + directives: opts.includeDirectives ? minifiedDirectives : [], }, }; };