DEV Community

⛵

Posted on

Grats Evaluation Report

This report is generated based on example business code and official examples.
Scores in this report are given by AI after horizontal comparison.
Evaluation Date: January 2026

Evaluation Version: grats@0.0.34

Evaluation Method: Deep source code audit based on example business code and official examples

📋 Basic Information

Item Content
Current Version 0.0.34
GitHub https://github.com/captbaritone/grats
Documentation https://grats.capt.dev
First Commit 2023-03-05
Latest Commit 2025-12-21

📊 Overall Score

Dimension Score (1-5) Brief Comment
1. Architecture 3.5 Lightweight dependencies, uses standard JSDoc comments, but requires build step, configuration is slightly complex
2. Type Definition 4.5 Fully supports native TypeScript syntax, powerful type inference, automatic interface field inheritance
3. Resolvers & Validation 2.8 Functional API is concise, parameter types are automatically inferred, but no built-in validation and DataLoader
4. Built-in Features 2.4 Core features are complete (Directives, Scalars, Subscriptions), but advanced features are missing
5. Ecosystem Integration 3.0 Excellent GraphQL Server compatibility, but ORM and validation libraries require manual integration

1. Architecture

Grats adopts the Static Analysis architecture pattern, analyzing JSDoc comments and type definitions in source code through the TypeScript compiler API to generate GraphQL Schema at compile time. This is an Implementation-First approach, using TypeScript code as the single source of truth.

Architecture Overview

Core Mechanisms:

  • Static Analysis: Uses TypeScript compiler API (ts.Program) to analyze source code (src/Extractor.ts)
  • JSDoc-driven: Marks GraphQL entities through JSDoc comments (@gqlType, @gqlField, etc.)
  • Code Generation: CLI tool generates executable TypeScript Schema files and GraphQL SDL files
  • Compiler Plugin: Provides optional TypeScript language service plugin for IDE real-time diagnostics

Workflow (src/lib.ts:90-146):

  1. Create Program using TypeScript compiler API
  2. Extract all definitions with JSDoc tags (extractSnapshotsFromProgram)
  3. Resolve type references, validate interface merging, handle default nullability
  4. Generate GraphQL AST (DocumentNode)
  5. Validate Schema and generate executable code (src/codegen/schemaCodegen.ts)

1.1 Dependency Complexity

Score: 4.0

Evidence:

  • Runtime Dependencies (grats/package.json lines 20-24):
    • commander: ^14.0.1 - CLI command-line tool
    • graphql: ^16.11.0 - GraphQL standard library
    • semver: ^7.7.2 - Version comparison tool
    • typescript: 5.9.2 - TypeScript compiler (fixed version)
  • No Decorator Dependencies: Does not require reflect-metadata or decorator support
  • No Reflection Library: Does not depend on runtime reflection mechanisms
  • Compiler Plugin (grats-ts-plugin/package.json): Independent optional package for IDE support

Analysis:

  • ✅ Only 4 runtime dependencies, lighter than decorator pattern (requires reflect-metadata, class-validator, etc.)
  • ✅ Core dependencies are only graphql and typescript, following minimal dependency principle
  • ⚠️ typescript is a runtime dependency (not devDependency), because compiler API is needed for static analysis
  • ✅ Compiler plugin (grats-ts-plugin) is optional dependency, does not affect core functionality

Actual Usage (typescript-graphql-schemas/grats/package.json):

  • Business code only needs to install grats as devDependency
  • Runtime only depends on graphql and generated Schema files

1.2 Build Flow

Score: 3.0

Evidence:

  • Must Run CLI: Must execute npx grats or grats command to generate Schema (src/cli.ts:47-53)
  • Generates Two Files:
    • .graphql file: GraphQL SDL definition (src/cli.ts:187-191)
    • .ts file: Executable TypeScript Schema (src/cli.ts:182-185)
  • Supports Watch Mode: --watch option supports automatic regeneration on file changes (src/cli.ts:48-49)
  • Transparent Build Process: Uses TypeScript compiler API, build logic is traceable (src/lib.ts:72-77)

Actual Usage Example (typescript-graphql-schemas/grats/package.json lines 7-8):

