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.
23 * @returns The host OS optimized maximum pool size.
25 export const availableParallelism
= (): number => {
26 let availableParallelism
= 1
28 availableParallelism
= os
.availableParallelism()
30 const cpus
= os
.cpus()
31 if (Array.isArray(cpus
) && cpus
.length
> 0) {
32 availableParallelism
= cpus
.length
35 return availableParallelism
39 * Sleeps for the given amount of milliseconds.
40 * @param ms - The amount of milliseconds to sleep.
41 * @returns A promise that resolves after the given amount of milliseconds.
44 export const sleep
= async (ms
: number): Promise
<void> => {
45 await new Promise(resolve
=> {
46 setTimeout(resolve
, ms
)
51 * Computes the retry delay in milliseconds using an exponential back off algorithm.
52 * @param retryNumber - The number of retries that have already been attempted
53 * @param delayFactor - The base delay factor in milliseconds
54 * @returns Delay in milliseconds
57 export const exponentialDelay
= (
61 const delay
= Math.pow(2, retryNumber
) * delayFactor
62 const randomSum
= delay
* 0.2 * secureRandom() // 0-20% of the delay
63 return delay
+ randomSum
67 * Computes the average of the given data set.
68 * @param dataSet - Data set.
69 * @returns The average of the given data set.
72 export const average
= (dataSet
: number[]): number => {
73 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
75 } else if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
79 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
85 * Computes the median of the given data set.
86 * @param dataSet - Data set.
87 * @returns The median of the given data set.
90 export const median
= (dataSet
: number[]): number => {
91 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
93 } else if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
96 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
98 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
99 sortedDataSet
[sortedDataSet
.length
>> 1]) /
105 * Rounds the given number to the given scale.
106 * The rounding is done using the "round half away from zero" method.
107 * @param num - The number to round.
108 * @param scale - The scale to round to.
109 * @returns The rounded number.
112 export const round
= (num
: number, scale
= 2): number => {
113 const rounder
= Math.pow(10, scale
)
114 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
118 * Is the given value a plain object?
119 * @param value - The value to check.
120 * @returns `true` if the given value is a plain object, `false` otherwise.
123 export const isPlainObject
= (value
: unknown
): value
is object
=>
124 typeof value
=== 'object' &&
126 value
.constructor
=== Object &&
127 Object.prototype
.toString
.call(value
) === '[object Object]'
130 * Detects whether the given value is a kill behavior or not.
131 * @typeParam KB - Which specific KillBehavior type to test against.
132 * @param killBehavior - Which kind of kill behavior to detect.
133 * @param value - Unknown value.
134 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
137 export const isKillBehavior
= <KB
extends KillBehavior
>(
141 return value
=== killBehavior
145 * Detects whether the given value is an asynchronous function or not.
146 * @param fn - Unknown value.
147 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
150 export const isAsyncFunction
= (
152 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
153 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
157 * Generates a cryptographically secure random number in the [0,1[ range
158 * @returns A number in the [0,1[ range
161 export const secureRandom
= (): number => {
162 return getRandomValues(new Uint32Array(1))[0] / 0x100000000
166 * Returns the minimum of the given numbers.
167 * If no numbers are given, `Number.POSITIVE_INFINITY` is returned.
168 * @param args - The numbers to get the minimum of.
169 * @returns The minimum of the given numbers.
172 export const min
= (...args
: number[]): number =>
174 (minimum
, num
) => (minimum
< num
? minimum
: num
),
175 Number.POSITIVE_INFINITY
179 * Returns the maximum of the given numbers.
180 * If no numbers are given, `Number.NEGATIVE_INFINITY` is returned.
181 * @param args - The numbers to get the maximum of.
182 * @returns The maximum of the given numbers.
185 export const max
= (...args
: number[]): number =>
187 (maximum
, num
) => (maximum
> num
? maximum
: num
),
188 Number.NEGATIVE_INFINITY
192 * Wraps a function so that it can only be called once.
193 * @param fn - The function to wrap.
194 * @param context - The context to bind the function to.
195 * @returns The wrapped function.
196 * @typeParam A - The function's arguments.
197 * @typeParam R - The function's return value.
198 * @typeParam C - The function's context.
201 // eslint-disable-next-line @typescript-eslint/no-explicit-any
202 export const once
= <A
extends any[], R
, C
extends ThisType
<any>>(
203 fn
: (...args
: A
) => R
,
205 ): ((...args
: A
) => R
) => {
207 return (...args
: A
) => {
208 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
210 result
= fn
.apply
<C
, A
, R
>(context
, args
)
211 ;(fn
as unknown
as undefined) = (context
as unknown
as undefined) =
218 export const buildTaskFunctionProperties
= <Data
, Response
>(
220 taskFunctionObject
: TaskFunctionObject
<Data
, Response
> | undefined
221 ): TaskFunctionProperties
=> {
224 ...(taskFunctionObject
?.priority
!= null && {
225 priority
: taskFunctionObject
.priority
,
227 ...(taskFunctionObject
?.strategy
!= null && {
228 strategy
: taskFunctionObject
.strategy
,