CarboAny / src /lib /apiAdapter.ts
Esketch's picture
deploy: [P4] Strangler Pattern API Adapter Release (Orphan Clean Build v2)
daaf9d7
Raw
History Blame Contribute Delete
4.35 kB
import { supabase } from './supabase'
// --- Strangler Pattern Configuration Swiches ---
const USE_GO_GATEWAY: Record<string, boolean> = {
'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<TReq, TRes>(
functionName: string,
payload: TReq
): Promise<TRes> {
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
}
}