guardz-axios
Version:
Type-safe HTTP client built on top of Axios with runtime validation using guardz. Part of the guardz ecosystem for comprehensive TypeScript type safety.
310 lines • 10.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SafeRequestBuilder = void 0;
exports.safeGet = safeGet;
exports.safePost = safePost;
exports.safePut = safePut;
exports.safePatch = safePatch;
exports.safeDelete = safeDelete;
exports.safeRequest = safeRequest;
exports.safe = safe;
exports.createSafeApiContext = createSafeApiContext;
exports.createTypedSafeGet = createTypedSafeGet;
exports.createTypedSafePost = createTypedSafePost;
const axios_1 = __importDefault(require("axios"));
const guardz_1 = require("guardz");
const status_types_1 = require("../types/status-types");
const axios_response_guards_1 = require("../guards/axios-response-guards");
/**
* Pattern 1: Curried Function (Google/Ramda style)
* Usage: const getUserSafely = safeGet({ guard: isUser });
* const result = await getUserSafely('https://api.example.com/users/1');
* // result is { status: Status.SUCCESS, data: User } | { status: Status.ERROR, data: AxiosError }
*/
function safeGet(config) {
return async (url, axiosConfig) => {
return executeRequest({
...axiosConfig,
url,
method: "GET",
}, config);
};
}
function safePost(config) {
return async (url, data, axiosConfig) => {
return executeRequest({
...axiosConfig,
url,
method: "POST",
data,
}, config);
};
}
function safePut(config) {
return async (url, data, axiosConfig) => {
return executeRequest({
...axiosConfig,
url,
method: "PUT",
data,
}, config);
};
}
function safePatch(config) {
return async (url, data, axiosConfig) => {
return executeRequest({
...axiosConfig,
url,
method: "PATCH",
data,
}, config);
};
}
function safeDelete(config) {
return async (url, axiosConfig) => {
return executeRequest({
...axiosConfig,
url,
method: "DELETE",
}, config);
};
}
/**
* Pattern 2: Configuration-first (Apollo/React Query style)
* Usage: const result = await safeRequest({ url: '/users/1', method: 'GET', guard: isUser });
*/
async function safeRequest(requestConfig) {
const { guard, tolerance, identifier, axiosInstance, validateResponse, timeout, ...axiosConfig } = requestConfig;
return executeRequest(axiosConfig, {
guard,
tolerance,
identifier,
axiosInstance,
validateResponse,
timeout,
});
}
/**
* Pattern 3: Fluent API Builder
* Usage: const result = await safe().get('/users/1').guard(isUser).execute();
*/
class SafeRequestBuilder {
constructor() {
this.config = {};
this.axiosConfig = {};
}
get(url) {
this.axiosConfig = { ...this.axiosConfig, method: "GET", url };
return this;
}
post(url, data) {
this.axiosConfig = { ...this.axiosConfig, method: "POST", url, data };
return this;
}
put(url, data) {
this.axiosConfig = { ...this.axiosConfig, method: "PUT", url, data };
return this;
}
patch(url, data) {
this.axiosConfig = { ...this.axiosConfig, method: "PATCH", url, data };
return this;
}
delete(url) {
this.axiosConfig = { ...this.axiosConfig, method: "DELETE", url };
return this;
}
guard(guardFn) {
const newBuilder = this;
newBuilder.config.guard = guardFn;
return newBuilder;
}
tolerance(enabled = true) {
this.config.tolerance = enabled;
return this;
}
identifier(id) {
this.config.identifier = id;
return this;
}
timeout(ms) {
this.config.timeout = ms;
this.axiosConfig.timeout = ms;
return this;
}
headers(headers) {
this.axiosConfig.headers = { ...this.axiosConfig.headers, ...headers };
return this;
}
baseURL(url) {
this.axiosConfig.baseURL = url;
return this;
}
async execute() {
if (!this.config.guard) {
throw new Error("Guard function is required. Use .guard() method to set it.");
}
return executeRequest(this.axiosConfig, this.config);
}
}
exports.SafeRequestBuilder = SafeRequestBuilder;
function safe() {
return new SafeRequestBuilder();
}
function createSafeApiContext(contextConfig = {}) {
const { baseURL, timeout, headers, defaultTolerance = false, axiosInstance = axios_1.default.create(), } = contextConfig;
if (baseURL)
axiosInstance.defaults.baseURL = baseURL;
if (timeout)
axiosInstance.defaults.timeout = timeout;
if (headers)
axiosInstance.defaults.headers = {
...axiosInstance.defaults.headers,
...headers,
};
return {
get: (url, config) => safeGet({
...config,
tolerance: config.tolerance ?? defaultTolerance,
axiosInstance,
})(url),
post: (url, config, data) => safePost({
...config,
tolerance: config.tolerance ?? defaultTolerance,
axiosInstance,
})(url, data),
put: (url, config, data) => safePut({
...config,
tolerance: config.tolerance ?? defaultTolerance,
axiosInstance,
})(url, data),
patch: (url, config, data) => safePatch({
...config,
tolerance: config.tolerance ?? defaultTolerance,
axiosInstance,
})(url, data),
delete: (url, config) => safeDelete({
...config,
tolerance: config.tolerance ?? defaultTolerance,
axiosInstance,
})(url),
request: (config) => safeRequest({
...config,
tolerance: config.tolerance ?? defaultTolerance,
axiosInstance,
}),
};
}
/**
* Core execution function that never throws
*/
async function executeRequest(axiosConfig, safeConfig) {
const { guard, tolerance = false, identifier, axiosInstance = axios_1.default, validateResponse = true, timeout, } = safeConfig;
const url = axiosConfig.url || "";
const method = axiosConfig.method || "GET";
const requestId = identifier || `${method} ${url}`;
const configWithHeaders = {
...axiosConfig,
headers: axiosConfig.headers ?? {},
};
try {
// Set timeout if provided
if (timeout) {
axiosConfig.timeout = timeout;
}
// Make the request
const response = await axiosInstance.request(configWithHeaders);
// Validate response structure if enabled
if (validateResponse && !(0, axios_response_guards_1.isAxiosResponse)(response)) {
return {
status: status_types_1.Status.ERROR,
code: 400, // Assuming 400 for validation errors
message: `Invalid response structure for ${requestId}`,
};
}
// Extract and validate data
const responseData = response.data;
if (tolerance) {
// Use tolerance mode - try to extract valid data even if validation fails
const errorMessages = [];
const toleranceConfig = {
identifier: identifier || requestId,
callbackOnError: (errorMessage) => {
errorMessages.push(errorMessage);
},
};
const validatedData = (0, guardz_1.guardWithTolerance)(responseData, guard, toleranceConfig);
if (errorMessages.length > 0) {
return {
status: status_types_1.Status.ERROR,
code: 400, // Assuming 400 for validation errors
message: `Data validation failed for ${requestId}: ${errorMessages.join(", ")}`,
};
}
else {
return {
status: status_types_1.Status.SUCCESS,
data: validatedData,
};
}
}
else {
// Strict validation
const errorMessages = [];
const strictConfig = {
identifier: identifier || requestId,
callbackOnError: (errorMessage) => {
errorMessages.push(errorMessage);
},
};
if (guard(responseData, strictConfig)) {
return {
status: status_types_1.Status.SUCCESS,
data: responseData,
};
}
else {
const errorMsg = errorMessages.length > 0
? `Data validation failed for ${requestId}: ${errorMessages.join(", ")}`
: `Data validation failed for ${requestId}`;
return {
status: status_types_1.Status.ERROR,
code: 400, // Assuming 400 for validation errors
message: errorMsg,
};
}
}
}
catch (error) {
// Always return error result, never throw
if (axios_1.default.isAxiosError(error)) {
return {
status: status_types_1.Status.ERROR,
code: error.response?.status || 500,
message: error.message || "Network Error",
};
}
else {
return {
status: status_types_1.Status.ERROR,
code: 500, // Assuming 500 for unexpected errors
message: `Unexpected error for ${requestId}: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
}
/**
* Helper to create typed safe get function
*/
function createTypedSafeGet(guard) {
return safeGet({ guard });
}
/**
* Helper to create typed safe post function
*/
function createTypedSafePost(guard) {
return safePost({ guard });
}
//# sourceMappingURL=safe-axios-never-throws.js.map