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.
- package/CHANGELOG.md +34 -0
- package/README.md +1 -1
- package/docs/http.md +1 -1
- package/docs/reference.md +4 -2
- package/docs/tasks.md +19 -2
- package/package.json +4 -5
- package/public.d.ts +112 -0
- package/src/attempt.js +17 -7
- package/src/durable/context.js +107 -64
- package/src/durable/error.js +39 -15
- package/src/durable/index.js +18 -10
- package/src/durable/journal.js +29 -14
- package/src/durable/json.js +26 -9
- package/src/durable/lease.js +15 -5
- package/src/durable/persist.js +60 -22
- package/src/durable/run.js +103 -41
- package/src/durable/send.js +30 -13
- package/src/durable/serve.js +30 -24
- package/src/durable/state.js +28 -13
- package/src/durable/store.js +19 -10
- package/src/durable/watch.js +31 -11
- package/src/errors.js +9 -3
- package/src/flow/attempts.js +49 -29
- package/src/flow/builder.js +260 -161
- package/src/flow/compile.js +86 -66
- package/src/flow/graph.js +108 -42
- package/src/flow/index.js +13 -7
- package/src/flow/items.js +79 -46
- package/src/flow/nodes.js +267 -117
- package/src/flow/refs.js +27 -13
- package/src/flow/resources.js +9 -3
- package/src/flow/run.js +287 -114
- package/src/flow/streams.js +44 -19
- package/src/flow/subflows.js +93 -53
- package/src/http/config.js +11 -5
- package/src/http/index.js +25 -14
- package/src/http/refresh.js +29 -12
- package/src/http/request.js +246 -151
- package/src/http/response.js +105 -50
- package/src/http/retry.js +52 -27
- package/src/http/socket.js +220 -172
- package/src/http/sse.js +113 -74
- package/src/http/throttle.js +9 -4
- package/src/http/url.js +58 -35
- package/src/http/xhr.js +29 -24
- package/src/index.js +1 -0
- package/src/key.js +3 -5
- package/src/limiter.js +146 -65
- package/src/offload.js +116 -85
- package/src/offload_module.js +594 -0
- package/src/options.js +8 -4
- package/src/redial.js +129 -0
- package/src/signal.js +14 -4
- package/src/stream.js +353 -198
- package/types/attempt.d.ts.map +1 -1
- package/types/durable/index.d.ts.map +1 -1
- package/types/durable/state.d.ts.map +1 -1
- package/types/durable/store.d.ts.map +1 -1
- package/types/errors.d.ts.map +1 -1
- package/types/http/index.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/limiter.d.ts +5 -1
- package/types/limiter.d.ts.map +1 -1
- package/types/offload.d.ts +2 -16
- package/types/offload.d.ts.map +1 -1
- package/types/redial.d.ts +28 -0
- package/types/redial.d.ts.map +1 -0
- package/types/stream.d.ts +2 -0
- package/types/stream.d.ts.map +1 -1
package/src/flow/builder.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
113
|
-
|
|
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__")
|
|
121
|
-
|
|
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))
|
|
128
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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))
|
|
146
|
-
|
|
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)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (options.
|
|
213
|
-
|
|
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))
|
|
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))
|
|
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")
|
|
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)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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")
|
|
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))
|
|
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))
|
|
341
|
-
|
|
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))
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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))
|
|
374
|
-
|
|
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))
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
409
|
-
|
|
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)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
else if (node.kind == "
|
|
449
|
-
|
|
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)
|
|
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)
|
|
458
|
-
|
|
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)
|
|
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))
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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))
|
|
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))
|
|
606
|
+
if (seen.has(current)) {
|
|
607
|
+
continue
|
|
608
|
+
}
|
|
512
609
|
seen.add(current)
|
|
513
|
-
const node = program.node_of(current)
|
|
514
|
-
if (node
|
|
515
|
-
|
|
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
|
}
|