CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-supabase--postgrest-js

Isomorphic PostgREST client for PostgreSQL database interactions with fluent API

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Structured error handling with PostgreSQL error details, custom error types, and response validation patterns.

Capabilities

PostgrestError Class

Custom error class that provides detailed information about PostgREST and PostgreSQL errors.

/**
 * Error format
 * {@link https://postgrest.org/en/stable/api.html?highlight=options#errors-and-http-status-codes}
 */
class PostgrestError extends Error {
  name: 'PostgrestError';
  details: string;
  hint: string;
  code: string;

  constructor(context: {
    message: string;
    details: string;
    hint: string;
    code: string;
  });
}

Usage Examples:

import { PostgrestClient, PostgrestError } from "@supabase/postgrest-js";

const client = new PostgrestClient("https://api.example.com");

// Handle PostgrestError specifically
const { data, error } = await client
  .from("users")
  .insert({ email: "duplicate@example.com" })
  .select();

if (error) {
  console.log("Error name:", error.name);        // "PostgrestError"
  console.log("Error message:", error.message);  // Human-readable message
  console.log("Error details:", error.details);  // Technical details
  console.log("Error hint:", error.hint);        // Suggestion for fixing
  console.log("Error code:", error.code);        // PostgreSQL error code
  
  // Handle specific error types
  if (error.code === "23505") {
    console.log("Duplicate key violation - record already exists");
  }
}

// Error in try-catch block
try {
  const { data } = await client
    .from("restricted_table")
    .select("*")
    .throwOnError(); // Throws instead of returning error
} catch (error) {
  if (error instanceof PostgrestError) {
    console.log("PostgreSQL Error:", error.code, error.message);
    console.log("Details:", error.details);
    console.log("Hint:", error.hint);
  } else {
    console.log("Other error:", error);
  }
}

Response Types

Different response types for success and failure scenarios.

/**
 * Response format
 * {@link https://github.com/supabase/supabase-js/issues/32}
 */
interface PostgrestResponseBase {
  status: number;
  statusText: string;
}

interface PostgrestResponseSuccess<T> extends PostgrestResponseBase {
  error: null;
  data: T;
  count: number | null;
}

interface PostgrestResponseFailure extends PostgrestResponseBase {
  error: PostgrestError;
  data: null;
  count: null;
}

type PostgrestSingleResponse<T> = PostgrestResponseSuccess<T> | PostgrestResponseFailure;
type PostgrestMaybeSingleResponse<T> = PostgrestSingleResponse<T | null>;
type PostgrestResponse<T> = PostgrestSingleResponse<T[]>;

Usage Examples:

// Standard response handling
const response = await client
  .from("users")
  .select("*")
  .eq("id", 123);

if (response.error) {
  // Handle error case
  console.log("Request failed:", response.error.message);
  console.log("HTTP Status:", response.status, response.statusText);
  // response.data is null, response.count is null
} else {
  // Handle success case
  console.log("Request succeeded:", response.data);
  console.log("Record count:", response.count);
  console.log("HTTP Status:", response.status, response.statusText);
  // response.error is null
}

// Type-safe response handling
function handleUserResponse(response: PostgrestResponse<User>) {
  if (response.error) {
    // TypeScript knows this is PostgrestResponseFailure
    console.error("Error code:", response.error.code);
    return;
  }
  
  // TypeScript knows this is PostgrestResponseSuccess<User[]>
  console.log("Users found:", response.data.length);
  response.data.forEach(user => {
    console.log("User:", user.name); // Fully typed
  });
}

// Single response handling
const singleResponse = await client
  .from("users")
  .select("*")
  .eq("id", 123)
  .single();

if (singleResponse.error) {
  console.log("User not found or multiple users found");
} else {
  // singleResponse.data is User, not User[]
  console.log("Found user:", singleResponse.data.name);
}

Error Mode Control

Control whether errors are thrown or returned in the response.

/**
 * If there's an error with the query, throwOnError will reject the promise by
 * throwing the error instead of returning it as part of a successful response.
 * {@link https://github.com/supabase/supabase-js/issues/92}
 */
throwOnError(): this & PostgrestBuilder<ClientOptions, Result, true>;

Usage Examples:

// Default behavior - errors returned in response
const { data, error } = await client
  .from("users")
  .select("*")
  .eq("id", 999);

if (error) {
  // Handle error manually
  console.log("Error occurred:", error.message);
}

