This repository has been archived by the owner on Sep 20, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
/
extended-schema-transformer.ts
143 lines (121 loc) · 7.17 KB
/
extended-schema-transformer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { GraphQLFieldConfig, GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
import { FieldsTransformationContext, FieldTransformationContext, GraphQLNamedFieldConfig, SchemaTransformationContext, SchemaTransformer, transformSchema } from 'graphql-transformer';
import { bindNullable, mapValues, maybeDo } from '../utils/utils';
import { ExtendedSchema, FieldMetadata, SchemaMetadata } from './extended-schema';
export type TransformationFunction<TConfig, TContext extends SchemaTransformationContext>
= (config: TConfig, context: TContext) => TConfig;
export interface GraphQLNamedFieldConfigWithMetadata<TSource = any, TContext = any> extends GraphQLNamedFieldConfig<TSource, TContext> {
metadata?: FieldMetadata;
}
export interface GraphQLFieldConfigWithMetadata<TSource = any, TContext = any> extends GraphQLFieldConfig<TSource, TContext> {
metadata?: FieldMetadata;
}
export type GraphQLFieldConfigMapWithMetadata<TSource = any, TContext = any> = { [name: string]: GraphQLFieldConfigWithMetadata<TSource, TContext> };
export interface ExtendedSchemaTransformer extends SchemaTransformer {
transformField?: TransformationFunction<GraphQLNamedFieldConfigWithMetadata<any, any>, FieldTransformationContext>;
transformFields?: TransformationFunction<GraphQLFieldConfigMapWithMetadata<any, any>, FieldsTransformationContext>;
}
// do everything as if fieldMetadata was a property of Field / FieldConfig
// transformObjectType: no action required
// transformField: get metadata via old type -> call (if defined) -> store metadata with new key (type + field)
// transformFields (if defined): get and delete all metadata via new type + new field names -> call -> store all metadata
/**
* Transforms an ExtendedSchema via transformation functions for the schema elements
*
* This makes it seem as if fieldMetadata was a property of GraphQLField / GraphQLFieldConfig - it can be modified in
* transformField() and transformFields(). Fields can even be added or removed in transformFields(). As long as the
* metadata property is carried around properly, everything should work as expected.
*
* @param schema
* @param transformer
* @returns {any}
*/
export function transformExtendedSchema(schema: ExtendedSchema, transformer: ExtendedSchemaTransformer) {
// This is a bit of an ugly data structure because it changes over time
// At the beginning, it is empty
// After transformField() is called for all fields of a type, it contains the *new* metadata map with new type names,
// new field names and new metadata values.
// If transformFields() is defined, for each type, the metadata keys are first removed completely (and passed to
// transformFields), then re-added properly with the even newer field names and metadata values
const fieldMetadata = new Map<string, FieldMetadata>();
// not using methods in here to work around https://github.com/Microsoft/TypeScript/issues/16765
const regularTransformer = {
...bindTransformerFunctions(transformer),
transformField: (config: GraphQLNamedFieldConfig<any, any>, context: FieldTransformationContext) => {
// non-object (interface) fields have no metadata
const type = context.oldOuterType;
if (!(type instanceof GraphQLObjectType)) {
if (transformer.transformField) {
return transformer.transformField(config, context);
} else {
return config;
}
}
// for object types, we need to do this even if the transformField method is not defined, just to do the
// potential *type* renaming
// enrich with metadata, using old type and field because we use the schema's metadata store
let extendedConfig: GraphQLNamedFieldConfigWithMetadata = {
...config,
metadata: schema.getFieldMetadata(type, context.oldField)
};
// if there is a transformer, call it
if (transformer.transformField) {
extendedConfig = transformer.transformField(extendedConfig, context);
}
// Now, if there is (still) metadata, set it in the new store with new type and field name
if (extendedConfig.metadata) {
fieldMetadata.set(`${context.newOuterType.name}.${extendedConfig.name}`, extendedConfig.metadata);
}
// Do the "normal" transformation, but strip out metadata
const { metadata, ...regularConfig } = extendedConfig;
return regularConfig;
},
transformFields: (config: GraphQLFieldConfigMap<any, any>, context: FieldsTransformationContext) => {
// If transformFields is not defined, we don't need to do anything
const fn: TransformationFunction<GraphQLFieldConfigMapWithMetadata<any, any>, FieldsTransformationContext> | undefined = maybeDo(transformer.transformFields, fn => fn.bind(transformer));
if (!fn) {
return config;
}
// non-object (interface) fields have no metadata
const type = context.oldOuterType;
if (!(type instanceof GraphQLObjectType)) {
return fn(config, context);
}
// Enrich config with metadata values from new metadata store, but delete them from the store
// because we will add all the even-newer metadata later
let extendedConfig: GraphQLFieldConfigMapWithMetadata = mapValues(config, (config, fieldName) => {
const key = `${context.newOuterType}.${fieldName}`;
const metadata = fieldMetadata.get(key);
fieldMetadata.delete(key);
return { ...config, metadata };
});
// call the transformer
const result = fn(extendedConfig, context);
// Now, store the even-newer metadata and strip the metadata property from the config
const regularResult = mapValues(result, ({ metadata, ...regularConfig }, fieldName) => {
if (metadata) {
const key = `${context.newOuterType}.${fieldName}`;
fieldMetadata.set(key, metadata);
}
return regularConfig;
});
// do the regular transform
return regularResult;
}
};
const newSchema = transformSchema(schema.schema, regularTransformer);
return new ExtendedSchema(newSchema, new SchemaMetadata({ fieldMetadata }));
}
function bindTransformerFunctions(t: SchemaTransformer): SchemaTransformer {
return {
transformScalarType: bindNullable(t.transformScalarType, t),
transformEnumType: bindNullable(t.transformEnumType, t),
transformInterfaceType: bindNullable(t.transformInterfaceType, t),
transformInputObjectType: bindNullable(t.transformInputObjectType, t),
transformUnionType: bindNullable(t.transformUnionType, t),
transformObjectType: bindNullable(t.transformObjectType, t),
transformDirective: bindNullable(t.transformDirective, t),
transformField: bindNullable(t.transformField, t),
transformInputField: bindNullable(t.transformInputField, t)
};
}