# Edge Function Shared Utilities Comprehensive error handling, logging, and type validation utilities for Supabase Edge Functions. ## Quick Start ### Using the Edge Function Wrapper (Recommended) The easiest way to create a new edge function with full error handling: ```typescript import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { corsHeaders } from '../_shared/cors.ts'; import { validateUUID, validateString } from '../_shared/typeValidation.ts'; serve(createEdgeFunction({ name: 'my-function', requireAuth: true, corsHeaders, }, async (req, { requestId, span, userId }) => { // Parse and validate request const body = await req.json(); validateUUID(body.id, 'id', { requestId }); validateString(body.name, 'name', { requestId }); // Your business logic here const result = await processRequest(body); return new Response( JSON.stringify({ success: true, data: result }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); })); ``` This automatically provides: - ✅ CORS handling - ✅ Authentication validation - ✅ Request/response logging - ✅ Distributed tracing - ✅ Comprehensive error handling - ✅ Performance monitoring ## Type Validation ### Basic Validation Functions ```typescript import { validateRequired, validateString, validateUUID, validateArray, validateUUIDArray, validateEntityType, validateActionType, validateObject, } from '../_shared/typeValidation.ts'; // Validate required field validateRequired(value, 'fieldName', { requestId }); // Validate string validateString(value, 'name', { requestId }); // Validate UUID validateUUID(value, 'userId', { requestId }); // Validate array with minimum length validateArray(value, 'itemIds', 1, { requestId }); // Validate array of UUIDs validateUUIDArray(value, 'submissionIds', 1, { requestId }); // Validate entity type (park, ride, company, etc.) validateEntityType(value, 'item_type', { requestId }); // Validate action type (create, edit, delete) validateActionType(value, 'action_type', { requestId }); ``` ### Submission Item Validation ```typescript import { validateSubmissionItem } from '../_shared/typeValidation.ts'; const item = validateSubmissionItem(rawData, { requestId }); // Returns: { id: string, item_type: ValidEntityType, action_type: ValidActionType } ``` ### Valid Entity Types The following entity types are recognized by the system: - `park` - `ride` - `manufacturer` - `operator` - `property_owner` - `designer` - `company` (consolidated type) - `ride_model` - `photo` - `milestone` - `timeline_event` ### Type Guards ```typescript import { isString, isUUID, isArray, isObject, isValidEntityType, isValidActionType, } from '../_shared/typeValidation.ts'; if (isString(value)) { // TypeScript now knows value is a string } if (isValidEntityType(type)) { // TypeScript knows type is ValidEntityType } ``` ## Error Handling ### Automatic Error Categorization The edge function wrapper automatically handles: #### Validation Errors (400 Bad Request) ```json { "error": "Invalid entity type: operator_invalid", "field": "item_type", "expected": "one of: park, ride, manufacturer, ...", "received": "operator_invalid", "requestId": "abc-123" } ``` #### Database Errors - **23505** → 409 Conflict (unique constraint violation) - **23503** → 400 Bad Request (foreign key violation) - **23514** → 400 Bad Request (check constraint violation) - **P0001** → 400 Bad Request (raised exception) - **42501** → 403 Forbidden (insufficient privilege) #### Authentication Errors (401 Unauthorized) ```json { "error": "Missing Authorization header", "requestId": "abc-123" } ``` ### Manual Error Formatting ```typescript import { formatEdgeError, toError } from '../_shared/errorFormatter.ts'; try { // Some operation } catch (error) { // Get human-readable error message const message = formatEdgeError(error); // Convert to Error instance const err = toError(error); throw err; } ``` ## Logging ### Structured Logging ```typescript import { edgeLogger } from '../_shared/logger.ts'; edgeLogger.info('Processing submission', { requestId, submissionId, itemCount, }); edgeLogger.warn('Slow query detected', { requestId, queryTime: 1500, query: 'fetch_submissions', }); edgeLogger.error('Failed to process item', { requestId, error: formatEdgeError(error), itemId, }); ``` ### Sensitive Data Protection The logger automatically redacts sensitive fields: - `password` - `token` - `secret` - `api_key` - `authorization` - `email` - `phone` - `ssn` - `credit_card` - `ip_address` - `session_id` ## Distributed Tracing ### Using Spans ```typescript import { startSpan, endSpan, addSpanEvent, setSpanAttributes, logSpan, } from '../_shared/logger.ts'; // Create a child span for a database operation const dbSpan = startSpan( 'fetch_submissions', 'DATABASE', getSpanContext(parentSpan), { 'db.operation': 'select', 'db.table': 'content_submissions', } ); try { addSpanEvent(dbSpan, 'query_start'); const result = await supabase .from('content_submissions') .select('*'); addSpanEvent(dbSpan, 'query_complete', { rowCount: result.data?.length, }); endSpan(dbSpan, 'ok'); } catch (error) { addSpanEvent(dbSpan, 'query_failed', { error: formatEdgeError(error), }); endSpan(dbSpan, 'error', error); throw error; } finally { logSpan(dbSpan); } ``` ## Migration Guide ### Before (Manual Error Handling) ```typescript Deno.serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { status: 204, headers: corsHeaders }); } try { const authHeader = req.headers.get('Authorization'); if (!authHeader) { return new Response( JSON.stringify({ error: 'Unauthorized' }), { status: 401 } ); } const body = await req.json(); // Business logic... return new Response( JSON.stringify({ success: true }), { status: 200 } ); } catch (error) { console.error('Error:', error); return new Response( JSON.stringify({ error: String(error) }), { status: 500 } ); } }); ``` ### After (Using Wrapper) ```typescript import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { validateString } from '../_shared/typeValidation.ts'; serve(createEdgeFunction({ name: 'my-function', requireAuth: true, corsHeaders, }, async (req, { requestId, userId }) => { const body = await req.json(); validateString(body.name, 'name', { requestId }); // Business logic... return new Response( JSON.stringify({ success: true }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); })); ``` ## Benefits 1. **Consistent Error Handling**: All errors are formatted consistently 2. **Better Debugging**: Request IDs and trace IDs link errors across services 3. **Type Safety**: Catch type mismatches early with clear error messages 4. **Security**: Automatic redaction of sensitive data in logs 5. **Performance Monitoring**: Built-in timing and span tracking 6. **Maintainability**: Less boilerplate code in each function ## Example: Moderation Approval Function See `process-selective-approval/index.ts` for a complete example of using these utilities in a production edge function. Key features demonstrated: - Type validation for submission items - Entity type checking - Database error handling - Distributed tracing across RPC calls - Performance monitoring