ai-workflows 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.md +305 -184
  4. package/dist/barrier.d.ts +159 -0
  5. package/dist/barrier.d.ts.map +1 -0
  6. package/dist/barrier.js +377 -0
  7. package/dist/barrier.js.map +1 -0
  8. package/dist/cascade-context.d.ts +149 -0
  9. package/dist/cascade-context.d.ts.map +1 -0
  10. package/dist/cascade-context.js +324 -0
  11. package/dist/cascade-context.js.map +1 -0
  12. package/dist/cascade-executor.d.ts +196 -0
  13. package/dist/cascade-executor.d.ts.map +1 -0
  14. package/dist/cascade-executor.js +384 -0
  15. package/dist/cascade-executor.js.map +1 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +27 -8
  18. package/dist/context.js.map +1 -1
  19. package/dist/cron-parser.d.ts +65 -0
  20. package/dist/cron-parser.d.ts.map +1 -0
  21. package/dist/cron-parser.js +294 -0
  22. package/dist/cron-parser.js.map +1 -0
  23. package/dist/cron-scheduler.d.ts +117 -0
  24. package/dist/cron-scheduler.d.ts.map +1 -0
  25. package/dist/cron-scheduler.js +176 -0
  26. package/dist/cron-scheduler.js.map +1 -0
  27. package/dist/database-context.d.ts +184 -0
  28. package/dist/database-context.d.ts.map +1 -0
  29. package/dist/database-context.js +428 -0
  30. package/dist/database-context.js.map +1 -0
  31. package/dist/dependency-graph.d.ts +157 -0
  32. package/dist/dependency-graph.d.ts.map +1 -0
  33. package/dist/dependency-graph.js +382 -0
  34. package/dist/dependency-graph.js.map +1 -0
  35. package/dist/digital-objects-adapter.d.ts +159 -0
  36. package/dist/digital-objects-adapter.d.ts.map +1 -0
  37. package/dist/digital-objects-adapter.js +229 -0
  38. package/dist/digital-objects-adapter.js.map +1 -0
  39. package/dist/durable-execution-cloudflare.d.ts +427 -0
  40. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  41. package/dist/durable-execution-cloudflare.js +510 -0
  42. package/dist/durable-execution-cloudflare.js.map +1 -0
  43. package/dist/durable-execution.d.ts +482 -0
  44. package/dist/durable-execution.d.ts.map +1 -0
  45. package/dist/durable-execution.js +594 -0
  46. package/dist/durable-execution.js.map +1 -0
  47. package/dist/durable-workflow.d.ts +176 -0
  48. package/dist/durable-workflow.d.ts.map +1 -0
  49. package/dist/durable-workflow.js +552 -0
  50. package/dist/durable-workflow.js.map +1 -0
  51. package/dist/every.d.ts +31 -2
  52. package/dist/every.d.ts.map +1 -1
  53. package/dist/every.js +63 -32
  54. package/dist/every.js.map +1 -1
  55. package/dist/graph/index.d.ts +8 -0
  56. package/dist/graph/index.d.ts.map +1 -0
  57. package/dist/graph/index.js +8 -0
  58. package/dist/graph/index.js.map +1 -0
  59. package/dist/graph/topological-sort.d.ts +121 -0
  60. package/dist/graph/topological-sort.d.ts.map +1 -0
  61. package/dist/graph/topological-sort.js +292 -0
  62. package/dist/graph/topological-sort.js.map +1 -0
  63. package/dist/index.d.ts +10 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/logger.d.ts +101 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +115 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/on.d.ts +35 -10
  72. package/dist/on.d.ts.map +1 -1
  73. package/dist/on.js +53 -19
  74. package/dist/on.js.map +1 -1
  75. package/dist/runtime.d.ts +169 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +275 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/send.d.ts.map +1 -1
  80. package/dist/send.js +4 -3
  81. package/dist/send.js.map +1 -1
  82. package/dist/telemetry.d.ts +150 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +388 -0
  85. package/dist/telemetry.js.map +1 -0
  86. package/dist/timer-registry.d.ts +77 -0
  87. package/dist/timer-registry.d.ts.map +1 -0
  88. package/dist/timer-registry.js +154 -0
  89. package/dist/timer-registry.js.map +1 -0
  90. package/dist/types.d.ts +105 -6
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +17 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/worker/durable-step.d.ts +481 -0
  95. package/dist/worker/durable-step.d.ts.map +1 -0
  96. package/dist/worker/durable-step.js +606 -0
  97. package/dist/worker/durable-step.js.map +1 -0
  98. package/dist/worker/index.d.ts +106 -0
  99. package/dist/worker/index.d.ts.map +1 -0
  100. package/dist/worker/index.js +124 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/state-adapter.d.ts +230 -0
  103. package/dist/worker/state-adapter.d.ts.map +1 -0
  104. package/dist/worker/state-adapter.js +409 -0
  105. package/dist/worker/state-adapter.js.map +1 -0
  106. package/dist/worker/topological-executor.d.ts +282 -0
  107. package/dist/worker/topological-executor.d.ts.map +1 -0
  108. package/dist/worker/topological-executor.js +396 -0
  109. package/dist/worker/topological-executor.js.map +1 -0
  110. package/dist/worker/workflow-builder.d.ts +286 -0
  111. package/dist/worker/workflow-builder.d.ts.map +1 -0
  112. package/dist/worker/workflow-builder.js +565 -0
  113. package/dist/worker/workflow-builder.js.map +1 -0
  114. package/dist/worker.d.ts +800 -0
  115. package/dist/worker.d.ts.map +1 -0
  116. package/dist/worker.js +2428 -0
  117. package/dist/worker.js.map +1 -0
  118. package/dist/workflow-builder.d.ts +287 -0
  119. package/dist/workflow-builder.d.ts.map +1 -0
  120. package/dist/workflow-builder.js +762 -0
  121. package/dist/workflow-builder.js.map +1 -0
  122. package/dist/workflow.d.ts +14 -30
  123. package/dist/workflow.d.ts.map +1 -1
  124. package/dist/workflow.js +136 -292
  125. package/dist/workflow.js.map +1 -1
  126. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  127. package/examples/02-content-moderation-cascade.ts +454 -0
  128. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  129. package/examples/04-database-persistence.ts +518 -0
  130. package/examples/README.md +173 -0
  131. package/package.json +21 -4
  132. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  133. package/src/__tests__/durable-workflow.test.ts +297 -0
  134. package/src/barrier.ts +507 -0
  135. package/src/cascade-context.ts +495 -0
  136. package/src/cascade-executor.ts +588 -0
  137. package/src/context.ts +51 -17
  138. package/src/cron-parser.ts +347 -0
  139. package/src/cron-scheduler.ts +239 -0
  140. package/src/database-context.ts +658 -0
  141. package/src/dependency-graph.ts +518 -0
  142. package/src/digital-objects-adapter.ts +351 -0
  143. package/src/durable-execution-cloudflare.ts +855 -0
  144. package/src/durable-execution.ts +1042 -0
  145. package/src/durable-workflow.ts +717 -0
  146. package/src/every.ts +104 -35
  147. package/src/graph/index.ts +19 -0
  148. package/src/graph/topological-sort.ts +412 -0
  149. package/src/index.ts +147 -0
  150. package/src/logger.ts +148 -0
  151. package/src/on.ts +81 -26
  152. package/src/runtime.ts +436 -0
  153. package/src/send.ts +4 -5
  154. package/src/telemetry.ts +577 -0
  155. package/src/timer-registry.ts +179 -0
  156. package/src/types.ts +146 -10
  157. package/src/worker/durable-step.ts +976 -0
  158. package/src/worker/index.ts +216 -0
  159. package/src/worker/state-adapter.ts +589 -0
  160. package/src/worker/topological-executor.ts +625 -0
  161. package/src/worker/workflow-builder.ts +871 -0
  162. package/src/worker.ts +2906 -0
  163. package/src/workflow-builder.ts +1068 -0
  164. package/src/workflow.ts +199 -355
  165. package/test/barrier-join.test.ts +442 -0
  166. package/test/barrier-unhandled-rejections.test.ts +359 -0
  167. package/test/cascade-context.test.ts +390 -0
  168. package/test/cascade-executor.test.ts +852 -0
  169. package/test/cron-parser.test.ts +314 -0
  170. package/test/cron-scheduler.test.ts +291 -0
  171. package/test/database-context.test.ts +770 -0
  172. package/test/db-provider-adapter.test.ts +862 -0
  173. package/test/dependency-graph.test.ts +512 -0
  174. package/test/durable-execution-cloudflare.test.ts +606 -0
  175. package/test/durable-execution-in-process.test.ts +286 -0
  176. package/test/durable-execution.test.ts +247 -0
  177. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  178. package/test/graph/topological-sort.test.ts +586 -0
  179. package/test/integration.test.ts +442 -0
  180. package/test/rpc-surface.test.ts +946 -0
  181. package/test/runtime.test.ts +262 -0
  182. package/test/schedule-timer-cleanup.test.ts +353 -0
  183. package/test/send-race-conditions.test.ts +400 -0
  184. package/test/type-safety-every.test.ts +303 -0
  185. package/test/worker/durable-cascade.test.ts +1117 -0
  186. package/test/worker/durable-step.test.ts +723 -0
  187. package/test/worker/topological-executor.test.ts +1240 -0
  188. package/test/worker/workflow-builder.test.ts +1067 -0
  189. package/test/worker.test.ts +608 -0
  190. package/test/workflow-builder.test.ts +1670 -0
  191. package/test/workflow-cron.test.ts +256 -0
  192. package/test/workflow-state-adapter.test.ts +923 -0
  193. package/test/workflow.test.ts +25 -22
  194. package/tsconfig.json +3 -1
  195. package/vitest.config.ts +38 -1
  196. package/vitest.workers.config.ts +44 -0
  197. package/wrangler.jsonc +22 -0
  198. package/.turbo/turbo-test.log +0 -7
  199. package/src/context.js +0 -83
  200. package/src/every.js +0 -267
  201. package/src/index.js +0 -71
  202. package/src/on.js +0 -79
  203. package/src/send.js +0 -111
  204. package/src/types.js +0 -4
  205. package/src/workflow.js +0 -455
  206. package/test/context.test.js +0 -116
  207. package/test/every.test.js +0 -282
  208. package/test/on.test.js +0 -80
  209. package/test/send.test.js +0 -89
  210. package/test/workflow.test.js +0 -224
  211. package/vitest.config.js +0 -7
