UNPKG

@wmmz/fn-api-client

Version:

Cliente HTTP para requisições API com tratamento de erros e respostas padronizadas

688 lines (683 loc) 22.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ApiClient: () => ApiClient, CubeGraphQLClient: () => CubeGraphQLClient, buildCubeQuery: () => buildCubeQuery, buildFields: () => buildFields, buildWhereClause: () => buildWhereClause }); module.exports = __toCommonJS(index_exports); // src/utils/graphqlHelpers.ts function buildFields(fields = {}) { return Object.entries(fields).map(([entity, entityFields]) => { const selectedFields = Object.entries(entityFields || {}).filter(([, selected]) => selected).map(([field]) => field).join("\n "); return selectedFields ? ` ${entity} { ${selectedFields} }` : ""; }).filter(Boolean).join("\n"); } function buildWhereClause(where = {}) { const conditions = Object.entries(where).map(([entity, entityFilters]) => { const filters = Object.entries(entityFilters || {}).map(([field, conditions2]) => { const filterStr = Object.entries(conditions2 || {}).map(([operator, value]) => { if (Array.isArray(value)) { return `${operator}: [${value.join(", ")}]`; } return `${operator}: ${JSON.stringify(value)}`; }).join(", "); return `${field}: {${filterStr}}`; }).join(", "); return filters ? `${entity}: {${filters}}` : ""; }).filter(Boolean).join(", "); return conditions ? `where: {${conditions}}` : ""; } function buildCubeQuery(options = {}) { const { limit, offset, where, fields, defaultEntity, defaultFields } = options; const whereClause = buildWhereClause(where); const selectedFields = buildFields(fields); const limitClause = limit ? `limit: ${limit}` : ""; const offsetClause = offset ? `offset: ${offset}` : ""; const params = [limitClause, offsetClause, whereClause].filter(Boolean).join("\n "); const defaultFieldsString = defaultFields ? ` ${defaultEntity} { ${defaultFields.join("\n ")} }` : ""; return ` query CubeQuery { cube${params ? `( ${params} )` : ""} { ${selectedFields || defaultFieldsString} } } `.trim(); } // src/services/GraphQLClient.ts var import_axios = __toESM(require("axios")); var GraphQLClient = class { /** * Cria uma nova instância do cliente GraphQL * @param config Configurações do cliente * @param config.baseURL URL do endpoint GraphQL * @param config.timeout Tempo limite em milissegundos (padrão: 30000) * @param config.headers Headers customizados para todas as requisições */ constructor(config) { this.api = import_axios.default.create({ baseURL: config.baseURL, timeout: config.timeout || 3e4, headers: { "Content-Type": "application/json", ...config.headers } }); } /** * Extrai e padroniza os dados de erro do Axios * @param error Erro original do Axios * @returns Objeto de erro padronizado com mensagem, status e detalhes * @internal */ extractErrorData(error) { return { message: error.response?.data?.message || "Ocorreu um erro na requisi\xE7\xE3o", status: error.response?.status || 500, code: error.code, details: error.response?.data }; } /** * Extrai e padroniza os dados da resposta do Axios * @param response Resposta original do Axios * @returns Objeto de resposta padronizado com dados, status e mensagem * @internal */ extractResponseData(response) { return { data: response.data.data, status: response.status, message: "Opera\xE7\xE3o realizada com sucesso" }; } /** * Executa uma query GraphQL * @param query String da query GraphQL * @param variables Variáveis a serem passadas para a query * @param callbacks Objeto com callbacks de sucesso e erro * @param callbacks.onSuccess Callback chamado quando a query é bem sucedida * @param callbacks.onError Callback chamado quando ocorre um erro * * @example * ```typescript * const query = ` * query GetProducts($category: String!, $limit: Int) { * products(category: $category, limit: $limit) { * id * name * price * } * } * ` * * const variables = { * category: 'electronics', * limit: 10 * } * * await client.query(query, variables, { * onSuccess: (response) => console.log('Produtos:', response.data.products), * onError: (error) => console.error('Erro:', error.message) * }) * ``` */ async query(query, variables = {}, { onSuccess, onError } = {}) { try { const response = await this.api.post("", { query, variables }); const formattedResponse = this.extractResponseData(response); onSuccess?.(formattedResponse); } catch (error) { const formattedError = this.extractErrorData(error); onError?.(formattedError); } } /** * Executa uma mutation GraphQL * @param mutation String da mutation GraphQL * @param variables Variáveis a serem passadas para a mutation * @param callbacks Objeto com callbacks de sucesso e erro * @param callbacks.onSuccess Callback chamado quando a mutation é bem sucedida * @param callbacks.onError Callback chamado quando ocorre um erro * * @example * ```typescript * const mutation = ` * mutation UpdateProduct($id: ID!, $input: ProductInput!) { * updateProduct(id: $id, input: $input) { * id * name * price * } * } * ` * * const variables = { * id: '123', * input: { * name: 'Novo Nome', * price: 99.99 * } * } * * await client.mutate(mutation, variables, { * onSuccess: (response) => console.log('Produto atualizado:', response.data.updateProduct), * onError: (error) => console.error('Erro ao atualizar:', error.message) * }) * ``` */ async mutate(mutation, variables = {}, { onSuccess, onError } = {}) { return this.query(mutation, variables, { onSuccess, onError }); } }; // src/services/CubeGraphQLClient.ts var CubeGraphQLClient = class extends GraphQLClient { /** * Realiza uma consulta GraphQL específica para o Cube * @param url Endpoint da API GraphQL do Cube * @param options Opções da query do Cube * @param options.limit Limite de registros por página * @param options.offset Número de registros para pular (paginação) * @param options.where Filtros a serem aplicados na consulta * @param options.fields Campos a serem retornados na consulta * @param options.defaultEntity Entidade padrão para consulta * @param options.defaultFields Campos padrão a serem retornados * @param callbacks Callbacks para sucesso e erro * * @example * ```typescript * // Exemplo de consulta com agregações * await client.query('/cubejs-api/graphql', { * where: { * vendas: { * data: { equals: '2024-03-01' } * } * }, * fields: { * vendas: { * total_vendas: true, * quantidade_pedidos: true, * ticket_medio: true * } * } * }, { * onSuccess: (response) => console.log('Métricas:', response.data), * onError: (error) => console.error('Erro:', error.message) * }) * ``` */ async query(url, options = {}, callbacks = {}) { const query = buildCubeQuery(options); await this.api.post(url, { query }); return super.query(query, {}, callbacks); } /** * Extrai dados da resposta do Axios e padroniza o formato específico do Cube * @param response Resposta original do Axios * @returns Objeto de resposta padronizado com dados do Cube * @internal */ extractResponseData(response) { return { data: response.data.data.cube, status: response.status, message: "Opera\xE7\xE3o realizada com sucesso" }; } }; // src/services/ApiClient.ts var import_axios2 = __toESM(require("axios")); var ApiClient = class { /** * Cria uma nova instância do cliente API * @param config Configurações do cliente * @param config.baseURL URL base para todas as requisições * @param config.timeout Tempo limite em milissegundos (padrão: 10000) * @param config.headers Headers customizados para todas as requisições */ constructor(config) { this.requestInterceptors = []; this.responseInterceptors = []; this.errorInterceptors = []; this.api = import_axios2.default.create({ baseURL: config.baseURL, timeout: config.timeout || 1e4, headers: { "Content-Type": "application/json", ...config.headers } }); } /** * Adiciona um interceptador de requisição. * Permite modificar a configuração da requisição antes do envio. * * @param interceptor Função que recebe e retorna a configuração da requisição * * @example * ```typescript * api.addRequestInterceptor((config) => { * const token = storage.getToken() * if (token) { * config.headers = { * ...config.headers, * Authorization: `Bearer ${token}` * } * } * return config * }) * ``` */ addRequestInterceptor(interceptor) { this.requestInterceptors.push(interceptor); } /** * Adiciona um interceptador de resposta. * Permite processar a resposta antes de retorná-la para o callback. * * @param interceptor Função que recebe e retorna a resposta processada * * @example * ```typescript * api.addResponseInterceptor((response) => { * console.log('Resposta interceptada:', response.status) * return { * ...response, * message: `[${new Date().toISOString()}] ${response.message}` * } * }) * ``` */ addResponseInterceptor(interceptor) { this.responseInterceptors.push(interceptor); } /** * Adiciona um interceptador de erro. * Permite processar erros antes de retorná-los para o callback. * * @param interceptor Função que recebe um erro e retorna erro processado ou nova resposta * * @example * ```typescript * api.addErrorInterceptor(async (error) => { * if (error.status === 401) { * await refreshToken() * // Pode retornar um novo ApiResponse ou o erro processado * return { * ...error, * message: 'Token renovado automaticamente' * } * } * return error * }) * ``` */ addErrorInterceptor(interceptor) { this.errorInterceptors.push(interceptor); } /** * Remove um interceptador de requisição específico. * @param interceptor Interceptador a ser removido */ removeRequestInterceptor(interceptor) { const index = this.requestInterceptors.indexOf(interceptor); if (index > -1) { this.requestInterceptors.splice(index, 1); } } /** * Remove um interceptador de resposta específico. * @param interceptor Interceptador a ser removido */ removeResponseInterceptor(interceptor) { const index = this.responseInterceptors.indexOf(interceptor); if (index > -1) { this.responseInterceptors.splice(index, 1); } } /** * Remove um interceptador de erro específico. * @param interceptor Interceptador a ser removido */ removeErrorInterceptor(interceptor) { const index = this.errorInterceptors.indexOf(interceptor); if (index > -1) { this.errorInterceptors.splice(index, 1); } } /** * Remove todos os interceptadores. */ clearInterceptors() { this.requestInterceptors = []; this.responseInterceptors = []; this.errorInterceptors = []; } /** * Aplica todos os interceptadores de requisição à configuração. * @param config Configuração inicial da requisição * @returns Configuração processada pelos interceptadores * @internal */ applyRequestInterceptors(config) { return this.requestInterceptors.reduce( (processedConfig, interceptor) => interceptor(processedConfig), config ); } /** * Aplica todos os interceptadores de resposta. * @param response Resposta original * @returns Resposta processada pelos interceptadores * @internal */ applyResponseInterceptors(response) { return this.responseInterceptors.reduce( (processedResponse, interceptor) => interceptor(processedResponse), response ); } /** * Aplica todos os interceptadores de erro. * @param error Erro original * @returns Erro processado ou nova resposta * @internal */ async applyErrorInterceptors(error) { let processedError = error; for (const interceptor of this.errorInterceptors) { try { const result = await interceptor(error); processedError = result; if ("data" in result) { break; } } catch (interceptorError) { console.warn("Erro no interceptador de erro:", interceptorError); } } return processedError; } /** * Extrai e padroniza os dados de erro do Axios * @param error Erro original do Axios * @returns Objeto de erro padronizado * @internal */ extractErrorData(error) { return { message: error.response?.data?.message || "Ocorreu um erro na requisi\xE7\xE3o", status: error.response?.status || 500, code: error.code, details: error.response?.data }; } /** * Extrai e padroniza os dados da resposta do Axios * @param response Resposta original do Axios * @returns Objeto de resposta padronizado * @internal */ extractResponseData(response) { const responseData = response.data; return { data: response.data, status: response.status, message: responseData.message || "Opera\xE7\xE3o realizada com sucesso" }; } /** * Realiza uma requisição GET * @param url Caminho da requisição (será concatenado com baseURL) * @param paramsOrCallbacks Parâmetros de query string ou objeto com callbacks * @param callbacks Objeto com callbacks de sucesso e erro (opcional se parâmetros forem fornecidos) * * @example * ```typescript * // GET simples * api.get('/usuarios/1', { * onSuccess: (response) => console.log('Usuário:', response.data), * onError: (error) => console.error('Erro:', error.message) * }) * * // GET com parâmetros * api.get('/votos', { userId: '123', agendaId: '456' }, { * onSuccess: (response) => console.log('Voto:', response.data), * onError: (error) => console.error('Erro:', error.message) * }) * ``` */ async get(url, paramsOrCallbacks, callbacks) { let params; let finalCallbacks; if (paramsOrCallbacks && ("onSuccess" in paramsOrCallbacks || "onError" in paramsOrCallbacks)) { params = void 0; finalCallbacks = paramsOrCallbacks; } else { params = paramsOrCallbacks; finalCallbacks = callbacks || {}; } const { onSuccess, onError } = finalCallbacks; try { const requestConfig = this.applyRequestInterceptors({ url, method: "GET", headers: {}, params }); const response = await this.api.get(requestConfig.url || url, { headers: requestConfig.headers, timeout: requestConfig.timeout, params: requestConfig.params || params }); let formattedResponse = this.extractResponseData(response); formattedResponse = this.applyResponseInterceptors(formattedResponse); onSuccess?.(formattedResponse); } catch (error) { let formattedError = this.extractErrorData(error); const processedError = await this.applyErrorInterceptors(formattedError); if ("data" in processedError) { onSuccess?.(processedError); } else { onError?.(processedError); } } } /** * Realiza uma requisição POST * @param url Caminho da requisição (será concatenado com baseURL) * @param data Dados a serem enviados no corpo da requisição * @param callbacks Objeto com callbacks de sucesso e erro * @param callbacks.onSuccess Callback chamado em caso de sucesso * @param callbacks.onError Callback chamado em caso de erro * * @example * ```typescript * const dados = { nome: 'João', idade: 30 } * api.post('/usuarios', dados, { * onSuccess: (response) => console.log('Criado:', response.data), * onError: (error) => console.error('Erro:', error.message) * }) * ``` */ async post(url, data, { onSuccess, onError } = {}) { try { const requestConfig = this.applyRequestInterceptors({ url, method: "POST", headers: {}, data }); const response = await this.api.post( requestConfig.url || url, requestConfig.data || data, { headers: requestConfig.headers, timeout: requestConfig.timeout } ); let formattedResponse = this.extractResponseData(response); formattedResponse = this.applyResponseInterceptors(formattedResponse); onSuccess?.(formattedResponse); } catch (error) { let formattedError = this.extractErrorData(error); const processedError = await this.applyErrorInterceptors(formattedError); if ("data" in processedError) { onSuccess?.(processedError); } else { onError?.(processedError); } } } /** * Realiza uma requisição PUT * @param url Caminho da requisição (será concatenado com baseURL) * @param data Dados a serem enviados no corpo da requisição * @param callbacks Objeto com callbacks de sucesso e erro * @param callbacks.onSuccess Callback chamado em caso de sucesso * @param callbacks.onError Callback chamado em caso de erro * * @example * ```typescript * const atualizacao = { idade: 31 } * api.put('/usuarios/1', atualizacao, { * onSuccess: (response) => console.log('Atualizado:', response.data), * onError: (error) => console.error('Erro:', error.message) * }) * ``` */ async put(url, data, { onSuccess, onError } = {}) { try { const requestConfig = this.applyRequestInterceptors({ url, method: "PUT", headers: {}, data }); const response = await this.api.put( requestConfig.url || url, requestConfig.data || data, { headers: requestConfig.headers, timeout: requestConfig.timeout } ); let formattedResponse = this.extractResponseData(response); formattedResponse = this.applyResponseInterceptors(formattedResponse); onSuccess?.(formattedResponse); } catch (error) { let formattedError = this.extractErrorData(error); const processedError = await this.applyErrorInterceptors(formattedError); if ("data" in processedError) { onSuccess?.(processedError); } else { onError?.(processedError); } } } /** * Realiza uma requisição DELETE * @param url Caminho da requisição (será concatenado com baseURL) * @param paramsOrCallbacks Parâmetros de query string ou objeto com callbacks * @param callbacks Objeto com callbacks de sucesso e erro (opcional se parâmetros forem fornecidos) * * @example * ```typescript * // DELETE simples * api.delete('/usuarios/1', { * onSuccess: (response) => console.log('Deletado com sucesso'), * onError: (error) => console.error('Erro ao deletar:', error.message) * }) * * // DELETE com parâmetros * api.delete('/votos', { userId: '123', agendaId: '456' }, { * onSuccess: (response) => console.log('Voto removido:', response.data), * onError: (error) => console.error('Erro:', error.message) * }) * ``` */ async delete(url, paramsOrCallbacks, callbacks) { let params; let finalCallbacks; if (paramsOrCallbacks && ("onSuccess" in paramsOrCallbacks || "onError" in paramsOrCallbacks)) { params = void 0; finalCallbacks = paramsOrCallbacks; } else { params = paramsOrCallbacks; finalCallbacks = callbacks || {}; } const { onSuccess, onError } = finalCallbacks; try { const requestConfig = this.applyRequestInterceptors({ url, method: "DELETE", headers: {}, params }); const response = await this.api.delete(requestConfig.url || url, { headers: requestConfig.headers, timeout: requestConfig.timeout, params: requestConfig.params || params }); let formattedResponse = this.extractResponseData(response); formattedResponse = this.applyResponseInterceptors(formattedResponse); onSuccess?.(formattedResponse); } catch (error) { let formattedError = this.extractErrorData(error); const processedError = await this.applyErrorInterceptors(formattedError); if ("data" in processedError) { onSuccess?.(processedError); } else { onError?.(processedError); } } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ApiClient, CubeGraphQLClient, buildCubeQuery, buildFields, buildWhereClause }); //# sourceMappingURL=index.js.map