Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce computed fields #1989

Merged
merged 116 commits into from Sep 13, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
acc0a1e
setup schema filtering.
gmac Sep 2, 2020
c682dd2
more tests.
gmac Sep 2, 2020
b524c35
isolate interfaces.
gmac Sep 4, 2020
c9b113d
add missing imports.
gmac Sep 4, 2020
aaa88a5
fix up build.
gmac Sep 4, 2020
7e22836
update integration test.
gmac Sep 4, 2020
6c00754
this safeguard is no longer necessary.
gmac Sep 4, 2020
15642e8
refactor into plugin architecture.
gmac Sep 5, 2020
c520140
reorganize tests.
gmac Sep 5, 2020
b04ac6a
curse you .DS_Store!
gmac Sep 5, 2020
2e61370
add shared endpoint.
gmac Sep 6, 2020
0a46178
refactor back into helper, support SDL declaration.
gmac Sep 7, 2020
9807405
consolidate endpoint logic.
gmac Sep 7, 2020
e87d145
no need for new type
yaacovCR Sep 7, 2020
936f45e
let's describe what we want to do the field, doesn't matter why
yaacovCR Sep 7, 2020
2d528ed
make isolate function sound more generic
yaacovCR Sep 7, 2020
62e798b
use mapSchema directly instead of TransformObjectFields
yaacovCR Sep 7, 2020
bc6ebc1
rename files to sound more generic
yaacovCR Sep 7, 2020
3c3ae30
split off applyComputationsFromSDL to only when necessary
yaacovCR Sep 7, 2020
b7d1ded
use directives instead of deprecation reasons
yaacovCR Sep 7, 2020
dd5fb3e
take computed out entirely from utility function
yaacovCR Sep 7, 2020
6c1f8d6
simplify
yaacovCR Sep 7, 2020
278f204
shorten
yaacovCR Sep 7, 2020
4351bd5
refactor
yaacovCR Sep 7, 2020
dd5d685
rename isolation function (again)
yaacovCR Sep 7, 2020
3e1519e
no need to remove the isolate instruction
yaacovCR Sep 7, 2020
485a0fe
remove unnecessary check
yaacovCR Sep 7, 2020
e12a15b
always isolate when field-level selection sets specified
yaacovCR Sep 8, 2020
7c7a5b9
allow fields syntax
yaacovCR Sep 8, 2020
1040566
updated docs (WIP).
gmac Sep 8, 2020
d62d837
baseSubschema should come first
yaacovCR Sep 8, 2020
acfc153
delegate to base schema when isolating fields
yaacovCR Sep 8, 2020
bd3385c
Merge pull request #2 from ardatan/initial-suggestions
gmac Sep 8, 2020
c8ebc9c
fix build
yaacovCR Sep 8, 2020
05f15bd
Temp disable no-unused-vars
ardatan Sep 8, 2020
4b833f8
Merge branch 'master' into gm-dynamic-merge-fields
ardatan Sep 8, 2020
00d100e
Temp disable no-unused-vars
ardatan Sep 8, 2020
d2e6c42
Merge branch 'master' into gm-dynamic-merge-fields
ardatan Sep 8, 2020
8ff7678
Merge branch 'master' into gm-dynamic-merge-fields
ardatan Sep 8, 2020
4ef338e
cleanup docs.
gmac Sep 9, 2020
f893c25
Merge branch 'gm-dynamic-merge-fields' of github.com:gmac/graphql-too…
gmac Sep 9, 2020
a35b991
[deploy_website] deploy website message.
gmac Sep 9, 2020
a467c39
Merge branch 'master' into gm-dynamic-merge-fields
ardatan Sep 9, 2020
772be86
failing tests
yaacovCR Sep 9, 2020
36493ef
allow test to pass by making field isolation opt-in
yaacovCR Sep 9, 2020
7741f6f
clean up subschema processing logic
yaacovCR Sep 9, 2020
6405b97
works when batching is disabled, so this is a bug in batching not wit…
yaacovCR Sep 9, 2020
be330f6
Merge branch 'master' into gm-dynamic-merge-fields
ardatan Sep 9, 2020
a5b5382
fix failing tests
yaacovCR Sep 9, 2020
1added0
Merge branch 'gm-dynamic-merge-fields' of github.com:gmac/graphql-too…
yaacovCR Sep 9, 2020
e2e86d9
Merge branch 'master' into gm-dynamic-merge-fields
gmac Sep 10, 2020
136b2f6
Merge branch 'gm-dynamic-merge-fields' of github.com:gmac/graphql-too…
gmac Sep 10, 2020
43eead6
reduce diff, add tests, rename prop
yaacovCR Sep 10, 2020
23af0ec
setup schema filtering.
gmac Sep 2, 2020
5f9ae0d
more tests.
gmac Sep 2, 2020
64206d7
isolate interfaces.
gmac Sep 4, 2020
e8bd1cc
add missing imports.
gmac Sep 4, 2020
3aba22e
fix up build.
gmac Sep 4, 2020
fadba11
update integration test.
gmac Sep 4, 2020
6e1e372
this safeguard is no longer necessary.
gmac Sep 4, 2020
92f5923
refactor into plugin architecture.
gmac Sep 5, 2020
ca1f8e5
reorganize tests.
gmac Sep 5, 2020
d1371dc
curse you .DS_Store!
gmac Sep 5, 2020
a9ca788
add shared endpoint.
gmac Sep 6, 2020
410a1d6
refactor back into helper, support SDL declaration.
gmac Sep 7, 2020
ba66160
consolidate endpoint logic.
gmac Sep 7, 2020
e09e954
updated docs (WIP).
gmac Sep 8, 2020
2cd39d0
cleanup docs.
gmac Sep 9, 2020
b3c9abd
no need for new type
yaacovCR Sep 7, 2020
511d21f
let's describe what we want to do the field, doesn't matter why
yaacovCR Sep 7, 2020
2a9863a
make isolate function sound more generic
yaacovCR Sep 7, 2020
4bb1020
use mapSchema directly instead of TransformObjectFields
yaacovCR Sep 7, 2020
f6bcd69
rename files to sound more generic
yaacovCR Sep 7, 2020
f4eb54d
split off applyComputationsFromSDL to only when necessary
yaacovCR Sep 7, 2020
4062cd7
use directives instead of deprecation reasons
yaacovCR Sep 7, 2020
289f3f5
take computed out entirely from utility function
yaacovCR Sep 7, 2020
8eef2d6
simplify
yaacovCR Sep 7, 2020
a805198
shorten
yaacovCR Sep 7, 2020
907d861
refactor
yaacovCR Sep 7, 2020
ce973f8
rename isolation function (again)
yaacovCR Sep 7, 2020
7c476b2
no need to remove the isolate instruction
yaacovCR Sep 7, 2020
f346461
remove unnecessary check
yaacovCR Sep 7, 2020
25172cf
always isolate when field-level selection sets specified
yaacovCR Sep 8, 2020
f5d3799
allow fields syntax
yaacovCR Sep 8, 2020
5e835c8
baseSubschema should come first
yaacovCR Sep 8, 2020
4e52e44
delegate to base schema when isolating fields
yaacovCR Sep 8, 2020
dc771a7
fix build
yaacovCR Sep 8, 2020
525b86d
Temp disable no-unused-vars
ardatan Sep 8, 2020
f43759e
[deploy_website] deploy website message.
gmac Sep 9, 2020
7e48ed3
failing tests
yaacovCR Sep 9, 2020
1be7ab6
allow test to pass by making field isolation opt-in
yaacovCR Sep 9, 2020
4c2c8b4
clean up subschema processing logic
yaacovCR Sep 9, 2020
a05d651
works when batching is disabled, so this is a bug in batching not wit…
yaacovCR Sep 9, 2020
0fd3fa8
fix failing tests
yaacovCR Sep 9, 2020
cd1e296
reduce diff, add tests, rename prop
yaacovCR Sep 10, 2020
4df3e58
Merge branch 'gm-dynamic-merge-fields' of github.com:gmac/graphql-too…
gmac Sep 10, 2020
e1533fc
failing test with single-visitation.
gmac Sep 10, 2020
60539bf
Merge branch 'gm-dynamic-merge-fields' of github.com:gmac/graphql-too…
gmac Sep 10, 2020
301b116
fix batch execution
yaacovCR Sep 10, 2020
4084393
useGatewaySchema => federate
yaacovCR Sep 10, 2020
b889cab
fix typo
yaacovCR Sep 10, 2020
0723719
docs changes
yaacovCR Sep 11, 2020
9f21e66
fix diff
yaacovCR Sep 11, 2020
4fe7cdc
simplify
yaacovCR Sep 11, 2020
bd1819d
change computed syntax to federated
yaacovCR Sep 11, 2020
584f56e
finish changing terms
yaacovCR Sep 11, 2020
ecf4e81
polish
yaacovCR Sep 11, 2020
3017364
consolidate tests
yaacovCR Sep 11, 2020
f84f66f
rename test file
yaacovCR Sep 11, 2020
80eda4f
fix test descriptions
yaacovCR Sep 11, 2020
d6522ad
remove duplicated tests
yaacovCR Sep 11, 2020
0c104d7
add note
yaacovCR Sep 11, 2020
0818eb9
tweak docs
yaacovCR Sep 11, 2020
b2839d5
cleanup extensions intro.
gmac Sep 12, 2020
802fc31
feedback
yaacovCR Sep 13, 2020
40e2a9a
Merge branch 'gm-dynamic-merge-fields' of github.com:gmac/graphql-too…
yaacovCR Sep 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/delegate/src/types.ts
Expand Up @@ -152,7 +152,7 @@ export interface SubschemaConfig<K = any, V = any, C = K> extends SubschemaPermu