package/src/barrier.ts ADDED
@@ -0,0 +1,507 @@
1
+ /**
2
+ * Barrier/Join semantics for parallel step coordination
3
+ *
4
+ * Provides synchronization primitives for coordinating parallel workflow steps:
5
+ * - waitForAll() - wait for all steps to complete
6
+ * - waitForAny(n) - wait for N of M steps to complete
7
+ * - Barrier class - manual synchronization point
8
+ * - withConcurrencyLimit() - limit concurrent executions
9
+ */
10
+
11
+ /**
12
+ * Progress information for a barrier
13
+ */
14
+ export interface BarrierProgress<T = unknown> {
15
+ /** Number of participants that have arrived */
16
+ arrived: number
17
+ /** Expected number of participants */
18
+ expected: number
19
+ /** Percentage complete (0-100) */
20
+ percentage: number
21
+ /** Most recently arrived value */
22
+ latest?: T
23
+ }
24
+
25
+ /**
26
+ * Options for barrier creation
27
+ */
28
+ export interface BarrierOptions<T = unknown> {
29
+ /** Timeout in milliseconds */
30
+ timeout?: number
31
+ /** AbortSignal for cancellation */
32
+ signal?: AbortSignal
33
+ /** Progress callback */
34
+ onProgress?: (progress: BarrierProgress<T>) => void
35
+ }
36
+
37
+ /**
38
+ * Options for waitForAll
39
+ */
40
+ export interface WaitForAllOptions {
41
+ /** Timeout in milliseconds */
42
+ timeout?: number
43
+ /** AbortSignal for cancellation */
44
+ signal?: AbortSignal
45
+ }
46
+
47
+ /**
48
+ * Options for waitForAny
49
+ */
50
+ export interface WaitForAnyOptions {
51
+ /** Timeout in milliseconds */
52
+ timeout?: number
53
+ /** AbortSignal for cancellation */
54
+ signal?: AbortSignal
55
+ /** Return partial results on timeout instead of throwing */
56
+ returnPartialOnTimeout?: boolean
57
+ }
58
+
59
+ /**
60
+ * Result from waitForAny
61
+ */
62
+ export interface WaitForAnyResult<T> {
63
+ /** Completed values */
64
+ completed: T[]
65
+ /** Number of still pending promises */
66
+ pending: number[]
67
+ /** Whether the operation timed out */
68
+ timedOut?: boolean
69
+ }
70
+
71
+ /**
72
+ * Options for concurrency limiting
73
+ */
74
+ export interface ConcurrencyOptions {
75
+ /** Collect errors instead of failing fast */
76
+ collectErrors?: boolean
77
+ }
78
+
79
+ /**
80
+ * Result type for barrier operations
81
+ */
82
+ export interface BarrierResult<T> {
83
+ values: T[]
84
+ timedOut: boolean
85
+ }
86
+
87
+ /**
88
+ * Error thrown when a barrier times out
89
+ */
90
+ export class BarrierTimeoutError extends Error {
91
+ public readonly timeout: number
92
+ public readonly arrived: number
93
+ public readonly expected: number
94
+
95
+ constructor(timeout: number, arrived: number, expected: number) {
96
+ super(`Barrier timeout after ${timeout}ms: ${arrived}/${expected} participants arrived`)
97
+ this.name = 'BarrierTimeoutError'
98
+ this.timeout = timeout
99
+ this.arrived = arrived
100
+ this.expected = expected
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Barrier class for manual synchronization
106
+ */
107
+ export class Barrier<T = unknown> {
108
+ private readonly _expected: number
109
+ private readonly _options: BarrierOptions<T>
110
+ private _arrived: T[] = []
111
+ private _waitResolve: ((values: T[]) => void) | null = null
112
+ private _waitReject: ((error: Error) => void) | null = null
113
+ private _timeoutId: ReturnType<typeof setTimeout> | null = null
114
+ private _cancelled = false
115
+ private _cancelError: Error | null = null
116
+ private _abortHandler: (() => void) | null = null
117
+
118
+ constructor(expectedCount: number, options: BarrierOptions<T> = {}) {
119
+ this._expected = expectedCount
120
+ this._options = options
121
+
122
+ // Set up abort signal listener with proper cleanup tracking
123
+ if (options.signal) {
124
+ this._abortHandler = () => {
125
+ this.cancel(new Error('Operation aborted'))
126
+ }
127
+ options.signal.addEventListener('abort', this._abortHandler, { once: true })
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Number of expected participants
133
+ */
134
+ get expectedCount(): number {
135
+ return this._expected
136
+ }
137
+
138
+ /**
139
+ * Number of participants that have arrived
140
+ */
141
+ get arrivedCount(): number {
142
+ return this._arrived.length
143
+ }
144
+
145
+ /**
146
+ * Whether all expected participants have arrived
147
+ */
148
+ get isComplete(): boolean {
149
+ return this._arrived.length >= this._expected
150
+ }
151
+
152
+ /**
153
+ * Record a participant's arrival at the barrier
154
+ */
155
+ arrive(value: T): void {
156
+ if (this._cancelled) {
157
+ return
158
+ }
159
+
160
+ this._arrived.push(value)
161
+
162
+ // Emit progress
163
+ if (this._options.onProgress) {
164
+ this._options.onProgress(this.getProgress())
165
+ }
166
+
167
+ // Check if barrier is complete
168
+ if (this.isComplete && this._waitResolve) {
169
+ this._clearTimeout()
170
+ this._waitResolve([...this._arrived])
171
+ this._waitResolve = null
172
+ this._waitReject = null
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Wait for all participants to arrive
178
+ */
179
+ wait(): Promise<T[]> {
180
+ if (this._cancelled && this._cancelError) {
181
+ return Promise.reject(this._cancelError)
182
+ }
183
+
184
+ if (this.isComplete) {
185
+ return Promise.resolve([...this._arrived])
186
+ }
187
+
188
+ return new Promise<T[]>((resolve, reject) => {
189
+ this._waitResolve = resolve
190
+ this._waitReject = reject
191
+
192
+ // Set up timeout if specified
193
+ if (this._options.timeout) {
194
+ this._timeoutId = setTimeout(() => {
195
+ if (this._waitReject) {
196
+ const error = new BarrierTimeoutError(
197
+ this._options.timeout!,
198
+ this._arrived.length,
199
+ this._expected
200
+ )
201
+ this._waitReject(error)
202
+ this._waitResolve = null
203
+ this._waitReject = null
204
+ }
205
+ }, this._options.timeout)
206
+ }
207
+ })
208
+ }
209
+
210
+ /**
211
+ * Reset the barrier for reuse
212
+ */
213
+ reset(): void {
214
+ this._clearTimeout()
215
+ this._clearAbortHandler()
216
+ this._arrived = []
217
+ this._waitResolve = null
218
+ this._waitReject = null
219
+ this._cancelled = false
220
+ this._cancelError = null
221
+ }
222
+
223
+ /**
224
+ * Cancel the barrier operation
225
+ */
226
+ cancel(error: Error): void {
227
+ this._cancelled = true
228
+ this._cancelError = error
229
+ this._clearTimeout()
230
+ this._clearAbortHandler()
231
+
232
+ if (this._waitReject) {
233
+ this._waitReject(error)
234
+ this._waitResolve = null
235
+ this._waitReject = null
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Dispose of the barrier and cleanup all resources
241
+ */
242
+ dispose(): void {
243
+ this._clearTimeout()
244
+ this._clearAbortHandler()
245
+ this._waitResolve = null
246
+ this._waitReject = null
247
+ }
248
+
249
+ /**
250
+ * Get current progress information
251
+ */
252
+ getProgress(): BarrierProgress<T> {
253
+ const latestValue = this._arrived[this._arrived.length - 1]
254
+ return {
255
+ arrived: this._arrived.length,
256
+ expected: this._expected,
257
+ percentage: Math.round((this._arrived.length / this._expected) * 100),
258
+ ...(latestValue !== undefined && { latest: latestValue }),
259
+ }
260
+ }
261
+
262
+ private _clearTimeout(): void {
263
+ if (this._timeoutId) {
264
+ clearTimeout(this._timeoutId)
265
+ this._timeoutId = null
266
+ }
267
+ }
268
+
269
+ private _clearAbortHandler(): void {
270
+ if (this._abortHandler && this._options.signal) {
271
+ this._options.signal.removeEventListener('abort', this._abortHandler)
272
+ this._abortHandler = null
273
+ }
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Create a new barrier
279
+ */
280
+ export function createBarrier<T = unknown>(
281
+ expectedCount: number,
282
+ options: BarrierOptions<T> = {}
283
+ ): Barrier<T> {
284
+ return new Barrier<T>(expectedCount, options)
285
+ }
286
+
287
+ /**
288
+ * Wait for all promises to complete
289
+ *
290
+ * Similar to Promise.all but with timeout and cancellation support
291
+ */
292
+ export async function waitForAll<T>(
293
+ promises: Promise<T>[],
294
+ options: WaitForAllOptions = {}
295
+ ): Promise<T[]> {
296
+ if (promises.length === 0) {
297
+ return []
298
+ }
299
+
300
+ const { timeout, signal } = options
301
+
302
+ // Track cleanup handlers
303
+ let abortHandler: (() => void) | null = null
304
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
305
+
306
+ const cleanup = () => {
307
+ if (abortHandler && signal) {
308
+ signal.removeEventListener('abort', abortHandler)
309
+ abortHandler = null
310
+ }
311
+ if (timeoutId) {
312
+ clearTimeout(timeoutId)
313
+ timeoutId = null
314
+ }
315
+ }
316
+
317
+ // Build array of promises to race against
318
+ const racers: Promise<T[]>[] = [Promise.all(promises)]
319
+
320
+ // Add abort signal handling
321
+ if (signal) {
322
+ const abortPromise = new Promise<never>((_, reject) => {
323
+ abortHandler = () => {
324
+ reject(new Error('Operation aborted'))
325
+ }
326
+ if (signal.aborted) {
327
+ abortHandler()
328
+ } else {
329
+ signal.addEventListener('abort', abortHandler, { once: true })
330
+ }
331
+ })
332
+ racers.push(abortPromise)
333
+ }
334
+
335
+ // Add timeout if specified
336
+ if (timeout) {
337
+ const timeoutPromise = new Promise<never>((_, reject) => {
338
+ timeoutId = setTimeout(() => {
339
+ reject(new BarrierTimeoutError(timeout, 0, promises.length))
340
+ }, timeout)
341
+ })
342
+ racers.push(timeoutPromise)
343
+ }
344
+
345
+ try {
346
+ return await Promise.race(racers)
347
+ } finally {
348
+ cleanup()
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Wait for N of M promises to complete
354
+ */
355
+ export async function waitForAny<T>(
356
+ n: number,
357
+ promises: Promise<T>[],
358
+ options: WaitForAnyOptions = {}
359
+ ): Promise<WaitForAnyResult<T>> {
360
+ const { timeout, returnPartialOnTimeout = false } = options
361
+
362
+ if (n === 0) {
363
+ return {
364
+ completed: [],
365
+ pending: promises.map((_, i) => i),
366
+ }
367
+ }
368
+
369
+ if (n > promises.length) {
370
+ throw new Error(`Cannot wait for ${n} of ${promises.length} promises`)
371
+ }
372
+
373
+ const completed: T[] = []
374
+ const errors: Error[] = []
375
+ const pendingIndices = new Set(promises.map((_, i) => i))
376
+ let resolved = false
377
+ let timedOut = false
378
+
379
+ return new Promise<WaitForAnyResult<T>>((resolve, reject) => {
380
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
381
+
382
+ const checkComplete = () => {
383
+ if (resolved) return
384
+
385
+ // Check if we have enough completions
386
+ if (completed.length >= n) {
387
+ resolved = true
388
+ if (timeoutId) clearTimeout(timeoutId)
389
+ resolve({
390
+ completed: [...completed],
391
+ pending: [...pendingIndices],
392
+ })
393
+ return
394
+ }
395
+
396
+ // Check if it's impossible to complete (too many failures)
397
+ const remaining = pendingIndices.size
398
+ const canComplete = completed.length + remaining
399
+ if (canComplete < n) {
400
+ resolved = true
401
+ if (timeoutId) clearTimeout(timeoutId)
402
+ reject(new Error(`Cannot complete: need ${n} but only ${canComplete} can succeed`))
403
+ }
404
+ }
405
+
406
+ // Track each promise
407
+ promises.forEach((promise, index) => {
408
+ promise
409
+ .then((value) => {
410
+ if (resolved) return
411
+ completed.push(value)
412
+ pendingIndices.delete(index)
413
+ checkComplete()
414
+ })
415
+ .catch((error) => {
416
+ if (resolved) return
417
+ errors.push(error)
418
+ pendingIndices.delete(index)
419
+ checkComplete()
420
+ })
421
+ })
422
+
423
+ // Set up timeout
424
+ if (timeout) {
425
+ timeoutId = setTimeout(() => {
426
+ if (resolved) return
427
+ timedOut = true
428
+
429
+ if (returnPartialOnTimeout) {
430
+ resolved = true
431
+ resolve({
432
+ completed: [...completed],
433
+ pending: [...pendingIndices],
434
+ timedOut: true,
435
+ })
436
+ } else {
437
+ resolved = true
438
+ reject(new BarrierTimeoutError(timeout, completed.length, n))
439
+ }
440
+ }, timeout)
441
+ }
442
+ })
443
+ }
444
+
445
+ /**
446
+ * Execute tasks with a concurrency limit
447
+ */
448
+ export async function withConcurrencyLimit<T>(
449
+ tasks: (() => Promise<T>)[],
450
+ limit: number,
451
+ options: ConcurrencyOptions = {}
452
+ ): Promise<(T | Error)[]> {
453
+ const { collectErrors = false } = options
454
+
455
+ if (tasks.length === 0) {
456
+ return []
457
+ }
458
+
459
+ const results: (T | Error)[] = new Array(tasks.length)
460
+ let currentIndex = 0
461
+ let hasError = false
462
+ let firstError: Error | null = null
463
+
464
+ const runTask = async (): Promise<void> => {
465
+ while (currentIndex < tasks.length) {
466
+ const index = currentIndex++
467
+ const task = tasks[index]
468
+
469
+ if (!task) continue
470
+
471
+ try {
472
+ results[index] = await task()
473
+ } catch (error) {
474
+ const err = error instanceof Error ? error : new Error(String(error))
475
+ if (collectErrors) {
476
+ results[index] = err
477
+ } else {
478
+ hasError = true
479
+ if (!firstError) firstError = err
480
+ throw err
481
+ }
482
+ }
483
+
484
+ if (hasError && !collectErrors) {
485
+ break
486
+ }
487
+ }
488
+ }
489
+
490
+ // Start workers up to the limit
491
+ const workers = Array(Math.min(limit, tasks.length))
492
+ .fill(null)
493
+ .map(() => runTask())
494
+
495
+ if (collectErrors) {
496
+ await Promise.allSettled(workers)
497
+ } else {
498
+ try {
499
+ await Promise.all(workers)
500
+ } catch {
501
+ // Re-throw the first error
502
+ if (firstError) throw firstError
503
+ }
504
+ }
505
+
506
+ return results
507
+ }