Skip to content
Clone
9301161
98 lines 3.3 KB
import { createAlova } from 'alova';
import fetchAdapter from 'alova/fetch';
import { createApis, withConfigType, mountApis } from './createApis';
import { recordApiResult } from './health';
import { newTraceparent } from './trace';
export const alovaInstance = createAlova({
baseURL: '/api/v1',
requestAdapter: fetchAdapter(),
beforeRequest: method => {
method.config.headers = {
...method.config.headers,
traceparent: newTraceparent()
};
},
responded: {
// Every settled response is reported to the health window before its body
// is parsed, so an unreachable backend is recognised even when the caller
// swallows the resulting error.
onSuccess: async (res, method) => {
recordApiResult({ status: res.status, url: method.url });
const contentType = res.headers.get('content-type') || '';
let body: unknown;
if (contentType.includes('application/json')) {
try {
body = await res.json();
} catch (err) {
// A gateway error page is handled by the envelope fallback below; a
// broken success payload must still reject the method unchanged.
if (res.status < 400) throw err;
body = null;
}
} else {
body = await res.text();
}
// A failing hop (dev-server proxy, reverse proxy, gateway) answers with
// plain text, never with the documented envelope. Synthesise one so
// `unwrapApi` still throws an ApiFailure that carries the real status
// instead of `code: undefined`.
if (res.status >= 400 && !isEnvelope(body)) {
return {
code: res.status,
message: res.status >= 500 ? '服务未响应(网关错误)' : '服务返回了非接口数据',
data: null,
error: null,
error_code: null
};
}
return body;
},
// Alova routes only real transport failures here (404/500 land on
// onSuccess). The error must be rethrown: a global onError that returns
// normally makes alova resolve the method instead, so callers would read
// `.code` off `undefined` and lose their ApiFailure/network-error branch.
onError: (err, method) => {
if ((err as Error | null)?.name !== 'AbortError') {
recordApiResult({ status: 0, url: method.url, networkError: true });
}
throw err;
}
}
});
function isEnvelope(value: unknown): value is { code: number } {
return typeof value === 'object' && value !== null && typeof (value as { code?: unknown }).code === 'number';
}
export const $$userConfigMap = withConfigType({});
const Apis = createApis(alovaInstance, $$userConfigMap);
mountApis(Apis);
export default Apis;
// Health gate primitives live next to the transport they observe; exported
// through the segment's public API so `app` and `entities` never deep-import.
export {
API_ERROR_BURST_FAILURES,
API_ERROR_MIN_SAMPLES,
API_ERROR_RATE_THRESHOLD,
API_PROBE_INTERVAL_MS,
getApiHealth,
recordApiResult,
retryApiHealth,
subscribeApiHealth,
trackFetch,
useApiHealth,
useApiUnavailable,
type ApiHealth,
type ApiResult
} from './health';
export { newTraceparent } from './trace';
// Re-export the generated DTO types through the segment's public API so
// consumers never deep-import globals.d (regenerated file, see alova.config).
export type * from './globals.d'