CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-relay-compiler

A high-performance GraphQL compilation tool built in Rust for building Relay applications with optimized queries and type generation.

Pending
Overview
Eval results
Files

language-server.mddocs/

Language Server

The Relay Compiler includes a full Language Server Protocol (LSP) implementation that provides IDE integration with real-time GraphQL validation, auto-completion, hover information, and go-to-definition functionality.

Starting the Language Server

{ .api }
relay-compiler lsp [OPTIONS]

Options

{ .api }
--config <PATH>              # Config file path
--output <KIND>              # Verbosity level (default: quiet-with-errors)
--locate-command <SCRIPT>    # Script for GraphQL entity location lookup

Output Levels

{ .api }
type OutputKind = "debug" | "quiet" | "quiet-with-errors" | "verbose";

Basic Usage

# Start with default configuration discovery
relay-compiler lsp

# Start with specific configuration
relay-compiler lsp --config ./relay.config.json

# Start with debug output for troubleshooting
relay-compiler lsp --output debug

# Start with custom entity location script
relay-compiler lsp --locate-command "./scripts/find-graphql-definition.sh"

IDE Integration

VS Code

The Relay LSP integrates with the official Relay extension for VS Code. The binary resolution logic is shared between the CLI and the extension.

Installation:

  1. Install the Relay extension from the VS Code marketplace
  2. Ensure relay-compiler is installed in your project
  3. Configure your relay.config.json
  4. The extension will automatically start the LSP

Other IDEs

Any editor that supports LSP can integrate with the Relay language server:

  • Vim/Neovim: Use LSP clients like nvim-lspconfig or vim-lsp
  • Emacs: Use lsp-mode or eglot
  • Sublime Text: Use LSP package
  • IntelliJ/WebStorm: Built-in LSP support

LSP Features

Real-time Validation

The language server provides immediate feedback on GraphQL syntax and semantic errors:

{ .api }
interface ValidationFeatures {
  syntaxErrors: boolean;          // GraphQL syntax validation
  schemaValidation: boolean;      // Schema conformance checking
  relaySpecificRules: boolean;    // Relay-specific validation rules
  fragmentValidation: boolean;    // Fragment usage validation
}

Auto-completion

Context-aware completions for:

  • Field names from GraphQL schema
  • Fragment names
  • Directive names and arguments
  • Type names
  • Enum values

Hover Information

Rich hover tooltips showing:

  • Field types and descriptions
  • Argument types and documentation
  • Fragment definitions
  • Schema documentation

Go-to-Definition

Navigate from usage to definition for:

  • Fragment definitions
  • Schema types and fields
  • Custom scalar definitions

Diagnostics

Real-time error and warning reporting:

{ .api }
interface DiagnosticTypes {
  errors: string[];             // Compilation errors
  warnings: string[];           // Potential issues
  deprecations: string[];       // Deprecated field usage
  suggestions: string[];        // Code improvement suggestions
}

Custom Entity Location

For implementation-first GraphQL schemas, you can provide a custom script to locate GraphQL entity definitions.

{ .api }
--locate-command <SCRIPT>

Script Interface

The locate command script receives entity information and should return location details:

# Script input (stdin):
# JSON object with entity information
{
  "type": "field" | "type" | "directive",
  "name": "string",
  "parent": "string",
  "schema": "path/to/schema"
}

# Script output (stdout):
# JSON object with location information
{
  "file": "path/to/file",
  "line": 42,
  "column": 15
}

Example Locate Script

#!/bin/bash
# find-graphql-definition.sh

input=$(cat)
entity_type=$(echo "$input" | jq -r '.type')
entity_name=$(echo "$input" | jq -r '.name')

