CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-neo4j-driver

The official Neo4j driver for JavaScript applications, enabling connection to and interaction with Neo4j graph databases.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Comprehensive error handling with retryable error detection and detailed error information for robust application development.

Capabilities

Neo4j Error Class

Base error class for all Neo4j-specific errors with detailed error codes and messages.

class Neo4jError extends Error {
  /** Neo4j error code (e.g., "Neo.ClientError.Statement.SyntaxError") */
  code: string;
  
  /**
   * Create a new Neo4j error
   * @param message - Error message describing the issue
   * @param code - Optional Neo4j error code
   */
  constructor(message: string, code?: string);
}

/**
 * Create a new Neo4j error
 * @param message - Error message
 * @param code - Optional error code
 * @returns Neo4jError instance
 */
function newError(message: string, code?: string): Neo4jError;

Usage Examples:

import { driver, auth, Neo4jError, newError } from "neo4j-driver";

const session = neo4jDriver.session();

try {
  // This will cause a syntax error
  await session.run("INVALID CYPHER QUERY");
} catch (error) {
  if (error instanceof Neo4jError) {
    console.log(`Neo4j Error: ${error.message}`);
    console.log(`Error Code: ${error.code}`);
    
    // Handle specific error types
    switch (error.code) {
      case "Neo.ClientError.Statement.SyntaxError":
        console.log("Cypher syntax error - check your query");
        break;
      case "Neo.ClientError.Security.Unauthorized":
        console.log("Authentication failed - check credentials");
        break;
      case "Neo.TransientError.Network.DatabaseUnavailable":
        console.log("Database unavailable - retry later");
        break;
      default:
        console.log(`Unhandled error type: ${error.code}`);
    }
  } else {
    console.log(`Other error: ${error.message}`);
  }
} finally {
  await session.close();
}

// Creating custom Neo4j errors
function validateInput(data: any) {
  if (!data.name) {
    throw newError("Name is required", "App.Validation.MissingName");
  }
  if (data.age < 0) {
    throw newError("Age cannot be negative", "App.Validation.InvalidAge");
  }
}

Retryable Error Detection

Utility function to determine if an error should be retried.

/**
 * Check if an error is retryable (transient error that may succeed on retry)
 * @param error - Error to check for retryability
 * @returns true if the error is retryable, false otherwise
 */
function isRetryableError(error: any): boolean;

Usage Examples:

import { isRetryableError } from "neo4j-driver";

