import { fetchEventSource, type EventSourceMessage, } from '@microsoft/fetch-event-source'; import { onMounted, ref, type Ref, watch, type WatchSource } from 'vue'; import { api, extractApiData, type ApiResponse } from './api'; export interface User { id: number; username: string; email: string; avatar?: string; createdAt: string; } export interface ListParams { page: number; pageSize: number; keyword?: string; } export interface ListResult { list: T[]; total: number; page: number; pageSize: number; } export const userApi = { async getUserInfo(): Promise { const response = await api.get>('/user/info'); return extractApiData(response); }, async updateUserInfo(data: Partial): Promise { const response = await api.put>('/user/info', data); return extractApiData(response); }, }; export const exampleApi = { async getList(params: ListParams): Promise> { const response = await api.get>>( '/example/list', { params } ); return extractApiData>(response); }, async create(data: unknown): Promise { const response = await api.post>( '/example/create', data ); return extractApiData(response); }, async update(id: number, data: unknown): Promise { const response = await api.put>( `/example/update/${id}`, data ); return extractApiData(response); }, async delete(id: number): Promise { await api.delete(`/example/delete/${id}`); }, async getDetail(id: number): Promise { const response = await api.get>( `/example/detail/${id}` ); return extractApiData(response); }, }; interface UseApiOptions { immediate?: boolean; watchSources?: WatchSource[]; } /** * Vue-friendly async state helper aligned with react-vite semantics: * - loading / error state * - explicit execute() * - refetch alias */ export function useApi( apiCall: () => Promise, options: UseApiOptions = {} ): { data: Ref; loading: Ref; error: Ref; execute: () => Promise; refetch: () => Promise; } { const data = ref(null) as Ref; const loading = ref(false); const error = ref(null); const execute = async (): Promise => { loading.value = true; error.value = null; try { data.value = await apiCall(); } catch (err) { error.value = err instanceof Error ? err : new Error('Unknown error'); } finally { loading.value = false; } }; if (options.immediate ?? true) { onMounted(execute); } if (options.watchSources && options.watchSources.length > 0) { watch(options.watchSources, () => { void execute(); }); } return { data, loading, error, execute, refetch: execute, }; } export type StreamResponse = T; export async function streamRequest( url: string, payload: unknown, onData: (response: StreamResponse) => void, onError?: (error: Error) => void ): Promise { try { await fetchEventSource(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), onmessage(msg: EventSourceMessage) { if (msg.event === 'FatalError') { throw new Error(msg.data); } try { onData(JSON.parse(msg.data) as StreamResponse); } catch { console.warn('Failed to parse stream chunk:', msg.data); } }, onerror(err) { if (onError) { onError(err as Error); } throw err; }, }); } catch (err) { if (onError) { onError(err as Error); } } }