Skip to content

Commit

Permalink
** Adds result transforming to the following transformers:
Browse files Browse the repository at this point in the history
= TransformCompositeFields
= TransformInterfaceFields and = TransformObjectFields
= TransformRootFields
= ExtendSchema
= MapFields
= WrapFields
= WrapType
- HoistField

** Still to do
- Use transformationContext to cache exact keys so that transformResult
methods are (a) optimized and (b) can better handle errors (e.g.
HoistField no longer has access to the depth of the hoisted field and
can no longer transform errors)
  • Loading branch information
yaacovCR committed Jul 3, 2020
1 parent 3a90563 commit d918dda
Show file tree
Hide file tree
Showing 13 changed files with 516 additions and 75 deletions.
3 changes: 2 additions & 1 deletion packages/utils/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ export interface Transform {

export type FieldNodeMapper = (
fieldNode: FieldNode,
fragments: Record<string, FragmentDefinitionNode>
fragments: Record<string, FragmentDefinitionNode>,
context: Record<string, any>
) => SelectionNode | Array<SelectionNode>;

export type FieldNodeMappers = Record<string, Record<string, FieldNodeMapper>>;
Expand Down
29 changes: 29 additions & 0 deletions packages/utils/src/fieldNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode
};
}

export function aliasFieldNode(fieldNode: FieldNode, str: string): FieldNode {
return {
...fieldNode,
alias: {
kind: Kind.NAME,
value: str,
},
};
}

