Download frontend/src/lib/api.ts from omaib/MuSProt: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/omaib/MuSProt/resolve/main/frontend/src/lib/api.ts
- Command line
-
hf download hf://spaces/omaib/MuSProt/frontend/src/lib/api.ts
-
curl -L -o api.ts https://huggingface.co/spaces/omaib/MuSProt/resolve/main/frontend/src/lib/api.ts
11.8 kB
| /** Typed client for the MuSProt explorer API. */ | |
| const API = (import.meta.env.VITE_PROTEIN_API_BASE as string | undefined) || '/api'; | |
| export type Fidelity = 'identical' | 'low' | 'medium' | 'high' | 'NA'; | |
| export interface LabelValue { | |
| label: string; | |
| value: number; | |
| } | |
| export interface Overview { | |
| generated: string; | |
| counts: { | |
| observations: number; | |
| sequences: number; | |
| multi_state_sequences: number; | |
| state_clusters: number; | |
| pdb_entries: number; | |
| uniprot_accessions: number; | |
| transitions: number; | |
| cross_state_transitions: number; | |
| }; | |
| states_per_sequence: LabelValue[]; | |
| observations_per_sequence: LabelValue[]; | |
| sequence_length: LabelValue[]; | |
| resolution: LabelValue[]; | |
| experimental_method: LabelValue[]; | |
| binding_status: LabelValue[]; | |
| chain_composition: LabelValue[]; | |
| nucleic_acid_binding: LabelValue[]; | |
| release_year: LabelValue[]; | |
| pair_fidelity: Array<LabelValue & { cross_state: number }>; | |
| state_fidelity: Array<{ label: string; state_pairs: number; observation_pairs: number }>; | |
| pair_similarity: { bin_width: number; all: number[]; cross_state: number[] }; | |
| tm_aln: { bin_width: number; all: number[] }; | |
| rmsd: { bin_width: number; all: number[] }; | |
| coverage: { bin_width: number; all: number[] }; | |
| outer_graph?: { | |
| level: string; | |
| groups: number; | |
| covered_sequences: number; | |
| coverage: number; | |
| edges: number; | |
| group_sizes: LabelValue[]; | |
| largest: Array<{ hset: string; label: string; n_sequences: number; n_obs: number; n_multistate: number }>; | |
| }; | |
| order?: { | |
| validity: LabelValue[]; | |
| evidence: Array<LabelValue & { cross_state: number }>; | |
| with_ligand_change: Array<{ label: string; with: number; without: number }>; | |
| }; | |
| } | |
| export interface SequenceSummary { | |
| sequence_id: string; | |
| uniprot_id: string | null; | |
| length: number | null; | |
| n_obs: number; | |
| n_states: number; | |
| n_entries: number; | |
| n_holo: number; | |
| n_apo: number; | |
| n_cross_pairs: number; | |
| largest_state: number; | |
| rep_pdb: string; | |
| rep_chain: string; | |
| cath_superfamily: string | null; | |
| methods: string[]; | |
| first_release: string | null; | |
| last_release: string | null; | |
| top_function: string | null; | |
| ecod_hset: string[]; | |
| pfam_ids: string[]; | |
| } | |
| export interface EcodNames { | |
| families: Record<string, { t_name: string; f_name: string }>; | |
| homologies: Record<string, { architecture: string; x_name: string; h_name: string }>; | |
| } | |
| export interface EcodGroup { | |
| hset: string[]; | |
| label: string; | |
| n_sequences: number; | |
| n_obs: number; | |
| n_multistate: number; | |
| n_states: number; | |
| } | |
| export interface StateInfo { | |
| state_id: number; | |
| n_members: number; | |
| } | |
| export interface Member { | |
| pdb_id: string; | |
| auth_asym_id: string; | |
| state_id: number; | |
| uniprot_id: string | null; | |
| experimental_method: string | null; | |
| resolution: number | null; | |
| binding_status: string | null; | |
| chain_composition: string | null; | |
| binders: string[]; | |
| release_date: string | null; | |
| resolved_coverage: number | null; | |
| cath_id: string | null; | |
| pfam_id: string | null; | |
| ecod_id: string | null; | |
| ecod_fid: string | null; | |
| } | |
| export interface StatePair { | |
| a: number; | |
| b: number; | |
| similarity: number | null; | |
| fidelity: string | null; | |
| n_pairs: number; | |
| } | |
| export interface RankedFunction { | |
| function: string; | |
| score: number | null; | |
| } | |
| export interface SequenceDetail extends SequenceSummary { | |
| states: StateInfo[]; | |
| members: Member[]; | |
| state_pairs: StatePair[]; | |
| heatmap_truncated: boolean; | |
| sequence?: string; | |
| functions?: RankedFunction[]; | |
| cath_superfamilies?: string[]; | |
| ecod_group: EcodGroup | null; | |
| ecod_names: EcodNames; | |
| } | |
| export const ENERGY_TERMS = ['Rosetta', 'FoldX', 'EvoEF2', 'RW', 'RW+'] as const; | |
| export type EnergyTerm = (typeof ENERGY_TERMS)[number]; | |
| export interface ChainDetail { | |
| pdb_id: string; | |
| auth_asym_id: string; | |
| sequence_id: string; | |
| uniprot_id: string | null; | |
| state_id: number | null; | |
| sequence: string; | |
| sequence_length: number | null; | |
| modeled_sequence_length: number | null; | |
| resolved_ranges: Array<[number, number]>; | |
| n_resolved_aa: number | null; | |
| resolved_coverage: number | null; | |
| binders: string[]; | |
| binding_status: string | null; | |
| experimental_method: string | null; | |
| resolution: number | null; | |
| pH: number | null; | |
| temp_K: number | null; | |
| experimental_details: string | null; | |
| chain_composition: string | null; | |
| nucleic_acid_binding: string | null; | |
| release_date: string | null; | |
| cath_domains: string[]; | |
| cath_superfamilies: string[]; | |
| pfam_ids: string[]; | |
| ecod_domains: string[]; | |
| ecod_fids: string[]; | |
| ecod_names: EcodNames; | |
| energies: Record<EnergyTerm, number | null>; | |
| scoring: { | |
| n_seqres: number | null; | |
| n_scored: number | null; | |
| scored_coverage: number | null; | |
| foldx_n_scored: number | null; | |
| foldx_coverage: number | null; | |
| }; | |
| functions: RankedFunction[]; | |
| family: (SequenceSummary & { states: StateInfo[] }) | null; | |
| } | |
| export interface Transition { | |
| pdb_id: string; | |
| auth_asym_id: string; | |
| state_id: number | null; | |
| tm_aln: number | null; | |
| rmsd: number | null; | |
| coverage: number | null; | |
| similarity: number | null; | |
| fidelity: string | null; | |
| state_similarity: number | null; | |
| state_fidelity: string | null; | |
| delta: Record<EnergyTerm, number | null>; | |
| order: OrderChange; | |
| experimental_method?: string | null; | |
| resolution?: number | null; | |
| binding_status?: string | null; | |
| chain_composition?: string | null; | |
| binders?: string[]; | |
| release_date?: string | null; | |
| } | |
| /** Observed order/disorder change from A to B (resolved-state sense, not intrinsic disorder). */ | |
| export interface OrderChange { | |
| valid: boolean | null; | |
| reason: string | null; | |
| n_shared_resolved: number | null; | |
| n_ordered: number | null; | |
| n_disordered: number | null; | |
| ordered_ranges: Array<[number, number]>; | |
| disordered_ranges: Array<[number, number]>; | |
| longest_ordered: number | null; | |
| longest_disordered: number | null; | |
| n_terminal_ordered: number | null; | |
| n_internal_ordered: number | null; | |
| evidence: string | null; | |
| with_ligand_change: boolean | null; | |
| } | |
| export interface OuterGraph { | |
| groups: Array<EcodGroup & { sequences: SequenceSummary[] }>; | |
| names: EcodNames['homologies']; | |
| } | |
| export interface InnerGraph { | |
| sequence: SequenceSummary; | |
| nodes: Member[]; | |
| n_total: number; | |
| states: StateInfo[]; | |
| state_pairs: StatePair[]; | |
| edges: Array<{ s: number; t: number; sim: number | null; fid: string | null }>; | |
| } | |
| export interface TransitionsResponse { | |
| pdb_id: string; | |
| auth_asym_id: string; | |
| state_id: number | null; | |
| sequence_id: string; | |
| transitions: Transition[]; | |
| } | |
| export interface ChainHit { | |
| pdb_id: string; | |
| auth_asym_id: string; | |
| sequence_id: string; | |
| uniprot_id: string | null; | |
| state_id: number | null; | |
| sequence_length: number | null; | |
| } | |
| export interface SearchResponse { | |
| query: string; | |
| kind: 'empty' | 'chain' | 'entry' | 'sequence' | 'uniprot' | 'text' | 'unavailable'; | |
| chains: ChainHit[]; | |
| sequences: SequenceSummary[]; | |
| entry_chains?: ChainHit[]; | |
| total?: number; | |
| } | |
| export interface SequenceListResponse { | |
| total: number; | |
| offset: number; | |
| items: SequenceSummary[]; | |
| } | |
| export interface BrowseParams { | |
| sort?: string; | |
| order?: 'asc' | 'desc'; | |
| q?: string; | |
| min_states?: number; | |
| min_obs?: number; | |
| method?: string; | |
| ligand?: 'both' | 'holo' | 'apo'; | |
| cath?: string; | |
| offset?: number; | |
| limit?: number; | |
| ids?: string; | |
| ecod?: string; | |
| pfam?: string; | |
| } | |
| export class ApiError extends Error { | |
| status: number; | |
| constructor(status: number, message: string) { | |
| super(message); | |
| this.status = status; | |
| } | |
| } | |
| const TIMEOUT_MS = 60_000; | |
| async function get<T>(path: string, signal?: AbortSignal): Promise<T> { | |
| // give up after TIMEOUT_MS so a stalled backend shows an error instead of loading forever | |
| const ctrl = new AbortController(); | |
| let timedOut = false; | |
| const timer = setTimeout(() => { | |
| timedOut = true; | |
| ctrl.abort(); | |
| }, TIMEOUT_MS); | |
| const onAbort = () => ctrl.abort(); | |
| signal?.addEventListener('abort', onAbort); | |
| let res: Response; | |
| try { | |
| res = await fetch(`${API}${path}`, { signal: ctrl.signal, headers: { Accept: 'application/json' } }); | |
| } catch (err) { | |
| if (timedOut) throw new ApiError(504, 'The server is taking too long to respond. Please try again in a moment.'); | |
| throw err; | |
| } finally { | |
| clearTimeout(timer); | |
| signal?.removeEventListener('abort', onAbort); | |
| } | |
| if (!res.ok) { | |
| let detail = res.statusText; | |
| try { | |
| const body = await res.json(); | |
| detail = body.detail || detail; | |
| } catch { | |
| /* keep status text */ | |
| } | |
| throw new ApiError(res.status, detail); | |
| } | |
| return res.json() as Promise<T>; | |
| } | |
| function qs(params: object): string { | |
| const sp = new URLSearchParams(); | |
| for (const [k, v] of Object.entries(params)) { | |
| if (v !== undefined && v !== null && v !== '') sp.set(k, String(v)); | |
| } | |
| const s = sp.toString(); | |
| return s ? `?${s}` : ''; | |
| } | |
| const enc = encodeURIComponent; | |
| export const api = { | |
| overview: (signal?: AbortSignal) => get<Overview>('/protein/overview', signal), | |
| search: (q: string, signal?: AbortSignal) => get<SearchResponse>(`/protein/search${qs({ q })}`, signal), | |
| sequences: (params: BrowseParams, signal?: AbortSignal) => | |
| get<SequenceListResponse>(`/protein/sequences${qs(params)}`, signal), | |
| sequence: (id: string, signal?: AbortSignal) => get<SequenceDetail>(`/protein/sequence/${enc(id)}`, signal), | |
| chain: (pdb: string, chain: string, signal?: AbortSignal) => | |
| get<ChainDetail>(`/protein/chain/${enc(pdb)}/${enc(chain)}`, signal), | |
| transitions: (pdb: string, chain: string, signal?: AbortSignal) => | |
| get<TransitionsResponse>(`/protein/chain/${enc(pdb)}/${enc(chain)}/transitions`, signal), | |
| outerGraph: (seqIds: string[], signal?: AbortSignal, perGroup = 18) => | |
| get<OuterGraph>(`/protein/graph/outer${qs({ seq: seqIds.join(','), per_group: perGroup })}`, signal), | |
| innerGraph: (id: string, maxNodes = 60, signal?: AbortSignal) => | |
| get<InnerGraph>(`/protein/graph/inner/${enc(id)}${qs({ max_nodes: maxNodes })}`, signal), | |
| docs: async (signal?: AbortSignal) => { | |
| const res = await fetch(`${API}/protein/docs`, { signal }); | |
| if (!res.ok) throw new ApiError(res.status, 'Documentation is not available'); | |
| return res.text(); | |
| }, | |
| downloadUrl: `${API}/protein/download/db`, | |
| }; | |
| /** Entry title from RCSB (best effort; the site works without it). */ | |
| const titleCache = new Map<string, Promise<string | null>>(); | |
| export function rcsbTitle(pdbId: string): Promise<string | null> { | |
| const id = pdbId.toUpperCase(); | |
| let p = titleCache.get(id); | |
| if (!p) { | |
| p = fetch(`https://data.rcsb.org/rest/v1/core/entry/${id}`) | |
| .then((r) => (r.ok ? r.json() : null)) | |
| .then((d) => (d?.struct?.title as string | undefined) ?? null) | |
| .catch(() => null); | |
| titleCache.set(id, p); | |
| } | |
| return p; | |
| } | |
| /** Chemical component names from RCSB (best effort). */ | |
| const compCache = new Map<string, Promise<string | null>>(); | |
| export function rcsbComponentName(compId: string): Promise<string | null> { | |
| const id = compId.toUpperCase(); | |
| let p = compCache.get(id); | |
| if (!p) { | |
| p = fetch(`https://data.rcsb.org/rest/v1/core/chemcomp/${enc(id)}`) | |
| .then((r) => (r.ok ? r.json() : null)) | |
| .then((d) => (d?.chem_comp?.name as string | undefined) ?? null) | |
| .catch(() => null); | |
| compCache.set(id, p); | |
| } | |
| return p; | |
| } | |
| /** Pfam family names from InterPro (best effort). */ | |
| const pfamCache = new Map<string, Promise<string | null>>(); | |
| export function pfamName(id: string): Promise<string | null> { | |
| const key = id.toUpperCase(); | |
| let p = pfamCache.get(key); | |
| if (!p) { | |
| p = fetch(`https://www.ebi.ac.uk/interpro/api/entry/pfam/${enc(key)}/`) | |
| .then((r) => (r.ok ? r.json() : null)) | |
| .then((d) => (d?.metadata?.name?.name as string | undefined) ?? null) | |
| .catch(() => null); | |
| pfamCache.set(key, p); | |
| } | |
| return p; | |
| } | |