Hone logo
Hone
Problems

Type-Safe GraphQL Schema Builder in TypeScript

Building GraphQL schemas can be tedious and error-prone, especially when dealing with complex types and relationships. This challenge asks you to create a TypeScript-based schema builder that enforces type safety during schema definition, reducing potential runtime errors and improving developer experience. A well-designed builder will allow you to define your schema in a declarative and type-safe manner.

Problem Description

You are tasked with creating a SchemaBuilder class in TypeScript that allows developers to define a GraphQL schema in a type-safe way. The builder should provide methods for defining types (objects, enums, scalars), queries, and mutations. The final output of the builder should be a GraphQL schema object compatible with libraries like graphql.

Key Requirements:

  • Type Safety: The builder must leverage TypeScript's type system to ensure that schema definitions are consistent and valid. Incorrect type usage should result in compile-time errors.
  • Declarative Syntax: The builder should provide a fluent, chainable API for defining the schema.
  • GraphQL Compatibility: The generated schema should be directly usable with standard GraphQL libraries.
  • Type Definitions: Support for defining GraphQL Object types, Enums, and Scalars.
  • Query and Mutation Definitions: Methods to define queries and mutations, including their arguments and return types.
  • Error Handling: The builder should provide reasonable error messages if the schema definition is invalid.

Expected Behavior:

The SchemaBuilder class should have methods like addType, addQuery, and addMutation. Each method should accept type definitions and return the builder itself, allowing for chaining. A build() method should then generate the final GraphQL schema object.

Edge Cases to Consider:

  • Circular Dependencies: Handle potential circular dependencies between types gracefully (e.g., by throwing an error or providing a mechanism to resolve them).
  • Invalid Type Definitions: Ensure that invalid type definitions (e.g., missing required fields, incorrect argument types) are caught and reported with clear error messages.
  • Nullability: Properly handle nullable fields in your type definitions.
  • Complex Types: Support nested object types and lists of types.

Examples

Example 1:

// Assume SchemaBuilder is defined (implementation below)
const builder = new SchemaBuilder();

const User = builder.addType({
  name: 'User',
  fields: {
    id: { type: 'ID!', description: 'Unique user ID' },
    name: { type: 'String', description: 'User name' },
    email: { type: 'String', description: 'User email' },
  },
});

const Query = builder.addQuery({
  name: 'Query',
  fields: {
    user: {
      type: User,
      args: {
        id: { type: 'ID!' },
      },
      resolve: (parent, args) => {
        // Mock resolver
        return { id: args.id, name: 'John Doe', email: 'john.doe@example.com' };
      },
    },
  },
});

const schema = builder.build();

// schema should be a valid GraphQL schema object.

Example 2:

const builder = new SchemaBuilder();

const Product = builder.addType({
    name: 'Product',
    fields: {
        id: { type: 'ID!', description: 'Unique product ID' },
        name: { type: 'String!', description: 'Product name' },
        price: { type: 'Float!', description: 'Product price' },
        tags: { type: 'String', description: 'Product tags', isList: true }
    }
});

const Mutation = builder.addMutation({
    name: 'Mutation',
    fields: {
        createProduct: {
            type: Product,
            args: {
                name: { type: 'String!' },
                price: { type: 'Float!' },
                tags: { type: 'String', isList: true }
            },
            resolve: (parent, args) => {
                // Mock resolver
                return { id: '123', ...args };
            }
        }
    }
});

const schema = builder.build();

Constraints

  • TypeScript Version: Use TypeScript 3.8 or higher.
  • GraphQL Library: The generated schema should be compatible with a standard GraphQL library (e.g., graphql). You don't need to include the library as a dependency, but the output should be in a format that can be used with it.
  • Schema Size: The builder should be able to handle schemas with up to 10 types, 5 queries, and 5 mutations. This is a soft constraint; exceeding it shouldn't cause a crash, but performance might degrade.
  • No External Schema Generation Libraries: You should not use external libraries specifically designed for generating GraphQL schemas. The goal is to implement the builder logic yourself.

Notes

  • Start by defining the basic structure of the SchemaBuilder class and its methods.
  • Consider using TypeScript interfaces or types to represent the different schema elements (types, queries, mutations, fields).
  • Think about how to handle type validation and error reporting.
  • The resolve functions in the examples are mock implementations. You don't need to implement actual data fetching logic. Focus on the schema building process.
  • The isList: true property in the Product example indicates that the field is a list of strings. Your builder should handle this correctly.
  • The ! suffix in the type definitions indicates that the field is non-nullable. Your builder should enforce this constraint.
Loading editor...
typescript