UNPKG

emitnlog

Version:

Emit n' Log: a modern, type-safe library for logging, event notifications, and observability in JavaScript/TypeScript apps.

663 lines (647 loc) 20 kB
'use strict'; // src/utils/common/canceled-error.ts var CanceledError = class extends Error { constructor(message = "the operation was cancelled") { super(message); Object.setPrototypeOf(this, new.target.prototype); this.name = "CanceledError"; } }; // src/utils/async/deferred-value.ts var createDeferredValue = () => { let resolve; let reject; let resolved = false; let rejected = false; const createPromise = () => new Promise((res, rej) => { resolve = (value) => { if (resolved || rejected) { return; } resolved = true; res(value); }; reject = (reason) => { if (resolved || rejected) { return; } rejected = true; rej(reason); }; }); let promise = createPromise(); const deferred = { get promise() { return promise; }, get resolved() { return resolved; }, get rejected() { return rejected; }, get settled() { return resolved || rejected; }, resolve: (value) => { resolve(value); }, reject: (reason) => { reject(reason); }, renew: () => { if (deferred.settled) { resolved = false; rejected = false; promise = createPromise(); } return deferred; } }; return deferred; }; // src/utils/async/debounce.ts var debounce = (fn, options) => { let timeoutId; let lastArgs; let pendingDeferred; let isExecuting = false; let hasLeadingBeenCalled = false; options = typeof options === "number" ? { delay: Math.max(0, Math.ceil(options)), waitForPrevious: false } : { waitForPrevious: false, ...options, delay: Math.max(0, Math.ceil(options.delay)) }; const executeFunction = async (args) => { if (isExecuting && options.waitForPrevious) { return pendingDeferred.promise; } isExecuting = true; try { const result = await fn(...args); if (pendingDeferred) { pendingDeferred.resolve(result); } return result; } catch (error) { if (pendingDeferred) { pendingDeferred.reject(error); } throw error; } finally { isExecuting = false; pendingDeferred = void 0; lastArgs = void 0; } }; const debouncedFunction = (...args) => { lastArgs = options.accumulator ? options.accumulator(lastArgs, args) : args; if (pendingDeferred) { return pendingDeferred.promise; } pendingDeferred = createDeferredValue(); if (options.leading && !hasLeadingBeenCalled && !isExecuting) { hasLeadingBeenCalled = true; const promise = executeFunction(args); if (timeoutId !== void 0) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { hasLeadingBeenCalled = false; timeoutId = void 0; }, options.delay); return promise; } if (timeoutId !== void 0) { clearTimeout(timeoutId); } const debouncedExecution = async () => { timeoutId = void 0; hasLeadingBeenCalled = false; if (lastArgs && !isExecuting) { try { await executeFunction(lastArgs); } catch { } } }; timeoutId = setTimeout(() => void debouncedExecution(), options.delay); return pendingDeferred.promise; }; debouncedFunction.cancel = (silent) => { if (timeoutId !== void 0) { clearTimeout(timeoutId); timeoutId = void 0; } hasLeadingBeenCalled = false; lastArgs = void 0; if (!silent && pendingDeferred && !pendingDeferred.settled) { pendingDeferred.reject(new CanceledError("The debounced function call was cancelled")); pendingDeferred = void 0; } }; debouncedFunction.flush = () => { if (timeoutId !== void 0) { clearTimeout(timeoutId); timeoutId = void 0; } if (lastArgs && !isExecuting) { hasLeadingBeenCalled = false; return executeFunction(lastArgs); } return void 0; }; return debouncedFunction; }; // src/utils/async/delay.ts var delay = (milliseconds) => new Promise((resolve) => { setTimeout(() => resolve(), Math.max(0, Math.ceil(milliseconds))); }); // src/logger/off-logger.ts var OFF_LOGGER = { get level() { return "off"; }, set level(_) { }, args: () => OFF_LOGGER, trace: () => void 0, t: () => void 0, debug: () => void 0, d: () => void 0, info: () => void 0, i: () => void 0, notice: () => void 0, n: () => void 0, warning: () => void 0, w: () => void 0, error: () => void 0, e: () => void 0, critical: () => void 0, c: () => void 0, alert: () => void 0, a: () => void 0, emergency: () => void 0, em: () => void 0, log: () => void 0 }; // src/utils/common/is-not-nullable.ts var isNotNullable = (value) => value !== void 0 && value !== null; // src/utils/common/exhaustive-check.ts var exhaustiveCheck = (_) => void 0; // src/logger/level-utils.ts var toLevelWeight = (level) => { switch (level) { case "trace": return 8; case "debug": return 7; case "info": return 6; case "notice": return 5; case "warning": return 4; case "error": return 3; case "critical": return 2; case "alert": return 1; case "emergency": return 0; default: return 20; } }; var shouldEmitEntry = (level, entryLevel) => level !== "off" && toLevelWeight(entryLevel) <= toLevelWeight(level); // src/logger/prefixed-logger.ts var prefixSymbol = Symbol.for("@emitnlog/logger/prefix"); var separatorSymbol = Symbol.for("@emitnlog/logger/separator"); var dataSymbol = Symbol.for("@emitnlog/logger/data"); var withPrefix = (logger, prefix, options) => { if (logger === OFF_LOGGER) { return OFF_LOGGER; } let prefixSeparator; let messageSeparator; const data = inspectPrefixedLogger(logger); if (data) { logger = data.rootLogger; prefixSeparator = data.separator; messageSeparator = data.messageSeparator; prefix = prefix ? `${data.prefix}${prefixSeparator}${prefix}` : data.prefix; } else { prefixSeparator = options?.prefixSeparator ?? "."; messageSeparator = options?.messageSeparator ?? ": "; if (options?.fallbackPrefix) { prefix = prefix ? `${options.fallbackPrefix}${prefixSeparator}${prefix}` : options.fallbackPrefix; } } const prefixedLogger = { [prefixSymbol]: prefix, [separatorSymbol]: prefixSeparator, [dataSymbol]: { rootLogger: logger, messageSeparator }, get level() { return logger.level; }, set level(value) { logger.level = value; }, args(...args) { logger.args(...args); return prefixedLogger; }, trace(message, ...args) { if (shouldEmitEntry(logger.level, "trace")) { logger.trace(toMessageProvider(prefixedLogger, message), ...args); } }, t(strings, ...values) { if (shouldEmitEntry(logger.level, "trace")) { logger.t(prefixTemplateString(prefixedLogger, strings), ...values); } }, debug(message, ...args) { if (shouldEmitEntry(logger.level, "debug")) { logger.debug(toMessageProvider(prefixedLogger, message), ...args); } }, d(strings, ...values) { if (shouldEmitEntry(logger.level, "debug")) { logger.d(prefixTemplateString(prefixedLogger, strings), ...values); } }, info(message, ...args) { if (shouldEmitEntry(logger.level, "info")) { logger.info(toMessageProvider(prefixedLogger, message), ...args); } }, i(strings, ...values) { if (shouldEmitEntry(logger.level, "info")) { logger.i(prefixTemplateString(prefixedLogger, strings), ...values); } }, notice(message, ...args) { if (shouldEmitEntry(logger.level, "notice")) { logger.notice(toMessageProvider(prefixedLogger, message), ...args); } }, n(strings, ...values) { if (shouldEmitEntry(logger.level, "notice")) { logger.n(prefixTemplateString(prefixedLogger, strings), ...values); } }, warning(message, ...args) { if (shouldEmitEntry(logger.level, "warning")) { logger.warning(toMessageProvider(prefixedLogger, message), ...args); } }, w(strings, ...values) { if (shouldEmitEntry(logger.level, "warning")) { logger.w(prefixTemplateString(prefixedLogger, strings), ...values); } }, error(error, ...args) { if (shouldEmitEntry(logger.level, "error")) { if (error instanceof Error) { logger.error(toMessageProvider(prefixedLogger, error.message), error, ...args); } else if (error && typeof error === "object" && "error" in error) { logger.error(toMessageProvider(prefixedLogger, String(error.error)), error, ...args); } else { logger.error(toMessageProvider(prefixedLogger, error), ...args); } } }, e(strings, ...values) { if (shouldEmitEntry(logger.level, "error")) { logger.e(prefixTemplateString(prefixedLogger, strings), ...values); } }, critical(message, ...args) { if (shouldEmitEntry(logger.level, "critical")) { logger.critical(toMessageProvider(prefixedLogger, message), ...args); } }, c(strings, ...values) { if (shouldEmitEntry(logger.level, "critical")) { logger.c(prefixTemplateString(prefixedLogger, strings), ...values); } }, alert(message, ...args) { if (shouldEmitEntry(logger.level, "alert")) { logger.alert(toMessageProvider(prefixedLogger, message), ...args); } }, a(strings, ...values) { if (shouldEmitEntry(logger.level, "alert")) { logger.a(prefixTemplateString(prefixedLogger, strings), ...values); } }, emergency(message, ...args) { if (shouldEmitEntry(logger.level, "emergency")) { logger.emergency(toMessageProvider(prefixedLogger, message), ...args); } }, em(strings, ...values) { if (shouldEmitEntry(logger.level, "emergency")) { logger.em(prefixTemplateString(prefixedLogger, strings), ...values); } }, log(level, message, ...args) { if (shouldEmitEntry(logger.level, level)) { logger.log(level, toMessageProvider(prefixedLogger, message), ...args); } } }; return prefixedLogger; }; var isPrefixedLogger = (logger) => isNotNullable(logger) && prefixSymbol in logger && typeof logger[prefixSymbol] === "string" && dataSymbol in logger; var inspectPrefixedLogger = (logger) => isPrefixedLogger(logger) ? { rootLogger: logger[dataSymbol].rootLogger, prefix: logger[prefixSymbol], separator: logger[separatorSymbol], messageSeparator: logger[dataSymbol].messageSeparator } : void 0; var prefixTemplateString = (prefixLogger, strings) => { const prefix = prefixLogger[prefixSymbol]; const messageSeparator = prefixLogger[dataSymbol].messageSeparator; const newStrings = Array.from(strings); newStrings[0] = `${prefix}${messageSeparator}${newStrings[0]}`; const prefixedStrings = Object.assign(newStrings, { raw: Array.from(strings.raw) }); prefixedStrings.raw[0] = `${prefix}${messageSeparator}${prefixedStrings.raw[0]}`; return prefixedStrings; }; var toMessageProvider = (prefixLogger, message) => () => { const messageString = typeof message === "function" ? message() : message; const messageSeparator = prefixLogger[dataSymbol].messageSeparator; return `${prefixLogger[prefixSymbol]}${messageSeparator}${messageString}`; }; // src/utils/async/poll.ts var startPolling = (operation, interval, options) => { const deferred = createDeferredValue(); interval = Math.max(0, Math.ceil(interval)); let resolving = false; let invocationIndex = -1; let active = true; let lastResult; const logger = withPrefix(options?.logger ?? OFF_LOGGER, "poll", { fallbackPrefix: "emitnlog" }); const polledOperation = () => { if (resolving || !active) { return; } invocationIndex++; if (options?.retryLimit !== void 0 && invocationIndex >= options.retryLimit) { logger.d`reached maximum retries (${options.retryLimit})`; void close(); return; } try { logger.d`invoking the operation for the ${invocationIndex + 1} time`; const result = operation(); if (result instanceof Promise) { resolving = true; void result.then((value) => { lastResult = value; if (options?.interrupt && options.interrupt(value, invocationIndex)) { void close(); } }).catch((error) => { logger.args(error).e`the operation rejected with an error: ${error}`; }).finally(() => { resolving = false; }); return; } lastResult = result; if (options?.interrupt && options.interrupt(result, invocationIndex)) { void close(); return; } } catch (error) { logger.args(error).e`the operation threw an error: ${error}`; } }; const close = async () => { if (active) { active = false; logger.d`closing the poll after ${invocationIndex + 1} invocations`; clearInterval(intervalId); deferred.resolve(lastResult); } await deferred.promise; }; if (options?.invokeImmediately) { polledOperation(); if (!active) { return { close, wait: deferred.promise }; } } const intervalId = setInterval(polledOperation, interval); if (options?.timeout && options.timeout >= 0) { void delay(options.timeout).then(() => { if (active) { if ("timeoutValue" in options) { lastResult = options.timeoutValue; } logger.d`timeout for the operation reached after ${options.timeout}ms`; void close(); } }); } return { close, wait: deferred.promise }; }; // src/utils/async/with-timeout.ts var withTimeout = (promise, timeout, timeoutValue) => Promise.race([promise, delay(timeout).then(() => timeoutValue)]); // src/utils/common/closed-error.ts var ClosedError = class extends Error { constructor(message = "the operation was performed after its scope was closed") { super(message); Object.setPrototypeOf(this, new.target.prototype); this.name = "ClosedError"; } }; // src/utils/common/generate-random-string.ts var generateRandomString = (length = 8) => { if (length < 8 || length > 128) { throw new Error("IllegalArgument: length must be a number between 8 and 128"); } const timestamp = Date.now(); return Array.from({ length }, () => { const entropy = timestamp + performance.now(); const randomIndex = Math.floor(Math.random() * entropy % 1 * UNIQUE_CHARACTERS.length); return UNIQUE_CHARACTERS.charAt(randomIndex); }).join(""); }; var UNIQUE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // src/utils/converter/errorify.ts var errorify = (value) => value instanceof Error ? value : new Error(String(value), { cause: value }); // src/utils/converter/stringify.ts var stringify = (value, options) => { const { includeStack = false, pretty = false, maxDepth = 5, useLocale = false, maxArrayElements = 100, maxProperties = 50 } = options || {}; const prepare = (val, depth = 0, seen = /* @__PURE__ */ new WeakSet()) => { const type = typeof val; switch (type) { case "string": case "number": case "bigint": case "boolean": case "undefined": case "symbol": case "function": return val; case "object": { if (val instanceof Date) { try { return useLocale ? val.toLocaleString() : val.toISOString(); } catch { return "[Invalid Date]"; } } if (val instanceof Error) { try { const message = val.message || val.name || "[unknown error]"; return includeStack && val.stack ? `${message} ${val.stack}` : message; } catch { return "[Invalid Error]"; } } if (val instanceof Map) { if (maxDepth < 0 || depth < maxDepth) { try { return prepare(Object.fromEntries(val), depth + 1, seen); } catch { } } return `Map(${val.size})`; } if (val instanceof Set) { if (maxDepth < 0 || depth < maxDepth) { try { return prepare(Array.from(val), depth + 1, seen); } catch { } } return `Set(${val.size})`; } if (val instanceof RegExp) { try { return val.toString(); } catch { return "[RegExp]"; } } if (!val) { return val; } if (seen.has(val)) { return "[Circular Reference]"; } seen.add(val); if (Array.isArray(val)) { if (maxDepth < 0 || depth < maxDepth) { if (maxArrayElements >= 0 && val.length > maxArrayElements) { const truncatedArray = val.slice(0, maxArrayElements); truncatedArray.push(`...(${val.length - maxArrayElements})`); val = truncatedArray; } try { return val.map((item) => prepare(item, depth + 1, seen)); } catch { } } return `Array(${val.length})`; } const stringValue = String(val); if (stringValue !== "[object Object]") { return stringValue; } if (maxDepth < 0 || depth < maxDepth) { try { let keys = Object.keys(val); if (!keys.length) { return "{}"; } if (maxProperties >= 0 && keys.length > maxProperties) { const originalLength = keys.length; keys = keys.slice(0, maxProperties); const truncatedObj = {}; for (let i = 0; i < maxProperties; i++) { const key = keys[i]; truncatedObj[key] = val[key]; } const truncatedKey = `...(${originalLength - maxProperties})`; keys.push(truncatedKey); truncatedObj[truncatedKey] = "..."; val = truncatedObj; } const result = {}; for (const key of keys) { result[key] = prepare(val[key], depth + 1, seen); } return result; } catch { } } return "[object Object]"; } default: return val; } }; const convert = (val) => { const type = typeof val; switch (type) { case "string": return val; case "number": case "bigint": case "boolean": case "undefined": case "symbol": case "function": return String(val); case "object": { try { return pretty ? JSON.stringify(val, void 0, 2) : JSON.stringify(val); } catch { if (Array.isArray(val)) { return `Array(${val.length})`; } try { const keys = Object.keys(val); return `{${keys.join(", ")}}`; } catch { return String(val); } } } default: return String(val); } }; try { const converted = prepare(value); return convert(converted); } catch { return "[Stringify Error]"; } }; exports.CanceledError = CanceledError; exports.ClosedError = ClosedError; exports.createDeferredValue = createDeferredValue; exports.debounce = debounce; exports.delay = delay; exports.errorify = errorify; exports.exhaustiveCheck = exhaustiveCheck; exports.generateRandomString = generateRandomString; exports.isNotNullable = isNotNullable; exports.startPolling = startPolling; exports.stringify = stringify; exports.withTimeout = withTimeout; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map