@settlemint/sdk-utils
Version:
Shared utilities and helper functions for SettleMint SDK modules
318 lines (311 loc) • 9.8 kB
JavaScript
//#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
exports.camelCaseToWords = camelCaseToWords;
exports.capitalizeFirstLetter = capitalizeFirstLetter;
exports.extractBaseUrlBeforeSegment = extractBaseUrlBeforeSegment;
exports.extractJsonObject = extractJsonObject;
exports.makeJsonStringifiable = makeJsonStringifiable;
exports.replaceUnderscoresAndHyphensWithSpaces = replaceUnderscoresAndHyphensWithSpaces;
exports.retryWhenFailed = retryWhenFailed;
exports.truncate = truncate;
exports.tryParseJson = tryParseJson;
//# sourceMappingURL=index.cjs.map