mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 10:11:13 -05:00
Implements complete plan to resolve duplicate span_id issues and metric collection errors: - Ensure edge handlers return proper Response objects to prevent double logging - Update collect-metrics to use valid metric categories, fix system_alerts query, and adjust returns - Apply detect-anomalies adjustments if needed and add defensive handling in wrapper - Prepare ground for end-to-end verification of location-related fixes
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:
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
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
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:
parkridemanufactureroperatorproperty_ownerdesignercompany(consolidated type)ride_modelphotomilestonetimeline_event
Type Guards
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)
{
"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)
{
"error": "Missing Authorization header",
"requestId": "abc-123"
}
Manual Error Formatting
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
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:
passwordtokensecretapi_keyauthorizationemailphonessncredit_cardip_addresssession_id
Distributed Tracing
Using Spans
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)
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)
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
- Consistent Error Handling: All errors are formatted consistently
- Better Debugging: Request IDs and trace IDs link errors across services
- Type Safety: Catch type mismatches early with clear error messages
- Security: Automatic redaction of sensitive data in logs
- Performance Monitoring: Built-in timing and span tracking
- 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