export function wrapFieldNode(fieldNode: FieldNode, path: Array<string>): FieldNode {
let newFieldNode = fieldNode;
path.forEach(fieldName => {
Expand Down Expand Up @@ -120,3 +130,22 @@ export function hoistFieldNodes({

return newFieldNodes;
}

export function unwrapValue(originalValue: any, path: Array<string>): any {
let newValue = originalValue;

const pathLength = path.length;
for (let i = 0; i < pathLength; i++) {
const responseKey = path[i];
const object = newValue[responseKey];
if (object == null) {
break;
}
newValue = object;
}

delete originalValue[path[0]];
Object.assign(originalValue, newValue);

return originalValue;
}
2 changes: 1 addition & 1 deletion packages/utils/src/visitResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function visitResult(
if (data != null) {
result.data = visitRoot(
data,
getOperationAST(request.document),
getOperationAST(request.document, undefined),
partialExecutionContext,
resultVisitorMap,
visitingErrors ? errors : undefined,
Expand Down
37 changes: 33 additions & 4 deletions packages/wrap/src/transforms/ExtendSchema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { GraphQLSchema, extendSchema, parse } from 'graphql';

import { Transform, IFieldResolver, IResolvers, Request, FieldNodeMappers } from '@graphql-tools/utils';
import {
Transform,
IFieldResolver,
IResolvers,
Request,
FieldNodeMappers,
ExecutionResult,
} from '@graphql-tools/utils';
import { addResolversToSchema } from '@graphql-tools/schema';
import { defaultMergedResolver } from '@graphql-tools/delegate';

import { ObjectValueTransformerMap, ErrorsTransformer } from '../types';

import MapFields from './MapFields';

export default class ExtendSchema implements Transform {
Expand All @@ -17,16 +26,24 @@ export default class ExtendSchema implements Transform {
resolvers = {},
defaultFieldResolver,
fieldNodeTransformerMap,
objectValueTransformerMap,
errorsTransformer,
}: {
typeDefs?: string;
resolvers?: IResolvers;
defaultFieldResolver?: IFieldResolver<any, any>;
fieldNodeTransformerMap?: FieldNodeMappers;
objectValueTransformerMap?: ObjectValueTransformerMap;
errorsTransformer?: ErrorsTransformer;
}) {
this.typeDefs = typeDefs;
this.resolvers = resolvers;
this.defaultFieldResolver = defaultFieldResolver != null ? defaultFieldResolver : defaultMergedResolver;
this.transformer = new MapFields(fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {});
this.transformer = new MapFields(
fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {},
objectValueTransformerMap,
errorsTransformer
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
Expand All @@ -41,7 +58,19 @@ export default class ExtendSchema implements Transform {
});
}

public transformRequest(originalRequest: Request): Request {
return this.transformer.transformRequest(originalRequest);
public transformRequest(
originalRequest: Request,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): Request {
return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext);
}

public transformResult(
originalResult: ExecutionResult,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): ExecutionResult {
return this.transformer.transformResult(originalResult, delegationContext, transformationContext);
}
}
47 changes: 35 additions & 12 deletions packages/wrap/src/transforms/HoistField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,42 @@ import {
removeObjectFields,
Transform,
Request,
ExecutionResult,
unwrapValue,
} from '@graphql-tools/utils';

import { defaultMergedResolver } from '@graphql-tools/delegate';

import MapFields from './MapFields';
import { createMergedResolver } from '@graphql-tools/delegate';

export default class HoistField implements Transform {
private readonly typeName: string;
private readonly path: Array<string>;
private readonly newFieldName: string;
private readonly pathToField: Array<string>;
private readonly oldFieldName: string;
private readonly transformer: Transform;

constructor(typeName: string, path: Array<string>, newFieldName: string) {
this.typeName = typeName;
this.path = path;
this.newFieldName = newFieldName;

this.pathToField = this.path.slice();
this.oldFieldName = this.pathToField.pop();
this.transformer = new MapFields({
[typeName]: {
[newFieldName]: fieldNode => wrapFieldNode(renameFieldNode(fieldNode, this.oldFieldName), this.pathToField),
const pathToField = path.slice();
const oldFieldName = pathToField.pop();

this.oldFieldName = oldFieldName;
this.pathToField = pathToField;
this.transformer = new MapFields(
{
[typeName]: {
[newFieldName]: fieldNode => wrapFieldNode(renameFieldNode(fieldNode, oldFieldName), pathToField),
},
},
});
{
[typeName]: value => {
return unwrapValue(value, pathToField);
},
}
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
Expand All @@ -53,14 +64,26 @@ export default class HoistField implements Transform {
newSchema = appendObjectFields(newSchema, this.typeName, {
[this.newFieldName]: {
type: targetType,
resolve: createMergedResolver({ fromPath: this.pathToField }),
resolve: defaultMergedResolver,
},
});

return this.transformer.transformSchema(newSchema);
}

public transformRequest(originalRequest: Request): Request {
return this.transformer.transformRequest(originalRequest);
public transformRequest(
originalRequest: Request,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): Request {
return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext);
}

public transformResult(
originalResult: ExecutionResult,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): ExecutionResult {
return this.transformer.transformResult(originalResult, delegationContext, transformationContext);
}
}
54 changes: 46 additions & 8 deletions packages/wrap/src/transforms/MapFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,71 @@ import { Transform, Request, FieldNodeMappers, ExecutionResult } from '@graphql-

import TransformCompositeFields from './TransformCompositeFields';

import { ObjectValueTransformerMap, ErrorsTransformer } from '../types';

export default class MapFields implements Transform {
private readonly transformer: TransformCompositeFields;

constructor(fieldNodeTransformerMap: FieldNodeMappers) {
constructor(
fieldNodeTransformerMap: FieldNodeMappers,
objectValueTransformerMap?: ObjectValueTransformerMap,
errorsTransformer?: ErrorsTransformer
) {
this.transformer = new TransformCompositeFields(
(_typeName, _fieldName, fieldConfig) => fieldConfig,
(typeName, fieldName, fieldNode, fragments) => {
(typeName, fieldName, fieldNode, fragments, context) => {
const typeTransformers = fieldNodeTransformerMap[typeName];
if (typeTransformers == null) {
return fieldNode;
return undefined;
}

const fieldNodeTransformer = typeTransformers[fieldName];
if (fieldNodeTransformer == null) {
return fieldNode;
return undefined;
}

return fieldNodeTransformer(fieldNode, fragments);
}
return fieldNodeTransformer(fieldNode, fragments, context);
},
objectValueTransformerMap != null
? (data, context) => {
if (data == null) {
return data;
}

const typeName = data.__typename;
if (typeName == null) {
return data;
}

const transformer = objectValueTransformerMap[typeName];
if (transformer == null) {
return data;
}

return transformer(data, context);
}
: undefined,
errorsTransformer != null ? errorsTransformer : undefined
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
return this.transformer.transformSchema(schema);
}

public transformRequest(request: Request): Request {
return this.transformer.transformRequest(request);
public transformRequest(
originalRequest: Request,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): Request {
return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext);
}

public transformResult(
originalResult: ExecutionResult,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): ExecutionResult {
return this.transformer.transformResult(originalResult, delegationContext, transformationContext);
}
}

0 comments on commit d918dda

Please sign in to comment.