{
  "scripts": {
    "print": "grats && node src/print.ts",
    "test": "grats && node src/schema.test.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Generated Schema File (typescript-graphql-schemas/grats/src/schema.ts):

  • Contains complete GraphQLSchema instance generation code
  • Automatically imports all Resolver functions
  • Type-safe Schema configuration interface (SchemaConfig)

Build Flow Details (src/cli.ts:173-211):

  1. Read tsconfig.json and parse Grats configuration (getTsConfig)
  2. Use TypeScript compiler API to analyze source code (extractSchemaAndDoc)
  3. Generate TypeScript Schema code (printExecutableSchema)
  4. Generate GraphQL SDL (printGratsSDL)
  5. Optional: Generate enums module (printEnumsModule)

Analysis:

  • ⚠️ Must run build command, cannot "write and use immediately"
  • ✅ Supports Watch mode, good development experience
  • ✅ Generated code is readable, easy to debug
  • ⚠️ Need to integrate build step into development workflow (e.g., package.json scripts)

1.3 Config & Language Magic

Score: 4.0

Evidence:

  • JSDoc Comment-driven: Uses standard JSDoc comments to mark GraphQL entities (src/Extractor.ts:60-73):
    • @gqlType - Object type
    • @gqlField - Field
    • @gqlQueryField - Query field
    • @gqlMutationField - Mutation field
    • @gqlEnum - Enum type
    • @gqlUnion - Union type
    • @gqlInterface - Interface type
  • TypeScript Compiler Plugin (optional): Provides IDE real-time diagnostics (src/tsPlugin/initTsPlugin.ts)
  • Configuration in tsconfig.json: Grats configuration is in the grats field of tsconfig.json (src/gratsConfig.ts:33)

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

/**
 * User information
 * @gqlType
 */
export type User = {
  /** @gqlField */
  id: Int
  /** @gqlField */
  name: string
  /** @gqlField */
  email: string
}

/** @gqlQueryField */
export function users(): User[] {
  return Array.from(userMap.values())
}
Enter fullscreen mode Exit fullscreen mode

Configuration Example (typescript-graphql-schemas/grats/tsconfig.json):

{
  "grats": {
    "importModuleSpecifierEnding": ".ts",
    "tsSchema": "./src/schema.ts"
  },
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "strict": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Compiler Plugin Configuration (examples/apollo-server/tsconfig.json):

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "grats-ts-plugin"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ✅ Uses standard JSDoc comments, conforms to TypeScript best practices
  • ✅ Does not depend on decorators or reflection metadata, no special compilation options needed
  • ⚠️ Requires many JSDoc comments, code is slightly verbose
  • ✅ Compiler plugin is optional, but provides better development experience
  • ⚠️ Configuration needs to be declared in tsconfig.json, increases configuration complexity

1.4 Ecosystem Integration

Score: 4.0

Evidence:

  • Standard npm Installation: Install via npm install grats or pnpm add grats (grats/package.json)
  • TypeScript Compiler Plugin: Provides grats-ts-plugin package, supports IDE real-time diagnostics (grats-ts-plugin/package.json)
  • Multiple GraphQL Server Support: Official examples include (grats/examples/):
    • Apollo Server (examples/apollo-server/)
    • GraphQL Yoga (examples/yoga/)
    • Express GraphQL HTTP (examples/express-graphql-http/)
    • Next.js (examples/next-js/)
  • Standard GraphQL Schema: Generated Schema is a standard GraphQLSchema instance, compatible with all GraphQL Servers

Actual Integration Example (typescript-graphql-schemas/grats/src/server.ts):

import { getSchema } from './schema.ts'
import { createYoga } from 'graphql-yoga'

const schema = getSchema({
  scalars: {
    DateTime: {
      // Custom scalar configuration
    }
  }
})

const yoga = createYoga({ schema })
Enter fullscreen mode Exit fullscreen mode

Apollo Server Integration (examples/apollo-server/server.ts):

import { getSchema } from './schema'

const server = new ApolloServer({
  schema: getSchema(),
})
Enter fullscreen mode Exit fullscreen mode

Next.js Integration (examples/next-js/app/api/graphql/route.ts):

  • Supports Next.js App Router
  • Uses standard GraphQL HTTP endpoint

Analysis:

  • ✅ Standard npm package, simple installation
  • ✅ Generated Schema is a standard GraphQL.js instance, excellent compatibility
  • ✅ Provides multiple Server integration examples, covering mainstream scenarios
  • ✅ Compiler plugin enhances IDE experience, but is optional
  • ⚠️ Need to manually configure grats field in tsconfig.json

Score Details

1.1 Dependency Complexity

Score: 4.0

Reasons:

  • Only 4 runtime dependencies (commander, graphql, semver, typescript)
  • No decorators, no reflection metadata dependencies
  • Compiler plugin is optional dependency
  • Lighter than decorator pattern (requires reflect-metadata, class-validator, etc.)

1.2 Build Flow

Score: 3.0

Reasons:

  • Must run CLI command to generate Schema, cannot "write and use immediately"
  • Supports Watch mode, good development experience
  • Generated code is readable, easy to debug
  • Need to integrate build step into development workflow

1.3 Config & Language Magic

Score: 4.0

Reasons:

  • Uses standard JSDoc comments, conforms to TypeScript best practices
  • Does not depend on decorators or reflection metadata
  • Requires many JSDoc comments, code is slightly verbose
  • Configuration needs to be declared in tsconfig.json

1.4 Ecosystem Integration

Score: 4.0

Reasons:

  • Standard npm installation, simple process
  • Generated Schema is a standard GraphQL.js instance, excellent compatibility
  • Provides multiple Server integration examples
  • Compiler plugin enhances IDE experience (optional)

Overall Score

Architecture Total Score: 3.5

Advantages:

  • Lightweight dependencies, no runtime overhead
  • Uses standard JSDoc comments, conforms to TypeScript best practices
  • Generated code is readable, easy to debug
  • Compatible with all standard GraphQL Servers

Disadvantages:

  • Must run build command, cannot "write and use immediately"
  • Requires many JSDoc comments, code is slightly verbose
  • Configuration needs to be declared in tsconfig.json, increases configuration complexity

2. Type Definition

Grats adopts the Implementation-First approach, with TypeScript code as the single source of truth. Through static analysis of TypeScript type definitions and JSDoc comments, it automatically generates GraphQL Schema. Type inference is based on the TypeScript compiler API, capable of deep analysis of type structures.

2.1 Single Source of Truth Implementation

Score: 4.5

Evidence:

  • TypeScript Code is Schema: TypeScript type definitions in typescript-graphql-schemas/grats/src/models/user.ts directly generate GraphQL Schema
  • Automatic Type Extraction: Analyzes source code through TypeScript compiler API (src/Extractor.ts) to extract type information
  • Bidirectional Sync: Both TypeScript types and GraphQL Schema are generated from the same code, no need to manually maintain two sets of definitions
  • Requires Build Step: Must run grats CLI command to generate Schema (src/cli.ts:182-191)

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

/**
 * User information
 * @gqlType
 */
export type User = {
  /** @gqlField */
  id: Int
  /** @gqlField */
  name: string
  /** @gqlField */
  email: string
}
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema (schema.graphql lines 83-89):

"""User information"""
type User {
  email: String
  id: Int
  name: String
  orders: [Order!]
}
Enter fullscreen mode Exit fullscreen mode

Generated TypeScript Schema (src/schema.ts lines 140-166):

  • Automatically generates executable GraphQLSchema instance
  • Automatically imports all Resolver functions
  • Type-safe Schema configuration interface

Analysis:

  • ✅ TypeScript code is the single source of truth, GraphQL Schema is automatically extracted from it
  • ✅ Type definitions and Schema definitions are completely synchronized, no need to manually maintain
  • ⚠️ Need to run build command to generate Schema, cannot "write and use immediately"
  • ✅ Generated code is readable, easy to debug and verify

2.2 Enum & String Union Support

Score: 5.0

Evidence:

  • Supports TypeScript Enum: src/Extractor.ts:2032-2082 enumEnumDeclaration method supports TypeScript enum declarations
  • Supports String Union Types: src/Extractor.ts:2084-2117 enumTypeAliasDeclaration method supports type Enum = 'A' | 'B' syntax
  • Zero Configuration: Directly uses native TypeScript syntax, no manual registration or mapping needed

Actual Usage Example (typescript-graphql-schemas/grats/src/models/menu.ts lines 6-10):

/**
 * Sugar level for coffee
 * @gqlEnum
 */
export type SugarLevel = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH'
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema (schema.graphql lines 13-19):

"""Sugar level for coffee"""
enum SugarLevel {
  HIGH
  LOW
  MEDIUM
  NONE
}
Enter fullscreen mode Exit fullscreen mode

TypeScript Enum Support (src/Extractor.ts:2032-2082):

  • Supports standard TypeScript enum declarations
  • Requires enum members to use string literal initialization (src/Extractor.ts:2186-2194)
  • If tsClientEnums is configured, requires enum to be exported (src/Extractor.ts:2045-2052)

String Union Type Support (src/Extractor.ts:2119-2177):

  • Supports type Enum = 'A' | 'B' | 'C' syntax
  • Automatically extracts all string literals as enum values
  • If tsClientEnums is configured, does not support type alias enums (src/Extractor.ts:2094-2097)

Analysis:

  • ✅ Fully supports native TypeScript enum syntax, zero configuration
  • ✅ Supports two approaches: TypeScript Enum and string union types
  • ✅ Type-safe: Enum values are completely synchronized in TypeScript and GraphQL
  • ✅ No manual mapping or registration needed, can be used directly

2.3 Interface Inheritance & Union Type Experience

Score: 4.5

Evidence:

  • Automatic Interface Field Inheritance: src/transforms/addInterfaceFields.ts automatically adds interface fields to implementing types
  • TypeScript implements Support: typescript-graphql-schemas/grats/src/models/menu.ts uses implements Food syntax
  • Union Type Support: src/Extractor.ts:666-702 unionTypeAliasDeclaration supports type Union = A | B syntax
  • Requires Manual __typename: Union types require manual definition of __typename field in classes (typescript-graphql-schemas/grats/src/models/menu.ts lines 17, 43)

Interface Inheritance Example (typescript-graphql-schemas/grats/src/interfaces/Food.ts):

/**
 * Food interface with common fields
 * @gqlInterface
 */
export interface Food {
  /** @gqlField */
  id: Int
  /** @gqlField */
  name: string
  /** @gqlField */
  price: Float
}
Enter fullscreen mode Exit fullscreen mode

Implementing Interface (typescript-graphql-schemas/grats/src/models/menu.ts lines 16-36):

/**
 * Coffee menu item
 * @gqlType
 */
export class Coffee implements Food {
  __typename = 'Coffee' as const
  /** @gqlField */
  id: Int
  /** @gqlField */
  name: string
  /** @gqlField */
  price: Float
  /** @gqlField */
  sugarLevel: SugarLevel
  /** @gqlField */
  origin: string
  // ... Interface fields are automatically inherited, no need to redeclare
}
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema (schema.graphql lines 31-38):

"""Coffee menu item"""
type Coffee implements Food {
  id: Int
  name: String
  origin: String
  price: Float
  sugarLevel: SugarLevel
}
Enter fullscreen mode Exit fullscreen mode

Interface Field Auto-Add Mechanism (src/transforms/addInterfaceFields.ts:24-53):

  • If functional fields are defined on the interface (using @gqlField), they are automatically added to all implementing types
  • Calculates interface implementation relationships through computeInterfaceMap (src/InterfaceGraph.ts:11-59)
  • Automatically merges interface fields when building Schema

Union Type Example (typescript-graphql-schemas/grats/src/models/menu.ts lines 61-65):

/**
 * Menu item union type
 * @gqlUnion
 */
export type MenuItem = Coffee | Dessert
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema (schema.graphql lines 21-22):

"""Menu item union type"""
union MenuItem = Coffee | Dessert
Enter fullscreen mode Exit fullscreen mode

Union Type Resolution (src/validations/validateTypenames.ts:22-68):

  • Requires all Union member types to have __typename field
  • Tracks which types have __typename field through hasTypename Set
  • If a type does not have __typename field, an error is reported (src/validations/validateTypenames.ts:42-60)

Analysis:

  • ✅ Interface fields are automatically inherited, implementing types don't need to redeclare common fields
  • ✅ Supports TypeScript implements syntax, conforms to native TypeScript practices
  • ✅ Supports defining functional fields on interfaces, automatically added to all implementing types
  • ⚠️ Union types require manual definition of __typename field, adds some boilerplate code
  • ✅ Union types support native TypeScript Union syntax, zero configuration

2.4 Type Inference Strength & Explicit Declaration Balance

Score: 4.0

Evidence:

  • Automatic Basic Type Inference: src/Extractor.ts automatically infers basic types like string, number, boolean through TypeScript compiler API
  • Automatic Array Type Inference: Supports Type[] and Array<Type> syntax, automatically infers as GraphQL List type
  • Automatic Optional Type Inference: Supports Type | null and Type? syntax, automatically infers nullability
  • Requires Explicit Type Aliases: For GraphQL-specific types (like Int, Float), need to use type aliases provided by grats package (typescript-graphql-schemas/grats/src/models/user.ts line 5)

Type Inference Example (typescript-graphql-schemas/grats/src/models/user.ts):

import type { Int } from 'grats'

/**
 * User information
 * @gqlType
 */
export type User = {
  /** @gqlField */
  id: Int              // Explicitly use Int type alias
  /** @gqlField */
  name: string         // Automatically inferred as String
  /** @gqlField */
  email: string        // Automatically inferred as String
}
Enter fullscreen mode Exit fullscreen mode

Array Type Inference (typescript-graphql-schemas/grats/src/models/order.ts line 26):

/** @gqlField */
itemIds: Int[]        // Automatically inferred as [Int!]
Enter fullscreen mode Exit fullscreen mode

Optional Type Inference (typescript-graphql-schemas/grats/src/models/menu.ts line 83):

/** @gqlQueryField */
export function menuItem(id: Int): MenuItem | null {
  // Return type automatically inferred as MenuItem (nullable)
}
Enter fullscreen mode Exit fullscreen mode

Type Resolution Mechanism (src/transforms/resolveTypes.ts:41-47):

  • Uses two-phase resolution: first extract all declarations, then resolve type references
  • Supports generic type parameters (src/transforms/resolveTypes.ts:73-98)
  • Tracks type declarations and references through TypeContext (src/TypeContext.ts:62-298)

Default Nullability Handling (src/transforms/applyDefaultNullability.ts):

  • If nullableByDefault: true is configured, all fields default to nullable
  • Can mark fields as non-nullable through @killsParentOnException
  • Supports strict semantic nullability (strictSemanticNullability)

Analysis:

  • ✅ Powerful type inference: automatically infers basic types, arrays, optional types
  • ✅ Deep type analysis: semantic analysis through TypeScript compiler API
  • ⚠️ Requires explicit type aliases: for GraphQL-specific types (Int, Float), need to use type aliases
  • ✅ Supports complex types: generics, union types, interfaces can all be correctly inferred
  • ✅ Type-safe: all type inference is type-safe, compile-time checked

Score Details

2.1 Single Source of Truth Implementation

Score: 4.5

Reasons:

  • TypeScript code is the single source of truth, GraphQL Schema is automatically extracted from it
  • Type definitions and Schema definitions are completely synchronized, no need to manually maintain
  • Need to run build command to generate Schema, cannot "write and use immediately"
  • Generated code is readable, easy to debug and verify

2.2 Enum & String Union Support

Score: 5.0

Reasons:

  • Fully supports native TypeScript enum syntax, zero configuration
  • Supports two approaches: TypeScript Enum and string union types
  • Type-safe: Enum values are completely synchronized in TypeScript and GraphQL
  • No manual mapping or registration needed, can be used directly

2.3 Interface Inheritance & Union Type Experience

Score: 4.5

Reasons:

  • Interface fields are automatically inherited, implementing types don't need to redeclare common fields
  • Supports TypeScript implements syntax, conforms to native TypeScript practices
  • Supports defining functional fields on interfaces, automatically added to all implementing types
  • Union types require manual definition of __typename field, adds some boilerplate code

2.4 Type Inference Strength & Explicit Declaration Balance

Score: 4.0

Reasons:

  • Powerful type inference: automatically infers basic types, arrays, optional types
  • Deep type analysis: semantic analysis through TypeScript compiler API
  • Requires explicit type aliases: for GraphQL-specific types (Int, Float), need to use type aliases
  • Supports complex types: generics, union types, interfaces can all be correctly inferred

Overall Score

Type Definition Total Score: 4.5

Advantages:

  • Fully supports native TypeScript syntax, zero configuration
  • Powerful type inference capability, automatically analyzes complex type structures
  • Interface fields are automatically inherited, reduces duplicate code
  • Type definitions and Schema definitions are completely synchronized

Disadvantages:

  • Need to run build command to generate Schema
  • Union types require manual definition of __typename field
  • Need to use type aliases for GraphQL-specific types

3. Resolvers & Validation

Grats Resolver definitions use a Functional API + JSDoc Comments approach. All Resolvers are ordinary TypeScript functions, marked as GraphQL fields through JSDoc comments. Parameter types are automatically inferred through the TypeScript type system, but parameters need to be explicitly declared.

Core Implementation Mechanism

Source Code Evidence:

  • Resolver signature handling: src/resolverSignature.ts defines the Resolver parameter type system
  • Parameter resolution: src/transforms/resolveResolverParams.ts resolves Resolver parameters, distinguishes GraphQL parameters, Context, Info
  • Context injection: Marks Context type through @gqlContext (src/Extractor.ts:74)

3.1 Development Experience (Code Conciseness)

Score: 4.0

Evidence:

  • Functional API: Resolvers in typescript-graphql-schemas/grats/src/models/user.ts are all ordinary functions
  • JSDoc Comment Marking: Marked through @gqlQueryField, @gqlMutationField, @gqlField
  • Automatic Parameter Type Inference: Function parameter types are automatically inferred through TypeScript
  • Requires JSDoc Comments: Each Resolver requires JSDoc comments, code is slightly verbose

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

/** @gqlQueryField */
export function users(): User[] {
  return Array.from(userMap.values())
}

/** @gqlQueryField */
export function user(id: Int): User {
  const user = userMap.get(id)
  if (!user) throw new GraphQLError('User not found')
  return user
}

/** @gqlField */
export function orders(user: User): Order[] {
  return getOrdersByUserId(user.id)
}

/** @gqlMutationField */
export function createUser(name: string, email: string): User {
  if (!email.includes('@')) {
    throw new GraphQLError('Invalid email format')
  }
  const id = incrementId()
  const newUser = { id, name, email } as unknown as User
  userMap.set(id, newUser)
  return newUser
}
Enter fullscreen mode Exit fullscreen mode

Code Volume Analysis:

  • Query Resolver: Each function about 3-5 lines of code + 1 line JSDoc comment
  • Mutation Resolver: Each function about 5-10 lines of code + 1 line JSDoc comment
  • Field Resolver: Each function about 2-4 lines of code + 1 line JSDoc comment

Analysis:

  • ✅ Functional API is concise and intuitive, conforms to TypeScript best practices
  • ✅ Parameter types are automatically inferred, no manual declaration needed
  • ⚠️ Requires JSDoc comments, each Resolver needs to be marked
  • ✅ Code structure is clear, easy to understand and maintain

3.2 Modular Design (Domain-Driven Development Support)

Score: 4.0

Evidence:

  • Organized by Domain: typescript-graphql-schemas/grats/src/models/ directory is organized by domain modules (user.ts, menu.ts, order.ts)
  • Type Definition and Resolver Combined: Each module contains type definitions, Query, Mutation, and Field Resolver
  • No Forced Modularization: Can split files by domain, or put all Resolvers in one file
  • No Module Boundaries: No forced module boundaries, developers need to consciously follow

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

// User module: contains type definitions, Query, Mutation, Field Resolver
/**
 * User information
 * @gqlType
 */
export type User = {
  /** @gqlField */
  id: Int
  /** @gqlField */
  name: string
  /** @gqlField */
  email: string
}

/** @gqlQueryField */
export function users(): User[] { ... }

/** @gqlQueryField */
export function user(id: Int): User { ... }

/** @gqlField */
export function orders(user: User): Order[] { ... }

/** @gqlMutationField */
export function createUser(name: string, email: string): User { ... }
Enter fullscreen mode Exit fullscreen mode

Module Organization:

  • Each module file contains:
    • Type definitions (@gqlType, @gqlInterface, @gqlUnion, @gqlEnum)
    • Query Resolver (@gqlQueryField)
    • Mutation Resolver (@gqlMutationField)
    • Field Resolver (@gqlField)

Analysis:

  • ✅ Supports organizing code by domain modules, type definitions and Resolvers are in the same module
  • ✅ Provides modular API, can split files by domain
  • ⚠️ Does not force modularization, can write coupled giant files (but can be avoided through design)
  • ✅ Code organization is clear, easy to maintain and test

3.3 Parameter Definition & Type Inference

Score: 4.0

Evidence:

  • Automatic Parameter Type Inference: In typescript-graphql-schemas/grats/src/models/user.ts line 28 user(id: Int), id type is automatically inferred as Int
  • Parameter Resolution Mechanism: src/transforms/resolveResolverParams.ts:33-68 automatically resolves Resolver parameters, distinguishes GraphQL parameters, Context, Info
  • Complete IDE Hints: TypeScript fully understands parameter types, IDE autocomplete works normally
  • Requires Explicit Parameter Declaration: Function parameters need explicit type declaration (e.g., id: Int)

Parameter Type System (src/resolverSignature.ts:48-97):

  • SourceResolverArgument: Parent object (source)
  • ArgumentsObjectResolverArgument: Arguments object (args: { id: Int })
  • ContextResolverArgument: Context parameter (ctx: Ctx)
  • DerivedContextResolverArgument: Derived Context (through @gqlContext function)
  • InformationResolverArgument: Info parameter (info: GraphQLResolveInfo)
  • NamedResolverArgument: Named parameter (id: Int)

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

/** @gqlQueryField */
export function user(id: Int): User {
  // id type automatically inferred as Int, IDE hints complete
  const user = userMap.get(id)
  if (!user) throw new GraphQLError('User not found')
  return user
}

/** @gqlMutationField */
export function updateUser(
  id: Int,
  name?: string | null,
  email?: string | null
): User {
  // All parameter types automatically inferred, optional parameters automatically handled
  const user = userMap.get(id)
  if (!user) throw new GraphQLError('User not found')
  if (name != null) user.name = name
  if (email != null) {
    if (!email.includes('@')) {
      throw new GraphQLError('Invalid email format')
    }
    user.email = email
  }
  return user
}
Enter fullscreen mode Exit fullscreen mode

Context Injection Example (examples/production-app/ViewerContext.ts lines 48-54):

/** @gqlContext */
export type Ctx = YogaInitialContext & { vc: VC; credits: number }

/** @gqlContext */
export function getVc(ctx: Ctx): VC {
  return ctx.vc
}

// Used in Resolver
/** @gqlQueryField */
export function currentUser(vc: VC): User {
  // vc type automatically inferred as VC, injected through derived Context
  return vc.getUserById(vc.userId())
}
Enter fullscreen mode Exit fullscreen mode

Parameter Resolution Mechanism (src/transforms/resolveResolverParams.ts:123-294):

  • Automatically identifies parameter types: identifies whether parameters are GraphQL parameters, Context, or Info through TypeScript type system
  • Supports derived Context: automatically injects derived Context defined through @gqlContext function
  • Validates parameter combinations: ensures parameter combinations are valid (e.g., cannot use positional parameters and arguments object simultaneously)

Analysis:

  • ✅ Parameter types are mostly automatically inferred: automatically infers parameter types through TypeScript type system
  • ✅ Complete IDE hints: TypeScript fully understands parameter types, IDE autocomplete works normally
  • ⚠️ Requires explicit parameter declaration: function parameters need explicit type declaration (although this is a TypeScript requirement, it increases code volume)
  • ✅ Type-safe: parameter types are completely synchronized with Schema definitions

3.4 Input Validation Mechanism

Score: 2.0

Evidence:

  • No Built-in Validation: All validation logic in typescript-graphql-schemas/grats/src/models/user.ts (lines 43-44, 58-60) needs to be manually written
  • Manual Error Throwing: Uses GraphQLError to manually throw validation errors
  • Validation Logic Scattered: Validation code is scattered across various Resolvers, difficult to reuse
  • No Declarative Validation API: Does not provide declarative validation API like .refine()

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

/** @gqlMutationField */
export function createUser(name: string, email: string): User {
  // Manually write validation logic
  if (!email.includes('@')) {
    throw new GraphQLError('Invalid email format')
  }
  const id = incrementId()
  const newUser = { id, name, email } as unknown as User
  userMap.set(id, newUser)
  return newUser
}

/** @gqlMutationField */
export function updateUser(
  id: Int,
  name?: string | null,
  email?: string | null
): User {
  const user = userMap.get(id)
  if (!user) throw new GraphQLError('User not found')
  if (name != null) user.name = name
  if (email != null) {
    // Validation logic repeated
    if (!email.includes('@')) {
      throw new GraphQLError('Invalid email format')
    }
    user.email = email
  }
  return user
}
Enter fullscreen mode Exit fullscreen mode

Complex Validation Example (typescript-graphql-schemas/grats/src/models/order.ts lines 77-82):

/** @gqlMutationField */
export function createOrder(userId: Int, items: Int[]): Order {
  // Multiple validation conditions need to be manually written
  if (!userMap.has(userId)) throw new GraphQLError('User not found')
  if (items.length === 0) throw new GraphQLError('At least one item is required')
  for (const id of items) {
    if (!menuMap.has(id)) throw new GraphQLError(`Menu item not found`)
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ❌ No built-in validation: all validation logic needs to be manually written
  • ❌ Validation logic scattered: validation code is scattered across various Resolvers, difficult to reuse
  • ❌ No declarative validation API: does not provide declarative validation API like .refine()
  • ⚠️ Can be implemented through helper functions: can create validation helper functions, but need to manually call them

3.5 Batch Loading (DataLoader) Integration

Score: 0.0

Evidence:

  • No Built-in DataLoader Support: No DataLoader-related implementation found in framework source code
  • Requires Manual Implementation: examples/production-app/ViewerContext.ts shows need to manually create DataLoader instances
  • Requires Manual Context Configuration: Need to manually add DataLoader instances to Context

Actual Usage Example (examples/production-app/ViewerContext.ts lines 18-36):

import DataLoader from "dataloader";
import { getPostsByIds, getUsersByIds, getLikesByIds } from "./Database";

export class VC {
  _postLoader: DataLoader<string, Post>;
  _userLoader: DataLoader<string, User>;
  _likeLoader: DataLoader<string, Like>;

  constructor() {
    // Need to manually create DataLoader instances
    this._postLoader = new DataLoader((ids) => getPostsByIds(this, ids));
    this._userLoader = new DataLoader((ids) => getUsersByIds(this, ids));
    this._likeLoader = new DataLoader((ids) => getLikesByIds(this, ids));
  }

  async getPostById(id: string): Promise<Post> {
    return this._postLoader.load(id);
  }
  // ...
}

/** @gqlContext */
export type Ctx = YogaInitialContext & { vc: VC; credits: number };
Enter fullscreen mode Exit fullscreen mode

Usage in Resolver (requires manual call):

/** @gqlField */
export function user(post: Post, vc: VC): User {
  // Need to manually call DataLoader
  return vc.getUserById(post.userId);
}
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ❌ No built-in support: framework does not provide any DataLoader-related API or tools
  • ❌ Requires lots of boilerplate code: need to manually create DataLoader instances, define Context types, configure Context injection
  • ❌ No automatic batching: need to manually implement batching logic
  • ❌ No cache control: need to manually implement cache logic

Score Details

3.1 Development Experience (Code Conciseness)

Score: 4.0

Reasons:

  • Functional API is concise and intuitive, conforms to TypeScript best practices
  • Parameter types are automatically inferred, no manual declaration needed
  • Requires JSDoc comments, each Resolver needs to be marked
  • Code structure is clear, easy to understand and maintain

3.2 Modular Design (Domain-Driven Development Support)

Score: 4.0

Reasons:

  • Supports organizing code by domain modules, type definitions and Resolvers are in the same module
  • Provides modular API, can split files by domain
  • Does not force modularization, can write coupled giant files (but can be avoided through design)
  • Code organization is clear, easy to maintain and test

3.3 Parameter Definition & Type Inference

Score: 4.0

Reasons:

  • Parameter types are mostly automatically inferred: automatically infers parameter types through TypeScript type system
  • Complete IDE hints: TypeScript fully understands parameter types, IDE autocomplete works normally
  • Requires explicit parameter declaration: function parameters need explicit type declaration (although this is a TypeScript requirement, it increases code volume)
  • Type-safe: parameter types are completely synchronized with Schema definitions

3.4 Input Validation Mechanism

Score: 2.0

Reasons:

  • No built-in validation: all validation logic needs to be manually written
  • Validation logic scattered: validation code is scattered across various Resolvers, difficult to reuse
  • No declarative validation API: does not provide declarative validation API like .refine()
  • Can be implemented through helper functions: can create validation helper functions, but need to manually call them

3.5 Batch Loading (DataLoader) Integration

Score: 0.0

Reasons:

  • No built-in support: framework does not provide any DataLoader-related API or tools
  • Requires lots of boilerplate code: need to manually create DataLoader instances, define Context types, configure Context injection
  • No automatic batching: need to manually implement batching logic
  • No cache control: need to manually implement cache logic

Overall Score

Resolvers & Validation Total Score: 2.8

Advantages:

  • Functional API is concise and intuitive, conforms to TypeScript best practices
  • Parameter types are automatically inferred, complete IDE hints
  • Supports organizing code by domain modules
  • Type-safe, parameter types are completely synchronized with Schema definitions

Disadvantages:

  • Requires JSDoc comments, code is slightly verbose
  • No built-in validation, need to manually implement all validation logic
  • No DataLoader support, need to manually implement batching
  • Validation logic scattered, difficult to reuse

4. Built-in Features

Grats provides some core built-in features, but advanced feature support is limited. Core features (Directives, Scalars, Subscriptions, Context) all have native support, but middleware, query complexity analysis, and other advanced features need to be manually implemented or through third-party libraries.

Feature Overview Table

Feature Status Description
Directive Support ✅ Built-in Define directives through @gqlDirective, use directives through @gqlAnnotate, supports all GraphQL directive locations, API is concise and type-safe
Extension Support ⚠️ Plugin/Additional Implementation Not built-in, but can be implemented through GraphQLError's extensions parameter, requires manual handling
Custom Scalars ✅ Built-in Mark custom scalar types through @gqlScalar, API is intuitive and type-safe
Subscription ✅ Built-in Native GraphQL Subscriptions support, implemented through @gqlSubscriptionField and AsyncIterable, supports real-time data push, underlying transport protocol has good compatibility (WebSocket, SSE, etc.), API is concise
Context Injection ✅ Built-in Native support for injecting context in Resolvers, mark Context type through @gqlContext, supports derived Context, type inference is complete, IDE hints are good
Middleware ⛔ Cannot Implement Completely does not support middleware mechanism, cannot inject logic before/after Resolver execution (such as logging, permission checks, performance monitoring)
Query Complexity Analysis ⛔ Cannot Implement Completely does not support query complexity analysis, cannot prevent complex query attacks
Depth Limiting ⛔ Cannot Implement Completely does not support depth limiting, cannot prevent deep query attacks
Batch Loading (DataLoader) ⛔ Cannot Implement No built-in support, need to manually create DataLoader instances (already evaluated in Phase 3)

Detailed Analysis

4.1 Directive Support

Status: ✅ Built-in

Evidence:

  • Directive Definition: src/Extractor.ts:67 defines DIRECTIVE_TAG = "gqlDirective"
  • Directive Usage: Mark directive usage through @gqlAnnotate (src/Extractor.ts:68)
  • Supports All Directive Locations: Supports FIELD_DEFINITION, ARGUMENT_DEFINITION, OBJECT, INTERFACE, ENUM, SCALAR, INPUT_OBJECT, INPUT_FIELD_DEFINITION, ENUM_VALUE, FRAGMENT_SPREAD, INLINE_FRAGMENT and all other GraphQL directive locations
  • Directive Argument Validation: src/validations/validateDirectiveArguments.ts validates directive argument types

Actual Usage Example (src/tests/fixtures/directives/directiveOnFieldDefinition.ts):

/**
 * This is my custom directive.
 * @gqlDirective on FIELD_DEFINITION
 */
export function max(args: { foo: Int }) {}

/**
 * All likes in the system. Note that there is no guarantee of order.
 * @gqlQueryField
 * @gqlAnnotate max(foo: 10)
 */
export function likes(args: { first?: Int | null }): string {
  return "hello";
}
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema:

"""This is my custom directive."""
directive @max(foo: Int!) on FIELD_DEFINITION

type Query {
  """All likes in the system. Note that there is no guarantee of order."""
  likes(first: Int): String @max(foo: 10)
}
Enter fullscreen mode Exit fullscreen mode

Directive Implementation Example (examples/production-app/graphql/directives.ts lines 6-20):

/**
 * Some fields cost credits to access. This directive specifies how many credits
 * a given field costs.
 *
 * @gqlDirective cost on FIELD_DEFINITION
 */
export function debitCredits(args: { credits: Int }, context: Ctx): void {
  if (context.credits < args.credits) {
    throw new GraphQLError(
      `Insufficient credits remaining. This field cost ${args.credits} credits.`,
    );
  }
  context.credits -= args.credits;
}
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ✅ Native support for defining and using GraphQL Directives
  • ✅ Supports all GraphQL directive locations
  • ✅ API is concise, defined and used through JSDoc comments
  • ✅ Directive arguments are type-safe, automatically validated
  • ⚠️ Directive implementation needs to be manually written (like debitCredits function), needs to be used with @graphql-tools/utils's mapSchema

4.2 Extension Support

Status: ⚠️ Plugin/Additional Implementation

Evidence:

  • Not Built-in: No native API related to Extensions found in framework source code
  • Can be Implemented through GraphQLError: Can implement error extensions through GraphQLError's extensions parameter
  • Cannot Declare Query Complexity: Cannot declare query complexity, execution time, and other extension information

Code Example (requires manual implementation):

import { GraphQLError } from 'graphql'

// Can be implemented through GraphQLError's extensions parameter
throw new GraphQLError('Expected error with extensions', {
  extensions: { code: 'EXPECTED_ERROR' }
})
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ⚠️ Not built-in support for Extensions native API
  • ⚠️ Can be implemented through GraphQLError's extensions parameter, but requires manual handling
  • ⚠️ Cannot declare query complexity, execution time, and other extension information
  • ⚠️ Needs to be used with GraphQL Server's middleware or plugins

4.3 Custom Scalars

Status: ✅ Built-in

Evidence:

  • Scalar Definition: src/Extractor.ts:62 defines SCALAR_TAG = "gqlScalar"
  • Actual Usage: typescript-graphql-schemas/grats/src/graphql/CustomScalars.ts shows custom scalar definition
  • Type-safe: Scalar types are completely synchronized with TypeScript types

Actual Usage Example (typescript-graphql-schemas/grats/src/graphql/CustomScalars.ts):

/** @gqlScalar */
export type DateTime = Date
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema (schema.graphql line 4):

scalar DateTime
Enter fullscreen mode Exit fullscreen mode

Generated TypeScript Schema (src/schema.ts lines 121-124):

const DateTimeType: GraphQLScalarType = new GraphQLScalarType({
  name: "DateTime",
  ...config.scalars.DateTime
});
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ✅ Easy to define new scalar types, just mark with @gqlScalar
  • ✅ API is intuitive, type-safe
  • ⚠️ Does not include common scalars (like DateTime, JSON, BigInt), need to manually define or use third-party libraries (like graphql-scalars)
  • ⚠️ Scalar serialization/deserialization logic needs to be manually provided in Schema configuration

4.4 Subscription

Status: ✅ Built-in

Evidence:

  • Subscription Field Marking: src/Extractor.ts:72 defines SUBSCRIPTION_FIELD_TAG = "gqlSubscriptionField"
  • AsyncIterable Support: src/validations/validateAsyncIterable.ts validates that subscription fields must return AsyncIterable
  • Actual Usage: examples/strict-semantic-nullability/Subscription.ts shows subscription usage

Actual Usage Example (examples/strict-semantic-nullability/Subscription.ts lines 3-9):

/** @gqlSubscriptionField */
export async function* countdown(args: { from: Int }): AsyncIterable<Int> {
  for (let i = args.from; i >= 0; i--) {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    yield i;
  }
}
Enter fullscreen mode Exit fullscreen mode

Generated GraphQL Schema:

type Subscription {
  countdown(from: Int!): Int!
}
Enter fullscreen mode Exit fullscreen mode

AsyncIterable Validation (src/validations/validateAsyncIterable.ts):

  • Requires all Subscription fields to return AsyncIterable
  • Automatically validated when building Schema

Analysis:

  • ✅ Native support for GraphQL Subscriptions
  • ✅ Supports real-time data push, implemented through async generator
  • ✅ Underlying transport protocol has good compatibility (supports WebSocket, SSE, etc. through graphql-yoga)
  • ✅ API is concise, type-safe
  • ✅ Automatically validates subscription field return types

4.5 Context Injection

Status: ✅ Built-in

Evidence:

  • Context Marking: src/Extractor.ts:74 defines CONTEXT_TAG = "gqlContext"
  • Derived Context: Supports defining derived Context through @gqlContext function
  • Parameter Resolution: src/transforms/resolveResolverParams.ts automatically identifies and injects Context parameters

Actual Usage Example (examples/production-app/ViewerContext.ts lines 48-54):

/** @gqlContext */
export type Ctx = YogaInitialContext & { vc: VC; credits: number }

/** @gqlContext */
export function getVc(ctx: Ctx): VC {
  return ctx.vc
}

// Used in Resolver
/** @gqlQueryField */
export function currentUser(vc: VC): User {
  // vc type automatically inferred as VC, injected through derived Context
  return vc.getUserById(vc.userId())
}
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ✅ Native support for injecting context in Resolvers
  • ✅ Supports derived Context, defined through @gqlContext function
  • ✅ Context type inference is complete, IDE hints are good
  • ✅ Automatically identifies Context parameters, no manual configuration needed

4.6 Middleware

Status: ⛔ Cannot Implement

Evidence:

  • No Related Support Found: No middleware-related code found in framework source code
  • No Middleware API: Does not provide API for injecting logic before/after Resolver execution

Analysis:

  • ❌ Completely does not support middleware mechanism
  • ❌ Cannot inject logic before/after Resolver execution (such as logging, permission checks, performance monitoring)
  • ⚠️ Can implement similar functionality by manually wrapping Resolvers, but requires additional boilerplate code
  • ⚠️ Can be implemented through GraphQL Server's middleware (like graphql-yoga's plugin system)

4.7 Query Complexity Analysis

Status: ⛔ Cannot Implement

Evidence:

  • No Related Support Found: No query complexity-related code found in framework source code
  • No Complexity Calculation API: Does not provide query complexity calculation and analysis API

Analysis:

  • ❌ Completely does not support query complexity analysis
  • ❌ Cannot prevent complex query attacks
  • ⚠️ Can be implemented through graphql-yoga's plugins (like graphql-query-complexity), but requires additional configuration

4.8 Depth Limiting

Status: ⛔ Cannot Implement

Evidence:

  • No Related Support Found: No depth limiting-related code found in framework source code
  • No Depth Limiting API: Does not provide query depth limiting API

Analysis:

  • ❌ Completely does not support depth limiting
  • ❌ Cannot prevent deep query attacks
  • ⚠️ Can be implemented through graphql-yoga's plugins (like graphql-depth-limit), but requires additional configuration

Built-in Features Overall Score

Score: 2.4

Scoring Basis:

  • Directives: ✅ Built-in (5 points) - Define through @gqlDirective, supports all GraphQL directive locations
  • Extensions: ⚠️ Plugin/Additional Implementation (2 points) - Can be implemented through GraphQLError's extensions parameter
  • DataLoader: ⛔ Cannot Implement (0 points) - No built-in support, need to manually create DataLoader instances
  • Scalars: ✅ Built-in (5 points) - Mark custom scalar types through @gqlScalar
  • Subscription: ✅ Built-in (5 points) - Native support, through @gqlSubscriptionField and AsyncIterable
  • Context: ✅ Built-in (5 points) - Native support, mark Context type through @gqlContext
  • Middleware: ⛔ Cannot Implement (0 points) - Completely does not support middleware mechanism
  • Query Complexity: ⛔ Cannot Implement (0 points) - Completely does not support query complexity analysis
  • Depth Limiting: ⛔ Cannot Implement (0 points) - Completely does not support depth limiting

Total Score: 22/45 = 2.4/5.0

Scoring Basis:

  • Core features are well supported: Directives, Scalars, Subscriptions, Context all provide native support
  • Advanced features have limited support: Middleware, Query Complexity, Depth Limiting are not supported
  • Feature completeness: Out of 9 features, 5 are built-in, 1 is plugin/additional implementation, 3 cannot be implemented

Advantages:

  1. Core Features Complete: Directives, Scalars, Subscriptions, Context all provide native support
  2. Type-safe: All supported features have complete type inference
  3. API is Concise: Supported features have intuitive and easy-to-use APIs

Disadvantages:

  1. Advanced Features Missing: Middleware, Query Complexity, Depth Limiting are not supported
  2. Requires Manual Implementation: Some features (like directive implementation) need to be manually written
  3. Depends on Third-party Libraries: Some features need to be used with GraphQL Server's plugins

5. Ecosystem Integration

Grats adopts a Standard GraphQL Schema Output + Manual Integration strategy. Outputs standard GraphQLSchema through getSchema(), can integrate with any GraphQL Server, but ORM and validation libraries require manual integration.

Core Integration Strategy

Source Code Evidence:

  • Schema output: typescript-graphql-schemas/grats/src/schema.ts's getSchema() function outputs standard GraphQLSchema instance
  • Standard compatibility: Generated Schema is a standard GraphQL.js instance, compatible with all GraphQL Servers
  • Official examples: examples/ directory contains multiple integration examples (Apollo Server, Yoga, Express GraphQL HTTP, Next.js)

5.1 ORM Integration Depth

Score: <-To Be Scored->

Evidence:

  • No Official Plugins: No official plugins found for Prisma, Drizzle, TypeORM, and other ORMs
  • Requires Manual Integration: All database operations in typescript-graphql-schemas/grats/src/models/user.ts are manually implemented (using in-memory Map)
  • Type Sync Requires Manual Maintenance: ORM model definitions are separated from GraphQL Schema definitions, need to manually maintain synchronization
  • Official Example: examples/production-app/Database.ts shows manually written database functions

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

// Need to manually define GraphQL types
/**
 * User information
 * @gqlType
 */
export type User = {
  /** @gqlField */
  id: Int
  /** @gqlField */
  name: string
  /** @gqlField */
  email: string
}

// Need to manually implement database queries
export const userMap = new Map<number, User>(USERS.map((u) => [u.id, { ...u } as unknown as User]))

/** @gqlQueryField */
export function users(): User[] {
  return Array.from(userMap.values())  // Manual query
}

/** @gqlQueryField */
export function user(id: Int): User {
  const user = userMap.get(id)  // Manual query
  if (!user) throw new GraphQLError('User not found')
  return user
}
Enter fullscreen mode Exit fullscreen mode

Official Example (examples/production-app/Database.ts):

  • All database operations are manually written functions
  • Uses in-memory arrays to simulate database
  • Need to manually implement all CRUD operations

Analysis:

  • ❌ No official plugins: Does not provide official plugins for Prisma, Drizzle, TypeORM, and other ORMs
  • ❌ Requires lots of glue code: Must manually write all database query logic
  • ❌ Type sync requires manual maintenance: ORM model definitions are separated from GraphQL Schema definitions, need to manually maintain synchronization
  • ⚠️ Can manually integrate: Although can manually integrate ORM, requires lots of boilerplate code, and type sync needs to be manually maintained

5.2 Validation Library Integration

Score: <-To Be Scored->

Evidence:

  • No Official Plugins: No official plugins found for Zod, Yup, Valibot, and other validation libraries
  • Requires Manual Integration: All validation logic needs to be manually written in Resolvers (typescript-graphql-schemas/grats/src/models/user.ts lines 43-44, 58-60)
  • Validation Logic Separated from Schema Definition: Validation code is scattered across various Resolvers, difficult to reuse

Actual Usage Example (typescript-graphql-schemas/grats/src/models/user.ts):

/** @gqlMutationField */
export function createUser(name: string, email: string): User {
  // Need to manually write validation logic
  if (!email.includes('@')) {
    throw new GraphQLError('Invalid email format')
  }
  const id = incrementId()
  const newUser = { id, name, email } as unknown as User
  userMap.set(id, newUser)
  return newUser
}

/** @gqlMutationField */
export function updateUser(
  id: Int,
  name?: string | null,
  email?: string | null
): User {
  const user = userMap.get(id)
  if (!user) throw new GraphQLError('User not found')
  if (name != null) user.name = name
  if (email != null) {
    // Validation logic repeated
    if (!email.includes('@')) {
      throw new GraphQLError('Invalid email format')
    }
    user.email = email
  }
  return user
}
Enter fullscreen mode Exit fullscreen mode

Complex Validation Example (typescript-graphql-schemas/grats/src/models/order.ts lines 77-82):

/** @gqlMutationField */
export function createOrder(userId: Int, items: Int[]): Order {
  // Multiple validation conditions need to be manually written
  if (!userMap.has(userId)) throw new GraphQLError('User not found')
  if (items.length === 0) throw new GraphQLError('At least one item is required')
  for (const id of items) {
    if (!menuMap.has(id)) throw new GraphQLError(`Menu item not found`)
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ❌ No official plugins: Does not provide official plugins for Zod, Yup, Valibot, and other validation libraries
  • ❌ Requires manual integration: All validation logic needs to be manually written in Resolvers
  • ❌ Validation logic separated from Schema definition: Validation code is scattered across various Resolvers, difficult to reuse
  • ❌ Requires lots of boilerplate code: Each validation scenario needs to be manually written
  • ⚠️ Can manually integrate: Although can manually integrate validation libraries (like calling Zod in Resolvers), requires lots of boilerplate code

5.3 GraphQL Server Compatibility

Score: <-To Be Scored->

Evidence:

  • Outputs Standard GraphQL Schema: typescript-graphql-schemas/grats/src/schema.ts's getSchema() function outputs standard GraphQLSchema instance
  • Compatible with Standard GraphQL.js: Generated Schema is a standard GraphQL.js instance, compatible with all GraphQL Servers
  • Official Examples Cover Mainstream Servers: examples/ directory contains multiple integration examples:
    • Apollo Server (examples/apollo-server/)
    • GraphQL Yoga (examples/yoga/)
    • Express GraphQL HTTP (examples/express-graphql-http/)

Apollo Server Integration (examples/apollo-server/server.ts):

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { getSchema } from "./schema";

async function main() {
  const server = new ApolloServer({ schema: getSchema() });
  const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
  });
  console.log(`Running a GraphQL API server at ${url}`);
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Yoga Integration (examples/yoga/server.ts):

import { createServer } from "node:http";
import { createYoga } from "graphql-yoga";
import { getSchema } from "./schema";

const yoga = createYoga({
  schema: getSchema(),
});

const server = createServer(yoga);
server.listen(4000, () => {
  console.log("Running a GraphQL API server at http://localhost:4000/graphql");
});
Enter fullscreen mode Exit fullscreen mode

Express GraphQL HTTP Integration (examples/express-graphql-http/server.ts):

import * as express from "express";
import { createHandler } from "graphql-http/lib/use/express";
import { getSchema } from "./schema";

const app = express();
app.post(
  "/graphql",
  createHandler({
    schema: getSchema(),
  }),
);
app.listen(4000);
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • ✅ Fully compatible: Fully compatible with all mainstream GraphQL Servers
  • ✅ Zero configuration: Just pass the generated Schema to the Server
  • ✅ Official examples are complete: Provides multiple mainstream Server integration examples
  • ✅ Type-safe: Schema configuration interface is type-safe (SchemaConfig)

5.4 Toolchain Integration

Score: <-To Be Scored->

Evidence:

TypeScript/JavaScript Support

TypeScript Support:

  • Core Mechanism: Grats is a TypeScript compiler plugin, generates GraphQL Schema through static analysis of TypeScript code
  • TypeScript Compiler Dependency: grats/src/cli.ts uses typescript package and TypeScript compiler API (ts.createProgram, ts.createCompilerHost)
  • TypeScript Plugin: grats/src/tsPlugin/initTsPlugin.ts provides TypeScript Language Service plugin, supports IDE real-time diagnostics
  • All Examples are TypeScript: All official examples (examples/apollo-server/, examples/yoga/, examples/next-js/, etc.) use TypeScript

JavaScript Support:

  • allowJs Option Support: examples/next-js/tsconfig.json line 8 configures "allowJs": true, indicating JavaScript files can be processed
  • But Requires TypeScript Compiler: Grats itself needs TypeScript compiler to run, even processing JavaScript files requires tsconfig.json configuration
  • Generated Code is TypeScript: examples/next-js/schema.ts shows generated Schema file is TypeScript format

Code Evidence:

grats/src/cli.ts (lines 1-50):

#!/usr/bin/env node

import * as E from "./Errors";
import { Location } from "graphql";
import { getParsedTsConfig } from "./";
import {
  SchemaAndDoc,
  buildSchemaAndDocResult,
  extractSchemaAndDoc,
} from "./lib";
import { Command } from "commander";
import { writeFileSync } from "fs";
import { resolve, dirname } from "path";
import { version } from "../package.json";
import { locate } from "./Locate";
import {
  printGratsSDL,
  printExecutableSchema,
  printEnumsModule,
} from "./printSchema";
import * as ts from "typescript";
Enter fullscreen mode Exit fullscreen mode

grats/examples/next-js/tsconfig.json (line 8):

    "allowJs": true,
Enter fullscreen mode Exit fullscreen mode

Runtime Environment Support

Node.js:

  • ✅ Explicitly Supported: All official examples are Node.js environment
    • examples/apollo-server/server.ts uses @apollo/server
    • examples/yoga/server.ts uses graphql-yoga and Node.js HTTP server
    • examples/express-graphql-http/server.ts uses Express
    • examples/production-app/server.ts uses graphql-yoga
  • Official Documentation Explicitly States: website/docs/01-getting-started/01-quick-start.mdx line 51 uses import { createServer } from "node:http"
  • package.json Engine Requirements: grats/package.json lines 46-48 specify "engines": { "node": ">=20 <=24" }

Bun:

  • ⚠️ Theoretically Supported but Not Verified:
    • Generated Schema code is standard TypeScript/JavaScript, theoretically can run in Bun
    • But no Bun-related documentation, examples, or configuration
    • All examples use Node.js runtime

Deno:

  • ⚠️ Theoretically Supported but Not Verified:
    • Generated Schema code is standard TypeScript/JavaScript, theoretically can run in Deno
    • But no Deno-related documentation, examples, or configuration
    • Grats CLI uses Node.js-specific APIs (fs, path), needs Node.js environment to run

Cloudflare Workers:

  • ⚠️ Theoretically Supported but Not Verified:
    • Generated Schema code is standard TypeScript/JavaScript, theoretically can run in Cloudflare Workers
    • But no Cloudflare Workers-related documentation, examples, or configuration
    • All examples are Node.js server-side code

Browser:

  • ⚠️ Only for Playground:
    • website/src/pages/exec/index.tsx uses @typescript/vfs to run TypeScript compiler in browser, but this is for online Playground, not actual usage scenario
    • website/src/pages/exec/index.tsx lines 15-24 simulate process.cwd(), indicating special handling is needed to run in browser
    • All actual usage examples are server-side code

Code Evidence:

grats/website/docs/01-getting-started/01-quick-start.mdx (lines 50-66):

import { createServer } from "node:http";
import { createYoga } from "graphql-yoga";
import { getSchema } from "./schema.js"; // Will be generated by Grats

/** @gqlQueryField */
export function hello(): string {
  return "Hello world!";
}

const yoga = createYoga({ schema: getSchema() });
const server = createServer(yoga);

server.listen(4000, () => {
  console.log("Running a GraphQL API server at http://localhost:4000/graphql");
});
Enter fullscreen mode Exit fullscreen mode

grats/package.json (lines 46-48):

  "engines": {
    "node": ">=20 <=24"
  },
Enter fullscreen mode Exit fullscreen mode

Build Tool Support

Next.js:

  • ✅ Official Example: examples/next-js/ provides complete Next.js App Router integration example
  • Next.js Configuration: examples/next-js/next.config.js uses default configuration, no special build tool configuration needed
  • TypeScript Configuration: examples/next-js/tsconfig.json uses Next.js recommended TypeScript configuration (moduleResolution: "bundler", allowJs: true)
  • Official Documentation: website/docs/05-examples/08-next-js.md provides Next.js integration documentation

TypeScript Compiler (tsc):

  • ✅ Core Dependency: All examples use tsc to compile TypeScript code
  • Build Scripts:
    • examples/apollo-server/package.json line 7: "start": "tsc && node dist/server.js"
    • examples/yoga/package.json line 7: "start": "tsc && node dist/server.js"
    • examples/express-graphql-http/package.json line 7: "start": "tsc && node dist/server.js"
  • TypeScript Configuration Examples:
    • examples/apollo-server/tsconfig.json uses module: "NodeNext", moduleResolution: "NodeNext"
    • examples/yoga/tsconfig.json uses module: "NodeNext", moduleResolution: "NodeNext"
    • examples/production-app/tsconfig.json uses module: "NodeNext", moduleResolution: "NodeNext"

Webpack:

  • ⚠️ No Official Configuration Example:
    • Documentation and examples do not provide webpack configuration examples
    • website/package.json line 45 contains "webpack": "^5.78.0", but this is for website build, not framework's build tool integration
    • Theoretically can integrate through TypeScript compiler, but requires user to configure themselves

Vite:

  • ⚠️ No Official Configuration Example:
    • Documentation and examples do not provide vite configuration examples
    • No vite-related configuration files or documentation
    • Theoretically can integrate through TypeScript compiler, but requires user to configure themselves

Rspack:

  • ⚠️ No Official Configuration Example:
    • Documentation and examples do not provide rspack configuration examples
    • No rspack-related configuration files or documentation
    • Theoretically can integrate through TypeScript compiler, but requires user to configure themselves

Grats CLI as Build Step:

  • ✅ Independent CLI Tool: grats/src/cli.ts implements independent command-line tool, does not depend on specific build tools
  • Workflow: website/docs/05-guides/01-workflows.md recommends running Grats as build step (npm run grats), then using TypeScript compiler to compile
  • Watch Mode: Supports --watch option, can automatically regenerate Schema during development

Code Evidence:

grats/examples/next-js/next.config.js (lines 1-5):

/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

grats/examples/apollo-server/tsconfig.json (lines 6-14):

  "compilerOptions": {
    "outDir": "dist",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "esnext",
    "lib": ["esnext"],
    "strict": true
  }
Enter fullscreen mode Exit fullscreen mode

grats/examples/yoga/tsconfig.json (lines 6-15):

  "compilerOptions": {
    "outDir": "dist",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "esnext",
    "lib": ["esnext"],
    "strict": true,
    "skipLibCheck": true
  }
Enter fullscreen mode Exit fullscreen mode

Analysis:

  • Native TypeScript Support: Completely based on TypeScript compiler, provides complete TypeScript support
  • Node.js Explicitly Supported: All official examples are Node.js environment, documentation explicitly states
  • ⚠️ JavaScript Support Limited: Although supports allowJs, requires TypeScript compiler, not true native JavaScript support
  • ⚠️ Other Runtime Environments Not Verified: Bun, Deno, Cloudflare Workers theoretically supported, but no documentation or examples
  • ⚠️ Browser Not Supported: Only for Playground, not actual usage scenario
  • Next.js Official Example: Provides complete Next.js integration example
  • TypeScript Compiler Integration: All examples use tsc, integration is simple
  • ⚠️ Other Build Tools No Official Support: webpack, vite, rspack have no official configuration examples, requires user to configure themselves
  • Independent CLI Tool: Does not depend on specific build tools, can be flexibly integrated as build step

Score Details

5.1 ORM Integration Depth

Score: <-To Be Scored->

Reasons:

  • No official plugins: Does not provide official plugins for Prisma, Drizzle, TypeORM, and other ORMs
  • Requires lots of glue code: Must manually write all database query logic
  • Type sync requires manual maintenance: ORM model definitions are separated from GraphQL Schema definitions, need to manually maintain synchronization
  • Can manually integrate: Although can manually integrate ORM, requires lots of boilerplate code

5.2 Validation Library Integration

Score: <-To Be Scored->

Reasons:

  • No official plugins: Does not provide official plugins for Zod, Yup, Valibot, and other validation libraries
  • Requires manual integration: All validation logic needs to be manually written in Resolvers
  • Validation logic separated from Schema definition: Validation code is scattered across various Resolvers, difficult to reuse
  • Requires lots of boilerplate code: Each validation scenario needs to be manually written

5.3 GraphQL Server Compatibility

Score: <-To Be Scored->

Reasons:

  • Fully compatible: Fully compatible with all mainstream GraphQL Servers
  • Zero configuration: Just pass the generated Schema to the Server
  • Official examples are complete: Provides multiple mainstream Server integration examples
  • Type-safe: Schema configuration interface is type-safe

5.4 Toolchain Integration

Score: <-To Be Scored->

Reasons:

  • Native TypeScript Support: Completely based on TypeScript compiler, provides complete TypeScript support, all examples are TypeScript
  • JavaScript Support Limited: Although supports allowJs, requires TypeScript compiler, not true native JavaScript support
  • Node.js Explicitly Supported: All official examples are Node.js environment, documentation explicitly states, package.json specifies Node.js engine requirements
  • Other Runtime Environments Not Verified: Bun, Deno, Cloudflare Workers theoretically supported (generated code is standard TypeScript/JavaScript), but no documentation, examples, or configuration
  • Browser Not Supported: Only for Playground (through @typescript/vfs), not actual usage scenario
  • Next.js Official Example: Provides complete Next.js App Router integration example and documentation
  • TypeScript Compiler Integration: All examples use tsc, integration is simple, runs as build step
  • Other Build Tools No Official Support: webpack, vite, rspack have no official configuration examples, requires user to configure themselves
  • Independent CLI Tool: Does not depend on specific build tools, can be flexibly integrated as build step, supports watch mode

Overall Score

Ecosystem Integration Total Score: 3.0

Advantages:

  • Excellent GraphQL Server compatibility, fully compatible with all mainstream Servers
  • Next.js official examples are complete
  • Standard GraphQL Schema output, integration is simple

Disadvantages:

  • ORM integration requires manual implementation, no official plugins
  • Validation library integration requires manual implementation, no official plugins
  • Requires lots of boilerplate code, type sync needs to be manually maintained

📝 Summary

Overall Score: 3.1/5.0

Dimension Score Description
Architecture 3.5 Lightweight dependencies, uses standard JSDoc comments, but requires build step, configuration is slightly complex
Type Definition 4.5 Fully supports native TypeScript syntax, powerful type inference, automatic interface field inheritance
Resolvers & Validation 2.8 Functional API is concise, parameter types are automatically inferred, but no built-in validation and DataLoader
Built-in Features 2.4 Core features are complete (Directives, Scalars, Subscriptions), but advanced features are missing
Ecosystem Integration 3.0 Excellent GraphQL Server compatibility, but ORM and validation libraries require manual integration

Overall Evaluation

Grats adopts the Static Analysis architecture pattern, analyzing JSDoc comments and type definitions in source code through the TypeScript compiler API to generate GraphQL Schema at compile time. This is an Implementation-First approach, using TypeScript code as the single source of truth. Type inference is strong, fully supports native TypeScript syntax, interface fields are automatically inherited. But requires build step, validation and DataLoader need to be manually implemented, advanced feature support is limited.

Core Advantages

  1. Uses Standard JSDoc: Conforms to TypeScript best practices, does not depend on decorators or reflection metadata
  2. Powerful Type Inference: Fully supports native TypeScript syntax, automatically analyzes complex type structures
  3. Automatic Interface Field Inheritance: No need to redeclare common fields when implementing interfaces
  4. Zero Configuration Enums: Supports TypeScript Enum and string union types, completely zero configuration
  5. Type Definitions and Schema Definitions Completely Synchronized: TypeScript code is the single source of truth

Main Disadvantages

  1. Requires Build Step: Must run grats CLI command to generate Schema, cannot "write and use immediately"
  2. Requires Many JSDoc Comments: Each Resolver requires JSDoc comments, code is slightly verbose
  3. No Built-in Validation: All validation logic needs to be manually written, validation code is repeated
  4. No DataLoader Support: Need to manually implement, requires lots of boilerplate code
  5. Advanced Features Missing: Middleware, Query Complexity, Depth Limiting are not supported

Use Cases

Recommended For

  • Teams that prefer Implementation-First approach
  • Projects that need powerful type inference
  • Projects that don't mind build steps
  • Projects that need automatic interface field inheritance

Not Recommended For

  • Projects that need "write and use immediately"
  • Projects that need validation or DataLoader
  • Projects that need advanced features (Middleware, query complexity)
  • Projects that want to reduce JSDoc comments

Improvement Suggestions

  1. Provide Validation and DataLoader Support: Reduce manual implementation, improve development efficiency
  2. Reduce JSDoc Comment Requirements: Reduce comments through better type inference
  3. Provide Middleware and Query Complexity Support: Meet advanced feature needs
  4. Support Runtime Build: Provide optional runtime build mode, reduce build step dependency

Top comments (0)