emitnlog
Version: 
Emit n' Log: a modern, type-safe library for logging, event notifications, and observability in JavaScript/TypeScript apps.
587 lines (573 loc) • 20.6 kB
text/typescript
import { a as Logger } from '../definition-BgOWOfmh.cjs';
export { S as StringifyOptions, s as stringify } from '../stringify-k3K813vK.cjs';
/**
 * Configuration options for the debounce utility.
 */
type DebounceOptions<TArgs extends unknown[] = unknown[]> = {
    /**
     * The delay in milliseconds to wait before executing the debounced function.
     */
    readonly delay: number;
    /**
     * If true, the function will be called immediately, i.e., on the leading edge of the timeout. If false (default), the
     * function will be called on the trailing edge.
     */
    readonly leading?: boolean;
    /**
     * If true, the debounce will wait for the previous promise to complete before processing new calls. If false
     * (default), new calls will be debounced immediately regardless of previous promise state.
     */
    readonly waitForPrevious?: boolean;
    /**
     * Optional function to accumulate arguments from multiple calls before execution. If provided, instead of using only
     * the last call's arguments, this function will be called to combine the previous accumulated arguments with the new
     * call's arguments.
     *
     * **Important**: The accumulator function is called immediately on every debounced function call (it is NOT
     * debounced). Therefore, it should be fast and free of side effects to avoid performance issues.
     *
     * @param previousArgs - The previously accumulated arguments (undefined for the first call)
     * @param currentArgs - The arguments from the current call
     * @returns The accumulated arguments to use for the next call or final execution
     */
    readonly accumulator?: (previousArgs: TArgs | undefined, currentArgs: TArgs) => [...TArgs];
};
/**
 * A debounced function that returns a promise.
 *
 * @template TArgs - The arguments type of the original function.
 * @template TReturn - The return type of the original function.
 */
type DebouncedFunction<TArgs extends unknown[], TReturn> = {
    /**
     * Calls the debounced function and returns a promise that resolves to the result. If the function is called multiple
     * times within the debounce delay, only the last call will be executed, and all callers will receive the same
     * result.
     */
    (...args: TArgs): Promise<TReturn>;
    /**
     * Cancels any pending debounced function calls.
     *
     * Behavior on cancel:
     *
     * - If there is a pending debounced call that has not executed yet, the promise returned to all callers of the
     *   debounced function will be rejected with a `emitnlog/utils/CanceledError`. This behavior can be changed by
     *   passing `true` as the value of `silent`.
     * - If there is no pending call, or the last call has already been executed and its promise settled, calling `cancel()`
     *   has no effect on already resolved/rejected promises.
     * - Internal timers and accumulated arguments are cleared, so subsequent calls start fresh.
     *
     * @param silent If true, pending promises are not rejected; they remain unsettled until callers time out or ignore
     *   them.
     */
    cancel(silent?: boolean): void;
    /**
     * Immediately executes the debounced function with the last provided arguments, bypassing the delay.
     */
    flush(): Promise<TReturn> | undefined;
};
/**
 * Creates a debounced version of a function that supports promises.
 *
 * When the debounced function is called multiple times within the delay period, only the last call will be executed.
 * All callers will receive the same result through their returned promises.
 *
 * By default, if the original function returns a promise, new calls will be debounced immediately without waiting. Set
 * `waitForPrevious: true` to wait for previous promises to complete.
 *
 * @example Basic usage
 *
 * ```ts
 * import { debounce } from 'emitnlog/utils';
 *
 * const search = debounce(async (query: string) => {
 *   const response = await fetch(`/api/search?q=${query}`);
 *   return response.json();
 * }, 300);
 *
 * // These calls will be debounced - only the last one will execute
 * const result1 = search('hello');
 * const result2 = search('hello world');
 * const result3 = search('hello world!');
 *
 * // All three promises will resolve to the same result
 * const [r1, r2, r3] = await Promise.all([result1, result2, result3]);
 * console.log(r1 === r2 && r2 === r3); // true
 * ```
 *
 * @example With leading edge execution
 *
 * ```ts
 * import { debounce } from 'emitnlog/utils';
 *
 * const saveData = debounce(
 *   async (data: any) => {
 *     return await api.save(data);
 *   },
 *   { delay: 1000, leading: true },
 * );
 *
 * // First call executes immediately, subsequent calls are debounced
 * await saveData({ id: 1 }); // Executes immediately
 * await saveData({ id: 2 }); // Debounced
 * ```
 *
 * @example With argument accumulation
 *
 * ```ts
 * import { debounce } from 'emitnlog/utils';
 *
 * const batchProcessor = debounce(
 *   async (ids: number[]) => {
 *     return await api.processBatch(ids);
 *   },
 *   {
 *     delay: 300,
 *     // Accumulator is fast and pure - just combines arrays
 *     accumulator: (prev, current) => {
 *       const prevIds = prev?.[0] || [];
 *       const currentIds = current[0];
 *       return [[...prevIds, ...currentIds]];
 *     },
 *   },
 * );
 *
 * batchProcessor([1, 2]);
 * batchProcessor([3, 4]);
 * batchProcessor([5]);
 * // Eventually processes [1, 2, 3, 4, 5] in a single call
 * ```
 *
 * @example With waiting for previous promises
 *
 * ```ts
 * import { debounce } from 'emitnlog/utils';
 *
 * const sequentialDebounce = debounce(
 *   async (value: string) => {
 *     await longRunningOperation(value);
 *     return `processed: ${value}`;
 *   },
 *   { delay: 300, waitForPrevious: true },
 * );
 *
 * sequentialDebounce('first'); // Starts long operation
 * sequentialDebounce('second'); // Waits for first to complete before debouncing
 * ```
 *
 * @template TArgs - The arguments type of the original function.
 * @template TReturn - The return type of the original function.
 * @param fn - The function to debounce.
 * @param options - The debounce delay in millisecond or the configuration options.
 * @returns A debounced version of the function that returns a promise.
 */
