Source:
ocean/docs/PATTERN_QUICK_REFERENCE.md| ✏️ Edit on GitHub
Ocean Platform Pattern Quick Reference
🚀 Edge Functions
❌ NEVER DO THIS
Deno.serve(async (req) => {
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
// ... manual CORS setup
}
// ... manual auth
// ... manual error handling
})
✅ ALWAYS DO THIS
import { createEdgeFunction } from '../_shared/function-wrapper.ts'
export default createEdgeFunction(async (req, context) => {
// You get for free:
// - CORS handling
// - Authentication (context.user, context.organization)
// - Error handling
// - Performance tracking
// - Request ID tracing
const { user, organization, supabase } = context
// Your business logic here
return new Response(JSON.stringify({ success: true }))
})
🔄 For Stripe Functions
import { createStripeFunction } from '../_shared/function-wrapper.ts'
export default createStripeFunction(async (req, context) => {
// Same as above PLUS context.stripe
const { stripe, user, organization } = context
const customer = await stripe.customers.create({
email: user.email,
metadata: { organization_id: organization.id },
})
return new Response(JSON.stringify({ customer }))
})
🎣 React Hooks
❌ NEVER DO THIS
const mutation = useMutation({
mutationFn: async (data) => {
/* ... */
},
onSuccess: () => {
toast.success('Success!')
queryClient.invalidateQueries({ queryKey: ['data'] })
},
onError: (error) => {
toast.error(error.message)
},
})
✅ ALWAYS DO THIS
import { useMutationWithToast } from '@/hooks/shared/use-mutation-with-toast'
const mutation = useMutationWithToast({
mutationFn: async (data) => {
/* ... */
},
successMessage: 'Successfully updated!',
errorMessage: 'Failed to update',
invalidateQueries: [['organizations'], ['billing']],
})
📊 For Complex Success Messages
const mutation = useMutationWithToast({
mutationFn: async (data) => {
/* ... */
},
successMessage: (result, variables) => `Successfully updated ${result.name}!`,
invalidateQueries: [['organizations']],
})
🗃️ Database & GraphQL
✅ Field Naming
# Always use snake_case in GraphQL schema
type Organization {
id: ID!
organization_id: String! # ✅ snake_case
stripe_customer_id: String # ✅ snake_case
created_at: String! # ✅ snake_case
# organizationId: String # ❌ NEVER camelCase
}
🔄 After Database Changes
# 1. Create migration
supabase migration new add_user_preferences
# 2. Reset local database
supabase db reset
# 3. Regenerate ALL types
pnpm codegen:all
# 4. If you added tables/columns, also run:
pnpm codegen:schema
📏 File Size Rules
✅ Good Structure
/components/billing/
├── BillingDashboard.tsx # 150 lines - Container
├── BillingDashboard.test.tsx # 200 lines - Tests
├── BillingMetrics.tsx # 100 lines - Sub-component
├── BillingChart.tsx # 120 lines - Sub-component
├── use-billing-data.ts # 80 lines - Hook
└── billing.types.ts # 50 lines - Types
❌ Bad Structure
/components/billing/
└── BillingDashboard.tsx # 800 lines - EVERYTHING
🔧 Common Patterns
API Error Handling
import { ApiError } from '@/lib/errors'
// Throw ApiError for proper toast handling
if (!data) {
throw new ApiError('No data found', 404)
}
Loading States
// Use skeleton components, not spinners
if (isLoading) {
return <BillingDashboardSkeleton />
}
Constants
// Never hardcode, always use constants
const QUERY_STALE_TIME = 5 * 60 * 1000 // 5 minutes
const MAX_RETRY_ATTEMPTS = 3
const DEFAULT_PAGE_SIZE = 20
🎯 Quick Checks
Before committing, ask yourself:
- Edge Function? → Using wrapper pattern?
- React Mutation? → Using
useMutationWithToast? - New File? → Under 400 lines?
- Database Change? → Ran
codegen:all? - API Response? → Using snake_case?
- Error Handling? → Using
ApiError? - Loading State? → Using skeleton?
- Magic Values? → Moved to constants?
🚨 If You See These, Stop and Refactor
Deno.serve(→ UsecreateEdgeFunctionuseMutation({→ UseuseMutationWithToast- File > 400 lines → Split it up
console.log→ Use proper logginganytype → Define proper types- Hardcoded strings → Extract constants
organizationId→ Useorganization_id- Manual CORS → Import from utils
📚 Where to Find Examples
- Edge Functions:
/supabase/functions/stripe-billing/index.ts - React Hooks:
/src/hooks/billing/use-subscriptions.ts - File Structure:
/src/components/billing/ - Types:
/src/types/database.generated.ts - Constants:
/src/lib/constants.ts
Remember: Patterns exist to make our lives easier. If you're writing boilerplate, you're probably missing a pattern!