import { supabase } from './supabase' // --- Strangler Pattern Configuration Swiches --- const USE_GO_GATEWAY: Record = { 'verify-receipt': false, 'nature-letter': false, 'embed-text': false, 'explain-methodology': false, 'mock-vcs': false, } let GO_GATEWAY_URL = 'http://localhost:8080' export function setUseGoGateway(functionName: string, use: boolean): void { if (functionName in USE_GO_GATEWAY) { USE_GO_GATEWAY[functionName] = use } } export function setGoGatewayUrl(url: string): void { GO_GATEWAY_URL = url } // --- Data Contract Verification Guard (Contract Assertions) --- /** * Go Gateway로 이주하거나 Deno 함수 통신 시 데이터 스키마 깨짐(Contract Broken)으로 인한 * 프론트엔드 폭사를 막는 런타임 타입 검증 가드 */ function assertDataContract(functionName: string, data: any): void { if (!data) { throw new Error(`[apiAdapter] API 계약 검증 실패: ${functionName} 응답이 비어있습니다.`) } switch (functionName) { case 'verify-receipt': if (data.success === undefined) { throw new Error(`[apiAdapter] API 계약 검증 실패: verify-receipt 응답에 필수 필드(success)가 누락되었습니다.`) } if (data.co2Reduction === undefined && data.co2_reduction === undefined) { throw new Error(`[apiAdapter] API 계약 검증 실패: verify-receipt 응답에 필수 필드(co2Reduction)가 누락되었습니다.`) } break; case 'nature-letter': if (!data.body) { throw new Error(`[apiAdapter] API 계약 검증 실패: nature-letter 응답에 필수 필드(body)가 누락되었습니다.`) } break; case 'embed-text': if (!Array.isArray(data.embedding)) { throw new Error(`[apiAdapter] API 계약 검증 실패: embed-text 응답에 필수 필드(embedding)가 누락되었습니다.`) } break; case 'explain-methodology': if (data.eligibilityScore === undefined && data.eligibility_score === undefined) { throw new Error(`[apiAdapter] API 계약 검증 실패: explain-methodology 응답에 필수 필드(eligibilityScore)가 누락되었습니다.`) } if (!data.explanation) { throw new Error(`[apiAdapter] API 계약 검증 실패: explain-methodology 응답에 필수 필드(explanation)가 누락되었습니다.`) } break; case 'mock-vcs': if (data.success === undefined) { throw new Error(`[apiAdapter] API 계약 검증 실패: mock-vcs 응답에 필수 필드(success)가 누락되었습니다.`) } if (!data.externalId && !data.external_id) { throw new Error(`[apiAdapter] API 계약 검증 실패: mock-vcs 응답에 필수 필드(externalId)가 누락되었습니다.`) } break; default: // Unrecognized function, bypass contract validation break; } } // --- Unified API Adapter Caller --- /** * 5대 Supabase Deno Edge Functions 호출을 단일 추상화하여 호출하고 * Go API 게이트웨이 완공 시 라우팅을 점진적으로 안전 우회(Strangler Pattern)시킵니다. */ export async function invokeEdgeFunction( functionName: string, payload: TReq ): Promise { const useGo = USE_GO_GATEWAY[functionName] || false if (useGo) { // 1. Go API Gateway로 우회 (fetch API) const targetUrl = `${GO_GATEWAY_URL}/api/v1/${functionName}` const response = await fetch(targetUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }) if (!response.ok) { const errText = await response.text() throw new Error(`[apiAdapter] Go Gateway API 호출 실패 (${response.status}): ${errText}`) } const resData = await response.json() // 런타임 데이터 계약 검증 assertDataContract(functionName, resData) return resData as TRes } else { // 2. 기존 Supabase Deno Edge Function 호출 const { data, error } = await supabase.functions.invoke(functionName, { body: payload as any, }) if (error) { throw new Error(error.message || String(error)) } // 런타임 데이터 계약 검증 assertDataContract(functionName, data) return data as TRes } }