case "$entity_type" in
  "field")
    # Search for field definition in resolvers
    location=$(grep -n "resolve.*$entity_name" src/resolvers/*.js | head -1)
    ;;
  "type")
    # Search for type definition
    location=$(grep -n "type $entity_name" src/schema/*.js | head -1)
    ;;
esac

if [[ -n "$location" ]]; then
  file=$(echo "$location" | cut -d':' -f1)
  line=$(echo "$location" | cut -d':' -f2)
  echo "{\"file\": \"$file\", \"line\": $line, \"column\": 1}"
fi

LSP Configuration

The language server uses the same configuration as the CLI compiler. All configuration options apply to LSP functionality.

Project-specific Settings

{
  "language": "typescript",
  "src": "./src",
  "schema": "./schema.graphql",
  "schemaExtensions": ["./src/schema-extensions"],
  "featureFlags": {
    "enable_fragment_argument_transform": "enabled"
  }
}

Multi-project Support

The LSP automatically detects which project context applies based on the file being edited:

{
  "sources": {
    "./apps/web/src": "web",
    "./apps/mobile/src": "mobile"
  },
  "projects": {
    "web": {
      "language": "typescript",
      "schema": "./apps/web/schema.graphql"
    },
    "mobile": {
      "language": "typescript", 
      "schema": "./apps/mobile/schema.graphql"
    }
  }
}

Schema Documentation Integration

The LSP can load and display schema documentation from various sources:

{ .api }
interface SchemaDocumentationLoader {
  loadFieldDocumentation(typeName: string, fieldName: string): Promise<string>;
  loadTypeDocumentation(typeName: string): Promise<string>;
  loadDirectiveDocumentation(directiveName: string): Promise<string>;
}

Documentation Sources

  • SDL Comments: Standard GraphQL SDL documentation comments
  • Custom Loaders: External documentation systems
  • Markdown Files: Documentation in markdown format

Extra Data Provider

Extend LSP functionality with custom data providers:

{ .api }
interface LSPExtraDataProvider {
  getFieldDefinitionSourceInfo(
    typeName: string,
    fieldName: string
  ): Promise<FieldDefinitionSourceInfo>;
  
  getFieldSchemaInfo(
    typeName: string,
    fieldName: string  
  ): Promise<FieldSchemaInfo>;
}

interface FieldDefinitionSourceInfo {
  filePath: string;
  line: number;
  column: number;
}

interface FieldSchemaInfo {
  description?: string;
  deprecationReason?: string;
  defaultValue?: any;
}

Custom Provider Example

use relay_lsp::{LSPExtraDataProvider, FieldDefinitionSourceInfo, FieldSchemaInfo};

pub struct CustomDataProvider {
    schema_registry: SchemaRegistry,
}

impl LSPExtraDataProvider for CustomDataProvider {
    async fn get_field_definition_source_info(
        &self,
        type_name: &str,
        field_name: &str,
    ) -> Option<FieldDefinitionSourceInfo> {
        // Custom logic to locate field definitions
        self.schema_registry.find_field_source(type_name, field_name)
    }
    
    async fn get_field_schema_info(
        &self,
        type_name: &str,
        field_name: &str,
    ) -> Option<FieldSchemaInfo> {
        // Custom logic to provide field information
        self.schema_registry.get_field_info(type_name, field_name)
    }
}

Error Handling

The LSP provides comprehensive error handling and reporting:

{ .api }
interface LSPErrors {
  ConfigurationError: string;    // Invalid configuration
  SchemaLoadError: string;       // Schema loading failures
  ValidationError: string;       // GraphQL validation failures
  FileSystemError: string;       // File access issues
  LocateCommandError: string;    // Custom locate script failures
}

Common Error Scenarios

  • Configuration not found: LSP will show helpful configuration discovery information
  • Schema file missing: Clear error with path resolution guidance
  • Invalid GraphQL syntax: Real-time syntax error highlighting
  • Locate command failures: Fallback to basic LSP functionality

Performance Optimization

The LSP is optimized for large codebases:

Incremental Updates

  • Only reprocesses changed files
  • Maintains in-memory schema cache
  • Efficient fragment dependency tracking

Memory Management

  • Configurable cache limits
  • Automatic cleanup of unused data
  • Efficient string interning

Watchman Integration

{ .api }
FORCE_NO_WATCHMAN=1    # Disable Watchman, use filesystem polling

When Watchman is available, the LSP uses it for efficient file watching. Otherwise, it falls back to filesystem polling.

Troubleshooting

Debug Mode

relay-compiler lsp --output debug

Debug mode provides detailed logging for:

  • Configuration loading
  • Schema parsing
  • File watching events
  • Validation cycles
  • Custom locate command execution

Log Output

The LSP writes logs to stderr, making it compatible with most LSP clients that capture and display server logs.

Common Issues

  1. LSP not starting: Check configuration file validity
  2. No completions: Verify schema file accessibility
  3. Incorrect go-to-definition: Configure locate command properly
  4. Performance issues: Enable Watchman or reduce project size

Install with Tessl CLI

npx tessl i tessl/npm-relay-compiler

docs

cli-usage.md

codemods.md

configuration.md

index.md

language-server.md

programmatic-api.md

tile.json