declare const debounce: <TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn | Promise<TReturn>, options: number | DebounceOptions<TArgs>) => DebouncedFunction<TArgs, TReturn>;
/**
 * A value the exposes a promise that can be resolved or rejected by external clients.
 *
 * Clients should cache the DeferredValue instance itself rather than its properties (like `promise`). This enables
 * proper usage of features like `renew()`, which may creates a new internal promise.
 *
 * @template T - The type of the promise's value.
 */
interface DeferredValue<T> {
    /**
     * The promise that can be resolved or rejected.
     */
    readonly promise: Promise<T>;
    /**
     * Whether the promise has been resolved.
     */
    readonly resolved: boolean;
    /**
     * Whether the promise has been rejected.
     */
    readonly rejected: boolean;
    /**
     * Whether the promise has been settled, i.e., if it has been resolved or rejected.
     */
    readonly settled: boolean;
    /**
     * Resolves the deferred value's promise with a value. Calling this method has no effect if the deferred value is
     * already settled.
     */
    readonly resolve: (value: T | PromiseLike<T>) => void;
    /**
     * Rejects the deferred value's promise with a reason. Calling this method has no effect if the deferred value is
     * already settled.
     */
    readonly reject: (reason?: unknown) => void;
    /**
     * Resets a settled (i.e., resolved or rejected) promise to an unsettled state, allowing the same deferred value
     * instance to be used in a new asynchronous operation after the previous one has completed. Calling this method has
     * no effect if the deferred value is not settled (i.e., if its promise is neither resolved nor rejected.)
     *
     * @example Reusing a deferred value
     *
     * ```ts
     * import { createDeferredValue } from 'emitnlog/utils';
     *
     * const deferred = createDeferredValue<string>();
     *
     * // First use
     * deferred.resolve('first');
     * await deferred.promise; // resolves to "first"
     *
     * // Renew for second use
     * deferred.renew();
     * deferred.resolve('second');
     * await deferred.promise; // resolves to "second"
     * ```
     *
     * @example Chainable usage
     *
     * ```ts
     * import { createDeferredValue } from 'emitnlog/utils';
     *
     * const deferred = createDeferredValue<number>();
     * deferred.resolve(1);
     * await deferred.promise;
     *
     * // Chain renew and resolve
     * deferred.renew().resolve(2);
     * await deferred.promise; // resolves to 2
     * ```
     *
     * @returns The same deferred value instance, allowing for method chaining.
     */
    readonly renew: () => this;
}
/**
 * Creates deferred value that exposes a promise that can be resolved or rejected by external clients. This is useful
 * for scenarios where you need to control when a promise resolves or rejects from outside the promise's executor
 * function, such as in event-driven architectures, manual coordination of asynchronous operations, or implementing
 * custom waiting mechanisms.
 *
 * Clients should cache the DeferredValue instance itself rather than destructuring its properties (like `promise`).
 * This ensures proper usage of features like `renew()`, which may creates a new internal promise.
 *
 * @example Basic usage
 *
 * ```ts
 * // Create a deferred value to be resolved later
 * const deferred = createDeferredValue<string>();
 *
 * // Pass the promise to consumers that need to wait for the value
 * function waitForValue(): Promise<string> {
 *   return deferred.promise;
 * }
 *
 * // Later, resolve the promise when the value becomes available
 * function provideValue(value: string): void {
 *   deferred.resolve(value);
 * }
 * ```
 *
 * @example Using with event listeners
 *
 * ```ts
 * // Create a deferred that will be resolved when an event occurs
 * function waitForEvent(notifier: EventNotifier, eventName: string): Promise<any> {
 *   const deferred = createDeferredValue<any>();
 *
 *   const handler = (data: any) => {
 *     deferred.resolve(data);
 *     emitter.off(eventName, handler);
 *   };
 *
 *   notifier.onEvent(eventName, handler);
 *   return deferred.promise;
 * }
 * ```
 *
 * @template T The type of value that the promise will resolve to.
 * @returns An object containing the promise and functions to resolve or reject it.
 */
