Claude-skill-registry edge-functions

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/edge-functions" ~/.claude/skills/majiayu000-claude-skill-registry-edge-functions && rm -rf "$T"
manifest: skills/data/edge-functions/SKILL.md
source content

Edge Functions

Deno edge function patterns for external integrations.

Announce: "I'm using edge-functions to implement edge function correctly."

Function Structure

Standard edge function pattern:

// supabase/functions/my-function/index.ts
import { createClient } from '@supabase/supabase-js'
import { MyRequestSchema } from '../types/schemas.ts'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

Deno.serve(async (request: Request) => {
  // Handle CORS preflight
  if (request.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    // Validate method
    if (request.method !== 'POST') {
      return new Response(
        JSON.stringify({ error: 'Method not allowed' }),
        { status: 405, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
      )
    }

    // Parse and validate request
    const body = await request.json()
    const validated = MyRequestSchema.parse(body)

    // Get auth header for Supabase client
    const authHeader = request.headers.get('Authorization')
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL')!,
      Deno.env.get('SUPABASE_ANON_KEY')!,
      { global: { headers: { Authorization: authHeader ?? '' } } }
    )

    // Process request
    const result = await processRequest(validated, supabase)

    return new Response(
      JSON.stringify(result),
      { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
    )

  } catch (error) {
    console.error('Error:', error)
    return new Response(
      JSON.stringify({ 
        error: error instanceof Error ? error.message : 'Unknown error' 
      }),
      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
    )
  }
})

Request Validation with Zod

// supabase/functions/types/schemas.ts
import { z } from 'zod'

export const GenerateEmbeddingRequest = z.object({
  text: z.string().min(1).max(10000),
  inputType: z.enum(['query', 'passage']).default('query')
})
export type GenerateEmbeddingRequestType = z.infer<typeof GenerateEmbeddingRequest>

export const CallLLMRequest = z.object({
  prompt: z.string().min(1),
  systemPrompt: z.string().optional(),
  model: z.string().default('gpt-4o-mini'),
  temperature: z.number().min(0).max(2).default(0.7),
  maxTokens: z.number().optional(),
  jsonSchema: z.record(z.any()).optional()
})
export type CallLLMRequestType = z.infer<typeof CallLLMRequest>

LLM Calling Pattern

// supabase/functions/call-llm/index.ts
import OpenAI from 'openai'

const openai = new OpenAI({
  baseURL: 'https://openrouter.ai/api/v1',
  apiKey: Deno.env.get('OPENROUTER_API_KEY')
})

async function callLLM(request: CallLLMRequestType): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = []
  
  if (request.systemPrompt) {
    messages.push({ role: 'system', content: request.systemPrompt })
  }
  messages.push({ role: 'user', content: request.prompt })

  const completion = await openai.chat.completions.create({
    model: request.model,
    messages,
    temperature: request.temperature,
    max_tokens: request.maxTokens,
    response_format: request.jsonSchema 
      ? { type: 'json_schema', json_schema: { name: 'response', schema: request.jsonSchema } }
      : undefined
  })

  return completion.choices[0]?.message?.content ?? ''
}

Embedding Generation Pattern

// supabase/functions/generate-embedding/index.ts
async function generateEmbedding(
  text: string, 
  inputType: 'query' | 'passage'
): Promise<number[]> {
  const response = await fetch(
    'https://api-inference.huggingface.co/models/thenlper/gte-small',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${Deno.env.get('HUGGINGFACE_API_KEY')}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        inputs: text,
        options: { wait_for_model: true }
      })
    }
  )

  if (!response.ok) {
    throw new Error(`Embedding API error: ${response.status}`)
  }

  const embedding = await response.json()
  
  // Validate dimensions
  if (!Array.isArray(embedding) || embedding.length !== 384) {
    throw new Error(`Invalid embedding dimensions: ${embedding?.length}`)
  }

  return embedding
}

Database Callback Pattern

Edge functions can call back to database:

// After processing, update database
async function notifyDatabase(supabase: SupabaseClient, result: any) {
  const { error } = await supabase.rpc('process_llm_result', {
    p_result: result
  })
  if (error) throw error
}

Function Whitelist Security

Only allow specific database functions to be called:

const ALLOWED_FUNCTIONS = ['update_place_traits', 'process_embedding'] as const
type AllowedFunction = typeof ALLOWED_FUNCTIONS[number]

function validateFunctionName(name: string): name is AllowedFunction {
  return ALLOWED_FUNCTIONS.includes(name as AllowedFunction)
}

// In handler
if (!validateFunctionName(request.function_name)) {
  return new Response(
    JSON.stringify({ error: 'Function not allowed' }),
    { status: 403, headers: corsHeaders }
  )
}

Environment Variables

Required env vars (set in Supabase dashboard):

SUPABASE_URL          # Automatic
SUPABASE_ANON_KEY     # Automatic
OPENROUTER_API_KEY    # For LLM calls
HUGGINGFACE_API_KEY   # For embeddings

Anti-Patterns

DON'T: Check API Key at Module Level

// WRONG: Crashes at cold start
const apiKey = Deno.env.get('API_KEY')!  // Throws if missing

// CORRECT: Check inside handler
Deno.serve(async (req) => {
  const apiKey = Deno.env.get('API_KEY')
  if (!apiKey) {
    return new Response(JSON.stringify({ error: 'API key not configured' }), { status: 500 })
  }
})

DON'T: Forget CORS

// WRONG: No CORS headers
return new Response(JSON.stringify(result))

// CORRECT: Always include CORS headers
return new Response(JSON.stringify(result), { 
  headers: { ...corsHeaders, 'Content-Type': 'application/json' }
})

DON'T: Expose Internal Errors

// WRONG: Leaks internal details
return new Response(JSON.stringify({ error: error.stack }))

// CORRECT: Generic error message
return new Response(JSON.stringify({ 
  error: error instanceof Error ? error.message : 'Internal error'
}))

Testing Locally

# Start Supabase with edge functions
supabase start

# Test function
curl -X POST http://localhost:54321/functions/v1/my-function \
  -H "Authorization: Bearer $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "test"}'

References

See

references/function-examples.md
for more patterns.