Download src/lib/apiAdapter.ts from Esketch/CarboAny: direct link, hf CLI and curl.
- Browser
- Download file 5.99 kB
-
https://huggingface.co/spaces/Esketch/CarboAny/resolve/main/src/lib/apiAdapter.ts
- Command line
-
hf download hf://spaces/Esketch/CarboAny/src/lib/apiAdapter.ts
-
curl -L -o apiAdapter.ts https://huggingface.co/spaces/Esketch/CarboAny/resolve/main/src/lib/apiAdapter.ts
5.99 kB
| import { supabase } from './supabase' | |
| // --- Strangler Pattern Configuration Switches --- | |
| 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' | |
| const GO_GATEWAY_ENDPOINTS: Record<string, (payload: unknown) => string> = { | |
| 'verify-receipt': (payload) => { | |
| const submissionId = getPayloadString(payload, 'submissionId') ?? getPayloadString(payload, 'id') | |
| return submissionId ? `/submissions/${encodeURIComponent(submissionId)}/verify` : '/verify-receipt' | |
| }, | |
| 'nature-letter': () => '/nature/letter', | |
| 'embed-text': () => '/methodologies/reseed', | |
| 'explain-methodology': () => '/methodologies/match', | |
| 'mock-vcs': () => '/registries/vcs/submit', | |
| } | |
| 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 | |
| } | |
| function getPayloadString(payload: unknown, key: string): string | undefined { | |
| if (!payload || typeof payload !== 'object') return undefined | |
| const value = (payload as Record<string, unknown>)[key] | |
| return typeof value === 'string' && value.trim().length > 0 ? value : undefined | |
| } | |
| function isEnvelopeResponse(value: unknown): value is { data: unknown; error?: unknown } { | |
| return ( | |
| !!value && | |
| typeof value === 'object' && | |
| 'data' in value && | |
| 'meta' in value && | |
| 'error' in value | |
| ) | |
| } | |
| function unwrapEnvelope(functionName: string, response: unknown): unknown { | |
| if (!isEnvelopeResponse(response)) return response | |
| if (response.error) { | |
| throw new Error(`[apiAdapter] ${functionName} Gateway μλ΅ μ€λ₯: ${String(response.error)}`) | |
| } | |
| return response.data | |
| } | |
| // --- Data Contract Verification Guard (Contract Assertions) --- | |
| /** | |
| * Go Gatewayλ‘ μ΄μ£Όνκ±°λ Deno ν¨μ ν΅μ μ λ°μ΄ν° μ€ν€λ§ κΉ¨μ§(Contract Broken)μΌλ‘ μΈν | |
| * νλ‘ νΈμλ νμ¬λ₯Ό λ§λ λ°νμ νμ κ²μ¦ κ°λ | |
| */ | |
| function assertDataContract(functionName: string, data: unknown): void { | |
| if (!data) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: ${functionName} μλ΅μ΄ λΉμ΄μμ΅λλ€.`) | |
| } | |
| const record = data as Record<string, unknown> | |
| switch (functionName) { | |
| case 'verify-receipt': | |
| if (record.success === undefined && record.status === undefined) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: verify-receipt μλ΅μ νμ νλ(success)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| if (record.co2Reduction === undefined && record.co2_reduction === undefined) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: verify-receipt μλ΅μ νμ νλ(co2Reduction)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| break; | |
| case 'nature-letter': | |
| if (!record.body) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: nature-letter μλ΅μ νμ νλ(body)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| break; | |
| case 'embed-text': | |
| if (!Array.isArray(record.embedding)) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: embed-text μλ΅μ νμ νλ(embedding)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| break; | |
| case 'explain-methodology': | |
| if (record.eligibilityScore === undefined && record.eligibility_score === undefined) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: explain-methodology μλ΅μ νμ νλ(eligibilityScore)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| if (!record.explanation) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: explain-methodology μλ΅μ νμ νλ(explanation)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| break; | |
| case 'mock-vcs': | |
| if (record.success === undefined) { | |
| throw new Error(`[apiAdapter] API κ³μ½ κ²μ¦ μ€ν¨: mock-vcs μλ΅μ νμ νλ(success)κ° λλ½λμμ΅λλ€.`) | |
| } | |
| if (!record.externalId && !record.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 endpoint = GO_GATEWAY_ENDPOINTS[functionName]?.(payload) ?? `/${functionName}` | |
| const targetUrl = `${GO_GATEWAY_URL}/api/v1${endpoint}` | |
| 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 rawData = await response.json() | |
| const resData = unwrapEnvelope(functionName, rawData) | |
| // λ°νμ λ°μ΄ν° κ³μ½ κ²μ¦ | |
| assertDataContract(functionName, resData) | |
| return resData as TRes | |
| } else { | |
| // 2. κΈ°μ‘΄ Supabase Deno Edge Function νΈμΆ | |
| const { data, error } = await supabase.functions.invoke(functionName, { | |
| body: payload as Record<string, unknown>, | |
| }) | |
| if (error) { | |
| throw new Error(error.message || String(error)) | |
| } | |
| // λ°νμ λ°μ΄ν° κ³μ½ κ²μ¦ | |
| const resData = unwrapEnvelope(functionName, data) | |
| assertDataContract(functionName, resData) | |
| return resData as TRes | |
| } | |
| } | |