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
JavaScript
;
// 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