/ src / utils / sequential.ts
sequential.ts
 1  type QueueItem<T extends unknown[], R> = {
 2    args: T
 3    resolve: (value: R) => void
 4    reject: (reason?: unknown) => void
 5    context: unknown
 6  }
 7  
 8  /**
 9   * Creates a sequential execution wrapper for async functions to prevent race conditions.
10   * Ensures that concurrent calls to the wrapped function are executed one at a time
11   * in the order they were received, while preserving the correct return values.
12   *
13   * This is useful for operations that must be performed sequentially, such as
14   * file writes or database updates that could cause conflicts if executed concurrently.
15   *
16   * @param fn - The async function to wrap with sequential execution
17   * @returns A wrapped version of the function that executes calls sequentially
18   */
19  export function sequential<T extends unknown[], R>(
20    fn: (...args: T) => Promise<R>,
21  ): (...args: T) => Promise<R> {
22    const queue: QueueItem<T, R>[] = []
23    let processing = false
24  
25    async function processQueue(): Promise<void> {
26      if (processing) return
27      if (queue.length === 0) return
28  
29      processing = true
30  
31      while (queue.length > 0) {
32        const { args, resolve, reject, context } = queue.shift()!
33  
34        try {
35          const result = await fn.apply(context, args)
36          resolve(result)
37        } catch (error) {
38          reject(error)
39        }
40      }
41  
42      processing = false
43  
44      // Check if new items were added while we were processing
45      if (queue.length > 0) {
46        void processQueue()
47      }
48    }
49  
50    return function (this: unknown, ...args: T): Promise<R> {
51      return new Promise((resolve, reject) => {
52        queue.push({ args, resolve, reject, context: this })
53        void processQueue()
54      })
55    }
56  }