async function executeWithRetry<T>(operation: () => Promise<T>, maxRetries: number = 3): Promise<T> {
  let lastError: any;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      
      if (!isRetryableError(error)) {
        console.log(`Non-retryable error: ${error.message}`);
        throw error;
      }
      
      if (attempt === maxRetries) {
        console.log(`Max retries (${maxRetries}) reached`);
        throw error;
      }
      
      const delay = Math.pow(2, attempt - 1) * 1000; // Exponential backoff
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

// Usage with session operations
const session = neo4jDriver.session();

try {
  const result = await executeWithRetry(async () => {
    return await session.run(`
      MATCH (p:Person {name: $name})
      SET p.lastAccessed = datetime()
      RETURN p
    `, { name: "Alice" });
  });
  
  console.log("Operation succeeded after retries");
} catch (error) {
  console.error("Operation failed after all retries:", error.message);
} finally {
  await session.close();
}

// Check specific errors
try {
  await session.run("MATCH (n) RETURN n");
} catch (error) {
  if (isRetryableError(error)) {
    console.log("This error can be retried:", error.message);
    // Implement retry logic
  } else {
    console.log("This error should not be retried:", error.message);
    // Handle as permanent failure
  }
}

Error Code Constants

Predefined constants for common Neo4j error codes.

declare const error: {
  /** Service unavailable error code */
  SERVICE_UNAVAILABLE: "Neo.TransientError.General.DatabaseUnavailable";
  
  /** Session expired error code */
  SESSION_EXPIRED: "Neo.TransientError.Transaction.Terminated";
  
  /** Protocol error code */
  PROTOCOL_ERROR: "Neo.ClientError.Request.Invalid";
};

Usage Examples:

import { error } from "neo4j-driver";

try {
  const result = await session.run("MATCH (n) RETURN count(n)");
} catch (err) {
  if (err instanceof Neo4jError) {
    switch (err.code) {
      case error.SERVICE_UNAVAILABLE:
        console.log("Database is unavailable - implement retry logic");
        // Retry with exponential backoff
        break;
        
      case error.SESSION_EXPIRED:
        console.log("Session expired - create new session");
        // Close current session and create new one
        break;
        
      case error.PROTOCOL_ERROR:
        console.log("Protocol error - check client/server compatibility");
        // Log for debugging, don't retry
        break;
        
      case "Neo.ClientError.Statement.SyntaxError":
        console.log("Cypher syntax error - fix the query");
        break;
        
      case "Neo.ClientError.Schema.ConstraintValidationFailed":
        console.log("Constraint violation - handle business logic error");
        break;
        
      default:
        console.log(`Unhandled error: ${err.code} - ${err.message}`);
    }
  }
}

// Using error constants in conditions
function shouldRetryError(error: any): boolean {
  if (!error.code) return false;
  
  const retryableCodes = [
    error.SERVICE_UNAVAILABLE,
    error.SESSION_EXPIRED,
    "Neo.TransientError.Network.ConnectionTimeout",
    "Neo.TransientError.Transaction.DeadlockDetected"
  ];
  
  return retryableCodes.includes(error.code);
}

Error Categories and Handling Patterns

Different types of errors require different handling strategies.

Transient Errors (Retryable):

// Network and availability errors
const transientErrors = [
  "Neo.TransientError.General.DatabaseUnavailable",
  "Neo.TransientError.Network.ConnectionTimeout", 
  "Neo.TransientError.Network.DatabaseUnavailable",
  "Neo.TransientError.Transaction.DeadlockDetected",
  "Neo.TransientError.Transaction.LockClientStopped"
];

async function handleTransientError<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (!isRetryableError(error) || attempt === maxRetries) {
        throw error;
      }
      
      const delay = baseDelay * Math.pow(2, attempt - 1);
      console.log(`Transient error, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw new Error("Should not reach here");
}

Client Errors (Non-Retryable):

// Syntax, constraint, and security errors
const clientErrors = [
  "Neo.ClientError.Statement.SyntaxError",
  "Neo.ClientError.Statement.SemanticError", 
  "Neo.ClientError.Schema.ConstraintValidationFailed",
  "Neo.ClientError.Security.Unauthorized",
  "Neo.ClientError.Security.Forbidden"
];

function handleClientError(error: Neo4jError): void {
  switch (error.code) {
    case "Neo.ClientError.Statement.SyntaxError":
      console.error("Cypher syntax error:", error.message);
      // Log query for debugging, don't retry
      break;
      
    case "Neo.ClientError.Schema.ConstraintValidationFailed":
      console.error("Constraint violation:", error.message);
      // Handle as business logic error
      throw new Error(`Data validation failed: ${error.message}`);
      
    case "Neo.ClientError.Security.Unauthorized":
      console.error("Authentication failed:", error.message);
      // Redirect to login or refresh token
      throw new Error("Please log in again");
      
    case "Neo.ClientError.Security.Forbidden":
      console.error("Access denied:", error.message);
      // Handle as authorization error
      throw new Error("Insufficient permissions");
      
    default:
      console.error("Client error:", error.code, error.message);
      throw error;
  }
}

Database Errors:

// Database-level errors
const databaseErrors = [
  "Neo.DatabaseError.General.UnknownError",
  "Neo.DatabaseError.Schema.IndexCreationFailed",
  "Neo.DatabaseError.Transaction.TransactionStartFailed"
];

function handleDatabaseError(error: Neo4jError): void {
  console.error("Database error:", error.code, error.message);
  
  // Log for monitoring/alerting
  logErrorForMonitoring(error);
  
  // Database errors are usually not retryable by the client
  throw new Error(`Database error: ${error.message}`);
}

Comprehensive Error Handling Example

class Neo4jService {
  private driver: Driver;
  
  constructor(uri: string, auth: AuthToken) {
    this.driver = driver(uri, auth);
  }
  
  async executeQuery<T>(
    query: string, 
    parameters: Parameters = {},
    transformer: (result: Result) => T
  ): Promise<T> {
    const session = this.driver.session();
    
    try {
      const result = await this.withRetry(async () => {
        return await session.run(query, parameters);
      });
      
      return transformer(result);
    } catch (error) {
      this.handleError(error, query, parameters);
      throw error;
    } finally {
      await session.close();
    }
  }
  
  private async withRetry<T>(
    operation: () => Promise<T>,
    maxRetries: number = 3
  ): Promise<T> {
    let lastError: any;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;
        
        if (!isRetryableError(error) || attempt === maxRetries) {
          throw error;
        }
        
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
        console.log(`Retry attempt ${attempt} after ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    throw lastError;
  }
  
  private handleError(error: any, query: string, parameters: Parameters): void {
    if (!(error instanceof Neo4jError)) {
      console.error("Non-Neo4j error:", error.message);
      return;
    }
    
    // Log error details
    console.error("Neo4j Error Details:", {
      code: error.code,
      message: error.message,
      query: query.substring(0, 100) + "...",
      parameterKeys: Object.keys(parameters)
    });
    
    // Categorize and handle error
    if (error.code.startsWith("Neo.TransientError")) {
      console.log("Transient error occurred - retry logic applied");
    } else if (error.code.startsWith("Neo.ClientError")) {
      this.handleClientError(error);
    } else if (error.code.startsWith("Neo.DatabaseError")) {
      this.handleDatabaseError(error);
    }
  }
  
  private handleClientError(error: Neo4jError): void {
    // Client error handling logic
    if (error.code.includes("Security")) {
      // Handle security errors
      console.error("Security error - check authentication");
    } else if (error.code.includes("Statement")) {
      // Handle query errors
      console.error("Query error - check Cypher syntax");
    }
  }
  
  private handleDatabaseError(error: Neo4jError): void {
    // Database error handling logic
    console.error("Database error - may need administrator attention");
    
    // Could send alert to monitoring system
    this.sendAlert("Database Error", error);
  }
  
  private sendAlert(type: string, error: Neo4jError): void {
    // Implementation for alerting system
    console.log(`ALERT: ${type} - ${error.code}: ${error.message}`);
  }
  
  async close(): Promise<void> {
    await this.driver.close();
  }
}

// Usage
const neo4jService = new Neo4jService(
  "neo4j://localhost:7687",
  auth.basic("neo4j", "password")
);

try {
  const people = await neo4jService.executeQuery(
    "MATCH (p:Person) WHERE p.age > $minAge RETURN p",
    { minAge: 25 },
    (result) => result.records.map(record => record.get("p").properties)
  );
  
  console.log(`Found ${people.length} people`);
} catch (error) {
  console.error("Failed to fetch people:", error.message);
} finally {
  await neo4jService.close();
}

docs

authentication.md

driver-management.md

error-handling.md

graph-types.md

index.md

reactive-programming.md

session-operations.md

temporal-types.md

transaction-management.md

tile.json