UNPKG

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
"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