{"version":3,"file":"index.mjs","sources":[""],"sourcesContent":["import { bubble } from \"@web3r/flowerkit/evt\";\r\nimport type { TGetFromServerArgs } from \"@web3r/flowerkit/net\";\r\nimport { getFromServer } from \"@web3r/flowerkit/net\";\r\nimport { getDocument } from \"ssr-window\";\r\nimport type { IResp } from \"../../types/api\";\r\n\r\n/**\r\n * Неизвестный ответ от REST по-умолчанию\r\n */\r\ntype TDefaultResp = Record<string, unknown>;\r\n\r\n/**\r\n * Функция-адаптер для преобразования ответа сервера.\r\n * @template TResponse Тип исходного ответа.\r\n * @template TResult Тип результата после адаптации.\r\n */\r\nexport type TAdapterFn<TResponse = IResp<TDefaultResp>, TResult = TResponse> = (resp: TResponse) => TResult;\r\n\r\n\r\n/**\r\n * Тип промиса API-запроса с поддержкой цепочки валидации.\r\n * @template TResult Тип результата API-запроса.\r\n */\r\nexport type TApiClientRequest<TResult> = Promise<TResult> & {\r\n  validate: <TValidated = TResult>(\r\n    schema: TValidationSchema<TResult, TValidated>,\r\n    options?: Record<string, unknown>,\r\n  ) => Promise<TValidated>;\r\n};\r\n\r\n\r\n/**\r\n * Параметры инициализации API-клиента.\r\n */\r\nexport type TArgs = Partial<{\r\n  url: string;\r\n  getAdapterFn: TAdapterFn;\r\n}>;\r\n\r\n\r\n/**\r\n * Асинхронная функция-адаптер для преобразования ответа сервера.\r\n * @template TResponse Тип исходного ответа.\r\n * @template TResult Тип результата после адаптации.\r\n */\r\nexport type TAsyncAdapterFn<TResponse = IResp<TDefaultResp>, TResult = TResponse> = (\r\n  resp: TResponse\r\n) => PromiseLike<TResult>;\r\n\r\n\r\n/**\r\n * Универсальный асинхронный адаптер для извлечения `data` из `IResp`.\r\n */\r\nexport type TAsyncRespAdapterFn = <TData extends TDefaultResp>(resp: IResp<TData>) => PromiseLike<TData>;\r\n\r\n\r\n/**\r\n * Допустимые значения заголовка `Content-Type` для API-запросов.\r\n */\r\nexport type TContentType =\r\n  | \"application/json\"\r\n  | \"application/octet-stream\"\r\n  | \"text/plain\"\r\n  | \"text/xml\"\r\n  | \"text/html\"\r\n  | \"multipart/form-data\";\r\n\r\n\r\n/**\r\n * Допустимые HTTP-методы запроса.\r\n */\r\nexport type TMethod =\r\n  | \"GET\"\r\n  | \"POST\"\r\n  | \"PUT\"\r\n  | \"DELETE\"\r\n  | \"PATCH\"\r\n  | \"OPTIONS\"\r\n  | \"TRACE\"\r\n  | \"HEAD\";\r\n\r\n\r\n/**\r\n * Синхронный или асинхронный адаптер ответа сервера.\r\n * @template TResponse Тип исходного ответа.\r\n * @template TResult Тип результата после адаптации.\r\n */\r\nexport type TRequestAdapterFn<TResponse = IResp<TDefaultResp>, TResult = TResponse> =\r\n  | TAdapterFn<TResponse, TResult>\r\n  | TAsyncAdapterFn<TResponse, TResult>;\r\n\r\n\r\n/**\r\n * Параметры HTTP-запроса API-клиента.\r\n * @template TResponse Тип исходного ответа.\r\n * @template TResult Тип результата после адаптации.\r\n */\r\nexport type TRequestProps<TResponse = IResp<TDefaultResp>, TResult = TResponse> = {\r\n  signal?: AbortSignal;\r\n  endpoint: string;\r\n  data?: unknown;\r\n  method?: TMethod;\r\n  contentType?: TContentType;\r\n  responseType?: TResponseType;\r\n  getAdapterFn?: TRequestAdapterFn<TResponse, TResult>;\r\n  credentials?: RequestCredentials;\r\n  headers?: Record<string, string>;\r\n  timeout?: number;\r\n};\r\n\r\n\r\n/**\r\n * Универсальный адаптер для извлечения `data` из `IResp`.\r\n */\r\nexport type TRespAdapterFn = <TData extends TDefaultResp>(resp: IResp<TData>) => TData;\r\n\r\n\r\n/**\r\n * Тип ожидаемого тела ответа.\r\n */\r\nexport type TResponseType = \"text\" | \"blob\" | \"arrayBuffer\" | \"json\";\r\n\r\ntype TAllowedMethod = NonNullable<TGetFromServerArgs<unknown>[\"method\"]>;\r\ntype TAllowedContentType = NonNullable<TGetFromServerArgs<unknown>[\"contentType\"]>;\r\ntype TAllowedData = TGetFromServerArgs<unknown>[\"data\"];\r\n\r\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\r\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\r\n};\r\n\r\nconst getAllowedMethod = (method: TMethod): TAllowedMethod => {\r\n  switch (method) {\r\n    case \"GET\":\r\n    case \"POST\":\r\n    case \"PUT\":\r\n    case \"DELETE\":\r\n    case \"HEAD\":\r\n    case \"OPTIONS\":\r\n    case \"TRACE\":\r\n      return method;\r\n    case \"PATCH\":\r\n      return \"POST\";\r\n    default:\r\n      return \"GET\";\r\n  }\r\n};\r\n\r\nconst getAllowedContentType = (contentType: TContentType): TAllowedContentType => {\r\n  switch (contentType) {\r\n    case \"application/json\":\r\n    case \"multipart/form-data\":\r\n      return contentType;\r\n    default:\r\n      return \"auto\";\r\n  }\r\n};\r\n\r\nconst getAllowedData = (data: unknown): TAllowedData => {\r\n  if (typeof data === \"undefined\" || data === null) {\r\n    return null;\r\n  }\r\n  if (data instanceof FormData) {\r\n    return data;\r\n  }\r\n  if (isPlainObject(data)) {\r\n    return data;\r\n  }\r\n  return null;\r\n};\r\n\r\n\r\n/**\r\n * Базовый контракт yup-схемы для валидации ответа.\r\n * @template TValue Тип входного значения для валидации.\r\n * @template TResult Тип результата после валидации.\r\n */\r\nexport type TValidationSchema<TValue = unknown, TResult = TValue> = {\r\n  validate?: (value: TValue, options?: Record<string, unknown>) => Promise<TResult> | TResult;\r\n  validateSync?: (value: TValue, options?: Record<string, unknown>) => TResult;\r\n};\r\n\r\n\r\n/**\r\n * Клиент для работы с API. Singleton.\r\n * @example\r\n * const Api = new ApiClient({\r\n *   url: process.env.API_URL,\r\n *   ver: \"api/v1\"\r\n * });\r\n * await Api.post({\r\n *   endpoint: \"myEndpoint/\",\r\n *   data: new FormData()\r\n * }).then(data => data);\r\n */\r\nexport class ApiClient {\r\n\r\n  /**\r\n   * Singleton-экземпляр API-клиента.\r\n   */\r\n  static instance: ApiClient | null;\r\n\r\n  /**\r\n   * Конфигурация API-клиента по умолчанию.\r\n   * Используется как fallback для `url` и `getAdapterFn`.\r\n   */\r\n  static defaults: Required<TArgs> = {\r\n    url: String(process.env?.API_URL ?? \"/api/v1\"),\r\n    getAdapterFn: (resp) => resp,\r\n  };\r\n\r\n  /**\r\n   * Имена всплывающих событий результата API-запросов.\r\n   */\r\n  static events = {\r\n    success: \"apiClient::success\",\r\n    error: \"apiClient::error\",\r\n  };\r\n\r\n  private getAdapterFn: TAdapterFn;\r\n\r\n  #version: string;\r\n\r\n  #url: string;\r\n\r\n  /**\r\n   * Сохраняет точные входной и выходной типы синхронного или асинхронного адаптера.\r\n   * @template TResponse Тип исходного ответа.\r\n   * @template TResult Тип результата после адаптации.\r\n   * @param adapter Адаптер ответа.\r\n   * @returns Переданный адаптер с сохраненными типами.\r\n   */\r\n  static defineAdapter = <TResponse, TResult>(\r\n    adapter: TAdapterFn<TResponse, TResult>\r\n  ): TAdapterFn<TResponse, TResult> => adapter;\r\n\r\n  constructor(props: TArgs) {\r\n\r\n    if (!ApiClient.instance) {\r\n      ApiClient.instance = this;\r\n    }\r\n\r\n    this.url = props?.url ?? ApiClient.defaults.url;\r\n    this.getAdapterFn = typeof props?.getAdapterFn === \"function\" ? props.getAdapterFn : ApiClient.defaults.getAdapterFn;\r\n\r\n    return ApiClient.instance;\r\n  }\r\n\r\n  /**\r\n   * Получает базовый URL API\r\n   * @returns {String} базовый URL\r\n   */\r\n  get url() {\r\n    return this.#url;\r\n  }\r\n\r\n  /**\r\n   * Устанавливает базовый URL API\r\n   * @param url{String} новый базовый URL\r\n   */\r\n  set url(url) {\r\n    this.#url = url;\r\n  }\r\n\r\n  /**\r\n   * Формирует полный URL endpoint из базового URL и относительного пути\r\n   * @param baseUrl{String} базовый URL\r\n   * @param endpoint{String} относительный путь endpoint\r\n   * @returns {String} полный URL endpoint\r\n   */\r\n  static getEndpoint(baseUrl: string, endpoint: string) {\r\n    return endpoint.startsWith(\"/\") || baseUrl.endsWith(\"/\") ? `${baseUrl}${endpoint}` : `${baseUrl}/${endpoint}`;\r\n  }\r\n\r\n  /**\r\n   * Отправляет GET запрос на endpoint\r\n   * @template TResponse - тип ответа сервера\r\n   * @template TResult - тип результата после адаптера\r\n   * @param params{Object} параметры запроса\r\n   * @param params.endpoint{String} адрес endpoint\r\n   * @param params.data{Object=} данные запроса\r\n   * @param params.signal{AbortSignal=} сигнал для отмены запроса\r\n   * @param params.responseType{String=} тип ожидаемого ответа (text, blob, arrayBuffer, json)\r\n   * @param params.getAdapterFn{Function=} функция-адаптер для преобразования ответа\r\n   * @param params.credentials{String=} режим передачи credentials\r\n   * @param params.headers{Object=} дополнительные заголовки\r\n   * @param params.timeout{Number=} таймаут запроса в миллисекундах\r\n   * @returns {Promise<TResult>} промис с результатом запроса\r\n   */\r\n  get<TResponse = unknown, TResult = TResponse>({\r\n    endpoint,\r\n    data,\r\n    ...props\r\n  }: TRequestProps<TResponse, TResult>): TApiClientRequest<TResult> {\r\n    return this.#appendValidationToRequest(this.#getRequestResult<TResponse, TResult>({\r\n      ...props,\r\n      endpoint,\r\n      data,\r\n      method: \"GET\",\r\n    }));\r\n  }\r\n\r\n  /**\r\n   * Отправляет POST запрос на endpoint\r\n   * @template TResponse - тип ответа сервера\r\n   * @template TResult - тип результата после адаптера\r\n   * @param params{Object} параметры запроса\r\n   * @param params.endpoint{String} адрес endpoint\r\n   * @param params.data{Object=} данные запроса\r\n   * @param params.signal{AbortSignal=} сигнал для отмены запроса\r\n   * @param params.contentType{String=} тип контента запроса\r\n   * @param params.responseType{String=} тип ожидаемого ответа (text, blob, arrayBuffer, json)\r\n   * @param params.getAdapterFn{Function=} функция-адаптер для преобразования ответа\r\n   * @param params.credentials{String=} режим передачи credentials\r\n   * @param params.headers{Object=} дополнительные заголовки\r\n   * @param params.timeout{Number=} таймаут запроса в миллисекундах\r\n   * @returns {Promise<TResult>} промис с результатом запроса\r\n   */\r\n  post<TResponse = unknown, TResult = TResponse>({\r\n    endpoint,\r\n    data,\r\n    ...props\r\n  }: TRequestProps<TResponse, TResult>): TApiClientRequest<TResult> {\r\n    return this.#appendValidationToRequest(this.#getRequestResult<TResponse, TResult>({\r\n      ...props,\r\n      endpoint,\r\n      data,\r\n      method: \"POST\",\r\n    }));\r\n  }\r\n\r\n  /**\r\n   * Добавляет к промису запроса чейнинговый метод `validate`.\r\n   * @private\r\n   * @template TResult Тип результата API-запроса.\r\n   * @param request{Promise<TResult>} промис запроса.\r\n   * @returns {TApiClientRequest<TResult>} расширенный промис запроса.\r\n   */\r\n  #appendValidationToRequest<TResult>(request: Promise<TResult>): TApiClientRequest<TResult> {\r\n    const withValidation = request as TApiClientRequest<TResult>;\r\n    withValidation.validate = <TValidated = TResult>(\r\n      schema: TValidationSchema<TResult, TValidated>,\r\n      options: Record<string, unknown> = {}\r\n    ): Promise<TValidated> => {\r\n      const hasAsyncValidation = !!schema && typeof schema.validate === \"function\";\r\n      const hasSyncValidation = !!schema && typeof schema.validateSync === \"function\";\r\n\r\n      if (!hasAsyncValidation && !hasSyncValidation) {\r\n        return Promise.reject(new TypeError(\"[ApiClient] Invalid validation schema. Use yup schema with validate or validateSync\"));\r\n      }\r\n\r\n      return request.then((resp: TResult) => {\r\n        if (hasAsyncValidation) {\r\n          return Promise.resolve((schema.validate as (value: TResult, opts?: Record<string, unknown>) => Promise<TValidated> | TValidated)(resp, options));\r\n        }\r\n        return Promise.resolve((schema.validateSync as (value: TResult, opts?: Record<string, unknown>) => TValidated)(resp, options));\r\n      });\r\n    };\r\n    return withValidation;\r\n  }\r\n\r\n  /**\r\n   * Выполняет запрос\r\n   * @param endpoint{String} endpoint запроса\r\n   * @param data{Object=} объект или FormData с данными\r\n   * @param signal{AbortSignal} экземпляр вызова AbortSignal\r\n   * @param type{String=} тип ожидаемого ответа\r\n   * @param contentType{String=} тип контента запроса\r\n   * @param method{String=} метод запроса\r\n   * @param credentials{String=}\r\n   * @param headers\r\n   * @returns {Promise<Response>}\r\n   */\r\n  async #getRequestResult<TResponse = unknown, TResult = TResponse>({\r\n    signal,\r\n    endpoint,\r\n    data,\r\n    method = \"GET\",\r\n    contentType = \"application/json\",\r\n    responseType = \"json\",\r\n    getAdapterFn,\r\n    credentials = \"include\",\r\n    headers = {\r\n      \"X-Requested-With\": \"XMLHttpRequest\",\r\n    },\r\n    timeout = 20000,\r\n  }: TRequestProps<TResponse, TResult>): Promise<TResult> {\r\n\r\n    const root = getDocument().documentElement;\r\n    const url = ApiClient.getEndpoint(this.url, endpoint);\r\n    const adapter = (getAdapterFn ?? this.getAdapterFn) as TRequestAdapterFn<TResponse, TResult>;\r\n\r\n    const request = getFromServer<TResponse>({\r\n      type: responseType,\r\n      url,\r\n      timeout,\r\n      data: getAllowedData(data),\r\n      method: getAllowedMethod(method),\r\n      contentType: getAllowedContentType(contentType),\r\n      signal,\r\n      credentials,\r\n      headers,\r\n    });\r\n\r\n    return await request\r\n      .then(async (resp: TResponse) => {\r\n        const adaptedResp = await adapter(resp);\r\n        bubble(root, ApiClient.events.success, { url, data, resp: adaptedResp });\r\n        return adaptedResp;\r\n      })\r\n      .catch((resp: unknown) => {\r\n        bubble(root, ApiClient.events.error, { url, data, resp });\r\n        return Promise.reject(resp);\r\n      });\r\n  }\r\n\r\n}\r\n\r\n"],"names":["isPlainObject","value","Array","isArray","getAllowedMethod","method","getAllowedContentType","contentType","getAllowedData","data","FormData","ApiClient","static","url","String","process","env","API_URL","getAdapterFn","resp","success","error","version","adapter","constructor","props","instance","this","defaults","getEndpoint","baseUrl","endpoint","startsWith","endsWith","get","appendValidationToRequest","getRequestResult","post","request","withValidation","validate","schema","options","hasAsyncValidation","hasSyncValidation","validateSync","Promise","reject","TypeError","then","resolve","signal","responseType","credentials","headers","timeout","root","getDocument","documentElement","getFromServer","type","async","adaptedResp","bubble","events","catch"],"mappings":"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"}