1 import { getRandomValues
} from
'node:crypto'
2 import * as os from
'node:os'
4 import type { TaskFunctionProperties
} from
'./utility-types.js'
5 import type { TaskFunctionObject
} from
'./worker/task-functions.js'
6 import type { KillBehavior
} from
'./worker/worker-options.js'
11 export const DEFAULT_TASK_NAME
= 'default'
14 * An intentional empty function.
16 export const EMPTY_FUNCTION
: () => void = Object.freeze(() => {
17 /* Intentionally empty */
21 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
22 * Always returns a value greater than zero.
24 * @returns The host OS optimized maximum pool size.
26 export const availableParallelism
= (): number => {
27 let availableParallelism
= 1
29 availableParallelism
= os
.availableParallelism()
31 const cpus
= os
.cpus()
32 if (Array.isArray(cpus
) && cpus
.length
> 0) {
33 availableParallelism
= cpus
.length
36 return availableParallelism
40 * Sleeps for the given amount of milliseconds.
42 * @param ms - The amount of milliseconds to sleep.
43 * @returns A promise that resolves after the given amount of milliseconds.
46 export const sleep
= async (ms
: number): Promise
<void> => {
47 await new Promise(resolve
=> {
48 setTimeout(resolve
, ms
)
53 * Computes the retry delay in milliseconds using an exponential back off algorithm.
55 * @param retryNumber - The number of retries that have already been attempted
56 * @param delayFactor - The base delay factor in milliseconds
57 * @returns Delay in milliseconds
60 export const exponentialDelay
= (
64 const delay
= Math.pow(2, retryNumber
) * delayFactor
65 const randomSum
= delay
* 0.2 * secureRandom() // 0-20% of the delay
66 return delay
+ randomSum
70 * Computes the average of the given data set.
72 * @param dataSet - Data set.
73 * @returns The average of the given data set.
76 export const average
= (dataSet
: number[]): number => {
77 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
79 } else if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
83 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
89 * Computes the median of the given data set.
91 * @param dataSet - Data set.
92 * @returns The median of the given data set.
95 export const median
= (dataSet
: number[]): number => {
96 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
98 } else if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
101 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
103 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
104 sortedDataSet
[sortedDataSet
.length
>> 1]) /
110 * Rounds the given number to the given scale.
111 * The rounding is done using the "round half away from zero" method.
113 * @param num - The number to round.
114 * @param scale - The scale to round to.
115 * @returns The rounded number.
118 export const round
= (num
: number, scale
= 2): number => {
119 const rounder
= Math.pow(10, scale
)
120 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
124 * Is the given value a plain object?
126 * @param value - The value to check.
127 * @returns `true` if the given value is a plain object, `false` otherwise.
130 export const isPlainObject
= (value
: unknown
): value
is object
=>
131 typeof value
=== 'object' &&
133 value
.constructor
=== Object &&
134 Object.prototype
.toString
.call(value
) === '[object Object]'
137 * Detects whether the given value is a kill behavior or not.
139 * @typeParam KB - Which specific KillBehavior type to test against.
140 * @param killBehavior - Which kind of kill behavior to detect.
141 * @param value - Unknown value.
142 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
145 export const isKillBehavior
= <KB
extends KillBehavior
>(
149 return value
=== killBehavior
153 * Detects whether the given value is an asynchronous function or not.
155 * @param fn - Unknown value.
156 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
159 export const isAsyncFunction
= (
161 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
162 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
166 * Generates a cryptographically secure random number in the [0,1[ range
168 * @returns A number in the [0,1[ range
171 export const secureRandom
= (): number => {
172 return getRandomValues(new Uint32Array(1))[0] / 0x100000000
176 * Returns the minimum of the given numbers.
177 * If no numbers are given, `Number.POSITIVE_INFINITY` is returned.
179 * @param args - The numbers to get the minimum of.
180 * @returns The minimum of the given numbers.
183 export const min
= (...args
: number[]): number =>
185 (minimum
, num
) => (minimum
< num
? minimum
: num
),
186 Number.POSITIVE_INFINITY
190 * Returns the maximum of the given numbers.
191 * If no numbers are given, `Number.NEGATIVE_INFINITY` is returned.
193 * @param args - The numbers to get the maximum of.
194 * @returns The maximum of the given numbers.
197 export const max
= (...args
: number[]): number =>
199 (maximum
, num
) => (maximum
> num
? maximum
: num
),
200 Number.NEGATIVE_INFINITY
204 * Wraps a function so that it can only be called once.
206 * @param fn - The function to wrap.
207 * @param context - The context to bind the function to.
208 * @returns The wrapped function.
210 * @typeParam A - The function's arguments.
211 * @typeParam R - The function's return value.
212 * @typeParam C - The function's context.
215 // eslint-disable-next-line @typescript-eslint/no-explicit-any
216 export const once
= <A
extends any[], R
, C
extends ThisType
<any>>(
217 fn
: (...args
: A
) => R
,
219 ): ((...args
: A
) => R
) => {
221 return (...args
: A
) => {
222 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
224 result
= fn
.apply
<C
, A
, R
>(context
, args
)
225 ;(fn
as unknown
as undefined) = (context
as unknown
as undefined) =
232 export const buildTaskFunctionProperties
= <Data
, Response
>(
234 taskFunctionObject
: TaskFunctionObject
<Data
, Response
> | undefined
235 ): TaskFunctionProperties
=> {
238 ...(taskFunctionObject
?.priority
!= null && {
239 priority
: taskFunctionObject
.priority
241 ...(taskFunctionObject
?.strategy
!= null && {
242 strategy
: taskFunctionObject
.strategy