declare const createDeferredValue: <T = void>() => DeferredValue<T>;
/**
 * Delays the execution of the code for the specified amount of milliseconds.
 *
 * @example
 *
 * ```ts
 * import { delay } from 'emitnlog/utils';
 *
 * // Wait for 500 milliseconds before continuing
 * await delay(500);
 * console.log('This will be logged after 500ms');
 *
 * // Chain multiple operations with delays
 * async function processWithDelays() {
 *   await step1();
 *   await delay(1000); // 1 second cooldown
 *   await step2();
 *   await delay(2000); // 2 second cooldown
 *   await step3();
 * }
 * ```
 *
 * @param milliseconds The amount of milliseconds to wait (0 if negatived, and ceil if decimal).
 * @returns A promise that resolves after the specified amount of milliseconds.
 */
declare const delay: (milliseconds: number) => Promise<void>;
/**
 * Configuration options for polling operations.
 *
 * @template T The type of value that the polling operation returns
 * @template V The type of value to return when a timeout occurs
 */
type PollingOptions<T, V> = {
    /**
     * Whether to invoke the operation immediately or instead wait for the first interval.
     */
    readonly invokeImmediately?: boolean;
    /**
     * Maximum time in milliseconds to poll before auto-stopping.
     */
    readonly timeout?: number;
    /**
     * Value to return when a timeout occurs.
     */
    readonly timeoutValue?: V;
    /**
     * Maximum number of times to call the operation before auto-stopping.
     */
    readonly retryLimit?: number;
    /**
     * Function that evaluates each result and returns true if polling should stop.
     *
     * @param result The result returned by the operation
     * @param invocationIndex The current invocation count (0-based)
     * @returns `true` to stop polling, `false` to continue
     */
    readonly interrupt?: (result: T, invocationIndex: number) => boolean;
    /**
     * Logger to capture polling events and errors.
     */
    readonly logger?: Logger;
};
/**
 * Polls a function at regular intervals until a condition is met, a timeout occurs, maximum retries are reached, or
 * it's manually stopped. Returns both a method to stop polling and a promise that resolves with the last result.
 *
 * The polling operation handles both synchronous and asynchronous (Promise-returning) functions. If the operation
 * throws an error or rejects, polling continues but the error is logged. If a previous asynchronous operation is still
 * resolving when the next interval occurs, that polling iteration is skipped.
 *
 * @example Simple polling at regular intervals
 *
 * ```ts
 * import { startPolling } from 'emitnlog/utils';
 *
 * // Poll every 5 seconds until manually stopped
 * const closeable = startPolling(() => fetchLatestData(), 5_000);
 *
 * // Stop polling after 30 seconds
 * await delay(30_000).then(close);
 *
 * // Get the final result
 * const finalData = await wait;
 * ```
 *
 * @example Basic polling until a condition is met
 *
 * ```ts
 * import { startPolling } from 'emitnlog/utils';
 *
 * const { wait, close } = startPolling(() => fetchStatus(), 1000, {
 *   interrupt: (status) => status === 'completed',
 * });
 *
 * // Later get the final result
 * const finalStatus = await wait;
 * ```
 *
 * @example Polling with timeout
 *
 * ```ts
 * import { startPolling } from 'emitnlog/utils';
 *
 * const { wait } = startPolling(() => checkJobStatus(jobId), 2000, {
 *   timeout: 30000, // Stop after 30 seconds
 *   interrupt: (status) => ['completed', 'failed'].includes(status),
 *   logger: console,
 * });
 *
 * const finalStatus = await wait;
 * if (finalStatus === 'completed') {
 *   // Job finished successfully
 * } else {
 *   // Either timed out or job failed
 * }
 * ```
 *
 * @example Polling with maximum retries
 *
 * ```ts
 * import { startPolling } from 'emitnlog/utils';
 *
 * const { wait } = startPolling(() => checkJobStatus(jobId), 2000, {
 *   retryLimit: 5, // Stop after 5 attempts
 *   logger: console,
 * });
 *
 * const finalStatus = await wait;
 * ```
 *
 * @example Manual control of polling
 *
 * ```ts
 * import { startPolling } from 'emitnlog/utils';
 *
 * const poll = startPolling(() => fetchDataPoints(), 5000);
 *
 * // Stop polling after some external event
 * eventEmitter.on('stop-polling', () => {
 *   poll.close();
 * });
 *
 * // Get the last result when polling stops
 * const lastDataPoints = await poll.wait;
 * ```
 *
 * @param operation Function to execute on each poll interval. Can return a value or a Promise.
 * @param interval Time in milliseconds between poll attempts
 * @param options Optional configuration for polling behavior
 * @returns An object with a `close()` method to manually stop polling and a `wait` Promise that resolves with the last
 *   result when polling stops
 */
