async-lube 2.0.0 → 2.1.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 (70) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/docs/http.md +1 -1
  4. package/docs/reference.md +4 -2
  5. package/docs/tasks.md +19 -2
  6. package/package.json +4 -5
  7. package/public.d.ts +112 -0
  8. package/src/attempt.js +17 -7
  9. package/src/durable/context.js +107 -64
  10. package/src/durable/error.js +39 -15
  11. package/src/durable/index.js +18 -10
  12. package/src/durable/journal.js +29 -14
  13. package/src/durable/json.js +26 -9
  14. package/src/durable/lease.js +15 -5
  15. package/src/durable/persist.js +60 -22
  16. package/src/durable/run.js +103 -41
  17. package/src/durable/send.js +30 -13
  18. package/src/durable/serve.js +30 -24
  19. package/src/durable/state.js +28 -13
  20. package/src/durable/store.js +19 -10
  21. package/src/durable/watch.js +31 -11
  22. package/src/errors.js +9 -3
  23. package/src/flow/attempts.js +49 -29
  24. package/src/flow/builder.js +260 -161
  25. package/src/flow/compile.js +86 -66
  26. package/src/flow/graph.js +108 -42
  27. package/src/flow/index.js +13 -7
  28. package/src/flow/items.js +79 -46
  29. package/src/flow/nodes.js +267 -117
  30. package/src/flow/refs.js +27 -13
  31. package/src/flow/resources.js +9 -3
  32. package/src/flow/run.js +287 -114
  33. package/src/flow/streams.js +44 -19
  34. package/src/flow/subflows.js +93 -53
  35. package/src/http/config.js +11 -5
  36. package/src/http/index.js +25 -14
  37. package/src/http/refresh.js +29 -12
  38. package/src/http/request.js +246 -151
  39. package/src/http/response.js +105 -50
  40. package/src/http/retry.js +52 -27
  41. package/src/http/socket.js +220 -172
  42. package/src/http/sse.js +113 -74
  43. package/src/http/throttle.js +9 -4
  44. package/src/http/url.js +58 -35
  45. package/src/http/xhr.js +29 -24
  46. package/src/index.js +1 -0
  47. package/src/key.js +3 -5
  48. package/src/limiter.js +146 -65
  49. package/src/offload.js +116 -85
  50. package/src/offload_module.js +594 -0
  51. package/src/options.js +8 -4
  52. package/src/redial.js +129 -0
  53. package/src/signal.js +14 -4
  54. package/src/stream.js +353 -198
  55. package/types/attempt.d.ts.map +1 -1
  56. package/types/durable/index.d.ts.map +1 -1
  57. package/types/durable/state.d.ts.map +1 -1
  58. package/types/durable/store.d.ts.map +1 -1
  59. package/types/errors.d.ts.map +1 -1
  60. package/types/http/index.d.ts.map +1 -1
  61. package/types/index.d.ts +1 -0
  62. package/types/index.d.ts.map +1 -1
  63. package/types/limiter.d.ts +5 -1
  64. package/types/limiter.d.ts.map +1 -1
  65. package/types/offload.d.ts +2 -16
  66. package/types/offload.d.ts.map +1 -1
  67. package/types/redial.d.ts +28 -0
  68. package/types/redial.d.ts.map +1 -0
  69. package/types/stream.d.ts +2 -0
  70. package/types/stream.d.ts.map +1 -1
@@ -15,7 +15,9 @@ const programs = new WeakMap()
15
15
  */
