Skip to main content

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:

  1. Edge Function? → Using wrapper pattern?
  2. React Mutation? → Using useMutationWithToast?
  3. New File? → Under 400 lines?
  4. Database Change? → Ran codegen:all?
  5. API Response? → Using snake_case?
  6. Error Handling? → Using ApiError?
  7. Loading State? → Using skeleton?
  8. Magic Values? → Moved to constants?

🚨 If You See These, Stop and Refactor

  1. Deno.serve( → Use createEdgeFunction
  2. useMutation({ → Use useMutationWithToast
  3. File > 400 lines → Split it up
  4. console.log → Use proper logging
  5. any type → Define proper types
  6. Hardcoded strings → Extract constants
  7. organizationId → Use organization_id
  8. 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!