UNPKG

@settlemint/sdk-utils

Version:

Shared utilities and helper functions for SettleMint SDK modules

229 lines (222 loc) • 7.71 kB
//#region src/logging/mask-tokens.ts /** * Masks sensitive SettleMint tokens in output text by replacing them with asterisks. * Handles personal access tokens (PAT), application access tokens (AAT), and service account tokens (SAT). * * @param output - The text string that may contain sensitive tokens * @returns The text with any sensitive tokens masked with asterisks * @example * import { maskTokens } from "@settlemint/sdk-utils/terminal"; * * // Masks a token in text * const masked = maskTokens("Token: sm_pat_****"); // "Token: ***" */ const maskTokens = (output) => { return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, "***"); }; //#endregion //#region src/logging/logger.ts /** * Creates a simple logger with configurable log level * * @param options - Configuration options for the logger * @param options.level - The minimum log level to output (default: warn) * @param options.prefix - The prefix to add to the log message (default: "") * @returns A logger instance with debug, info, warn, and error methods * * @example * import { createLogger } from "@/utils/logging/logger"; * * const logger = createLogger({ level: 'info' }); * * logger.info('User logged in', { userId: '123' }); * logger.error('Operation failed', new Error('Connection timeout')); */ function createLogger(options = {}) { const { level = "warn", prefix = "" } = options; const logLevels = { debug: 0, info: 1, warn: 2, error: 3, none: 4 }; const currentLevelValue = logLevels[level]; const formatArgs = (args) => { if (args.length === 0 || args.every((arg) => arg === undefined || arg === null)) { return ""; } const formatted = args.map((arg) => { if (arg instanceof Error) { return `\n${arg.stack || arg.message}`; } if (typeof arg === "object" && arg !== null) { return `\n${JSON.stringify(arg, null, 2)}`; } return ` ${String(arg)}`; }).join(""); return `, args:${formatted}`; }; const shouldLog = (level$1) => { return logLevels[level$1] >= currentLevelValue; }; return { debug: (message, ...args) => { if (shouldLog("debug")) { console.debug(`\x1b[32m${prefix}[DEBUG] ${maskTokens(message)}${maskTokens(formatArgs(args))}\x1b[0m`); } }, info: (message, ...args) => { if (shouldLog("info")) { console.info(`\x1b[34m${prefix}[INFO] ${maskTokens(message)}${maskTokens(formatArgs(args))}\x1b[0m`); } }, warn: (message, ...args) => { if (shouldLog("warn")) { console.warn(`\x1b[33m${prefix}[WARN] ${maskTokens(message)}${maskTokens(formatArgs(args))}\x1b[0m`); } }, error: (message, ...args) => { if (shouldLog("error")) { console.error(`\x1b[31m${prefix}[ERROR] ${maskTokens(message)}${maskTokens(formatArgs(args))}\x1b[0m`); } } }; } /** * Default logger instance with standard configuration */ const logger = createLogger(); //#endregion //#region src/retry.ts /** * Retry a function when it fails. * @param fn - The function to retry. * @param maxRetries - The maximum number of retries. * @param initialSleepTime - The initial time to sleep between exponential backoff retries. * @param stopOnError - The function to stop on error. * @returns The result of the function or undefined if it fails. * @example * import { retryWhenFailed } from "@settlemint/sdk-utils"; * import { readFile } from "node:fs/promises"; * * const result = await retryWhenFailed(() => readFile("/path/to/file.txt"), 3, 1_000); */ async function retryWhenFailed(fn, maxRetries = 5, initialSleepTime = 1e3, stopOnError) { let retries = 0; const maxAttempts = maxRetries + 1; while (retries < maxAttempts) { try { return await fn(); } catch (e) { const error = e; if (typeof stopOnError === "function") { if (stopOnError(error)) { throw error; } } if (retries >= maxRetries) { throw e; } const baseDelay = 2 ** retries * initialSleepTime; const jitterAmount = initialSleepTime * (Math.random() / 10); const delay = baseDelay + jitterAmount; retries += 1; logger.warn(`An error occurred ${error.message}, retrying in ${delay.toFixed(0)}ms (retry ${retries} of ${maxRetries})...`); await new Promise((resolve) => setTimeout(resolve, delay)); } } throw new Error("Retry failed"); } //#endregion //#region src/http/fetch-with-retry.ts /** * Retry an HTTP request with exponential backoff and jitter. * Only retries on server errors (5xx), rate limits (429), timeouts (408), and network errors. * * @param input - The URL or Request object to fetch * @param init - The fetch init options * @param maxRetries - Maximum number of retry attempts * @param initialSleepTime - Initial sleep time between retries in ms * @returns The fetch Response * @throws Error if all retries fail * @example * import { fetchWithRetry } from "@settlemint/sdk-utils/http"; * * const response = await fetchWithRetry("https://api.example.com/data"); */ async function fetchWithRetry(input, init, maxRetries = 5, initialSleepTime = 3e3) { return retryWhenFailed(async () => { const response = await fetch(input, init); if (response.ok) { return response; } if (response.status < 500 && response.status !== 429 && response.status !== 408 && response.status !== 0) { return response; } throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`); }, maxRetries, initialSleepTime); } //#endregion //#region src/http/graphql-fetch-with-retry.ts /** * Executes a GraphQL request with automatic retries using exponential backoff and jitter. * Only retries on server errors (5xx), rate limits (429), timeouts (408), and network errors. * Will also retry if the GraphQL response contains errors. * * @param input - The URL or Request object for the GraphQL endpoint * @param init - Optional fetch configuration options * @param maxRetries - Maximum retry attempts before failing (default: 5) * @param initialSleepTime - Initial delay between retries in milliseconds (default: 3000) * @returns The parsed GraphQL response data * @throws Error if all retries fail or if GraphQL response contains errors * @example * import { graphqlFetchWithRetry } from "@settlemint/sdk-utils/http"; * * const data = await graphqlFetchWithRetry<{ user: { id: string } }>( * "https://api.example.com/graphql", * { * method: "POST", * headers: { "Content-Type": "application/json" }, * body: JSON.stringify({ * query: `query GetUser($id: ID!) { * user(id: $id) { * id * } * }`, * variables: { id: "123" } * }) * } * ); */ async function graphqlFetchWithRetry(input, init, maxRetries = 5, initialSleepTime = 3e3) { return retryWhenFailed(async () => { const response = await fetchWithRetry(input, init); const json = await response.json(); if (json.errors) { throw new Error(`GraphQL errors in response: ${json.errors.map((error) => error.message).join(", ")}`); } return json.data; }, maxRetries, initialSleepTime); } //#endregion //#region src/http/headers.ts function appendHeaders(headers, additionalHeaders) { const defaultHeaders = typeof headers === "function" ? headers() : headers; const filteredAdditionalHeaders = Object.entries(additionalHeaders).filter(([_, value]) => value !== undefined); if (Array.isArray(defaultHeaders)) { return [...defaultHeaders, ...filteredAdditionalHeaders]; } if (defaultHeaders instanceof Headers) { return new Headers([...defaultHeaders, ...filteredAdditionalHeaders]); } return { ...defaultHeaders, ...Object.fromEntries(filteredAdditionalHeaders) }; } //#endregion exports.appendHeaders = appendHeaders; exports.fetchWithRetry = fetchWithRetry; exports.graphqlFetchWithRetry = graphqlFetchWithRetry; //# sourceMappingURL=http.cjs.map