frontend / src /supabase.ts
delqhi's picture
Deploy frontend agent
5112d5b verified
Raw
History Blame Contribute Delete
9.71 kB
import { createClient, SupabaseClient } from '@supabase/supabase-js';
type EnvResolution = {
value: string | null;
envName: string | null;
candidates: string[];
};
type DbConnectionMode = 'dedicated' | 'shared_fallback' | 'inactive';
type ResolvedDbConnection = {
url: string | null;
key: string | null;
urlEnv: string | null;
keyEnv: string | null;
urlCandidates: string[];
keyCandidates: string[];
mode: DbConnectionMode;
};
type RedactedDbConnection = Omit<ResolvedDbConnection, 'url' | 'key'> & {
urlPresent: boolean;
keyPresent: boolean;
};
type ResolvedGlobalDbConnection = Omit<ResolvedDbConnection, 'mode'> & { mode: 'shared' | 'inactive' };
type RedactedGlobalDbConnection = Omit<RedactedDbConnection, 'mode'> & { mode: 'shared' | 'inactive' };
export type DualDbRoutingStatus = {
global: RedactedGlobalDbConnection;
dedicated: RedactedDbConnection;
memoryPlane: {
schema: 'memory_plane';
table: 'items';
};
docsSyncQueue: {
table: 'docs_sync_queue';
};
};
export type MemoryPlaneInsertInput = {
content: string;
summary?: string;
metadata?: Record<string, unknown>;
memoryType?: 'semantic' | 'episodic';
provenanceType?: string;
sourceRunId?: string;
confidence?: number;
};
export type DocsSyncQueueInsertInput = {
targetAgent: string;
docType: 'connector' | 'framework' | 'repo_internal';
docSourceUrl: string;
metadata?: Record<string, unknown>;
requestedBy?: string;
};
const GLOBAL_URL_ENV_CANDIDATES = ['SIN_SUPABASE_URL', 'SUPABASE_URL'];
const GLOBAL_KEY_ENV_CANDIDATES = ['SIN_SUPABASE_SERVICE_ROLE_KEY', 'SUPABASE_SERVICE_ROLE_KEY'];
const DEDICATED_URL_ENV_CANDIDATES = [
'SUPABASE_FRONTEND_URL',
'SUPABASE_CODER_URL',
'SIN_FRONTEND_DEDICATED_SUPABASE_URL',
'SIN_FRONTEND_SUPABASE_URL',
];
const DEDICATED_KEY_ENV_CANDIDATES = [
'SUPABASE_FRONTEND_KEY',
'SUPABASE_CODER_KEY',
'SIN_FRONTEND_DEDICATED_SUPABASE_KEY',
'SIN_FRONTEND_SUPABASE_KEY',
];
let globalSupabaseInstance: SupabaseClient | null = null;
let dedicatedSupabaseInstance: SupabaseClient | null = null;
function resolveEnv(candidates: string[]): EnvResolution {
for (const candidate of candidates) {
const value = process.env[candidate]?.trim();
if (value) {
return { value, envName: candidate, candidates };
}
}
return { value: null, envName: null, candidates };
}
function resolveGlobalDbConnection(): ResolvedGlobalDbConnection {
const url = resolveEnv(GLOBAL_URL_ENV_CANDIDATES);
const key = resolveEnv(GLOBAL_KEY_ENV_CANDIDATES);
return {
url: url.value,
key: key.value,
urlEnv: url.envName,
keyEnv: key.envName,
urlCandidates: url.candidates,
keyCandidates: key.candidates,
mode: url.value && key.value ? 'shared' : 'inactive',
};
}
function resolveDedicatedDbConnection(): ResolvedDbConnection {
const dedicatedUrl = resolveEnv(DEDICATED_URL_ENV_CANDIDATES);
const dedicatedKey = resolveEnv(DEDICATED_KEY_ENV_CANDIDATES);
if (dedicatedUrl.value && dedicatedKey.value) {
return {
url: dedicatedUrl.value,
key: dedicatedKey.value,
urlEnv: dedicatedUrl.envName,
keyEnv: dedicatedKey.envName,
urlCandidates: dedicatedUrl.candidates,
keyCandidates: dedicatedKey.candidates,
mode: 'dedicated',
};
}
const global = resolveGlobalDbConnection();
if (global.url && global.key) {
return {
url: global.url,
key: global.key,
urlEnv: global.urlEnv,
keyEnv: global.keyEnv,
urlCandidates: dedicatedUrl.candidates,
keyCandidates: dedicatedKey.candidates,
mode: 'shared_fallback',
};
}
return {
url: null,
key: null,
urlEnv: null,
keyEnv: null,
urlCandidates: dedicatedUrl.candidates,
keyCandidates: dedicatedKey.candidates,
mode: 'inactive',
};
}
function getOrCreateClient(existing: SupabaseClient | null, connection: { url: string | null; key: string | null }) {
if (existing) return existing;
if (!connection.url || !connection.key) {
throw new Error('missing_supabase_credentials');
}
return createClient(connection.url, connection.key, { auth: { persistSession: false } });
}
export function getGlobalSupabaseClient(): SupabaseClient {
const connection = resolveGlobalDbConnection();
globalSupabaseInstance = getOrCreateClient(globalSupabaseInstance, connection);
return globalSupabaseInstance;
}
export function getDedicatedSupabaseClient(): SupabaseClient {
const connection = resolveDedicatedDbConnection();
dedicatedSupabaseInstance = getOrCreateClient(dedicatedSupabaseInstance, connection);
return dedicatedSupabaseInstance;
}
export function getFrontendDualDbRoutingStatus(): DualDbRoutingStatus {
const global = resolveGlobalDbConnection();
const dedicated = resolveDedicatedDbConnection();
return {
global: redactGlobalConnection(global),
dedicated: redactConnection(dedicated),
memoryPlane: {
schema: 'memory_plane',
table: 'items',
},
docsSyncQueue: {
table: 'docs_sync_queue',
},
};
}
function redactGlobalConnection(connection: ResolvedGlobalDbConnection): RedactedGlobalDbConnection {
return {
urlPresent: Boolean(connection.url),
keyPresent: Boolean(connection.key),
urlEnv: connection.urlEnv,
keyEnv: connection.keyEnv,
urlCandidates: connection.urlCandidates,
keyCandidates: connection.keyCandidates,
mode: connection.mode,
};
}
function redactConnection(connection: ResolvedDbConnection): RedactedDbConnection {
return {
urlPresent: Boolean(connection.url),
keyPresent: Boolean(connection.key),
urlEnv: connection.urlEnv,
keyEnv: connection.keyEnv,
urlCandidates: connection.urlCandidates,
keyCandidates: connection.keyCandidates,
mode: connection.mode,
};
}
export async function insertDedicatedMemoryItem(input: MemoryPlaneInsertInput) {
const connection = resolveDedicatedDbConnection();
const rows = await runPgQuery(
connection,
`insert into memory_plane.items (memory_type, provenance_type, source_run_id, content, summary, metadata, confidence)
values (
${sqlString(input.memoryType || 'episodic')},
${sqlString(input.provenanceType || 'agent_scratchpad')},
${sqlNullableString(input.sourceRunId)},
${sqlString(input.content)},
${sqlNullableString(input.summary)},
${sqlJson(input.metadata || {})}::jsonb,
${sqlNullableNumber(input.confidence)}
)
returning id, memory_type, provenance_type, source_run_id, content, summary, metadata, created_at`,
);
const item = Array.isArray(rows) ? rows[0] : null;
if (!item) {
throw new Error('memory_plane_insert_failed:no_row_returned');
}
return {
ok: true,
routing: getFrontendDualDbRoutingStatus().dedicated,
item,
};
}
export async function enqueueDocsSyncRequest(input: DocsSyncQueueInsertInput) {
const client = getGlobalSupabaseClient();
const { data, error } = await client
.from('docs_sync_queue')
.insert({
target_agent: input.targetAgent,
doc_type: input.docType,
doc_source_url: input.docSourceUrl,
metadata: input.metadata || {},
status: 'pending',
requested_by: input.requestedBy || 'sin-frontend',
})
.select('id,target_agent,doc_type,doc_source_url,status,requested_by,created_at')
.single();
if (error) {
throw new Error(`docs_sync_queue_insert_failed:${error.message}`);
}
return {
ok: true,
routing: getFrontendDualDbRoutingStatus().global,
queue: data,
};
}
export async function listRecentDedicatedMemoryItems(limit = 5) {
const connection = resolveDedicatedDbConnection();
const rows = await runPgQuery(
connection,
`select id, memory_type, provenance_type, source_run_id, content, metadata, created_at
from memory_plane.items
order by created_at desc
limit ${Math.min(Math.max(limit, 1), 20)}`,
);
return Array.isArray(rows) ? rows : [];
}
export async function listRecentDocsSyncQueueRows(targetAgent = 'sin-frontend', limit = 5) {
const client = getGlobalSupabaseClient();
const { data, error } = await client
.from('docs_sync_queue')
.select('id,target_agent,doc_type,doc_source_url,status,requested_by,created_at,error_message')
.eq('target_agent', targetAgent)
.order('created_at', { ascending: false })
.limit(Math.min(Math.max(limit, 1), 20));
if (error) {
throw new Error(`docs_sync_queue_select_failed:${error.message}`);
}
return Array.isArray(data) ? data : [];
}
async function runPgQuery(connection: { url: string | null; key: string | null }, sql: string) {
if (!connection.url || !connection.key) {
throw new Error('missing_pg_query_credentials');
}
const response = await fetch(`${connection.url.replace(/\/+$/, '')}/pg/query`, {
method: 'POST',
headers: {
apikey: connection.key,
Authorization: `Bearer ${connection.key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: sql }),
});
const payload = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`pg_query_failed:${response.status}:${JSON.stringify(payload ?? {})}`);
}
return Array.isArray(payload) ? payload : [];
}
function sqlString(value: string) {
return `'${String(value).replace(/'/g, "''")}'`;
}
function sqlNullableString(value: string | undefined) {
return value ? sqlString(value) : 'null';
}
function sqlNullableNumber(value: number | undefined) {
return typeof value === 'number' && Number.isFinite(value) ? String(value) : 'null';
}
function sqlJson(value: unknown) {
return sqlString(JSON.stringify(value ?? {}));
}