Savvi Studio

Error Handling Examples

These examples demonstrate proper error handling patterns with database operations.

Referenced by: best-practices.md, patterns.md

Example 1: Basic Error Handling

import { withClient } from '@/lib/db';
import { createResource } from '@db/graph';
import { ZodError } from 'zod';

try {
    const id = await withClient(async (client) => {
        return await createResource(client, {
            p_type_namespace: userInput,
            p_external_id: externalId,
            p_data: untrustedData
        });
    });
} catch (error) {
    if (error instanceof ZodError) {
        // Validation error
        console.error('Validation error:', error.errors);
        throw new ValidationError('Invalid input', error.errors);
    } else if (error.code === '23505') {
        // PostgreSQL unique violation
        console.error('Duplicate entry');
        throw new DuplicateError('Resource already exists');
    } else {
        // Other database error
        console.error('Database error:', error);
        throw new DatabaseError('Operation failed');
    }
}

Example 2: Handling Duplicate External ID

import { test } from '@/test-utils-integration/config/database.context';
import { createResource } from '@db/graph';

test('handles duplicate external_id', async ({ newDbClient }) => {
    // Create first resource
    await createResource(newDbClient, {
        p_type_namespace: 'test.type',
        p_external_id: 'duplicate'
    });
    
    // Attempt to create duplicate
    await expect(
        createResource(newDbClient, {
            p_type_namespace: 'test.type',
            p_external_id: 'duplicate'
        })
    ).rejects.toThrow(/already exists/);
});

Example 3: PostgreSQL Error Codes

import { withClient } from '@/lib/db';

try {
    await withClient(async (client) => {
        return await client.query('INSERT INTO ...');
    });
} catch (error) {
    switch (error.code) {
        case '23505':
            // unique_violation
            throw new Error('Duplicate entry');
        case '23503':
            // foreign_key_violation
            throw new Error('Referenced record does not exist');
        case '23502':
            // not_null_violation
            throw new Error('Required field missing');
        case '40001':
            // serialization_failure
            throw new Error('Transaction conflict, retry');
        default:
            throw new Error(`Database error: ${error.message}`);
    }
}

Example 4: Validation Error Details

import { createResource } from '@db/graph';
import { ZodError } from 'zod';

try {
    await createResource(client, {
        p_type_namespace: 'invalid type',
        p_data: 'not an object'
    });
} catch (error) {
    if (error instanceof ZodError) {
        // Get detailed validation errors
        const errors = error.errors.map(err => ({
            path: err.path.join('.'),
            message: err.message,
            code: err.code
        }));
        
        console.error('Validation failed:', errors);
        // [
        //   { path: 'p_type_namespace', message: 'Invalid format', code: 'custom' },
        //   { path: 'p_data', message: 'Expected object', code: 'invalid_type' }
        // ]
    }
}

Example 5: Graceful Degradation

import { withClient } from '@/lib/db';
import { createResource } from '@db/graph';

async function createResourceWithFallback(data: ResourceData) {
    try {
        return await withClient(async (client) => {
            return await createResource(client, {
                p_type_namespace: data.type,
                p_external_id: data.id,
                p_data: data.attributes
            });
        });
    } catch (error) {
        if (error.code === '23505') {
            // Already exists, fetch existing
            console.warn('Resource exists, fetching existing');
            return await withClient(async (client) => {
                const resources = await getResource(client, {
                    p_type_namespace: data.type,
                    p_external_id: data.id
                });
                return resources[0].id;
            });
        }
        throw error;
    }
}