declare const startPolling: <T, const V = undefined>(operation: () => T | Promise<T>, interval: number, options?: PollingOptions<T, V>) => {
    readonly wait: Promise<T | V | undefined>;
    readonly close: () => Promise<void>;
};
/**
 * A type representing the return value of the `setTimeout` and `setInterval` functions.
 */
type Timeout = ReturnType<typeof setTimeout>;
/**
 * Wraps a given promise and resolves with its value if it completes within the specified timeout. If the timeout
 * elapses, the returned promise resolves with a provided fallback value or undefined if no `timeoutValue` is provided.
 *
 * Note: The original promise continues to run in the background even if the timeout occurs. If caching promises,
 * consider caching the original promise instead of the timed one, since it may resolve successfully on a subsequent
 * access.
 *
 * @example
 *
 * ```ts
 * import { withTimeout } from 'emitnlog/utils';
 *
 * const promise: Promise<string | undefined> = withTimeout(fetchContent(uri), 5000);
 * const content: string = await promise.then((value) => value ?? '');
 * ```
 *
 * @example
 *
 * ```ts
 * import { withTimeout } from 'emitnlog/utils';
 *
 * const promise: Promise<string | -1> = withTimeout(fetchContent(uri), 5000, -1);
 * const content: string | undefined = await promise.then((value) => (value === -1 ? undefined : value));
 * ```
 *
 * @param promise The promise to be wrapped with a timeout.
 * @param timeout The maximum duration (in milliseconds) to wait before resolving with `timeoutValue`. (0 if negatived,
 *   and ceil if decimal).
 * @param timeoutValue The value to resolve with if the timeout is reached before the promise completes.
 * @returns A promise that resolves with the original promise's value if completed within the timeout, or with
 *   `timeoutValue` otherwise.
 */
