@wmmz/fn-api-client
Version:
Cliente HTTP para requisições API com tratamento de erros e respostas padronizadas
647 lines (644 loc) • 20.7 kB
JavaScript
// 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
import axios from "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 = axios.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
import axios2 from "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 = axios2.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);
}
}
}
};
export {
ApiClient,
CubeGraphQLClient,
buildCubeQuery,
buildFields,
buildWhereClause
};
//# sourceMappingURL=index.mjs.map