UNPKG

@settlemint/sdk-utils

Version:

Shared utilities and helper functions for SettleMint SDK modules

309 lines (303 loc) • 9.54 kB
//#region src/json.ts /** * Attempts to parse a JSON string into a typed value, returning a default value if parsing fails. * * @param value - The JSON string to parse * @param defaultValue - The value to return if parsing fails or results in null/undefined * @returns The parsed JSON value as type T, or the default value if parsing fails * * @example * import { tryParseJson } from "@settlemint/sdk-utils"; * * const config = tryParseJson<{ port: number }>( * '{"port": 3000}', * { port: 8080 } * ); * // Returns: { port: 3000 } * * const invalid = tryParseJson<string[]>( * 'invalid json', * [] * ); * // Returns: [] */ function tryParseJson(value, defaultValue = null) { try { const parsed = JSON.parse(value); if (parsed === undefined || parsed === null) { return defaultValue; } return parsed; } catch (_err) { return defaultValue; } } /** * Extracts a JSON object from a string. * * @param value - The string to extract the JSON object from * @returns The parsed JSON object, or null if no JSON object is found * @throws {Error} If the input string is too long (longer than 5000 characters) * @example * import { extractJsonObject } from "@settlemint/sdk-utils"; * * const json = extractJsonObject<{ port: number }>( * 'port info: {"port": 3000}', * ); * // Returns: { port: 3000 } */ function extractJsonObject(value) { if (value.length > 5e3) { throw new Error("Input too long"); } const result = /\{([\s\S]*)\}/.exec(value); if (!result) { return null; } return tryParseJson(result[0]); } /** * Converts a value to a JSON stringifiable format. * * @param value - The value to convert * @returns The JSON stringifiable value * * @example * import { makeJsonStringifiable } from "@settlemint/sdk-utils"; * * const json = makeJsonStringifiable<{ amount: bigint }>({ amount: BigInt(1000) }); * // Returns: '{"amount":"1000"}' */ function makeJsonStringifiable(value) { if (value === undefined || value === null) { return value; } return tryParseJson(JSON.stringify(value, (_, value$1) => typeof value$1 === "bigint" ? value$1.toString() : value$1)); } //#endregion //#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/string.ts /** * Capitalizes the first letter of a string. * * @param val - The string to capitalize * @returns The input string with its first letter capitalized * * @example * import { capitalizeFirstLetter } from "@settlemint/sdk-utils"; * * const capitalized = capitalizeFirstLetter("hello"); * // Returns: "Hello" */ function capitalizeFirstLetter(val) { return String(val).charAt(0).toUpperCase() + String(val).slice(1); } /** * Converts a camelCase string to a human-readable string. * * @param s - The camelCase string to convert * @returns The human-readable string * * @example * import { camelCaseToWords } from "@settlemint/sdk-utils"; * * const words = camelCaseToWords("camelCaseString"); * // Returns: "Camel Case String" */ function camelCaseToWords(s) { const result = s.replace(/([a-z])([A-Z])/g, "$1 $2"); const withSpaces = result.replace(/([A-Z])([a-z])/g, " $1$2"); const capitalized = capitalizeFirstLetter(withSpaces); return capitalized.replace(/\s+/g, " ").trim(); } /** * Replaces underscores and hyphens with spaces. * * @param s - The string to replace underscores and hyphens with spaces * @returns The input string with underscores and hyphens replaced with spaces * * @example * import { replaceUnderscoresAndHyphensWithSpaces } from "@settlemint/sdk-utils"; * * const result = replaceUnderscoresAndHyphensWithSpaces("Already_Spaced-Second"); * // Returns: "Already Spaced Second" */ function replaceUnderscoresAndHyphensWithSpaces(s) { return s.replace(/[-_]/g, " "); } /** * Truncates a string to a maximum length and appends "..." if it is longer. * * @param value - The string to truncate * @param maxLength - The maximum length of the string * @returns The truncated string or the original string if it is shorter than the maximum length * * @example * import { truncate } from "@settlemint/sdk-utils"; * * const truncated = truncate("Hello, world!", 10); * // Returns: "Hello, wor..." */ function truncate(value, maxLength) { if (value.length <= maxLength) { return value; } return `${value.slice(0, maxLength)}...`; } //#endregion //#region src/url.ts /** * Extracts the base URL before a specific segment in a URL. * * @param baseUrl - The base URL to extract the path from * @param pathSegment - The path segment to start from * @returns The base URL before the specified segment * @example * ```typescript * import { extractBaseUrlBeforeSegment } from "@settlemint/sdk-utils/url"; * * const baseUrl = extractBaseUrlBeforeSegment("https://example.com/api/v1/subgraphs/name/my-subgraph", "/subgraphs"); * // Returns: "https://example.com/api/v1" * ``` */ function extractBaseUrlBeforeSegment(baseUrl, pathSegment) { const url = new URL(baseUrl); if (pathSegment.trim() === "") { return url.toString(); } const segmentIndex = url.pathname.indexOf(pathSegment); return url.origin + (segmentIndex >= 0 ? url.pathname.substring(0, segmentIndex) : url.pathname); } //#endregion export { camelCaseToWords, capitalizeFirstLetter, extractBaseUrlBeforeSegment, extractJsonObject, makeJsonStringifiable, replaceUnderscoresAndHyphensWithSpaces, retryWhenFailed, truncate, tryParseJson }; //# sourceMappingURL=index.js.map