diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 86f0348..e46373c 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -38,9 +38,11 @@ export const apiService = { ) ); }, - post: (url: string, data: any) => apiClient.post(url, data), - put: (url: string, data: any) => apiClient.put(url, data), - delete: (url: string) => apiClient.delete(url), + /** #801: Generic response type so callers can infer the response shape. */ + post: (url: string, data: unknown) => apiClient.post(url, data), + put: (url: string, data: unknown) => apiClient.put(url, data), + patch: (url: string, data: unknown) => apiClient.patch(url, data), + delete: (url: string) => apiClient.delete(url), }; export { default as apiClient } from './axios.config'; diff --git a/src/services/api/requestQueue.ts b/src/services/api/requestQueue.ts index a8598b2..7dd5e5c 100644 --- a/src/services/api/requestQueue.ts +++ b/src/services/api/requestQueue.ts @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import type { ApiClient } from '../../types/apiClient'; import { InternalAxiosRequestConfig } from 'axios'; import * as Network from 'expo-network'; @@ -64,7 +65,8 @@ class RequestQueue { private listeners: ((count: number) => void)[] = []; private monitoringInterval: ReturnType | null = null; private networkListener: (() => void) | null = null; - private apiClient: any | null = null; + // #802: typed as ApiClient to prevent passing arbitrary objects + private apiClient: ApiClient | null = null; private metrics: QueueMetrics = { totalQueued: 0, byPriority: { critical: 0, high: 0, normal: 0, low: 0 }, @@ -202,7 +204,7 @@ class RequestQueue { } } - async processQueue(apiClient?: any): Promise { + async processQueue(apiClient?: ApiClient): Promise { if (this.isProcessing) return; if (apiClient) { @@ -283,7 +285,7 @@ class RequestQueue { .catch(() => {}); } - startMonitoring(apiClient?: any): void { + startMonitoring(apiClient?: ApiClient): void { if (apiClient) { this.apiClient = apiClient; } diff --git a/src/store/createStore.ts b/src/store/createStore.ts index c12732c..977a85a 100644 --- a/src/store/createStore.ts +++ b/src/store/createStore.ts @@ -45,13 +45,15 @@ export const createStore = ( storage: createJSONStorage(() => asyncMMKVStorage), partialize, migrate, - onRehydrateStorage: (state) => { - return (state, error) => { + onRehydrateStorage: (_initialState) => { + // #803: explicitly type both params so implicit `any` is eliminated. + // `error` is `Error | undefined` per the Zustand persist middleware signature. + return (_rehydratedState: T | undefined, error: Error | undefined) => { if (error) { appLogger.errorSync('an error happened during hydration', error instanceof Error ? error : new Error(String(error))) logger.errorSync('an error happened during hydration', error as Error) } - } + }; }, }), { name: `Teach-This-${name}` } diff --git a/src/types/apiClient.ts b/src/types/apiClient.ts new file mode 100644 index 0000000..6b357d2 --- /dev/null +++ b/src/types/apiClient.ts @@ -0,0 +1,20 @@ +// src/types/apiClient.ts +// Typed interface that RequestQueue uses to replay queued requests. +// The exported `apiClient` from axios.config.ts satisfies this interface. + +import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; + +/** + * Minimum contract required by RequestQueue to execute queued HTTP requests. + * + * Typed as a callable (for replaying raw configs) plus named method overloads + * so TypeScript can verify the actual apiClient satisfies it at compile time. + */ +export interface ApiClient { + (config: InternalAxiosRequestConfig): Promise; + get(url: string, config?: object): Promise>; + post(url: string, data?: unknown, config?: object): Promise>; + put(url: string, data?: unknown, config?: object): Promise>; + patch(url: string, data?: unknown, config?: object): Promise>; + delete(url: string, config?: object): Promise>; +} diff --git a/src/types/axios.d.ts b/src/types/axios.d.ts new file mode 100644 index 0000000..d33cf10 --- /dev/null +++ b/src/types/axios.d.ts @@ -0,0 +1,33 @@ +// src/types/axios.d.ts +// Module augmentation for custom Axios request config properties. +// This is the single source of truth — no more `as any` casts needed. + +import 'axios'; + +declare module 'axios' { + interface InternalAxiosRequestConfig { + /** + * Set to `true` once a 401 has triggered a token-refresh attempt for this + * request, preventing infinite refresh loops. + */ + _retry?: boolean; + + /** + * Running count of retry attempts for 429 (rate-limit) and 5xx (server + * error) responses. Starts at 0 and increments before each retry. + */ + _retryCount?: number; + + /** + * Epoch milliseconds recorded when the request was dispatched. Used to + * calculate response latency in the response interceptor. + */ + _requestStartMs?: number; + + /** + * Performance timing finish callback injected by the request interceptor. + * Set to `undefined` after first use to avoid double-reporting on retries. + */ + _timingFinish?: ((success: boolean, statusCode?: number) => unknown) | undefined; + } +}