UNPKG

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
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 };