@settlemint/sdk-utils
Version:
Shared utilities and helper functions for SettleMint SDK modules
226 lines (220 loc) • 7.64 kB
JavaScript
//#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
export { appendHeaders, fetchWithRetry, graphqlFetchWithRetry };
//# sourceMappingURL=http.js.map