Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 22 additions & 36 deletions examples/production-app/graphql/directives.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,31 @@
import { defaultFieldResolver, GraphQLError, GraphQLSchema } from "graphql";
import {
defaultFieldResolver,
GraphQLError,
GraphQLFieldConfig,
} from "graphql";
import { Int } from "grats";
import { Ctx } from "../ViewerContext";
import { getDirective, MapperKind, mapSchema } from "@graphql-tools/utils";

/**
* Some fields cost credits to access. This directive specifies how many credits
* a given field costs.
*
* @gqlDirective cost on FIELD_DEFINITION
* @gqlDirective
*/
export function debitCredits(args: { credits: Int }, context: Ctx): void {
if (context.credits < args.credits) {
// Using `GraphQLError` here ensures the error is not masked by Yoga.
throw new GraphQLError(
`Insufficient credits remaining. This field cost ${args.credits} credits.`,
);
}
context.credits -= args.credits;
}

type CostArgs = { credits: Int };

// Monkey patches the `resolve` function of fields with the `@cost` directive
// to deduct credits from the user's account when the field is accessed.
export function applyCreditLimit(schema: GraphQLSchema): GraphQLSchema {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const costDirective = getDirective(schema, fieldConfig, "cost", [
"grats",
"directives",
]);
if (costDirective == null || costDirective.length === 0) {
return fieldConfig;
}

const originalResolve = fieldConfig.resolve ?? defaultFieldResolver;
fieldConfig.resolve = (source, args, context, info) => {
debitCredits(costDirective[0] as CostArgs, context);
return originalResolve(source, args, context, info);
};
return fieldConfig;
},
});
export function cost<T>(
field: GraphQLFieldConfig<T, Ctx>,
credits: Int,
): GraphQLFieldConfig<T, Ctx> {
const originalResolve = field.resolve ?? defaultFieldResolver;
field.resolve = (source, resolverArgs, context, info) => {
if (context.credits < credits) {
// Using `GraphQLError` here ensures the error is not masked by Yoga.
throw new GraphQLError(
`Insufficient credits remaining. This field cost ${credits} credits.`,
);
}
context.credits -= credits;
return originalResolve(source, resolverArgs, context, info);
};
return field;
}
15 changes: 3 additions & 12 deletions examples/production-app/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createLike as mutationCreateLikeResolver } from "./models/Like";
import { createPost as mutationCreatePostResolver } from "./models/Post";
import { createUser as mutationCreateUserResolver } from "./models/User";
import { GraphQLSchema, GraphQLDirective, DirectiveLocation, GraphQLNonNull, GraphQLInt, GraphQLObjectType, GraphQLList, GraphQLString, GraphQLScalarType, GraphQLID, GraphQLInterfaceType, GraphQLBoolean, GraphQLInputObjectType } from "graphql";
import { cost } from "./graphql/directives";
export function getSchema(): GraphQLSchema {
const DateType: GraphQLScalarType = new GraphQLScalarType({
description: "A date and time. Serialized as a Unix timestamp.\n\n**Note**: The `@specifiedBy` directive does not point to a real spec, but is\nincluded here for demonstration purposes.",
Expand Down Expand Up @@ -54,7 +55,7 @@ export function getSchema(): GraphQLSchema {
return postIdResolver(source);
}
},
likes: {
likes: cost({
description: "All the likes this post has received.\n**Note:** You can use this connection to access the number of likes.",
name: "likes",
type: LikeConnectionType,
Expand All @@ -72,20 +73,10 @@ export function getSchema(): GraphQLSchema {
type: GraphQLInt
}
},
extensions: {
grats: {
directives: [{
name: "cost",
args: {
credits: 10
}
}]
}
},
resolve(source, args, _context, info) {
return source.likes(args, info);
}
},
}, {credits: 10}),
publishedAt: {
description: "The date and time at which the post was created.",
name: "publishedAt",
Expand Down
2 changes: 0 additions & 2 deletions examples/production-app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { getSchema } from "./schema";
import { VC } from "./ViewerContext";
import { addGraphQLScalarSerialization } from "./graphql/CustomScalars";
import { useDeferStream } from "@graphql-yoga/plugin-defer-stream";
import { applyCreditLimit } from "./graphql/directives";

let schema = getSchema();
schema = applyCreditLimit(schema);
schema = addGraphQLScalarSerialization(schema);

const yoga = createYoga({
Expand Down
Loading