import { EventEmitter } from "events"; export interface ThreadOptions { /** * How long (in milliseconds) to keep the {@link Thread} or {@link ThreadPool} active after completing a task before terminating it. * Keeping the `Thread` or `ThreadPool` open will decrease repeat startup times, but will cause the program to hang and not exit if the {@link Thread.close} method is not called. * Default is `0` (close immediately). * @default 0 */ idleTimeout?: number; threadpoolId?: string; } /** * Abstraction around Bun workers to enable working with them as promises. * @typeParam T - The signature of your callback function, including its arguments and return type. */ export declare class Thread any> extends EventEmitter { private worker; private timer; private threadpoolId; private _queued; /** * How many tasks are currently waiting to use the thread. * * Every time you call the {@link run} method, this value is incremented by 1. * * Every time the `run` method resolves to a value, this value is decremented by 1. * @readonly */ get queued(): number; private set queued(value); /** * Whether the `Thread` is currently busy running a task or not. It is possible the check this while a task is still running. * The status is stored on the main thread while the task is performed on the underlying worker. To wait until the `Thread` is not busy, await the {@link idle} property. */ get busy(): boolean; /** * A unique integer identifier for the referenced `Thread`. May be `undefined` if the underlying worker is currently closed. */ get id(): number | undefined; /** * The callback function to be executed in parallel upon calling the asychronous {@link run} method. */ readonly fn: T; private _idleTimeout; /** * How long (in milliseconds) to keep the `Thread` active after completing a task before terminating it. * Keeping the `Thread` open will decrease repeat startup times, but will cause the program to hang and not exit if the {@link close} method is not called. * Default is `0` (close immediately). Set to `Infinity` to keep the `Thread` open until closed manually. * Changing this value will restart the `Thread`'s internal timer. * @default 0 * @throws `RangeError` if value < 0 */ get idleTimeout(): number; /** * How long (in milliseconds) to keep the `Thread` active after completing a task before terminating it. * Keeping the `Thread` open will decrease repeat startup times, but will cause the program to hang and not exit if the {@link close} method is not called. * Default is `0` (close immediately). Set to `Infinity` to keep the `Thread` open until closed manually. * Changing this value will restart the `Thread`'s internal timer. * @default 0 * @throws `RangeError` if value < 0 */ set idleTimeout(value: number); /** * Whether the `Thread`'s underlying worker is currently instantiated or not. */ get closed(): boolean; /** * A promise that resolves once the `Thread` has finished its task and reached an idle state. Resolves immediately if the `Thread` is not busy. Used internally by the {@link ThreadPool} class. * @example * ```ts * import { Thread } from "bun-threads"; * * const countUp = new Thread((countUpTo: number) => { * let current: number = 0 * for (let i = 0; i <= countUpTo; i++) { * current = i * } * return current * }) * * const countDown = new Thread((countDownFrom: number) => { * let current: number = countDownFrom * for (let i = countDownFrom; i >= 0; i--) { * current = i * } * return current * }) * * countUp.run(1_000_000) * countDown.run(1_000_000) * * // you can use the idle property to get the **thread** that finishes first, not the result * Promise.race([countUp.idle, countDown.idle]).then((winner) => { * // do it again * winner.run(1_000_000).then(async (value: number) => { * if (value === 0) { * console.log('countDown was the winner') * } * else { * console.log('countUp was the winner') * } * }).then(() => { * countUp.close() * countDown.close() * }) * }) * ``` */ get idle(): Promise; /** * Create a new `Thread` to run tasks on a separate Bun worker thread. * @param fn * The callback function to be executed in parallel upon calling the asynchronous {@link run} method. * Argument types must be serializable using the {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types structuredClone()} algorithm. * Callback functions can not be closures or rely upon top level imports, as they do not have access to variables or imports outside of their isolated worker thread environment. * They can however use dynamic imports using the `const myPackage = await import('some_package')` syntax. * @param options a {@link ThreadOptions} configuration object for the thread. * @example * ```ts * import { Thread } from "bun-threads"; * * const threadWithImports = new Thread(async (num: number) => { * const sqlite = await import('bun:sqlite') * const db = new sqlite.Database('./db.sqlite') * db.run("INSERT INTO answers VALUES(?)", [ num ]) * }) * ``` */ constructor(fn: T, options?: ThreadOptions); /** * Execute the callback that was specified in the {@link constructor} in a separate worker thread. * @param args An array of arguments to pass to the callback function. * If your callback function does not have arguments, you still must pass an empty array. * This is required for TypeScript to be able infer arguments. * Argument types must be serializable using the {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types structuredClone()} algorithm. * @returns A `Promise>>` that resolves to the return type of your callback function. */ run(...args: Parameters): Promise>>; /** * Terminate the underlying worker. It is safe to call this method more than once, as subsequent calls result in a no-op. * @param [force=false] This method will wait for the `Thread` to finish its queued tasks unless `force` is set to true. Default is `false`. * @see {@link busy}, {@link idle}, and/or {@link queued} on how to check first whether the thread has completed its task. * @returns A Promise\ that resolves to whether the underlying worker was actually terminated. `true` if the worker was terminated, `false` if the worker was already terminated (a no-op). * @example * ```ts * import { Thread } from "bun-threads"; * * const waitThenReturn = async (str: string) => { * await Bun.sleep(100) * return str * } * * // this code will wait for the thread to finish its operation before closing, printing 'hello' * const threadOne = new Thread(waitThenReturn) * threadOne.run('hello').then((result) => console.log(result)) * threadOne.close() // force defaults to false * * // this code will force the thread to close without waiting for it to finish its operation, 'world' never gets printed * const threadTwo = new Thread(waitThenReturn) * threadTwo.run('world').then((result) => console.log(result)) * threadTwo.close(true) * ``` */ close(force?: boolean): Promise; /** * Adds the `listener` function to the end of the listeners array for the `idle` event. This event fires every time a thread has completed all of its pending tasks. * No checks are made to see if the `listener` has already been added. * Multiple calls passing the same combination of `idle` and `listener` will result in the `listener` being added, and called, multiple times. * By default, event listeners are invoked in the order they are added. The `emitter.prependListener()` method can be used as an alternative to add the * event listener to the beginning of the listeners array. * @returns A reference to the `EventEmitter`, so that calls can be chained. * @example * ```ts * import { Thread } from "bun-threads"; * * const helloWorld = new Thread(() => { * return 'hello world' * }) * * const add = new Thread((a: number, b: number) => { * return a + b * }) * * const idleHandler = (thread: Thread) => { * console.log(`Thread ${thread.id} is now idle.`) * } * * helloWorld.on('idle', idleHandler) * add.on('idle', idleHandler) * * helloWorld.run() * add.run(1, 2) * * helloWorld.close() * add.close() * ``` */ on(eventName: 'idle', listener: (thread: Thread) => void): this; /** * Adds the `listener` function to the end of the listeners array for the `busy` event. This event fires every time a thread has switched from an idle state to working on a task. * No checks are made to see if the `listener` has already been added. * Multiple calls passing the same combination of `busy` and `listener` will result in the `listener` being added, and called, multiple times. * By default, event listeners are invoked in the order they are added. The `emitter.prependListener()` method can be used as an alternative to add the * event listener to the beginning of the listeners array. * @returns A reference to the `EventEmitter`, so that calls can be chained. * @example * ```ts * import { Thread } from "bun-threads"; * * const countOccurences = new Thread((char: string, inString: string) => { * let occurences: number = 0 * for (let i = 0; i < inString.length; i++) { * if (inString[i] === char) { * occurences++ * } * } * return occurences * }) * * countOccurences.on('busy', () => { * console.log('Begun counting occurences in a separate thread.') * }) * * console.log(await countOccurences.run('o', 'hello world')) * console.log(await countOccurences.run('e', 'Answer to the Ultimate Question of Life, The Universe, and Everything')) * countOccurences.close() * ``` */ on(eventName: 'busy', listener: (thread: Thread) => void): this; /** * Adds the `listener` function to the end of the listeners array for the `close` event. This event fires when a thread has closed its underlying worker object. * A thread can still be reused by calling run() again, but will have longer startup times vs. not closing it before calling run() again, as a worker has to be created again after closing. * No checks are made to see if the `listener` has already been added. * Multiple calls passing the same combination of `close` and `listener` will result in the `listener` being added, and called, multiple times. * By default, event listeners are invoked in the order they are added. The `emitter.prependListener()` method can be used as an alternative to add the * event listener to the beginning of the listeners array. * @returns A reference to the `EventEmitter`, so that calls can be chained. * @example * ```ts * import { Thread } from "bun-threads"; * * const scramble = new Thread((toScramble: string) => { * const randomNumber = (min: number, max: number) => { * return Math.random() * (max - min) + min; * } * const oldArr: string[] = toScramble.split('') * const newArr: string[] = [] * while (oldArr.length > 0) { * const rand: number = randomNumber(0, oldArr.length) * newArr.push(oldArr.splice(rand, 1)[0]!) * } * return newArr.join('') * }, { idleTimeout: 500 }) * * scramble.on('close', () => { * console.log(`Scramble thread has completed its work and has closed after its idleTimeout of ${scramble.idleTimeout} milliseconds.`) * }) * * console.log(await scramble.run('hello world')) // outputs a randomly rearranged 'hello world' * ``` */ on(eventName: 'close', listener: (thread: Thread) => void): this; /** * Adds a **one-time** `listener` function for the event named `idle`. The next time `idle` is triggered, this listener is removed and then invoked. * This event fires every time a thread has completed all of its pending tasks. * By default, event listeners are invoked in the order they are added. The `emitter.prependOnceListener()` method can be used as an alternative to add the * event listener to the beginning of the listeners array. * @returns A reference to the `EventEmitter`, so that calls can be chained. * @example * ```ts * import { Thread } from "bun-threads"; * * const reverse = new Thread((longStringtoReverse: string) => { * return longStringtoReverse.split('').toReversed().join('') * }) * * reverse.once('idle', () => console.log('Reverse thread is now idling.')) * * reverse.run('Answer to the Ultimate Question of Life, The Universe, and Everything').then((result) => console.log('Reversed:', result)) * console.log('doing some other work in the meantime...') * console.log('working...') * console.log('working...') * reverse.close() * ``` */ once(eventName: 'idle', listener: (thread: Thread) => void): this; /** * Adds a **one-time** `listener` function for the event named `busy`. The next time `busy` is triggered, this listener is removed and then invoked. * This event fires every time a thread has switched from an idle state to working on a task. * By default, event listeners are invoked in the order they are added. The `emitter.prependOnceListener()` method can be used as an alternative to add the * event listener to the beginning of the listeners array. * @returns A reference to the `EventEmitter`, so that calls can be chained. * @example * ```ts * import { Thread } from "bun-threads"; * * const generate = new Thread((length: number, min: number = 0, max: number = 100) => { * const arr: number[] = [] * for (let i = 0; i < length; i++) { * arr.push(Math.round(Math.random() * (max - min) + min)) * } * return arr * }, { idleTimeout: 0 }) * * generate.once('busy', () => console.log('Thread is busy generating a random number array...')) * * generate.run(100).then((result: number[]) => console.log(result)) * console.log('Doing other work in the meantime...') * ``` */ once(eventName: 'busy', listener: (thread: Thread) => void): this; /** * Adds a **one-time** `listener` function for the event named `close`. The next time `close` is triggered, this listener is removed and then invoked. * This event fires once when a thread has closed its underlying worker object. * By default, event listeners are invoked in the order they are added. The `emitter.prependOnceListener()` method can be used as an alternative to add the * event listener to the beginning of the listeners array. * @returns A reference to the `EventEmitter`, so that calls can be chained. * @example * ```ts * import { Thread } from "bun-threads"; * * const sumThread = new Thread((start: number, end: number) => { * let sum: number = 0 * for (let i = start; i <= end; i++) { * sum += i * } * return sum * }, { idleTimeout: 0 }) * * sumThread.once('close', () => console.log('sumThread has finished operation and is shutting down...')) * sumThread.run(0, 1_000_000).then((sum: number) => console.log(sum)) * ``` */ once(eventName: 'close', listener: (thread: Thread) => void): this; prependListener(eventName: 'idle' | 'busy' | 'close', listener: (thread: Thread) => void): this; prependOnceListener(eventName: 'idle' | 'busy' | 'close', listener: (thread: Thread) => void): this; }