16
16
  export function compile(definition) {
17
17
  const cached = programs.get(definition)
18
- if (cached) return cached
18
+ if (cached) {
19
+ return cached
20
+ }
19
21
  const source_nodes = definition.list.slice(0, definition.count)
20
22
  /** @type {Map<unknown, string>} */
21
23
  const by_ref = new Map(
@@ -24,45 +26,6 @@ export function compile(definition) {
24
26
  )
25
27
  )
26
28
  const by_name = new Set(by_ref.values())
27
- /**
28
- * @param {unknown} target
29
- * @returns {string | undefined}
30
- */
31
- function name_of(target) {
32
- return typeof target == "string"
33
- ? by_name.has(target)
34
- ? target
35
- : void 0
36
- : by_ref.get(target)
37
- }
38
- /**
39
- * @param {unknown} target
40
- * @param {string} message
41
- * @returns {string}
42
- */
43
- function resolve(target, message) {
44
- const name = name_of(target)
45
- if (name == null) throw Error(
46
- message.replace("%s", describe(target))
47
- )
48
- return name
49
- }
50
- /**
51
- * @param {DefinitionNode} node
52
- * @returns {Record<string, "keep" | "queue" | "restart"> | undefined}
53
- */
54
- function to_arrive(node) {
55
- /** @type {Record<string, "keep" | "queue" | "restart"> | undefined} */
56
- let arrive
57
- for (const dep of node.deps) {
58
- if (!/** @type {RefProbe} */(dep)/**/?.[ARRIVE]) continue
59
- (arrive ??= Object.create(null))[resolve(
60
- to_ref(dep),
61
- `Flow node "${node.name}" depends on %s, which is not added`
62
- )] = /** @type {{ policy: "keep" | "queue" | "restart" }} */(/** @type {unknown} */(dep))/**/.policy
63
- }
64
- return arrive
65
- }
66
29
  /** @type {ProgramNode[]} */
67
30
  const nodes = source_nodes.map(
68
31
  node => ({
@@ -100,7 +63,9 @@ export function compile(definition) {
100
63
  for (const dep of node.deps) {
101
64
  const source = /** @type {ProgramNode} */(nodes_by_name.get(dep))/**/
102
65
  source.dependents.push(node.name)
103
- if (node.arrive?.[dep] == "queue") source.queuers.push(node.name)
66
+ if (node.arrive?.[dep] == "queue") {
67
+ source.queuers.push(node.name)
68
+ }
104
69
  }
105
70
  }
106
71
  /** @type {Map<string, number>} */
@@ -108,7 +73,9 @@ export function compile(definition) {
108
73
  /** @type {string[]} */
109
74
  const sorted = []
110
75
  for (const root of nodes) {
111
- if (marks.has(root.name)) continue
76
+ if (marks.has(root.name)) {
77
+ continue
78
+ }
112
79
  marks.set(root.name, 1)
113
80
  const path = [ root.name ]
114
81
  const cursors = [ 0 ]
@@ -127,10 +94,14 @@ export function compile(definition) {
127
94
  cursors[top] = cursor + 1
128
95
  const dep = /** @type {string} */(node.deps[cursor])/**/
129
96
  const mark = marks.get(dep)
130
- if (mark == 2) continue
131
- if (mark == 1) throw Error(
132
- `Circular dependency: ${[ ...path.slice(path.indexOf(dep)), dep ].join(" -> ")}`
133
- )
97
+ if (mark == 2) {
98
+ continue
99
+ }
100
+ if (mark == 1) {
101
+ throw Error(
102
+ `Circular dependency: ${[ ...path.slice(path.indexOf(dep)), dep ].join(" -> ")}`
103
+ )
104
+ }
134
105
  marks.set(dep, 1)
135
106
  path.push(dep)
136
107
  cursors.push(0)
@@ -138,26 +109,6 @@ export function compile(definition) {
138
109
  }
139
110
  /** @type {Map<string, Set<string>>} */
140
111
  const downstream = new Map()
141
- /**
142
- * @param {string} name
143
- * @returns {Set<string>}
144
- */
145
- function downstream_of(name) {
146
- let found = downstream.get(name)
147
- if (found) return found
148
- found = new Set()
149
- const stack = [ name ]
150
- while (stack.length) {
151
- const current = /** @type {string} */(stack.pop())/**/
152
- for (const dependent of /** @type {ProgramNode} */(nodes_by_name.get(current))/**/.dependents) {
153
- if (found.has(dependent)) continue
154
- found.add(dependent)
155
- stack.push(dependent)
156
- }
157
- }
158
- downstream.set(name, found)
159
- return found
160
- }
161
112
  /** @type {Map<string, ProgramEdge>} */
162
113
  const edges = new Map()
163
114
  for (const edge of definition.edges) {
@@ -249,4 +200,73 @@ export function compile(definition) {
249
200
  }
250
201
  programs.set(definition, program)
251
202
  return program
203
+ /**
204
+ * @param {string} name
205
+ * @returns {Set<string>}
206
+ */
207
+ function downstream_of(name) {
208
+ let found = downstream.get(name)
209
+ if (found) {
210
+ return found
211
+ }
212
+ found = new Set()
213
+ const stack = [ name ]
214
+ while (stack.length) {
215
+ const current = /** @type {string} */(stack.pop())/**/
216
+ for (const dependent of /** @type {ProgramNode} */(nodes_by_name.get(current))/**/.dependents) {
217
+ if (found.has(dependent)) {
218
+ continue
219
+ }
220
+ found.add(dependent)
221
+ stack.push(dependent)
222
+ }
223
+ }
224
+ downstream.set(name, found)
225
+ return found
226
+ }
227
+ /**
228
+ * @param {unknown} target
229
+ * @returns {string | undefined}
230
+ */
231
+ function name_of(target) {
232
+ return typeof target == "string"
233
+ ? by_name.has(target)
234
+ ? target
235
+ : void 0
236
+ : by_ref.get(target)
237
+ }
238
+ /**
239
+ * @param {unknown} target
240
+ * @param {string} message
241
+ * @returns {string}
242
+ */
243
+ function resolve(target, message) {
244
+ const name = name_of(target)
245
+ if (name == null) {
246
+ throw Error(
247
+ message.replace("%s", describe(target))
248
+ )
249
+ }
250
+ return name
251
+ }
252
+ /**
253
+ * @param {DefinitionNode} node
254
+ * @returns {Record<string, "keep" | "queue" | "restart"> | undefined}
255
+ */
256
+ function to_arrive(node) {
257
+ /** @type {Record<string, "keep" | "queue" | "restart"> | undefined} */
258
+ let arrive
259
+ for (const dep of node.deps) {
260
+ if (!/** @type {RefProbe} */(dep)/**/?.[ARRIVE]) {
261
+ continue
262
+ }
263
+ /** @type {Record<string, "keep" | "queue" | "restart">} */
264
+ const target = arrive ??= Object.create(null)
265
+ target[resolve(
266
+ to_ref(dep),
267
+ `Flow node "${node.name}" depends on %s, which is not added`
268
+ )] = /** @type {{ policy: "keep" | "queue" | "restart" }} */(/** @type {unknown} */(dep))/**/.policy
269
+ }
270
+ return arrive
271
+ }
252
272
  }
package/src/flow/graph.js CHANGED
@@ -31,14 +31,18 @@ export function abandon(c, deps, race) {
31
31
  )
32
32
  while (stack.length) {
33
33
  const name = /** @type {string} */(stack.pop())/**/
34
- if (name == race || region.has(name)) continue
34
+ if (name == race || region.has(name)) {
35
+ continue
36
+ }
35
37
  region.add(name)
36
38
  stack.push(
37
39
  ...get_record(c, name).node.deps,
38
40
  ...c.program.sources.get(name) ?? []
39
41
  )
40
42
  }
41
- if (!region.size) return
43
+ if (!region.size) {
44
+ return
45
+ }
42
46
  const dead = new Set(region).add(race)
43
47
  /** @type {string[]} */
44
48
  const needed = []
@@ -51,25 +55,33 @@ export function abandon(c, deps, race) {
51
55
  )
52
56
  : [
53
57
  ...dependents,
54
- ...c.program.transitions.get(name) ?? []
58
+ .../** @type {string[]} */(c.program.transitions.get(name))/**/
55
59
  ]
56
60
  if (targets.some(
57
61
  target => !dead.has(target) && may_run(c, target, dead)
58
- )) needed.push(name)
62
+ )) {
63
+ needed.push(name)
64
+ }
65
+ }
66
+ for (const name of needed) {
67
+ region.delete(name)
59
68
  }
60
- for (const name of needed) region.delete(name)
61
69
  while (needed.length) {
62
70
  const name = /** @type {string} */(needed.pop())/**/
63
71
  for (const source of [
64
72
  ...get_record(c, name).node.deps,
65
73
  ...c.program.sources.get(name) ?? []
66
74
  ]) {
67
- if (region.delete(source)) needed.push(source)
75
+ if (region.delete(source)) {
76
+ needed.push(source)
77
+ }
68
78
  }
69
79
  }
70
80
  for (const name of region) {
71
81
  const record = get_record(c, name)
72
- if (settled_statuses.has(record.status)) continue
82
+ if (settled_statuses.has(record.status)) {
83
+ continue
84
+ }
73
85
  record.backlog = []
74
86
  record.backlog_head = 0
75
87
  record.retrigger = false
@@ -89,7 +101,9 @@ export function abandon(c, deps, race) {
89
101
  settle(c, name, "skipped", void 0, is_running)
90
102
  record.abandoned = true
91
103
  }
92
- for (const name of region) release_unused(c, name, void 0)
104
+ for (const name of region) {
105
+ release_unused(c, name, void 0)
106
+ }
93
107
  }
94
108
  /**
95
109
  * @param {RunContext} c
@@ -99,19 +113,27 @@ export function abandon(c, deps, race) {
99
113
  * @returns {void}
100
114
  */
101
115
  export function enter(c, targets, source, force) {
102
- if (c.run_status != "running" || !targets.length) return
116
+ if (c.run_status != "running" || !targets.length) {
117
+ return
118
+ }
103
119
  /** @type {Set<string>} */
104
120
  const touched = new Set()
105
121
  for (const target of targets) {
106
- for (const name of reset(c, target, true, force)) touched.add(name)
122
+ for (const name of reset(c, target, true, force)) {
123
+ touched.add(name)
124
+ }
107
125
  }
108
126
  for (const target of targets) {
109
127
  const record = get_record(c, target)
110
128
  record.activators.add(source)
111
129
  record.activated = true
112
130
  }
113
- for (const target of targets) try_start(c, target)
114
- for (const name of touched) try_start(c, name)
131
+ for (const target of targets) {
132
+ try_start(c, target)
133
+ }
134
+ for (const name of touched) {
135
+ try_start(c, name)
136
+ }
115
137
  }
116
138
  /**
117
139
  * @param {RunContext} c
@@ -189,14 +211,26 @@ function is_live(c, name, target, visited) {
189
211
  const stack = [ name ]
190
212
  while (stack.length) {
191
213
  const current = /** @type {string} */(stack.pop())/**/
192
- if (visited.has(current)) continue
214
+ if (visited.has(current)) {
215
+ continue
216
+ }
193
217
  visited.add(current)
194
- if (c.program.is_downstream(target, current)) continue
218
+ if (c.program.is_downstream(target, current)) {
219
+ continue
220
+ }
195
221
  const record = get_record(c, current)
196
- if (active_statuses.has(record.status)) return true
197
- if (record.status != "idle") continue
198
- if (record.activated) return true
199
- for (const source of c.program.sources.get(current) ?? []) stack.push(source)
222
+ if (active_statuses.has(record.status)) {
223
+ return true
224
+ }
225
+ if (record.status != "idle") {
226
+ continue
227
+ }
228
+ if (record.activated) {
229
+ return true
230
+ }
231
+ for (const source of c.program.sources.get(current) ?? []) {
232
+ stack.push(source)
233
+ }
200
234
  }
201
235
  return false
202
236
  }
@@ -211,10 +245,14 @@ function kill(c, name, source) {
211
245
  c,
212
246
  () => {
213
247
  const record = get_record(c, name)
214
- if (c.run_status != "running" || record.status != "idle" || record.activated) return
248
+ if (c.run_status != "running" || record.status != "idle" || record.activated) {
249
+ return
250
+ }
215
251
  const visited = new Set([ name, source ])
216
- for (const other of c.program.sources.get(name) ?? []) {
217
- if (other != source && is_live(c, other, name, visited)) return
252
+ for (const other of /** @type {string[]} */(c.program.sources.get(name))/**/) {
253
+ if (other != source && is_live(c, other, name, visited)) {
254
+ return
255
+ }
218
256
  }
219
257
  settle(c, name, "skipped", void 0)
220
258
  }
@@ -226,7 +264,9 @@ function kill(c, name, source) {
226
264
  * @returns {void}
227
265
  */
228
266
  export function kill_transitions(c, name) {
229
- for (const target of c.program.transitions.get(name) ?? []) kill(c, target, name)
267
+ for (const target of /** @type {string[]} */(c.program.transitions.get(name))/**/) {
268
+ kill(c, target, name)
269
+ }
230
270
  }
231
271
  /**
232
272
  * @param {RunContext} c
@@ -236,8 +276,12 @@ export function kill_transitions(c, name) {
236
276
  */
237
277
  function may_run(c, name, dead) {
238
278
  const record = get_record(c, name)
239
- if (settled_statuses.has(record.status)) return false
240
- if (record.status != "idle" || record.activated) return true
279
+ if (settled_statuses.has(record.status)) {
280
+ return false
281
+ }
282
+ if (record.status != "idle" || record.activated) {
283
+ return true
284
+ }
241
285
  return (c.program.sources.get(name) ?? []).some(
242
286
  source => is_live(
243
287
  c,
@@ -257,27 +301,37 @@ function may_run(c, name, dead) {
257
301
  export function reset(c, name, is_root, force) {
258
302
  /** @type {Set<string>} */
259
303
  const names = new Set()
260
- /** @type {{ from: string | undefined, name: string }[]} */
261
- const stack = [ { from: void 0, name } ]
304
+ /** @type {{ forward: boolean, from: string | undefined, name: string }[]} */
305
+ const stack = [
306
+ { forward: false, from: void 0, name }
307
+ ]
262
308
  while (stack.length) {
263
- const entry = /** @type {{ from: string | undefined, name: string }} */(stack.pop())/**/
309
+ const entry = /** @type {{ forward: boolean, from: string | undefined, name: string }} */(stack.pop())/**/
264
310
  const node_name = entry.name
265
- if (names.has(node_name)) continue
311
+ if (names.has(node_name)) {
312
+ continue
313
+ }
266
314
  const node_record = get_record(c, node_name)
267
315
  const arrive = entry.from == null ? void 0 : node_record.node.arrive?.[entry.from]
268
- if (arrive == "keep" || arrive == "queue") continue
316
+ if (arrive == "keep" || arrive == "queue") {
317
+ continue
318
+ }
269
319
  if (entry.from != null && node_record.status == "done") {
270
320
  const from = get_record(c, entry.from)
271
321
  if (node_record.node.options.join == "race"
272
322
  ? from.status != "done"
273
- : from.fatal && from.node.options.release) continue
323
+ : from.fatal && from.node.options.release) {
324
+ continue
325
+ }
274
326
  }
275
327
  const overlap = arrive == "restart" ? "restart" : node_record.node.options.overlap ?? "restart"
276
328
  const keeps = overlap == "restart"
277
329
  ? node_record.status == "running"
278
330
  : active_statuses.has(node_record.status) || holds_backlog(node_record)
279
331
  if (keeps && !(force && node_name == name)) {
280
- if (overlap != "ignore") node_record.retrigger = true
332
+ if (overlap != "ignore" && !entry.forward) {
333
+ node_record.retrigger = true
334
+ }
281
335
  continue
282
336
  }
283
337
  names.add(node_name)
@@ -285,26 +339,38 @@ export function reset(c, name, is_root, force) {
285
339
  const next = [
286
340
  ...get_record(c, node_name).node.dependents
287
341
  ]
288
- for (const target of c.program.forward.get(node_name) ?? []) {
342
+ const dependent_count = next.length
343
+ for (const target of /** @type {string[]} */(c.program.forward.get(node_name))/**/) {
289
344
  const target_record = get_record(c, target)
290
345
  const is_owned = target_record.activators.has(node_name) || target_record.activators.has("*")
291
- if (is_owned || !target_record.activated && settled_statuses.has(target_record.status)) next.push(target)
292
- }
293
- for (let i = next.length; i--;) stack.push(
294
- {
295
- from: node_name,
296
- name: /** @type {string} */(next[i])/**/
346
+ if (is_owned || !target_record.activated && settled_statuses.has(target_record.status)) {
347
+ next.push(target)
297
348
  }
298
- )
349
+ }
350
+ for (let i = next.length; i--;) {
351
+ stack.push(
352
+ {
353
+ forward: i >= dependent_count,
354
+ from: node_name,
355
+ name: /** @type {string} */(next[i])/**/
356
+ }
357
+ )
358
+ }
299
359
  }
300
360
  for (const node_name of names) {
301
361
  const record = get_record(c, node_name)
302
362
  clear_record(c, record)
303
- if (node_name == name && is_root) continue
363
+ if (node_name == name && is_root) {
364
+ continue
365
+ }
304
366
  for (const source of record.activators) {
305
- if (names.has(source)) record.activators.delete(source)
367
+ if (names.has(source)) {
368
+ record.activators.delete(source)
369
+ }
370
+ }
371
+ if ((c.program.sources.get(node_name) ?? []).some(source => names.has(source))) {
372
+ record.activators.delete("*")
306
373
  }
307
- if ((c.program.sources.get(node_name) ?? []).some(source => names.has(source))) record.activators.delete("*")
308
374
  record.activated = !c.incoming.has(node_name) || record.activators.size > 0
309
375
  }
310
376
  return names
package/src/flow/index.js CHANGED
@@ -9,14 +9,20 @@ export default /** @type {import("../../public.js").FlowFunction} */(/** @type {
9
9
  */
10
10
  function(options = {}) {
11
11
  for (const key of Object.keys(options)) {
12
- if (key != "concurrency" && key != "maxSteps") throw TypeError(`Unknown flow option "${key}"`)
12
+ if (key != "concurrency" && key != "maxSteps") {
13
+ throw TypeError(`Unknown flow option "${key}"`)
14
+ }
15
+ }
16
+ if (options.concurrency != null && !is_count(options.concurrency)) {
17
+ throw TypeError(
18
+ "The concurrency of a flow must be a positive integer or Infinity"
19
+ )
20
+ }
21
+ if (options.maxSteps != null && !is_count(options.maxSteps)) {
22
+ throw TypeError(
23
+ "The maxSteps of a flow must be a positive integer or Infinity"
24
+ )
13
25
  }
14
- if (options.concurrency != null && !is_count(options.concurrency)) throw TypeError(
15
- "The concurrency of a flow must be a positive integer or Infinity"
16
- )
17
- if (options.maxSteps != null && !is_count(options.maxSteps)) throw TypeError(
18
- "The maxSteps of a flow must be a positive integer or Infinity"
19
- )
20
26
  return create_builder(
21
27
  {
22
28
  count: 0,
package/src/flow/items.js CHANGED
@@ -17,12 +17,16 @@ async function collect_items(iterator, signal) {
17
17
  for (;;) {
18
18
  const step = iterator.next()
19
19
  const { done, value } = signal ? await race_signal(step, signal) : await step
20
- if (done) return items
20
+ if (done) {
21
+ return items
22
+ }
21
23
  items.push(value)
22
24
  }
23
25
  } finally {
24
- if (signal?.aborted) await iterator.return?.()
25
- .catch(noop)
26
+ if (signal?.aborted) {
27
+ await iterator.return?.()
28
+ .catch(noop)
29
+ }
26
30
  }
27
31
  }
28
32
  /**
@@ -31,9 +35,11 @@ async function collect_items(iterator, signal) {
31
35
  */
32
36
  export function each(target) {
33
37
  const is_flow = !!target?.[DEFINITION]
34
- if (!is_flow && typeof target != "function") throw TypeError(
35
- "flow.each() needs a function or a flow"
36
- )
38
+ if (!is_flow && typeof target != "function") {
39
+ throw TypeError(
40
+ "flow.each() needs a function or a flow"
41
+ )
42
+ }
37
43
  /** @type {TaskRef} */
38
44
  const run_all = is_flow
39
45
  ? async (/** @type {unknown} */ items) => Promise.all(
@@ -68,7 +74,7 @@ export function each(target) {
68
74
  export async function run_each(c, record, token) {
69
75
  const { node } = record
70
76
  const { name, options } = node
71
- const [ source, ...rest ] = node.deps.length ? args_of(c, record) : [ c.state ]
77
+ const [ source, ...rest ] = node.deps.length ? args_of(record) : [ c.state ]
72
78
  const parent = /** @type {AbortController} */(record.controller)/**/.signal
73
79
  /** @type {unknown[]} */
74
80
  let items
@@ -79,10 +85,14 @@ export async function run_each(c, record, token) {
79
85
  : to_items(source, parent)
80
86
  items = Array.isArray(collected) ? collected : await collected
81
87
  } catch (error) {
82
- if (token != record.token || parent.aborted) return { kind: "stale" }
88
+ if (token != record.token || parent.aborted) {
89
+ return { kind: "stale" }
90
+ }
83
91
  return { kind: "error", value: error }
84
92
  }
85
- if (token != record.token) return { kind: "stale" }
93
+ if (token != record.token) {
94
+ return { kind: "stale" }
95
+ }
86
96
  record.item_list = items
87
97
  record.item_source = source
88
98
  const controller = new AbortController()
@@ -97,6 +107,35 @@ export async function run_each(c, record, token) {
97
107
  /** @type {{ error: unknown, index: number | undefined } | undefined} */
98
108
  let failure
99
109
  let next = 0
110
+ const workers = Math.min(
111
+ options.concurrency ?? Infinity,
112
+ items.length
113
+ )
114
+ try {
115
+ await Promise.all(
116
+ Array.from({ length: workers }, worker)
117
+ )
118
+ } finally {
119
+ unlink()
120
+ }
121
+ if (token != record.token) {
122
+ return { kind: "stale" }
123
+ }
124
+ if (failure) {
125
+ return {
126
+ index: failure.index,
127
+ items: true,
128
+ kind: "error",
129
+ value: failure.error
130
+ }
131
+ }
132
+ const values = items.map(
133
+ (_, index) => done[index]?.value
134
+ )
135
+ if (!done.some(item => item?.failed) && !failed_sub_keys(record).length) {
136
+ record.items = void 0
137
+ }
138
+ return { kind: "done", value: values }
100
139
  /**
101
140
  * @param {unknown} error
102
141
  * @param {number | undefined} index
@@ -108,10 +147,13 @@ export async function run_each(c, record, token) {
108
147
  new CancelError("Another item failed")
109
148
  )
110
149
  }
150
+ /** @returns {Promise<void>} */
111
151
  async function worker() {
112
152
  while (!failure && next < items.length) {
113
153
  const index = next++
114
- if (done[index]) continue
154
+ if (done[index]) {
155
+ continue
156
+ }
115
157
  const item = items[index]
116
158
  const key = String(index)
117
159
  const outcome = await run_attempts(
@@ -143,7 +185,9 @@ export async function run_each(c, record, token) {
143
185
  )
144
186
  )
145
187
  )
146
- if (token != record.token || outcome.kind == "stale") return
188
+ if (token != record.token || outcome.kind == "stale") {
189
+ return
190
+ }
147
191
  if (outcome.kind == "done") {
148
192
  if (is_sentinel(outcome.value)) {
149
193
  stop(
@@ -172,14 +216,20 @@ export async function run_each(c, record, token) {
172
216
  record.runs.get(key)
173
217
  )
174
218
  )
175
- if (token != record.token) return
176
- if (is_sentinel(value)) throw TypeError(
177
- `The catch of flow node "${name}" cannot return goto() or skip() for an item`
178
- )
219
+ if (token != record.token) {
220
+ return
221
+ }
222
+ if (is_sentinel(value)) {
223
+ throw TypeError(
224
+ `The catch of flow node "${name}" cannot return goto() or skip() for an item`
225
+ )
226
+ }
179
227
  c.errors[name + "." + key] = outcome.value
180
228
  done[index] = { value }
181
229
  } catch (catch_error) {
182
- if (token == record.token) stop(catch_error, index)
230
+ if (token == record.token) {
231
+ stop(catch_error, index)
232
+ }
183
233
  return
184
234
  }
185
235
  } else if (options.optional) {
@@ -191,29 +241,6 @@ export async function run_each(c, record, token) {
191
241
  }
192
242
  }
193
243
  }
194
- const workers = Math.min(
195
- options.concurrency ?? Infinity,
196
- items.length
197
- )
198
- try {
199
- await Promise.all(
200
- Array.from({ length: workers }, worker)
201
- )
202
- } finally {
203
- unlink()
204
- }
205
- if (token != record.token) return { kind: "stale" }
206
- if (failure) return {
207
- index: failure.index,
208
- items: true,
209
- kind: "error",
210
- value: failure.error
211
- }
212
- const values = items.map(
213
- (_, index) => done[index]?.value
214
- )
215
- if (!done.some(item => item?.failed) && !failed_sub_keys(record).length) record.items = void 0
216
- return { kind: "done", value: values }
217
244
  }
218
245
  /**
219
246
  * @param {unknown} source
@@ -221,13 +248,19 @@ export async function run_each(c, record, token) {
221
248
  * @returns {unknown[] | Promise<unknown[]>}
222
249
  */
223
250
  export function to_items(source, signal) {
224
- if (source == null) return []
225
- if (typeof /** @type {Partial<Iterable<unknown>>} */(source)/**/[Symbol.iterator] == "function") return Array.from(
226
- /** @type {Iterable<unknown>} */(source)/**/
227
- )
228
- if (typeof /** @type {Partial<AsyncIterable<unknown>>} */(source)/**/[Symbol.asyncIterator] != "function") throw TypeError(
229
- "flow.each() needs an iterable or an async iterable as the first dependency"
230
- )
251
+ if (source == null) {
252
+ return []
253
+ }
254
+ if (typeof /** @type {Partial<Iterable<unknown>>} */(source)/**/[Symbol.iterator] == "function") {
255
+ return Array.from(
256
+ /** @type {Iterable<unknown>} */(source)/**/
257
+ )
258
+ }
259
+ if (typeof /** @type {Partial<AsyncIterable<unknown>>} */(source)/**/[Symbol.asyncIterator] != "function") {
260
+ throw TypeError(
261
+ "flow.each() needs an iterable or an async iterable as the first dependency"
262
+ )
263
+ }
231
264
  return collect_items(
232
265
  /** @type {AsyncIterable<unknown>} */(source)/**/[Symbol.asyncIterator](),
233
266
  signal