// Throw on error - errors thrown as exceptions
try {
  const { data } = await client
    .from("users")
    .select("*")
    .eq("id", 999)
    .throwOnError(); // Will throw if error occurs
  
  // No need to check for error - if we reach here, it succeeded
  console.log("Success:", data);
} catch (error) {
  // Handle thrown error
  if (error instanceof PostgrestError) {
    console.log("Database error:", error.message);
  } else {
    console.log("Network or other error:", error);
  }
}

// Useful for promise chains
client
  .from("users")
  .insert({ name: "John Doe", email: "john@example.com" })
  .select()
  .throwOnError()
  .then(({ data }) => {
    // Only executes on success
    console.log("Created user:", data);
    return data;
  })
  .catch((error) => {
    // Only executes on error
    console.error("Failed to create user:", error.message);
  });

Common Error Patterns

Handle common PostgreSQL and PostgREST error scenarios.

Usage Examples:

// Database constraint violations
const { data, error } = await client
  .from("users")
  .insert({ email: "existing@example.com" });

if (error) {
  switch (error.code) {
    case "23505": // unique_violation
      console.log("Email already exists");
      break;
    case "23503": // foreign_key_violation
      console.log("Referenced record doesn't exist");
      break;
    case "23514": // check_violation
      console.log("Data doesn't meet constraint requirements");
      break;
    case "23502": // not_null_violation
      console.log("Required field is missing");
      break;
    default:
      console.log("Database error:", error.code, error.message);
  }
}

// Permission and access errors
const { data: restrictedData, error: accessError } = await client
  .from("admin_table")
  .select("*");

if (accessError) {
  switch (accessError.code) {
    case "42501": // insufficient_privilege
      console.log("Access denied - insufficient permissions");
      break;
    case "42P01": // undefined_table
      console.log("Table does not exist or is not accessible");
      break;
    case "42703": // undefined_column
      console.log("Column does not exist");
      break;
    default:
      console.log("Access error:", accessError.message);
  }
}

// Query syntax and validation errors
const { data: queryData, error: queryError } = await client
  .from("users")
  .select("invalid_syntax((()))");

if (queryError) {
  if (queryError.code.startsWith("42")) {
    console.log("SQL syntax or schema error:", queryError.message);
    console.log("Hint:", queryError.hint);
  }
}

Network and Connection Errors

Handle network-level and connection errors.

Usage Examples:

// Network timeout handling
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // 5 second timeout

try {
  const { data } = await client
    .from("large_table")
    .select("*")
    .abortSignal(controller.signal)
    .throwOnError();
} catch (error) {
  if (error.name === 'AbortError') {
    console.log("Request timed out");
  } else if (error instanceof PostgrestError) {
    console.log("Database error:", error.message);
  } else {
    console.log("Network error:", error.message);
  }
}

// Connection error handling
const { data, error } = await client
  .from("users")
  .select("*");

if (error) {
  // Check for network/connection errors vs database errors
  if (error.code === '' && error.message.includes('fetch')) {
    console.log("Network connection error");
    // Maybe show retry option to user
  } else if (error.code) {
    console.log("Database error:", error.code, error.message);
  } else {
    console.log("Unknown error:", error.message);
  }
}

