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
@@ -61,15 +61,6 @@ const overlaps = new Set(
61
61
  */
62
62
  export function create_builder(definition) {
63
63
  const { count, list, names, refs } = definition
64
- /**
65
- * @param {string} name
66
- * @param {number=} except
67
- * @returns {boolean}
68
- */
69
- function is_taken(name, except) {
70
- const index = names.get(name)
71
- return index != null && index < count && index !== except
72
- }
73
64
  return {
74
65
  [DEFINITION]: definition,
75
66
  /**
@@ -96,8 +87,8 @@ export function create_builder(definition) {
96
87
  for (const dep of missing) {
97
88
  builder = builder.add(dep)
98
89
  const added_definition = builder[DEFINITION]
99
- const auto_entry = added_definition.list[added_definition.count - 1]
100
- if (auto_entry) auto_entry.auto = true
90
+ const auto_entry = /** @type {DefinitionNode} */(added_definition.list[added_definition.count - 1])/**/
91
+ auto_entry.auto = true
101
92
  }
102
93
  return builder.add(ref, ...args)
103
94
  }
@@ -109,109 +100,167 @@ export function create_builder(definition) {
109
100
  : typeof target == "function"
110
101
  ? "task"
111
102
  : void 0
112
- if (!kind) throw TypeError(
113
- "A flow node must be a function, an input, a flow or a stream"
114
- )
103
+ if (!kind) {
104
+ throw TypeError(
105
+ "A flow node must be a function, an input, a flow or a stream"
106
+ )
107
+ }
115
108
  const base = options.name ?? (is_stream(ref)
116
109
  ? "stream"
117
110
  : kind == "flow"
118
111
  ? "flow"
119
112
  : ref.name || (kind == "task" ? "node" : "input"))
120
- if (typeof base != "string" || !base || base.includes(".") || base == "__proto__") throw TypeError(
121
- `Flow node name must be a non-empty string without ".", other than "__proto__": ${String(base)}`
122
- )
113
+ if (typeof base != "string" || !base || base.includes(".") || base == "__proto__") {
114
+ throw TypeError(
115
+ `Flow node name must be a non-empty string without ".", other than "__proto__": ${String(base)}`
116
+ )
117
+ }
123
118
  const added = refs.get(ref)
124
119
  const replacing = added != null && added < count && /** @type {DefinitionNode} */(list[added])/**/.auto
125
120
  ? added
126
121
  : void 0
127
- if (options.name != null && is_taken(base, replacing)) throw Error(
128
- `Flow node "${base}" is already defined`
129
- )
130
- let name = base
131
- for (let i = 2; is_taken(name, replacing); i++) name = base + "_" + i
132
- if (added != null && added < count && replacing == null) throw Error(
133
- `${describe(ref)} is already added as flow node "${/** @type {DefinitionNode} */(list[added])/**/.name}"`
134
- )
135
- for (const dep of deps) {
136
- if (Array.isArray(dep)) throw TypeError(
137
- `Pass the dependencies of flow node "${name}" as separate arguments, not as an array: add(node, a, b)`
122
+ if (options.name != null && is_taken(base, replacing)) {
123
+ throw Error(
124
+ `Flow node "${base}" is already defined`
138
125
  )
139
- if (!is_ref(to_ref(dep))) throw TypeError(
140
- `The dependencies of flow node "${name}" must be functions, inputs, flows or streams`
126
+ }
127
+ let name = base
128
+ for (let i = 2; is_taken(name, replacing); i++) {
129
+ name = base + "_" + i
130
+ }
131
+ if (added != null && added < count && replacing == null) {
132
+ throw Error(
133
+ `${describe(ref)} is already added as flow node "${/** @type {DefinitionNode} */(list[added])/**/.name}"`
141
134
  )
142
135
  }
136
+ for (const dep of deps) {
137
+ if (Array.isArray(dep)) {
138
+ throw TypeError(
139
+ `Pass the dependencies of flow node "${name}" as separate arguments, not as an array: add(node, a, b)`
140
+ )
141
+ }
142
+ if (!is_ref(to_ref(dep))) {
143
+ throw TypeError(
144
+ `The dependencies of flow node "${name}" must be functions, inputs, flows or streams`
145
+ )
146
+ }
147
+ }
143
148
  const allowed = kind == "input" ? input_option_keys : node_option_keys
144
149
  for (const key of Object.keys(options)) {
145
- if (!allowed.has(key)) throw TypeError(
146
- `Unknown option "${key}" for ${kind == "input" ? "input" : "flow"} node "${name}"`
150
+ if (!allowed.has(key)) {
151
+ throw TypeError(
152
+ `Unknown option "${key}" for ${kind == "input" ? "input" : "flow"} node "${name}"`
153
+ )
154
+ }
155
+ }
156
+ if (options.catch != null && typeof options.catch != "function") {
157
+ throw TypeError(
158
+ `The catch of flow node "${name}" must be a function`
159
+ )
160
+ }
161
+ if (options.release != null && typeof options.release != "function") {
162
+ throw TypeError(
163
+ `The release of flow node "${name}" must be a function`
164
+ )
165
+ }
166
+ if (options.when != null && typeof options.when != "function") {
167
+ throw TypeError(
168
+ `The when of flow node "${name}" must be a function`
169
+ )
170
+ }
171
+ if (options.finish != null && typeof options.finish != "boolean") {
172
+ throw TypeError(
173
+ `The finish of flow node "${name}" must be a boolean`
174
+ )
175
+ }
176
+ if (options.optional != null && typeof options.optional != "boolean") {
177
+ throw TypeError(
178
+ `The optional of flow node "${name}" must be a boolean`
179
+ )
180
+ }
181
+ if (options.catch != null && options.fallback != null) {
182
+ throw TypeError(
183
+ `Flow node "${name}" cannot have both catch and fallback`
184
+ )
185
+ }
186
+ if (options.optional && (options.catch != null || options.fallback != null)) {
187
+ throw TypeError(
188
+ `Flow node "${name}" cannot be optional with catch or fallback, which handle the failure instead`
189
+ )
190
+ }
191
+ if (!to_array(options.fallback).every(is_ref)) {
192
+ throw TypeError(
193
+ `The fallback of flow node "${name}" must be functions, inputs, flows or streams`
194
+ )
195
+ }
196
+ if (to_array(options.fallback).includes(ref)) {
197
+ throw TypeError(
198
+ `The fallback of flow node "${name}" cannot be the node itself, which would fail again`
199
+ )
200
+ }
201
+ if (options.join != null && !joins.has(options.join)) {
202
+ throw TypeError(
203
+ `The join of flow node "${name}" must be "all", "any" or "race"`
204
+ )
205
+ }
206
+ if (options.join != null && !deps.length) {
207
+ throw TypeError(
208
+ `The join of flow node "${name}" needs dependencies to join`
209
+ )
210
+ }
211
+ if (options.concurrency != null && !ref[EACH]) {
212
+ throw TypeError(
213
+ `The concurrency of flow node "${name}" applies only to flow.each(): use flow({ concurrency }) for nodes`
214
+ )
215
+ }
216
+ if (options.concurrency != null && !is_count(options.concurrency)) {
217
+ throw TypeError(
218
+ `The concurrency of flow node "${name}" must be a positive integer or Infinity`
219
+ )
220
+ }
221
+ if (options.retry != null && !is_retry(options.retry)) {
222
+ throw TypeError(
223
+ `The retry of flow node "${name}" must be a count or { count, delay, when }`
224
+ )
225
+ }
226
+ if (options.overlap != null && !overlaps.has(options.overlap)) {
227
+ throw TypeError(
228
+ `The overlap of flow node "${name}" must be "restart", "ignore", "rerun" or "queue"`
147
229
  )
148
230
  }
149
- if (options.catch != null && typeof options.catch != "function") throw TypeError(
150
- `The catch of flow node "${name}" must be a function`
151
- )
152
- if (options.release != null && typeof options.release != "function") throw TypeError(
153
- `The release of flow node "${name}" must be a function`
154
- )
155
- if (options.when != null && typeof options.when != "function") throw TypeError(
156
- `The when of flow node "${name}" must be a function`
157
- )
158
- if (options.finish != null && typeof options.finish != "boolean") throw TypeError(
159
- `The finish of flow node "${name}" must be a boolean`
160
- )
161
- if (options.optional != null && typeof options.optional != "boolean") throw TypeError(
162
- `The optional of flow node "${name}" must be a boolean`
163
- )
164
- if (options.catch != null && options.fallback != null) throw TypeError(
165
- `Flow node "${name}" cannot have both catch and fallback`
166
- )
167
- if (options.optional && (options.catch != null || options.fallback != null)) throw TypeError(
168
- `Flow node "${name}" cannot be optional with catch or fallback, which handle the failure instead`
169
- )
170
- if (!to_array(options.fallback).every(is_ref)) throw TypeError(
171
- `The fallback of flow node "${name}" must be functions, inputs, flows or streams`
172
- )
173
- if (to_array(options.fallback).includes(ref)) throw TypeError(
174
- `The fallback of flow node "${name}" cannot be the node itself, which would fail again`
175
- )
176
- if (options.join != null && !joins.has(options.join)) throw TypeError(
177
- `The join of flow node "${name}" must be "all", "any" or "race"`
178
- )
179
- if (options.join != null && !deps.length) throw TypeError(
180
- `The join of flow node "${name}" needs dependencies to join`
181
- )
182
- if (options.concurrency != null && !ref[EACH]) throw TypeError(
183
- `The concurrency of flow node "${name}" applies only to flow.each(): use flow({ concurrency }) for nodes`
184
- )
185
- if (options.concurrency != null && !is_count(options.concurrency)) throw TypeError(
186
- `The concurrency of flow node "${name}" must be a positive integer or Infinity`
187
- )
188
- if (options.retry != null && !is_retry(options.retry)) throw TypeError(
189
- `The retry of flow node "${name}" must be a count or { count, delay, when }`
190
- )
191
- if (options.overlap != null && !overlaps.has(options.overlap)) throw TypeError(
192
- `The overlap of flow node "${name}" must be "restart", "ignore", "rerun" or "queue"`
193
- )
194
231
  const queues = deps.some(
195
232
  dep => /** @type {{ policy?: string }} */(/** @type {unknown} */(dep))/**/?.policy == "queue"
196
233
  )
197
- if (options.limit != null && options.overlap != "queue" && !queues) throw TypeError(
198
- `The limit of flow node "${name}" applies only to overlap: "queue" or a flow.queue() dependency`
199
- )
200
- if (options.limit != null && !is_count(options.limit)) throw TypeError(
201
- `The limit of flow node "${name}" must be a positive integer or Infinity`
202
- )
203
- if (options.overflow != null && options.overflow != "drop" && options.overflow != "wait") throw TypeError(
204
- `The overflow of flow node "${name}" must be "drop" or "wait"`
205
- )
206
- if (options.overflow != null && options.limit == null) throw TypeError(
207
- `The overflow of flow node "${name}" needs a limit`
208
- )
209
- if (options.overflow == "wait" && !deps.some(dep => is_stream(to_ref(dep)))) throw TypeError(
210
- `The overflow "wait" of flow node "${name}" needs a stream dependency to stop reading`
211
- )
212
- if (options.timeout != null && !(typeof options.timeout == "number" && options.timeout > 0)) throw TypeError(
213
- `The timeout of flow node "${name}" must be a positive number of milliseconds`
214
- )
234
+ if (options.limit != null && options.overlap != "queue" && !queues) {
235
+ throw TypeError(
236
+ `The limit of flow node "${name}" applies only to overlap: "queue" or a flow.queue() dependency`
237
+ )
238
+ }
239
+ if (options.limit != null && !is_count(options.limit)) {
240
+ throw TypeError(
241
+ `The limit of flow node "${name}" must be a positive integer or Infinity`
242
+ )
243
+ }
244
+ if (options.overflow != null && options.overflow != "drop" && options.overflow != "wait") {
245
+ throw TypeError(
246
+ `The overflow of flow node "${name}" must be "drop" or "wait"`
247
+ )
248
+ }
249
+ if (options.overflow != null && options.limit == null) {
250
+ throw TypeError(
251
+ `The overflow of flow node "${name}" needs a limit`
252
+ )
253
+ }
254
+ if (options.overflow == "wait" && !deps.some(dep => is_stream(to_ref(dep)))) {
255
+ throw TypeError(
256
+ `The overflow "wait" of flow node "${name}" needs a stream dependency to stop reading`
257
+ )
258
+ }
259
+ if (options.timeout != null && !(typeof options.timeout == "number" && options.timeout > 0)) {
260
+ throw TypeError(
261
+ `The timeout of flow node "${name}" must be a positive number of milliseconds`
262
+ )
263
+ }
215
264
  const shared = list.length == count
216
265
  const next_list = shared ? list : list.slice(0, count)
217
266
  const next_names = shared ? names : /** @type {Map<string, number>} */(new Map())/**/
@@ -268,9 +317,7 @@ export function create_builder(definition) {
268
317
  }
269
318
  )
270
319
  },
271
- /**
272
- * @returns {string[]}
273
- */
320
+ /** @returns {string[]} */
274
321
  check() {
275
322
  /** @type {Program} */
276
323
  let program
@@ -288,14 +335,18 @@ export function create_builder(definition) {
288
335
  /** @type {string[]} */
289
336
  const stack = []
290
337
  for (const node of program.nodes) {
291
- if (program.incoming.has(node.name)) continue
338
+ if (program.incoming.has(node.name)) {
339
+ continue
340
+ }
292
341
  reachable.add(node.name)
293
342
  stack.push(node.name)
294
343
  }
295
344
  while (stack.length) {
296
345
  const name = /** @type {string} */(stack.pop())/**/
297
- for (const target of program.transitions.get(name) ?? []) {
298
- if (reachable.has(target)) continue
346
+ for (const target of /** @type {string[]} */(program.transitions.get(name))/**/) {
347
+ if (reachable.has(target)) {
348
+ continue
349
+ }
299
350
  reachable.add(target)
300
351
  stack.push(target)
301
352
  }
@@ -307,27 +358,36 @@ export function create_builder(definition) {
307
358
  node => `Flow node "${node.name}" never starts: it does not run with the flow, and no node that runs goes to it`
308
359
  )
309
360
  for (const node of program.nodes) {
310
- if (node.options.join != "race") continue
361
+ if (node.options.join != "race") {
362
+ continue
363
+ }
311
364
  for (const dep of node.deps) {
312
365
  const stream = find_stream(program, dep)
313
- if (stream == dep) problems.push(
314
- `Flow node "${node.name}" races the stream "${dep}", so the race ends with its first value`
315
- )
316
- else if (stream != null) problems.push(
317
- `Flow node "${node.name}" races "${dep}", which runs again for every value of the stream "${stream}", so the race ends with its first value`
318
- )
366
+ if (stream == dep) {
367
+ problems.push(
368
+ `Flow node "${node.name}" races the stream "${dep}", so the race ends with its first value`
369
+ )
370
+ } else if (stream != null) {
371
+ problems.push(
372
+ `Flow node "${node.name}" races "${dep}", which runs again for every value of the stream "${stream}", so the race ends with its first value`
373
+ )
374
+ }
319
375
  }
320
376
  }
321
377
  /** @type {Set<string>} */
322
378
  const racing = new Set()
323
379
  for (const node of program.nodes) {
324
- if (node.options.join != "race") continue
380
+ if (node.options.join != "race") {
381
+ continue
382
+ }
325
383
  /** @type {Set<string>} */
326
384
  const seen = new Set()
327
385
  const region = [ ...node.deps ]
328
386
  while (region.length) {
329
387
  const name = /** @type {string} */(region.pop())/**/
330
- if (name == node.name || seen.has(name)) continue
388
+ if (name == node.name || seen.has(name)) {
389
+ continue
390
+ }
331
391
  seen.add(name)
332
392
  racing.add(name)
333
393
  region.push(
@@ -337,9 +397,11 @@ export function create_builder(definition) {
337
397
  }
338
398
  }
339
399
  for (const node of program.nodes) {
340
- if (node.options.finish && !racing.has(node.name)) problems.push(
341
- `Flow node "${node.name}" has finish, but no race can abandon it`
342
- )
400
+ if (node.options.finish && !racing.has(node.name)) {
401
+ problems.push(
402
+ `Flow node "${node.name}" has finish, but no race can abandon it`
403
+ )
404
+ }
343
405
  }
344
406
  check_snapshot_names(program, "", problems)
345
407
  return problems
@@ -350,12 +412,16 @@ export function create_builder(definition) {
350
412
  * @param {EdgeSelect=} select
351
413
  */
352
414
  edge(from, to, select) {
353
- if (!is_ref(from)) throw TypeError(
354
- "An edge must start from a function, an input, a flow or a stream"
355
- )
356
- if (definition.edges.some(edge => edge.from == from)) throw Error(
357
- `The edge from ${describe(from)} is already defined`
358
- )
415
+ if (!is_ref(from)) {
416
+ throw TypeError(
417
+ "An edge must start from a function, an input, a flow or a stream"
418
+ )
419
+ }
420
+ if (definition.edges.some(edge => edge.from == from)) {
421
+ throw Error(
422
+ `The edge from ${describe(from)} is already defined`
423
+ )
424
+ }
359
425
  /** @type {DefinitionEdge} */
360
426
  let edge
361
427
  const is_map = !!to && typeof to == "object" && !Array.isArray(to) && !is_ref(to) && Object.values(to).every(is_ref)
@@ -370,9 +436,11 @@ export function create_builder(definition) {
370
436
  targets: [ ...new Set(Object.values(map)) ]
371
437
  }
372
438
  } else if (typeof select == "function") {
373
- if (!Array.isArray(to) || !to.every(is_ref)) throw TypeError(
374
- `The edge from ${describe(from)} must list its targets, or map keys to them`
375
- )
439
+ if (!Array.isArray(to) || !to.every(is_ref)) {
440
+ throw TypeError(
441
+ `The edge from ${describe(from)} must list its targets, or map keys to them`
442
+ )
443
+ }
376
444
  edge = {
377
445
  from,
378
446
  labels: to.map(target => [ "", target ]),
@@ -380,14 +448,15 @@ export function create_builder(definition) {
380
448
  select,
381
449
  targets: [ ...to ]
382
450
  }
383
- } else if (is_ref(to)) edge = {
384
- from,
385
- labels: [ [ "", to ] ],
386
- map: void 0,
387
- select: () => to,
388
- targets: [ to ]
389
- }
390
- else if (Array.isArray(to) && to.every(is_ref)) {
451
+ } else if (is_ref(to)) {
452
+ edge = {
453
+ from,
454
+ labels: [ [ "", to ] ],
455
+ map: void 0,
456
+ select: () => to,
457
+ targets: [ to ]
458
+ }
459
+ } else if (Array.isArray(to) && to.every(is_ref)) {
391
460
  edge = {
392
461
  from,
393
462
  labels: to.map(target => [ "", target ]),
@@ -405,9 +474,11 @@ export function create_builder(definition) {
405
474
  select: result => [ result ],
406
475
  targets: [ ...new Set(Object.values(map)) ]
407
476
  }
408
- } else throw TypeError(
409
- `Invalid edge from ${describe(from)}`
410
- )
477
+ } else {
478
+ throw TypeError(
479
+ `Invalid edge from ${describe(from)}`
480
+ )
481
+ }
411
482
  return create_builder(
412
483
  {
413
484
  ...definition,
@@ -422,13 +493,6 @@ export function create_builder(definition) {
422
493
  (node, i) => [ node.name, "n" + i ]
423
494
  )
424
495
  )
425
- /**
426
- * @param {string} text
427
- * @returns {string}
428
- */
429
- function escape(text) {
430
- return text.replace(/"/g, "#quot;")
431
- }
432
496
  const lines = [ "flowchart TD" ]
433
497
  for (const node of program.nodes) {
434
498
  const id = ids.get(node.name)
@@ -441,34 +505,47 @@ export function create_builder(definition) {
441
505
  const label = escape(
442
506
  marks.length ? `${node.name} (${marks.join(", ")})` : node.name
443
507
  )
444
- if (node.each) lines.push(
445
- `\t${id}@{ shape: procs, label: "${label}" }`
446
- )
447
- else if (node.kind == "input") lines.push(`\t${id}[/"${label}"/]`)
448
- else if (node.kind == "flow") lines.push(`\t${id}[["${label}"]]`)
449
- else lines.push(`\t${id}["${label}"]`)
508
+ if (node.each) {
509
+ lines.push(
510
+ `\t${id}@{ shape: procs, label: "${label}" }`
511
+ )
512
+ } else if (node.kind == "input") {
513
+ lines.push(`\t${id}[/"${label}"/]`)
514
+ } else if (node.kind == "flow") {
515
+ lines.push(`\t${id}[["${label}"]]`)
516
+ } else {
517
+ lines.push(`\t${id}["${label}"]`)
518
+ }
450
519
  }
451
520
  /** @type {[string, string, string][]} */
452
521
  const arrows = []
453
522
  for (const [ from, edge ] of program.edges) {
454
- for (const [ label, target ] of edge.labels) arrows.push([ from, label, target ])
523
+ for (const [ label, target ] of edge.labels) {
524
+ arrows.push([ from, label, target ])
525
+ }
455
526
  }
456
527
  for (const node of program.nodes) {
457
- for (const target of node.fallback) arrows.push(
458
- [ node.name, "fallback", target ]
459
- )
528
+ for (const target of node.fallback) {
529
+ arrows.push(
530
+ [ node.name, "fallback", target ]
531
+ )
532
+ }
460
533
  }
461
534
  /** @type {Set<string>} */
462
535
  const links = new Set()
463
- for (const [ from, , target ] of arrows) links.add(from + "\n" + target)
536
+ for (const [ from, , target ] of arrows) {
537
+ links.add(from + "\n" + target)
538
+ }
464
539
  for (const node of program.nodes) {
465
540
  for (const dep of node.deps) {
466
541
  const arrive = node.arrive?.[dep]
467
- if (!links.has(dep + "\n" + node.name)) lines.push(
468
- arrive
469
- ? `\t${ids.get(dep)} -->|"${arrive}"| ${ids.get(node.name)}`
470
- : `\t${ids.get(dep)} --> ${ids.get(node.name)}`
471
- )
542
+ if (!links.has(dep + "\n" + node.name)) {
543
+ lines.push(
544
+ arrive
545
+ ? `\t${ids.get(dep)} -->|"${arrive}"| ${ids.get(node.name)}`
546
+ : `\t${ids.get(dep)} --> ${ids.get(node.name)}`
547
+ )
548
+ }
472
549
  }
473
550
  }
474
551
  for (const [ from, label, target ] of arrows) {
@@ -480,6 +557,13 @@ export function create_builder(definition) {
480
557
  )
481
558
  }
482
559
  return lines.join("\n")
560
+ /**
561
+ * @param {string} text
562
+ * @returns {string}
563
+ */
564
+ function escape(text) {
565
+ return text.replace(/"/g, "#quot;")
566
+ }
483
567
  },
484
568
  /**
485
569
  * @param {unknown} state
@@ -487,7 +571,9 @@ export function create_builder(definition) {
487
571
  */
488
572
  run(state, options) {
489
573
  for (const key of Object.keys(options ?? {})) {
490
- if (!run_option_keys.has(key)) throw TypeError(`Unknown run option "${key}"`)
574
+ if (!run_option_keys.has(key)) {
575
+ throw TypeError(`Unknown run option "${key}"`)
576
+ }
491
577
  }
492
578
  return start_run(
493
579
  compile(definition),
@@ -496,6 +582,15 @@ export function create_builder(definition) {
496
582
  )
497
583
  }
498
584
  }
585
+ /**
586
+ * @param {string} name
587
+ * @param {number=} except
588
+ * @returns {boolean}
589
+ */
590
+ function is_taken(name, except) {
591
+ const index = names.get(name)
592
+ return index != null && index < count && index !== except
593
+ }
499
594
  }
500
595
  /**
501
596
  * @param {Program} program
@@ -508,11 +603,15 @@ function find_stream(program, name) {
508
603
  const stack = [ name ]
509
604
  while (stack.length) {
510
605
  const current = /** @type {string} */(stack.pop())/**/
511
- if (seen.has(current)) continue
606
+ if (seen.has(current)) {
607
+ continue
608
+ }
512
609
  seen.add(current)
513
- const node = program.node_of(current)
514
- if (node?.source) return current
515
- if (node) stack.push(...node.deps)
610
+ const node = /** @type {ProgramNode} */(program.node_of(current))/**/
611
+ if (node.source) {
612
+ return current
613
+ }
614
+ stack.push(...node.deps)
516
615
  }
517
616
  return void 0
518
617
  }