declare const withTimeout: <T, const R = undefined>(promise: Promise<T>, timeout: number, timeoutValue?: R) => Promise<T | R>;
/**
 * Error used to indicate that an operation was cancelled intentionally.
 */
declare class CanceledError extends Error {
    constructor(message?: string);
}
/**
 * Error used to indicate that an operation was performed after its scope was closed.
 */
declare class ClosedError extends Error {
    constructor(message?: string);
}
/**
 * Exhaustive check to ensure all cases are handled.
 *
 * Typically used in switch statements to ensure all cases are handled at compile time.
 *
 * @example
 *
 * ```ts
 * import { exhaustiveCheck } from 'emitnlog/utils';
 *
 * type Fruit = 'apple' | 'banana' | 'orange';
 * const fruit: Fruit = 'apple';
 * switch (fruit) {
 *   case 'apple':
 *     return '🍎';
 *   case 'banana':
 *     return '🍌';
 *   case 'orange':
 *     return '🍊';
 *   default:
 *     exhaustiveCheck(fruit);
 * }
 * ```
 *
 * @param _ - The never-used parameter.
 */
declare const exhaustiveCheck: (_: never) => undefined;
/**
 * Generates a random string of the specified length using "safe" alphanumeric characters (i.e., "A-Z", "a-z" and
 * "0-9"). Uses multiple entropy sources to improve randomness, but is NOT cryptographically secure.
 *
 * @example
 *
 * ```ts
 * import { generateRandomString } from 'emitnlog/utils';
 *
 * // Generate a random string with default length (8)
 * const id = generateRandomString();
 *
 * // Generate a random string with custom length
 * const longerId = generateRandomString(64);
 * ```
 *
 * @param length - Defaults to 8. Must be a number between 8 and 128.
 * @returns A random string of the specified length.
 * @throws An error if length is not in the range between 8 and 128 (inclusive).
 * @note NOT SUITABLE FOR CRYPTOGRAPHIC OR SECURITY-CRITICAL PURPOSES.
 * For security-sensitive applications, use a cryptographically secure random generator instead.
 */
declare const generateRandomString: (length?: number) => string;
/**
 * Check if a value is not undefined or null. A non-nullable return is typed as such.
 *
 * @example
 *
 * ```ts
 * import { isNotNullable } from 'emitnlog/utils';
 *
 * const arr: (string | undefined | null)[] = ['a', null, 'b', undefined, 'c'];
 * const result: string[] = arr.filter(isNotNullable);
 * ```
 *
 * @param value - The value to check.
 * @returns `true` if the value is not undefined or null, `false` otherwise.
 */
declare const isNotNullable: <T>(value: T | undefined | null) => value is NonNullable<T>;
/**
 * Converts a given value into an instance of Error.
 *
 * @example
 *
 * ```ts
 * import { errorify } from 'emitnlog/utils';
 * const error = errorify('An error occurred');
 * ```
 *
 * @param {unknown} value - The value to convert into an Error.
 * @returns {Error} The converted Error object.
 */
declare const errorify: (value: unknown) => Error;
export { CanceledError, ClosedError, type DebounceOptions, type DebouncedFunction, type DeferredValue, type PollingOptions, type Timeout, createDeferredValue, debounce, delay, errorify, exhaustiveCheck, generateRandomString, isNotNullable, startPolling, withTimeout };