export interface MergedTypeConfig<K = any, V = any> {
selectionSet?: string;
fields?: Record<string, { selectionSet?: string }>;
fields?: Record<string, MergedFieldConfig>;
resolve?: MergedTypeResolver;
fieldName?: string;
args?: (originalResult: any) => Record<string, any>;
Expand All @@ -161,6 +161,10 @@ export interface MergedTypeConfig<K = any, V = any> {
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>;
}

export interface MergedFieldConfig {
selectionSet?: string;
}

export type MergedTypeResolver = (
originalResult: any,
context: Record<string, any>,
Expand Down
2 changes: 2 additions & 0 deletions packages/stitch/src/index.ts
@@ -1,2 +1,4 @@
export { stitchSchemas } from './stitchSchemas';
export { forwardArgsToSelectionSet } from './selectionSetArgs';

export { ComputedFieldsPlugin } from './plugins/computedFieldsPlugin';
167 changes: 167 additions & 0 deletions packages/stitch/src/plugins/computedFieldsPlugin.ts
@@ -0,0 +1,167 @@
import {
isSubschemaConfig,
SubschemaConfig,
MergedTypeConfig,
MergedFieldConfig,
Endpoint,
} from '@graphql-tools/delegate';

import { filterSchema, pruneSchema, getImplementingTypes } from '@graphql-tools/utils';

import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType } from 'graphql';

import { IStitchSchemasOptions, StitchSchemasPlugin } from '../types';

interface ComputedFieldConfig extends MergedFieldConfig {
computed?: string;
}

export class ComputedFieldsPlugin implements StitchSchemasPlugin {
public preconfigure(config: IStitchSchemasOptions): IStitchSchemasOptions {
if (config.subschemas) {
const mapped: Array<GraphQLSchema | SubschemaConfig | Array<SubschemaConfig>> = [];

config.subschemas.forEach(schemaLikeObject => {
if (isSubschemaConfig(schemaLikeObject) && schemaLikeObject.merge) {
mapped.push(...isolateComputedMergeSchemas(schemaLikeObject as SubschemaConfig));
} else {
mapped.push(schemaLikeObject);
}
});

config.subschemas = mapped;
}
return config;
}
}

function isolateComputedMergeSchemas(subschemaConfig: SubschemaConfig): Array<SubschemaConfig> {
const staticTypes: Record<string, MergedTypeConfig> = {};
const computedTypes: Record<string, MergedTypeConfig> = {};

Object.keys(subschemaConfig.merge).forEach((typeName: string) => {
const mergedTypeConfig: MergedTypeConfig = subschemaConfig.merge[typeName];
staticTypes[typeName] = mergedTypeConfig;

if (mergedTypeConfig.fields) {
const staticFields: Record<string, MergedFieldConfig> = {};
const computedFields: Record<string, MergedFieldConfig> = {};

Object.keys(mergedTypeConfig.fields).forEach((fieldName: string) => {
const mergedFieldConfig: ComputedFieldConfig = mergedTypeConfig.fields[fieldName] as ComputedFieldConfig;

if (mergedFieldConfig.selectionSet && mergedFieldConfig.computed) {
computedFields[fieldName] = mergedFieldConfig;
} else {
staticFields[fieldName] = mergedFieldConfig;
}
delete mergedFieldConfig.computed;
});

const computedFieldCount = Object.keys(computedFields).length;
const objectType = subschemaConfig.schema.getType(typeName) as GraphQLObjectType;

if (computedFieldCount && computedFieldCount !== Object.keys(objectType.getFields()).length) {
staticTypes[typeName] = {
...mergedTypeConfig,
fields: Object.keys(staticFields).length ? staticFields : undefined,
};
computedTypes[typeName] = { ...mergedTypeConfig, fields: computedFields };
}
}
});

if (Object.keys(computedTypes).length) {
const endpoint = getSharedEndpoint(subschemaConfig);
return [
filterComputedSubschema({ ...subschemaConfig, endpoint, merge: computedTypes }),
filterStaticSubschema({ ...subschemaConfig, endpoint, merge: staticTypes }, computedTypes),
];
}

return [subschemaConfig];
}

function getSharedEndpoint(subschemaConfig: SubschemaConfig): Endpoint {
yaacovCR marked this conversation as resolved.
Show resolved Hide resolved
if (subschemaConfig.endpoint) {
return subschemaConfig.endpoint;
} else if (subschemaConfig.executor) {
return {
executor: subschemaConfig.executor,
batch: true,
};
}
}

function filterStaticSubschema(
subschemaConfig: SubschemaConfig,
computedTypes: Record<string, MergedTypeConfig>
): SubschemaConfig {
const typesForInterface: Record<string, string[]> = {};
subschemaConfig.schema = pruneSchema(
filterSchema({
schema: subschemaConfig.schema,
objectFieldFilter: (typeName: string, fieldName: string) =>
!(computedTypes[typeName] && computedTypes[typeName].fields[fieldName]),
interfaceFieldFilter: (typeName: string, fieldName: string) => {
if (!typesForInterface[typeName]) {
typesForInterface[typeName] = getImplementingTypes(typeName, subschemaConfig.schema);
}
return !typesForInterface[typeName].some(implementingTypeName => {
return computedTypes[implementingTypeName] && computedTypes[implementingTypeName].fields[fieldName];
});
},
}),
{
skipUnimplementedInterfacesPruning: true,
}
);

const remainingTypes = subschemaConfig.schema.getTypeMap();
Object.keys(subschemaConfig.merge).forEach(mergeType => {
if (!remainingTypes[mergeType]) {
delete subschemaConfig.merge[mergeType];
}
});

if (!Object.keys(subschemaConfig.merge).length) {
delete subschemaConfig.merge;
}

return subschemaConfig;
}

function filterComputedSubschema(subschemaConfig: SubschemaConfig): SubschemaConfig {
const rootFields: Record<string, boolean> = {};

Object.keys(subschemaConfig.merge).forEach(typeName => {
rootFields[subschemaConfig.merge[typeName].fieldName] = true;
});

const interfaceFields: Record<string, Record<string, boolean>> = {};
Object.keys(subschemaConfig.merge).forEach(typeName => {
(subschemaConfig.schema.getType(typeName) as GraphQLObjectType).getInterfaces().forEach(int => {
Object.keys((subschemaConfig.schema.getType(int.name) as GraphQLInterfaceType).getFields()).forEach(
intFieldName => {
if (subschemaConfig.merge[typeName].fields[intFieldName]) {
interfaceFields[int.name] = interfaceFields[int.name] || {};
interfaceFields[int.name][intFieldName] = true;
}
}
);
});
});

subschemaConfig.schema = pruneSchema(
filterSchema({
schema: subschemaConfig.schema,
rootFieldFilter: (operation: string, fieldName: string) => !!(operation === 'Query' && rootFields[fieldName]),
objectFieldFilter: (typeName: string, fieldName: string) =>
!!(subschemaConfig.merge[typeName] && subschemaConfig.merge[typeName].fields[fieldName]),
interfaceFieldFilter: (typeName: string, fieldName: string) =>
!!(interfaceFields[typeName] && interfaceFields[typeName][fieldName]),
})
);

return subschemaConfig;
}
46 changes: 26 additions & 20 deletions packages/stitch/src/stitchSchemas.ts
Expand Up @@ -28,26 +28,32 @@ import { createStitchingInfo, completeStitchingInfo, addStitchingInfo } from './
import { IStitchSchemasOptions } from './types';
import { SubschemaConfig, isSubschemaConfig } from '@graphql-tools/delegate';

export function stitchSchemas({
subschemas = [],
types = [],
typeDefs,
schemas = [],
onTypeConflict,
mergeDirectives,
mergeTypes = false,
typeMergingOptions,
resolvers = {},
schemaDirectives,
inheritResolversFromInterfaces = false,
logger,
allowUndefinedInResolve = true,
resolverValidationOptions = {},
directiveResolvers,
schemaTransforms = [],
parseOptions = {},
pruningOptions,
}: IStitchSchemasOptions): GraphQLSchema {
export function stitchSchemas(config: IStitchSchemasOptions): GraphQLSchema {
if (config.plugins) {
config = config.plugins.reduce((memo, plugin) => (plugin.preconfigure ? plugin.preconfigure(memo) : memo), config);
}

const {
subschemas = [],
types = [],
typeDefs,
schemas = [],
onTypeConflict,
mergeDirectives,
mergeTypes = false,
typeMergingOptions,
resolvers = {},
schemaDirectives,
inheritResolversFromInterfaces = false,
logger,
allowUndefinedInResolve = true,
resolverValidationOptions = {},
directiveResolvers,
schemaTransforms = [],
parseOptions = {},
pruningOptions,
} = config;

if (typeof resolverValidationOptions !== 'object') {
throw new Error('Expected `resolverValidationOptions` to be an object');
}
Expand Down
5 changes: 5 additions & 0 deletions packages/stitch/src/types.ts
Expand Up @@ -68,6 +68,7 @@ export interface IStitchSchemasOptions<TContext = any> extends Omit<IExecutableS
mergeDirectives?: boolean;
yaacovCR marked this conversation as resolved.
Show resolved Hide resolved
mergeTypes?: boolean | Array<string> | MergeTypeFilter;
typeMergingOptions?: TypeMergingOptions;
plugins?: Array<StitchSchemasPlugin>;
}

export interface TypeMergingOptions {
Expand All @@ -89,6 +90,10 @@ export type OnTypeConflict = (
}
) => GraphQLNamedType;

export interface StitchSchemasPlugin {
preconfigure?: (config: IStitchSchemasOptions) => IStitchSchemasOptions;
}

declare module '@graphql-tools/utils' {
interface IFieldResolverOptions<TSource = any, TContext = any, TArgs = any> {
fragment?: string;
Expand Down