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
TypeScript
import { a as Logger } from '../definition-BgOWOfmh.js';
export { S as StringifyOptions, s as stringify } from '../stringify-k3K813vK.js';
/**
* 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 };