// Retry logic for network errors
async function queryWithRetry(maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const { data, error } = await client
      .from("users")
      .select("*");
    
    if (!error) {
      return { data, error: null };
    }
    
    // Retry only on network errors, not database errors
    if (error.code === '' && attempt < maxRetries - 1) {
      console.log(`Attempt ${attempt + 1} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
      continue;
    }
    
    return { data: null, error };
  }
}

Error Context and Debugging

Extract detailed information for debugging and logging.

Usage Examples:

// Comprehensive error logging
const { data, error } = await client
  .from("complex_table")
  .select(`
    id,
    name,
    related_table (
      id,
      value
    )
  `)
  .eq("status", "active");

if (error) {
  // Log complete error context
  console.error("Query failed:", {
    message: error.message,
    details: error.details,
    hint: error.hint,
    code: error.code,
    httpStatus: error.status || 'unknown',
    timestamp: new Date().toISOString(),
    query: "complex_table with relations",
    filters: { status: "active" }
  });
  
  // Extract actionable information
  if (error.hint) {
    console.log("Suggested fix:", error.hint);
  }
  
  if (error.details) {
    console.log("Technical details:", error.details);
  }
}

// Custom error handling function
function handlePostgrestError(error: PostgrestError, context: string) {
  const errorInfo = {
    context,
    timestamp: new Date().toISOString(),
    error: {
      name: error.name,
      message: error.message,
      code: error.code,
      details: error.details,
      hint: error.hint
    }
  };
  
  // Log to external service
  console.error("PostgREST Error:", JSON.stringify(errorInfo, null, 2));
  
  // Return user-friendly message
  if (error.code === "23505") {
    return "This record already exists. Please use different values.";
  } else if (error.code.startsWith("23")) {
    return "Data validation failed. Please check your input.";
  } else if (error.code.startsWith("42")) {
    return "There was a problem with the request. Please try again.";
  } else {
    return "An unexpected error occurred. Please try again later.";
  }
}

// Usage with custom handler
const { data, error } = await client
  .from("users")
  .insert({ email: "test@example.com" })
  .select();

if (error) {
  const userMessage = handlePostgrestError(error, "user_creation");
  // Show userMessage to user
  console.log("User message:", userMessage);
}

Type-Safe Error Handling

Leverage TypeScript for better error handling patterns.

Usage Examples:

// Type guard for PostgrestError
function isPostgrestError(error: any): error is PostgrestError {
  return error instanceof PostgrestError;
}

// Generic error handler with type safety
async function safeQuery<T>(
  queryFn: () => Promise<PostgrestResponse<T>>
): Promise<{ data: T[] | null; error: string | null }> {
  try {
    const response = await queryFn();
    
    if (response.error) {
      if (isPostgrestError(response.error)) {
        return {
          data: null,
          error: `Database error: ${response.error.message}`
        };
      } else {
        return {
          data: null,
          error: "An unexpected error occurred"
        };
      }
    }
    
    return {
      data: response.data,
      error: null
    };
  } catch (error) {
    if (isPostgrestError(error)) {
      return {
        data: null,
        error: `Query failed: ${error.message}`
      };
    } else {
      return {
        data: null,
        error: "Network or system error occurred"
      };
    }
  }
}

// Usage with type safety
const result = await safeQuery(() => 
  client.from("users").select("*").eq("active", true)
);

if (result.error) {
  console.log("Error:", result.error);
} else {
  // result.data is type-safe User[] | null
  console.log("Users:", result.data?.length);
}

Error Recovery Strategies

Implement strategies for recovering from different types of errors.

Usage Examples:

// Fallback query on error
async function getUserWithFallback(userId: number) {
  // Try primary query
  const { data, error } = await client
    .from("users_view")
    .select("*")
    .eq("id", userId)
    .single();
  
  if (!error) {
    return { data, error: null };
  }
  
  // If view fails, try base table
  if (error.code === "42P01") { // undefined_table
    console.log("View not available, trying base table");
    return await client
      .from("users")
      .select("*")
      .eq("id", userId)
      .single();
  }
  
  return { data: null, error };
}

// Progressive degradation
async function getDataWithDegradation() {
  // Try full featured query first
  let { data, error } = await client
    .from("enhanced_users")
    .select(`
      *,
      profiles (*),
      settings (*)
    `);
  
  if (!error) {
    return { data, level: "full" };
  }
  
  // Fallback to basic query
  console.log("Enhanced query failed, trying basic query");
  ({ data, error } = await client
    .from("users")
    .select("id, name, email"));
  
  if (!error) {
    return { data, level: "basic" };
  }
  
  return { data: null, level: "none", error };
}

// Automatic retry with exponential backoff
async function queryWithExponentialBackoff<T>(
  queryFn: () => Promise<PostgrestResponse<T>>,
  maxRetries = 3
): Promise<PostgrestResponse<T>> {
  let lastError: PostgrestError | null = null;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await queryFn();
    
    if (!response.error) {
      return response;
    }
    
    lastError = response.error;
    
    // Only retry on network errors, not database constraint errors
    if (response.error.code && !response.error.code.startsWith('08')) {
      // Don't retry database logic errors
      break;
    }
    
    if (attempt < maxRetries - 1) {
      const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
      console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  return { data: null, error: lastError, count: null, status: 0, statusText: '' };
}

Install with Tessl CLI

npx tessl i tessl/npm-supabase--postgrest-js

docs

client-configuration.md

error-handling.md

filtering-conditions.md

index.md

query-operations.md

result-transformation.md

stored-procedures.md

tile.json