Skip to content

Commit

Permalink
Merge pull request #233 from 19majkel94/typedefs-resolvers
Browse files Browse the repository at this point in the history
Add support for typeDefs and resolvers ecosystem compatibility
  • Loading branch information
MichalLytek authored Jan 21, 2019
2 parents 036a40b + 1fdebba commit 63d4858
Show file tree
Hide file tree
Showing 9 changed files with 697 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features
- **Breaking Change**: change the default `PrintSchemaOptions` option `commentDescriptions` to false (no more `#` comments in SDL)
- add support for passing `PrintSchemaOptions` in `buildSchema.emitSchemaFile` (e.g. `commentDescriptions: true` to restore previous behavior)
- add `buildTypeDefsAndResolvers` utils function for generating apollo-like `typeDefs` and `resolvers` pair (#233)

## v0.16.0
### Features
Expand Down
34 changes: 34 additions & 0 deletions docs/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,37 @@ bootstrap();
Remember to install `apollo-server` package from npm - it's not bundled with TypeGraphQL.

Of course you can use `express-graphql` middleware, `graphql-yoga` or whatever you want 😉

## Create typeDefs and resolvers map

TypeGraphQL also provides a second way to generate the GraphQL schema - the `buildTypeDefsAndResolvers` function.

It accepts the same `BuildSchemaOptions` like the `buildSchema` function but instead of an executable `GraphQLSchema`, it creates a typeDefs and resolversMap pair that you can use e.g. with [`graphql-tools`](https://github.com/apollographql/graphql-tools):
```typescript
import { makeExecutableSchema } from "graphql-tools";

const { typeDefs, resolvers } = await buildTypeDefsAndResolvers({
resolvers: [FirstResolver, SecondResolver],
});

const schema = makeExecutableSchema({ typeDefs, resolvers });
```

Or even with other libraries that expect the schema info in that shape, like [`apollo-link-state`](https://github.com/apollographql/apollo-link-state):
```typescript
import { withClientState } from 'apollo-link-state';

const { typeDefs, resolvers } = await buildTypeDefsAndResolvers({
resolvers: [FirstResolver, SecondResolver],
});

const stateLink = withClientState({
// ...other options like `cache`
typeDefs,
resolvers,
});

// ...the rest of `ApolloClient` initialization code
```

Be aware that some of the TypeGraphQL features (i.a. [query complexity](complexity.md)) might not work with `buildTypeDefsAndResolvers` approach because they use some low-level `graphql-js` features.
13 changes: 1 addition & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,6 @@ export * from "./decorators";
export * from "./scalars";
export * from "./errors";
export * from "./interfaces";

export { buildSchema, buildSchemaSync, BuildSchemaOptions } from "./utils/buildSchema";
export {
emitSchemaDefinitionFile,
emitSchemaDefinitionFileSync,
} from "./utils/emitSchemaDefinitionFile";
export {
useContainer,
ContainerType,
ContainerGetter,
UseContainerOptions,
} from "./utils/container";
export * from "./utils";

export { PubSubEngine } from "graphql-subscriptions";
3 changes: 2 additions & 1 deletion src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from "./Complexity";
export * from "./Publisher";
export * from "./ResolverData";
export * from "./ResolverFilterData";
export * from "./ResolverTopicData";
export * from "./ResolverInterface";
export * from "./resolvers-map";
export * from "./ResolverTopicData";
30 changes: 30 additions & 0 deletions src/interfaces/resolvers-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
GraphQLScalarType,
GraphQLFieldResolver,
GraphQLTypeResolver,
GraphQLIsTypeOfFn,
} from "graphql";

export interface ResolversMap<TSource = any, TContext = any> {
[key: string]:
| ResolverObject<TSource, TContext>
| ResolverOptions<TSource, TContext>
| GraphQLScalarType
| EnumResolver;
}

export interface ResolverObject<TSource = any, TContext = any> {
[key: string]: ResolverOptions<TSource, TContext>;
}

export interface EnumResolver {
[key: string]: string | number;
}

export interface ResolverOptions<TSource = any, TContext = any> {
fragment?: string;
resolve?: GraphQLFieldResolver<TSource, TContext>;
subscribe?: GraphQLFieldResolver<TSource, TContext>;
__resolveType?: GraphQLTypeResolver<TSource, TContext>;
__isTypeOf?: GraphQLIsTypeOfFn<TSource, TContext>;
}
11 changes: 11 additions & 0 deletions src/utils/buildTypeDefsAndResolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { printSchema } from "graphql";

import { BuildSchemaOptions, buildSchema } from "./buildSchema";
import { createResolversMap } from "./createResolversMap";

export async function buildTypeDefsAndResolvers(options: BuildSchemaOptions) {
const schema = await buildSchema(options);
const typeDefs = printSchema(schema);
const resolvers = createResolversMap(schema);
return { typeDefs, resolvers };
}
86 changes: 86 additions & 0 deletions src/utils/createResolversMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
GraphQLScalarType,
GraphQLEnumType,
GraphQLObjectType,
GraphQLInterfaceType,
GraphQLUnionType,
GraphQLFieldMap,
GraphQLSchema,
GraphQLTypeResolver,
GraphQLAbstractType,
} from "graphql";

import { ResolversMap, EnumResolver, ResolverObject } from "../interfaces";

export function createResolversMap(schema: GraphQLSchema): ResolversMap {
const typeMap = schema.getTypeMap();
return Object.keys(typeMap)
.filter(typeName => !typeName.includes("__"))
.reduce<ResolversMap>((resolversMap, typeName) => {
const type = typeMap[typeName];
if (type instanceof GraphQLObjectType) {
resolversMap[typeName] = {
__isTypeOf: type.isTypeOf || undefined,
...generateFieldsResolvers(type.getFields()),
};
}
if (type instanceof GraphQLInterfaceType) {
resolversMap[typeName] = {
__resolveType: generateTypeResolver(type, schema),
...generateFieldsResolvers(type.getFields()),
};
}
if (type instanceof GraphQLScalarType) {
resolversMap[typeName] = type;
}
if (type instanceof GraphQLEnumType) {
const enumValues = type.getValues();
resolversMap[typeName] = enumValues.reduce<EnumResolver>((enumMap, { name, value }) => {
enumMap[name] = value;
return enumMap;
}, {});
}
if (type instanceof GraphQLUnionType) {
resolversMap[typeName] = {
__resolveType: generateTypeResolver(type, schema),
};
}
return resolversMap;
}, {});
}

function generateTypeResolver(
abstractType: GraphQLAbstractType,
schema: GraphQLSchema,
): GraphQLTypeResolver<any, any> {
if (abstractType.resolveType) {
return async (source, context, info) => {
const detectedType = await abstractType.resolveType!(source, context, info);
if (detectedType instanceof GraphQLObjectType) {
return detectedType.name;
}
return detectedType;
};
}

const possibleObjectTypes = schema.getPossibleTypes(abstractType);
return async (source, context, info) => {
for (const objectType of possibleObjectTypes) {
if (objectType.isTypeOf && (await objectType.isTypeOf(source, context, info))) {
return objectType.name;
}
}
return null;
};
}

function generateFieldsResolvers(fields: GraphQLFieldMap<any, any>): ResolverObject {
return Object.keys(fields).reduce<ResolverObject>((fieldsMap, fieldName) => {
const field = fields[fieldName];
fieldsMap[fieldName] = {
subscribe: field.subscribe,
resolve: field.resolve,
};
return fieldsMap;
}, {});
}
4 changes: 4 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { buildSchema, buildSchemaSync, BuildSchemaOptions } from "./buildSchema";
export { buildTypeDefsAndResolvers } from "./buildTypeDefsAndResolvers";
export { emitSchemaDefinitionFile, emitSchemaDefinitionFileSync } from "./emitSchemaDefinitionFile";
export { useContainer, ContainerType, ContainerGetter, UseContainerOptions } from "./container";
Loading

0 comments on commit 63d4858

Please sign in to comment.