synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
366 lines (359 loc) • 17 kB
TypeScript
import { j as IStorage } from './storage.interface-BA_ktyDz.js';
/**
* Тип для функции отписки от событий
*/
type Unsubscribe = VoidFunction;
/**
* Опции для выполнения запроса
*/
interface QueryOptions {
/** Отключить кэширование для этого запроса */
disableCache?: boolean;
/** Signal для отмены запроса */
signal?: AbortSignal;
/** Таймаут в миллисекундах (переопределяет глобальный) */
timeout?: number;
/** Дополнительные заголовки */
headers?: Headers;
/** Пользовательский контекст */
context?: Record<string, any>;
/** Ключи заголовков, влияющие на кэш (для конкретного запроса) */
cacheableHeaderKeys?: string[];
/** Формат ответа (переопределяет формат из RequestDefinition) */
responseFormat?: ResponseFormat;
/** Название файла при скачивании (переопределяет fileName из RequestDefinition) */
fileName?: string;
/** Тип файла при скачивании (переопределяет fileType из RequestDefinition) */
fileType?: string;
/** Автоматически скачать файл после получения */
downloadFile?: boolean;
/**
* Функция для повторного выполнения запроса
*/
retry?: <T = any, R = any>(params: T, options?: QueryOptions) => Promise<R>;
}
/**
* Метаданные для файла
*/
interface FileMetadata {
/** Имя файла */
fileName: string;
/** Тип файла (MIME-тип) */
fileType: string;
/** Размер файла в байтах */
size?: number;
/** Дата создания файла */
createdAt?: Date | string;
/** Дата изменения файла */
updatedAt?: Date | string;
}
/**
* Результат скачивания файла
*/
interface FileDownloadResult<T = Blob | ArrayBuffer> {
/** Данные файла */
data: T;
/** Метаданные файла */
metadata: FileMetadata;
/** HTTP-статус */
status: number;
/** Текст статуса */
statusText: string;
/** Заголовки ответа */
headers: Headers;
/** Успешна ли загрузка */
ok: boolean;
}
/**
* Результат выполнения запроса
*/
interface QueryResult<T = any, E = Error> {
/** Данные ответа (при успешном запросе) */
data?: T;
/** Ошибка (при неуспешном запросе) */
error?: E;
/** Флаг успешности запроса */
ok: boolean;
/** HTTP-статус */
status: number;
/** Текстовое описание статуса */
statusText: string;
/** Заголовки ответа */
headers: Headers;
/** Результат скачивания файла (если responseFormat - Blob или ArrayBuffer) */
fileDownloadResult?: FileDownloadResult;
fromCache?: boolean;
}
/**
* Конфигурация эндпоинта
*/
interface EndpointConfig<RequestParams extends Record<string, any> = any, RequestResult = any> {
/** Функция для создания определения запроса из параметров */
request: (params: RequestParams, context?: Record<string, any>) => RequestDefinition<RequestParams>;
/** Настройки кэша для эндпоинта */
cache?: CacheConfig;
/** Теги эндпоинта для группировки в кэше */
tags?: string[];
/** Теги, которые инвалидируются при успешном запросе */
invalidatesTags?: string[];
/** Функция для подготовки заголовков (дополняет глобальную) */
prepareHeaders?: (headers: Headers, context: ApiContext<RequestParams>) => Promise<Headers>;
/** Добавить ключи заголовков, влияющие на кэш (Дополняет глобавльные ключи) */
includeCacheableHeaderKeys?: string[];
/** Исключить ключи заголовков, влияющие на кэш (Дополняет глобавльные ключи) */
excludeCacheableHeaderKeys?: string[];
}
/**
* Состояние эндпоинта
* Содержит информацию о текущем состоянии запроса и данные
*/
interface EndpointState {
/** Статус запроса */
status: 'idle' | 'loading' | 'success' | 'error';
/** Ошибка (при неуспешном запросе) */
error?: Error;
/** Количество вызовов */
fetchCounts: number;
/** Метаданные эндпоинта */
meta: Endpoint['meta'];
/** Какие заголовки участвовали в формировании ключа кэша (итоговые) */
cacheableHeaders: string[];
}
/**
* Состояние самого запроса
*/
interface RequestState<ResponseData = any, RequestParams extends Record<string, any> = any, E = Error> {
status: 'loading' | 'success' | 'error' | 'idle';
data?: ResponseData;
error?: E;
headers: Record<string, any> | Headers;
requestParams: RequestParams;
fromCache: boolean;
}
interface SubscribeOptions {
/** Автоматически отписаться после завершения запроса */
autoUnsubscribe?: boolean;
}
/**
* Дополнительные методы для request
*/
interface RequestResponseModify<T, P extends Record<string, any> = any> {
id: string;
/**
* Подписка на изменения состояния запроса
*/
subscribe: (listener: (state: RequestState<T, P>) => void, options?: SubscribeOptions) => VoidFunction;
/**
* Ожидание завершения запроса
* @returns Promise с результатом запроса
*/
wait: () => Promise<QueryResult<T, Error>>;
waitWithCallbacks: (handlers: {
idle?: (request: RequestState<T, P>) => void;
loading?: (request: RequestState<T, P>) => void;
success?: (data: T | undefined, request: RequestState<T, P>) => void;
error?: (error: Error | undefined, request: RequestState<T, P>) => void;
}) => Promise<QueryResult<T, Error>>;
/**
* Отменить запрос
*/
abort: VoidFunction;
then<TResult1 = QueryResult<T, Error>, TResult2 = never>(onfulfilled?: ((value: QueryResult<T, Error>) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: Error) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: Error) => TResult | PromiseLike<TResult>) | undefined | null): Promise<QueryResult<T, Error> | TResult>;
finally(onfinally?: VoidFunction | undefined | null): Promise<QueryResult<T, Error>>;
}
/**
* Структура эндпоинта
*
* Эндпоинт - это всего лишь определение того6 как будет вызван метод
* Эндпоинт может быть вызван в разных частях приложения с разными параметрами
* По этому нет смысла хранить ответы так как они будут перезаписываться
* метод subscribe больше нужен для мониторинга
* meta - метаинформация по эндпоинту (то как он сконфигурирован)
*/
interface Endpoint<RequestParams extends Record<string, any> = any, ResponseData = any> {
/** Счетчик вызова конкретного эндпоинта в проекте */
fetchCounts: number;
/** Выполнить запрос с параметрами */
request: (params: RequestParams, options?: QueryOptions) => RequestResponseModify<ResponseData>;
/** Подписаться на изменения состояния эндпоинта (в основном для сбора статистики) */
subscribe: (callback: (state: EndpointState) => void) => Unsubscribe;
/** Сбросить состояние */
reset: () => Promise<void>;
/** Метаданные эндпоинта */
meta: {
/** Имя эндпоинта */
name: string;
/** Теги эндпоинта */
tags: string[];
/** Теги, которые инвалидируются */
invalidatesTags: string[];
/** Настройки кэша */
cache: CacheConfig;
};
destroy: VoidFunction;
}
/**
* Функция для создания типизированных эндпоинтов
*/
type CreateEndpoint = <RequestParams extends Record<string, any>, RequestResult>(config: EndpointConfig<RequestParams, RequestResult>) => EndpointConfig<RequestParams, RequestResult>;
/**
* Форматы ответа от сервера
*/
declare enum ResponseFormat {
/** JSON-объект (по умолчанию) */
Json = "json",
/** Blob-объект для файлов */
Blob = "blob",
/** ArrayBuffer для бинарных данных */
ArrayBuffer = "arrayBuffer",
/** Текстовый формат */
Text = "text",
/** FormData для форм */
FormData = "formData",
/** Без преобразования - возвращает сырой ответ */
Raw = "raw"
}
/**
* Настройки кэша
* Может быть объектом с параметрами или boolean (true для кэширования с настройками по умолчанию, false для отключения)
*/
type CacheConfig = boolean | {
/** Время жизни кэша в миллисекундах */
ttl?: number;
/** Настройки периодической очистки */
cleanup?: {
/** Включить периодическую очистку */
enabled: boolean;
/** Интервал очистки в миллисекундах */
interval?: number;
};
/** Инвалидировать кэш при ошибке */
invalidateOnError?: boolean;
};
/**
* Определение запроса
* Содержит всю необходимую информацию для выполнения HTTP-запроса
*/
interface RequestDefinition<RequestParams extends Record<string, any>> {
/** Путь запроса (относительный или абсолютный URL) */
path: string;
/** HTTP-метод */
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
/** Тело запроса (автоматически сериализуется) */
body?: any;
/** Параметры запроса (автоматически добавляются в URL) */
query?: RequestParams;
/** HTTP-заголовки */
headers?: Record<string, string>;
/** Формат ответа (по умолчанию json) */
responseFormat?: ResponseFormat;
/** Имя файла для автоматического скачивания */
fileName?: string;
/** Тип контента для автоматического скачивания */
fileType?: string;
}
/**
* Контекст API для использования в prepareHeaders и других функциях
* Содержит вспомогательные методы и информацию о запросе
*/
interface ApiContext<RequestParams extends Record<string, any> = any> {
/** Параметры запроса */
requestParams?: RequestParams;
/** Получить значение из localStorage */
getFromStorage: <T>(key: string) => T | undefined;
/** Получить значение cookie */
getCookie: (name: string) => string | undefined;
/** Поддержка для дополнительных свойств */
[key: string]: any;
}
/**
* Аргументы для создания fetch-запроса
*/
interface FetchBaseQueryArgs {
/** Базовый URL для всех запросов */
baseUrl: string;
/** Функция для подготовки заголовков, может быть асинхронной */
prepareHeaders?: (headers: Headers, context: ApiContext) => Promise<Headers>;
/** Таймаут запроса в миллисекундах */
timeout?: number;
/** Пользовательская fetch-функция */
fetchFn?: typeof fetch;
credentials?: RequestCredentials;
}
interface CreateApiClientOptions<T extends Record<string, EndpointConfig<any, any>> = Record<string, EndpointConfig<any, any>>> {
storage: IStorage;
/** Настройки кэша
* если явно указан false - значит ни один запрос НЕ будет кэшироваться, даже если в эндпоинтах указаны параметры
* */
cache?: CacheConfig;
/** Базовый запрос или его настройки */
baseQuery: FetchBaseQueryArgs;
/** Функция для создания эндпоинтов */
endpoints?: (create: CreateEndpoint) => Promise<T>;
/** Глобальные заголовки, влияющие на кэш */
cacheableHeaderKeys?: string[];
}
/**
* Извлечение типа параметров из конфигурации эндпоинта
*/
type ExtractParamsType<T> = T extends EndpointConfig<infer P, any> ? P : never;
/**
* Извлечение типа результата из конфигурации эндпоинта
*/
type ExtractResultType<T> = T extends EndpointConfig<any, infer R> ? R : never;
type EndpointsResult<F> = F extends (create: any) => Promise<infer R> ? R : never;
declare class ApiClient<EndpointsFn extends (create: CreateEndpoint) => Promise<Record<string, EndpointConfig<any, any>>>> {
/** Хранилище запросов */
private queryStorage;
private readonly cacheableHeaderKeys;
private readonly globalCacheConfig;
private readonly baseQueryConfig;
private readonly storageExternal;
private readonly createEndpoints;
/** Реестр эндпоинтов */
private endpoints;
constructor(options: Omit<CreateApiClientOptions, 'endpoints'> & {
endpoints: EndpointsFn;
});
init(): Promise<this>;
private initializeEndpoints;
/**
* Получает все эндпоинты с улучшенной типизацией
* @returns Типизированный объект эндпоинтов
*/
getEndpoints(): {
[K in keyof EndpointsResult<EndpointsFn>]: Endpoint<ExtractParamsType<EndpointsResult<EndpointsFn>[K]>, ExtractResultType<EndpointsResult<EndpointsFn>[K]>>;
};
/**
* Выполняет запрос к API с типизацией и обработкой ошибок
* @param endpointName Имя эндпоинта (с подсказками TypeScript)
* @param params Параметры запроса (с типизацией)
* @param options Опции запроса
* @returns Promise с типизированным результатом запроса
*/
request<K extends keyof EndpointsResult<EndpointsFn> & string>(endpointName: K, params: ExtractParamsType<EndpointsResult<EndpointsFn>[K]>, options?: QueryOptions): Promise<QueryResult<ExtractResultType<EndpointsResult<EndpointsFn>[K]>, Error>>;
destroy(): Promise<void>;
}
/**
* Логгер для API
*/
declare const apiLogger: {
debug: (message: string, ...args: any[]) => void;
log: (message: string, ...args: any[]) => void;
info: (message: string, ...args: any[]) => void;
warn: (message: string, ...args: any[]) => void;
error: (message: string, ...args: any[]) => void;
};
/**
* Создает уникальный идентификатор
* @returns Строка с уникальным идентификатором
*/
declare function createUniqueId(name?: string): string;
/**
* Преобразует объект Headers в обычный объект
* @param headers Объект Headers
* @returns Обычный объект с заголовками
*/
declare function headersToObject(headers: Headers): Record<string, string>;
export { ApiClient, ResponseFormat, apiLogger, createUniqueId, headersToObject };