useAppFetch
useAppFetch() trả về authenticated data-plane fetcher của surface. Truyền path tương đối của app và RequestInit; Core resolve dưới backend của environment đã chọn, inject bearer credential, bind cancellation theo mount và refresh credential hết hạn đúng một lần.
import { useState } from 'react';
import { useAppFetch } from '@sota/platform';
export function SaveSettingsButton() {
const appFetch = useAppFetch();
const [status, setStatus] = useState('idle');
async function save() {
setStatus('saving');
const response = await appFetch('/settings', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ digest: 'weekly' }),
});
if (!response.ok) return setStatus('error');
const saved = await response.json();
setStatus(saved.digest === 'weekly' ? 'saved' : 'error');
}
return <button type="button" onClick={save}>{status}</button>;
}Kết quả là Response chuẩn; parse JSON và domain error vẫn là logic app. URL khác origin hoặc path thoát scoped base path bị từ chối. Không tự thêm hay lưu authorization header.
| Case | Behavior |
|---|---|
| Response thường | Trả nguyên trạng, kể cả non-2xx. |
| Credential hết hạn | Core refresh descriptor và retry đúng một lần trong cùng environment. |
| Surface unmount | Request signal bị abort. |
| Stream body one-shot | Core tee trước khi có thể retry credential. |