@expressive/steps 0.0.0-stage → 0.1.1

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/dist/index.js ADDED
@@ -0,0 +1,1683 @@
1
+ import { Model, get, has, map, unbind } from "@expressive/mvc";
2
+ import { event, listener, touch, watch } from "@expressive/mvc/observable";
3
+
4
+ //#region src/steps.ts
5
+ const HOOKS = new Set(["before", "after"]);
6
+ const KEYS = [
7
+ "status",
8
+ "active",
9
+ "queue",
10
+ "waiting",
11
+ "runs",
12
+ "error"
13
+ ];
14
+ const SLACK = 8;
15
+ const LONGEST = 2 ** 31 - 1;
16
+ const GATES = /* @__PURE__ */ new WeakMap();
17
+ const UNCAUGHT = ["gate", "end"];
18
+ const UNPLANNED = [
19
+ "end",
20
+ "gate",
21
+ "wait"
22
+ ];
23
+ const RESERVED = [
24
+ "then",
25
+ "end",
26
+ "gate",
27
+ "wait"
28
+ ];
29
+ const REFRESH = Symbol.for("@expressive/mvc.refresh");
30
+ const ISSUED = /* @__PURE__ */ new WeakMap();
31
+ const RETRY = /* @__PURE__ */ new WeakSet();
32
+ const RULES = /* @__PURE__ */ new WeakSet();
33
+ const KINDS = [
34
+ "reset",
35
+ "catch",
36
+ "abort",
37
+ "close"
38
+ ];
39
+ const MODES = {
40
+ failed: "error",
41
+ aborted: "abort",
42
+ stopped: "stop"
43
+ };
44
+ const UNITS = {
45
+ ms: 1,
46
+ sec: 1e3,
47
+ min: 6e4,
48
+ hr: 36e5,
49
+ day: 864e5
50
+ };
51
+ const TOKEN = { catch(handler) {
52
+ return ISSUED.get(this).rescue(this, handler);
53
+ } };
54
+ function install(self) {
55
+ const runner = function(onfulfilled, onrejected) {
56
+ if (onfulfilled && typeof onfulfilled == "object") throw rule("then(steps) must be called inside a step.");
57
+ return control.settled().then(onfulfilled, onrejected);
58
+ };
59
+ const control = Control.new({
60
+ _self: self,
61
+ _runner: runner
62
+ });
63
+ const read = {
64
+ status: () => control.status,
65
+ active: () => control.active,
66
+ queue: () => control.queue.map((ticket) => ticket.args),
67
+ waiting: () => control.run.waits.map(({ id, step, args, answers, until }) => ({
68
+ id,
69
+ step,
70
+ args,
71
+ answers,
72
+ ...until !== void 0 && { until }
73
+ })),
74
+ runs: () => control.runs,
75
+ error: () => control.error
76
+ };
77
+ for (const key of KEYS) Object.defineProperty(runner, key, {
78
+ enumerable: true,
79
+ get() {
80
+ return touch(this, key, read[key]());
81
+ }
82
+ });
83
+ Object.defineProperties(runner, {
84
+ then: { value: runner },
85
+ self: { value: self },
86
+ get: { value: () => Object.fromEntries(KEYS.map((key) => [key, read[key]()])) },
87
+ end: { value: (...value) => void (valueless(value), control.conclude()) },
88
+ stop: { value: (reason) => control.stop(false, reason) },
89
+ abort: { value: (reason) => control.stop(true, reason) },
90
+ only: { value: () => outside("only") },
91
+ commit: { value: () => outside("commit") },
92
+ on: { value: () => outside("on") },
93
+ restore: { value: () => outside("restore") },
94
+ signal: { get: () => outside("signal", "read") },
95
+ retry: { value: () => outside("retry") },
96
+ retried: { get: () => outside("retried", "read") }
97
+ });
98
+ event(runner);
99
+ Object.defineProperty(self, "then", {
100
+ configurable: true,
101
+ get() {
102
+ const view = touch(this, "then", runner);
103
+ return view === runner ? runner : Object.setPrototypeOf((...args) => runner(...args), view);
104
+ }
105
+ });
106
+ const { _hooks: hooks, _steps: steps } = control;
107
+ const wire = () => {
108
+ for (const key of steps.keys()) delete self[key];
109
+ steps.clear();
110
+ hooks.clear();
111
+ for (let proto = Object.getPrototypeOf(self); proto !== Model.prototype; proto = Object.getPrototypeOf(proto)) for (const [key, desc] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
112
+ if (!(desc.get && unbind(desc.get) !== desc.get) || key == "new" || key[0] == "_" || Object.hasOwn(self, key)) continue;
113
+ if (key == "end") throw rule(`${self.constructor.name} declares end; return the result from run, end early with this.then.end().`);
114
+ if (RESERVED.includes(key)) throw rule(`${self.constructor.name} declares ${key}; then, end, gate and wait name plan tokens - rename the step.`);
115
+ if (HOOKS.has(key)) {
116
+ if (!hooks.has(key)) hooks.set(key, unbind(desc.get));
117
+ continue;
118
+ }
119
+ const step = (...args) => control.call(key, args);
120
+ steps.set(key, unbind(desc.get));
121
+ Object.defineProperty(self, key, key == "run" ? {
122
+ value: step,
123
+ writable: true,
124
+ configurable: true
125
+ } : {
126
+ configurable: true,
127
+ get() {
128
+ touch(this, key);
129
+ return control.accepts(key) ? step : void 0;
130
+ }
131
+ });
132
+ }
133
+ };
134
+ wire();
135
+ listener(self, (key) => void (key === REFRESH && wire()));
136
+ self.get(null, () => control.stop());
137
+ }
138
+ var Aborted = class extends Error {
139
+ name = "Aborted";
140
+ };
141
+ var Stopped = class extends Error {
142
+ name = "Stopped";
143
+ };
144
+ /**
145
+ * A runnable Model: every method is a step, a step schedules the next by calling it.
146
+ *
147
+ * Fields carry data between steps. Each step is atomic and registers its own reset; a failure unwinds
148
+ * what committed. `run()` starts a run; awaiting the instance joins it. In-process only.
149
+ *
150
+ * @example
151
+ * class Checkout extends Steps {
152
+ * cart!: Cart;
153
+ * charge?: Charge = undefined;
154
+ *
155
+ * async run() {
156
+ * this.pay();
157
+ * await this;
158
+ * return this.charge!.id;
159
+ * }
160
+ *
161
+ * protected async pay() {
162
+ * this.charge = await stripe.charge(this.cart.total);
163
+ * this.then.on({ reset: () => stripe.refund(this.charge!.id) });
164
+ * }
165
+ * }
166
+ *
167
+ * const id = await Checkout.new({ cart });
168
+ */
169
+ var Steps = class extends Model {
170
+ /** Why a run or subtree ended: `abort()`, or a lost race. */
171
+ static Aborted = Aborted;
172
+ /** Why a run or subtree ended: `stop()`. */
173
+ static Stopped = Stopped;
174
+ constructor(...args) {
175
+ super(...args);
176
+ install(this);
177
+ }
178
+ };
179
+ var Control = class extends Model {
180
+ status = "idle";
181
+ active = void 0;
182
+ runs = 0;
183
+ error = void 0;
184
+ queue = has();
185
+ run = new Run();
186
+ _self;
187
+ _runner;
188
+ _steps = /* @__PURE__ */ new Map();
189
+ _hooks = /* @__PURE__ */ new Map();
190
+ _gates = /* @__PURE__ */ new Map();
191
+ _current = void 0;
192
+ new() {
193
+ const { _runner } = this;
194
+ listener(this.queue, (key) => {
195
+ if (signal(key)) event(this, "queue");
196
+ });
197
+ return this.set((key) => void event(_runner, key));
198
+ }
199
+ call(key, args) {
200
+ if (key == "run") return this.start(args);
201
+ const { _current: current } = this;
202
+ const wait = current && current.route(key, args);
203
+ if (wait) return current.wake(wait, key, args);
204
+ throw rule(`${key} cannot be called from outside; call run() to start a run.`);
205
+ }
206
+ accepts(key) {
207
+ return !!this._current?.accepts(key);
208
+ }
209
+ hook(key, ...args) {
210
+ return this._hooks.get(key)?.apply(this._self, args);
211
+ }
212
+ start(args) {
213
+ const ticket = { args };
214
+ ticket.promise = new Promise((resolve, reject) => {
215
+ ticket.resolve = resolve;
216
+ ticket.reject = reject;
217
+ });
218
+ ticket.promise.catch(() => {});
219
+ if (this._current) this.queue.push(ticket);
220
+ else this.begin(ticket);
221
+ return handle(ticket, (resolve, reject) => ticket.promise.then(resolve, reject));
222
+ }
223
+ begin(ticket) {
224
+ const run = new Run({ _ticket: ticket });
225
+ this._current = this.run = run;
226
+ this.error = void 0;
227
+ this.status = "running";
228
+ run.start();
229
+ }
230
+ advance() {
231
+ const next = this.queue.pop(0);
232
+ if (next) this.begin(next);
233
+ }
234
+ tail() {
235
+ return this.queue.get(-1) || this.run._ticket;
236
+ }
237
+ settled() {
238
+ if (!this._current) {
239
+ if (this._steps.get("run")?.length) return Promise.reject(/* @__PURE__ */ new Error(`${this._self} is idle. run() takes arguments; call it to start a run.`));
240
+ this.start([]);
241
+ }
242
+ return this.join();
243
+ }
244
+ join() {
245
+ const ticket = this.tail();
246
+ const tickets = [
247
+ this._current?._ticket,
248
+ ...this.queue,
249
+ ticket
250
+ ].filter(Boolean);
251
+ for (const each of tickets) each.awaited = true;
252
+ return ticket.promise.then((value) => this.tail() === ticket ? value : this.join(), (error) => this.tail() === ticket ? Promise.reject(error) : this.join());
253
+ }
254
+ conclude() {
255
+ const { _current: run } = this;
256
+ if (run && !run.stale()) run.terminate(run.node("end", []));
257
+ }
258
+ stop(undo, reason) {
259
+ const { _current: current, queue } = this;
260
+ if (!current) return;
261
+ const message = typeof reason == "string" ? reason : `${this._self} was ${undo ? "aborted" : "stopped"}.`;
262
+ const stopped = new (undo ? Aborted : Stopped)(message, reason === void 0 ? void 0 : { cause: reason });
263
+ for (const ticket of queue) ticket.reject(stopped);
264
+ queue.clear();
265
+ current._stopped = stopped;
266
+ current.release();
267
+ current.halt(undo ? "aborted" : "stopped");
268
+ }
269
+ };
270
+ var Run = class extends Model {
271
+ control = get(Control);
272
+ frames = has(Frame);
273
+ waits = has(Wait);
274
+ _ticket;
275
+ _entries = [];
276
+ _counts = /* @__PURE__ */ new Map();
277
+ _on = /* @__PURE__ */ new Map();
278
+ _finished = /* @__PURE__ */ new Set();
279
+ _reset = /* @__PURE__ */ new Set();
280
+ _writer = /* @__PURE__ */ new Map();
281
+ _lost = /* @__PURE__ */ new Set();
282
+ _losing = /* @__PURE__ */ new Map();
283
+ _held = /* @__PURE__ */ new Map();
284
+ _inline = /* @__PURE__ */ new Map();
285
+ _errors = /* @__PURE__ */ new Map();
286
+ _owner = /* @__PURE__ */ new Map();
287
+ _parent = /* @__PURE__ */ new Map();
288
+ _slots = /* @__PURE__ */ new Map();
289
+ _catches = /* @__PURE__ */ new Map();
290
+ _branches = /* @__PURE__ */ new Map();
291
+ _lineage = /* @__PURE__ */ new Map();
292
+ _retried = /* @__PURE__ */ new Map();
293
+ _gates = /* @__PURE__ */ new Map();
294
+ _groups = /* @__PURE__ */ new Set();
295
+ _pending = /* @__PURE__ */ new Map();
296
+ _holding = /* @__PURE__ */ new Map();
297
+ _dropped = /* @__PURE__ */ new Set();
298
+ _spent = /* @__PURE__ */ new Set();
299
+ _unwinding = void 0;
300
+ _drained = [];
301
+ _quiet = /* @__PURE__ */ new Set();
302
+ _halted = void 0;
303
+ _ending = void 0;
304
+ _settling = void 0;
305
+ _error = void 0;
306
+ _stopped = void 0;
307
+ _root = void 0;
308
+ _result = void 0;
309
+ _tail = void 0;
310
+ _await = void 0;
311
+ new() {
312
+ const { control } = this;
313
+ listener(this.waits, (key) => {
314
+ if (!(signal(key) && control._current === this)) return;
315
+ event(control, "waiting");
316
+ control.status = this.waits.size ? "waiting" : this._halted || "running";
317
+ });
318
+ }
319
+ start() {
320
+ const node = this.node("run", this._ticket.args);
321
+ this._root = node.id;
322
+ this.exec(node).then(() => {
323
+ if (!this._halted && !this._ending) this.end();
324
+ });
325
+ }
326
+ node(key, args) {
327
+ const count = (this._counts.get(key) || 0) + 1;
328
+ this._counts.set(key, count);
329
+ return Object.assign(Object.create(TOKEN), {
330
+ id: `${key}#${count}`,
331
+ key,
332
+ args
333
+ });
334
+ }
335
+ route(key, args, from) {
336
+ const open = this.waits.filter((wait) => wait.answers.includes(key));
337
+ return open.find((wait) => wait._from === from) || open.find((wait) => wait.args[0] === args[0]) || open[0];
338
+ }
339
+ accepts(key, except) {
340
+ return this.waits.any((wait) => wait !== except && wait._open && wait.answers.includes(key));
341
+ }
342
+ flight() {
343
+ return this.frames.filter((frame) => !frame._undo);
344
+ }
345
+ stale() {
346
+ return !!(this._halted || this._ending);
347
+ }
348
+ dropped(id) {
349
+ return this.line(id).some((at) => this._dropped.has(at) || at !== id && this._dropped.has(at + "^"));
350
+ }
351
+ line(id) {
352
+ const ids = [];
353
+ for (let at = id; at; at = this._parent.get(at)) ids.push(at);
354
+ return ids;
355
+ }
356
+ async exec(item, from) {
357
+ if (this.stale() || this.dropped(item.id)) return;
358
+ const rescue = this._catches.get(item.id);
359
+ if (rescue && !this._branches.has(item.id)) return this.guard(item, from, rescue);
360
+ if (item.group) {
361
+ await Promise.all(item.group.map((member) => this.exec(member, from)));
362
+ return;
363
+ }
364
+ if (item.answers) {
365
+ const answer = await this.park(item, from);
366
+ if (answer) {
367
+ const frame = await answer.frame;
368
+ await this.cover(frame);
369
+ await this.complete(frame);
370
+ answer.done();
371
+ }
372
+ } else if (item.key == "end") await this.terminate(item);
373
+ else {
374
+ const wait = this.racing(item);
375
+ if (wait) return void this.wake(wait, item.key, item.args, item);
376
+ const frame = await this.invoke(item);
377
+ await this.cover(frame, item);
378
+ await this.complete(frame);
379
+ }
380
+ }
381
+ async cover(frame, item) {
382
+ const handler = frame && this._on.get(frame.id)?.find(({ kind }) => kind == "catch");
383
+ if (!handler) return this.descend(frame);
384
+ const { id, key, args, _failed: failed } = frame;
385
+ const shield = id + "^";
386
+ const branch = { failed };
387
+ item ||= Object.assign(Object.create(TOKEN), {
388
+ id,
389
+ key,
390
+ args
391
+ });
392
+ if (!failed) {
393
+ this._branches.set(shield, branch);
394
+ await this.descend(frame);
395
+ this._branches.delete(shield);
396
+ }
397
+ if (!branch.failed) return;
398
+ const { cause } = branch.failed;
399
+ const restore = await this.serial(() => failed ? this.rewind(id, cause) : this.recover(item, cause, "error", false));
400
+ this._dropped.delete(shield);
401
+ if (this.stale()) return restore();
402
+ const node = this.given(handler, cause);
403
+ this._parent.set(node.id, id);
404
+ this._lineage.set(node.id, {
405
+ restore,
406
+ caught: item
407
+ });
408
+ this._retried.set(node.id, this.retried(item));
409
+ await this.exec(node, frame);
410
+ restore();
411
+ }
412
+ async rewind(id, cause) {
413
+ const entry = this._entries.find((each) => each.id == id);
414
+ const { prior, log } = this._held.get(id);
415
+ const kept = /* @__PURE__ */ new Set();
416
+ this._reset.add(id);
417
+ this._held.delete(id);
418
+ try {
419
+ await this.fire(this._on.get(id), "reset", void 0, kept);
420
+ entry.unwound = true;
421
+ } catch (thrown) {
422
+ entry.unwound = "failed";
423
+ this.halt("failed", thrown);
424
+ }
425
+ const self = this.control._self;
426
+ let done = false;
427
+ return (into, all) => {
428
+ if (done) return;
429
+ done = true;
430
+ for (const key in prior) {
431
+ const wrote = into?._wrote.has(key);
432
+ if (wrote) into._prior[key] = prior[key];
433
+ if (all || !wrote && !kept.has(key)) self[key] = prior[key];
434
+ }
435
+ for (const [key, undo] of [...log].reverse()) if (all || !into?._changed.has(key)) undo();
436
+ };
437
+ }
438
+ async complete(frame) {
439
+ const on = frame && this._on.get(frame.id);
440
+ if (!on) return;
441
+ const { id } = frame;
442
+ const losing = [...this._losing].filter(([racer]) => this.line(racer).includes(id));
443
+ await Promise.all(losing.map(([, unwind]) => unwind));
444
+ if (this.stale() || this.dropped(id)) return;
445
+ this._finished.add(id);
446
+ try {
447
+ await this.fire(on, "close", void 0, /* @__PURE__ */ new Set());
448
+ } catch (error) {
449
+ if (!this.caught(error, id)) this.halt("failed", error);
450
+ }
451
+ }
452
+ async fire(on, kind, cause, kept) {
453
+ for (const handler of [...on || []].reverse()) if (handler.kind == kind) await this.compensate(this.given(handler, cause), cause, kept, kind == "reset" || kind == "close" || cause === void 0);
454
+ }
455
+ given({ node, bare, kind }, cause) {
456
+ if (bare && (kind == "catch" || kind == "abort" && cause !== void 0)) {
457
+ node.args = [serial(cause)];
458
+ this._errors.set(node.id, cause);
459
+ } else if (kind == "catch" && this._inline.has(node.id)) node.args = [cause];
460
+ return node;
461
+ }
462
+ racing(item) {
463
+ const line = this.line(item.id);
464
+ return this.waits.filter((wait) => !!wait._race && line.includes(wait.id) && wait.answers.includes(item.key))[0];
465
+ }
466
+ lose(wait, won, answer) {
467
+ const race = wait._race;
468
+ const losers = (race.group || [race]).filter((racer) => !won.includes(racer.id));
469
+ const cause = new Aborted(`${answer.key} answered ${wait.id}.`);
470
+ for (const racer of losers) {
471
+ this._dropped.add(racer.id);
472
+ this._lost.add(racer.id);
473
+ }
474
+ for (const frame of this.discard()) frame._abort.abort(cause);
475
+ for (const [release, from] of this._holding) if (this.dropped(from)) release();
476
+ for (const racer of losers) this._losing.set(racer.id, this.serial(async () => {
477
+ await this.quiet(racer.id);
478
+ (await this.recover(racer, cause, "abort"))(void 0, false, racer.id);
479
+ }));
480
+ }
481
+ quiet(id) {
482
+ const busy = () => this.flight().some((frame) => this.line(frame.id).includes(id));
483
+ return new Promise((resolve) => {
484
+ const check = () => {
485
+ if (busy()) return;
486
+ this._quiet.delete(check);
487
+ resolve();
488
+ };
489
+ this._quiet.add(check);
490
+ check();
491
+ });
492
+ }
493
+ terminate(item) {
494
+ this.release();
495
+ return this.end(item);
496
+ }
497
+ serial(task) {
498
+ const prior = this._unwinding;
499
+ const next = (async () => {
500
+ await prior;
501
+ return task();
502
+ })();
503
+ this._unwinding = next;
504
+ return next;
505
+ }
506
+ async guard(item, from, rescue) {
507
+ const branch = {};
508
+ this._branches.set(item.id, branch);
509
+ await this.exec(item, from);
510
+ this._branches.delete(item.id);
511
+ if (!branch.failed) return;
512
+ const { cause } = branch.failed;
513
+ const restore = await this.serial(() => this.recover(item, cause, "error"));
514
+ const { key, fn } = rescue;
515
+ const parent = this._parent.get(item.id);
516
+ if (this.stale() || !fn) return restore();
517
+ const node = this.node(key || "then", key ? [serial(cause)] : [cause]);
518
+ if (key) this._errors.set(node.id, cause);
519
+ this._parent.set(node.id, parent);
520
+ this._lineage.set(node.id, {
521
+ restore,
522
+ caught: item
523
+ });
524
+ this._retried.set(node.id, this.retried(item));
525
+ if (!key) {
526
+ this._inline.set(node.id, fn);
527
+ this._owner.set(node.id, from);
528
+ }
529
+ await this.exec(node, from);
530
+ restore();
531
+ }
532
+ retried(item) {
533
+ return this._retried.get(item.id) || 0;
534
+ }
535
+ copy(item, retried = this.retried(item) + 1) {
536
+ const { id, key, args, group, answers, race } = item;
537
+ const node = this.node(key, args);
538
+ const rescue = this._catches.get(id);
539
+ this._retried.set(node.id, retried);
540
+ if (group) {
541
+ node.group = group.map((member) => this.copy(member, retried));
542
+ this._groups.add(node.id);
543
+ adopt(this._parent, node.group, node.id);
544
+ }
545
+ if (answers) node.answers = answers;
546
+ if (race) {
547
+ node.race = this.copy(race, retried);
548
+ adopt(this._parent, [node.race], node.id);
549
+ }
550
+ if (rescue) {
551
+ node.rescue = item.rescue;
552
+ this._catches.set(node.id, rescue);
553
+ }
554
+ return node;
555
+ }
556
+ async recover(item, cause, mode, own = true) {
557
+ const ids = this._entries.filter((entry) => !this._spent.has(entry.id) && (own || entry.id != item.id) && this.line(entry.id).includes(item.id));
558
+ const unwound = await this.unwind(cause, mode, new Set(ids.map((entry) => entry.id)));
559
+ let done = false;
560
+ const restore = (into, all, within) => {
561
+ if (done) return;
562
+ done = true;
563
+ this.refill(unwound, into, all, within);
564
+ };
565
+ if (unwound.failed) {
566
+ const { cause: thrown } = unwound.failed;
567
+ restore();
568
+ if (mode == "abort") chain(thrown, cause);
569
+ this.halt("failed", thrown);
570
+ }
571
+ return restore;
572
+ }
573
+ caught(error, id) {
574
+ if (this.stale() || RULES.has(error)) return false;
575
+ let at;
576
+ for (const each of this.line(id)) if (each != id && this._branches.has(at = each + "^")) break;
577
+ else if (this._branches.has(at = each)) break;
578
+ else at = void 0;
579
+ if (!at) return false;
580
+ const branch = this._branches.get(at);
581
+ if (branch.failed) return true;
582
+ branch.failed = { cause: error };
583
+ this._dropped.add(at);
584
+ for (const frame of this.discard()) frame._abort.abort();
585
+ return true;
586
+ }
587
+ discard() {
588
+ for (const wait of this.waits.filter((wait) => this.dropped(wait.id))) this.waits.delete(wait);
589
+ for (const [id, cancel] of this._pending) if (this.dropped(id)) cancel();
590
+ return this.flight().filter((frame) => this.dropped(frame.id));
591
+ }
592
+ async descend(frame) {
593
+ if (frame) await this.sequence(frame._items, frame);
594
+ }
595
+ async sequence(items, from) {
596
+ for (const [index, item] of items.entries()) {
597
+ if (item.key != "gate") {
598
+ await this.exec(item, from);
599
+ continue;
600
+ }
601
+ const release = await this.acquire(item, from);
602
+ if (!release) return;
603
+ try {
604
+ await this.sequence(items.slice(index + 1), from);
605
+ } finally {
606
+ release();
607
+ }
608
+ return;
609
+ }
610
+ }
611
+ acquire(item, from) {
612
+ if (this.stale() || this.dropped(item.id)) return;
613
+ const { id } = item;
614
+ const { max, key } = this._slots.get(id);
615
+ const gate = this.gate(from, key);
616
+ const line = this.line(from.id);
617
+ const hold = {
618
+ run: this,
619
+ from: from.id
620
+ };
621
+ const inside = [...gate.held].some((each) => each.run === this && line.includes(each.from));
622
+ const self = this.control._self;
623
+ return new Promise((resolve) => {
624
+ let quiet;
625
+ let limit = 0;
626
+ let over = false;
627
+ const finish = (release) => {
628
+ over = true;
629
+ quiet?.();
630
+ this._pending.delete(id);
631
+ resolve(release);
632
+ };
633
+ const release = () => {
634
+ if (!gate.held.delete(hold)) return;
635
+ this._holding.delete(release);
636
+ pump(gate);
637
+ };
638
+ const waiter = {
639
+ hold,
640
+ max: typeof max != "function" ? () => max : max.length ? () => limit : max,
641
+ grant: () => {
642
+ this._holding.set(release, from.id);
643
+ finish(release);
644
+ }
645
+ };
646
+ if (inside) return finish(() => {});
647
+ this._pending.set(id, () => {
648
+ gate.queue = gate.queue.filter((each) => each !== waiter);
649
+ finish();
650
+ });
651
+ if (typeof max == "function") quiet = max.length ? watch(self, ($) => {
652
+ limit = max($);
653
+ pump(gate);
654
+ return over ? null : void 0;
655
+ }) : listener(self, (key) => void (signal(key) && pump(gate)));
656
+ gate.queue.push(waiter);
657
+ pump(gate);
658
+ });
659
+ }
660
+ gate(from, key) {
661
+ const { control } = this;
662
+ if (key instanceof Object) return gate(GATES, key);
663
+ if (key !== void 0) return gate(control._gates, key);
664
+ const group = this.line(from.id).find((id) => this._groups.has(id));
665
+ return group ? gate(this._gates, group) : gate(control._gates, control);
666
+ }
667
+ park(item, from) {
668
+ return new Promise((resolve) => {
669
+ const { id, answers, race, args } = item;
670
+ const until = answers.length ? void 0 : Date.now() + args[0];
671
+ this.waits.add({
672
+ id,
673
+ step: from.key,
674
+ args: from.args,
675
+ answers,
676
+ until,
677
+ _race: race,
678
+ _from: from,
679
+ _park: resolve
680
+ });
681
+ if (race) this.exec(race, from);
682
+ });
683
+ }
684
+ wake(wait, key, args, node = this.node(key, args)) {
685
+ const { _ticket } = this;
686
+ let done;
687
+ let fail;
688
+ const answered = handle(_ticket, (res, rej) => {
689
+ done = res;
690
+ fail = rej;
691
+ });
692
+ const answer = {
693
+ done: () => this._halted ? fail(this.reason()) : done(),
694
+ fail
695
+ };
696
+ const won = this.line(node.id);
697
+ wait._answer = answer;
698
+ this._parent.set(node.id, wait.id);
699
+ this.waits.delete(wait);
700
+ if (wait._race) this.lose(wait, won, node);
701
+ answer.frame = this.invoke(node, wait.id);
702
+ return answered;
703
+ }
704
+ release() {
705
+ this.waits.clear();
706
+ for (const cancel of this._pending.values()) cancel();
707
+ for (const release of this._holding.keys()) release();
708
+ }
709
+ async invoke(node, wake, undo) {
710
+ const { control } = this;
711
+ const { id, key, args } = node;
712
+ const inline = this._inline.get(id);
713
+ const lineage = this._lineage.get(id);
714
+ const fn = inline || control._steps.get(key);
715
+ const frame = this.frames.add({
716
+ id,
717
+ key,
718
+ args,
719
+ _wake: wake,
720
+ _undo: undo,
721
+ _owner: this._owner.get(id),
722
+ _retried: this.retried(node),
723
+ ...lineage && {
724
+ _restore: lineage.restore,
725
+ _caught: lineage.caught
726
+ }
727
+ });
728
+ const input = this._errors.has(id) ? [this._errors.get(id)] : args;
729
+ let body = false;
730
+ control.active = key;
731
+ try {
732
+ control.hook("before", key, args);
733
+ if (!fn) throw rule(key == "run" ? `${control._self} declares no run - add async run() to start a run.` : `${key} was removed by a hot update.`);
734
+ for (;;) try {
735
+ const call = inline ? fn(...input) : fn.apply(frame._proxy, input);
736
+ const out = id == this._root ? await frame.open(call) : await call;
737
+ frame._settled = true;
738
+ const entry = frame.commit(out);
739
+ frame._restore?.(frame);
740
+ control.hook("after", key, entry);
741
+ return frame;
742
+ } catch (thrown) {
743
+ if (frame._entry) throw thrown;
744
+ let error = thrown;
745
+ let irreversible = [...frame._lost];
746
+ let fixed = irreversible.length || frame._committed;
747
+ body = !frame._settled;
748
+ if (error instanceof Promise && body && !fixed) {
749
+ await this.fire(frame._on, "reset", void 0, /* @__PURE__ */ new Set());
750
+ this.reset(frame);
751
+ const failed = await frame.pause(error);
752
+ if (!failed) continue;
753
+ error = failed.cause;
754
+ }
755
+ if (fixed) this._entries.push(frame._fixed = {
756
+ ...frame.journal(),
757
+ error,
758
+ ...irreversible.length && { irreversible }
759
+ });
760
+ throw error;
761
+ }
762
+ } catch (error) {
763
+ if (frame._entry && !undo) {
764
+ frame._restore?.();
765
+ this.halt("failed", error);
766
+ return;
767
+ }
768
+ const { signal } = frame._abort;
769
+ const fixed = frame._lost.size || frame._committed;
770
+ const lost = this.line(id).some((at) => this._lost.has(at));
771
+ const aborted = signal.aborted && error === signal.reason;
772
+ const on = !fixed && !undo && frame._on.length ? frame._on : void 0;
773
+ const defer = on && !aborted && !this.stale();
774
+ const entry = {
775
+ id,
776
+ key,
777
+ args,
778
+ next: [],
779
+ wrote: [],
780
+ error,
781
+ ...wake && { wake },
782
+ ...frame._retried && { retried: frame._retried }
783
+ };
784
+ frame._settled = true;
785
+ if (!fixed && !defer) this.restore(frame._prior, frame._log);
786
+ frame._restore?.();
787
+ if (undo) throw error;
788
+ if (on) {
789
+ entry.on = grouped(on);
790
+ this._on.set(id, on);
791
+ this._entries.push(entry);
792
+ }
793
+ if (defer) this._held.set(id, {
794
+ prior: frame._prior,
795
+ log: frame._log
796
+ });
797
+ if (aborted) return;
798
+ if (body && !fixed && !lost && !this.stale() && !RULES.has(error) && !!on?.some(({ kind }) => kind == "catch")) {
799
+ frame._failed = { cause: error };
800
+ try {
801
+ control.hook("after", key, entry);
802
+ } catch (thrown) {
803
+ this.halt("failed", thrown);
804
+ }
805
+ return frame;
806
+ }
807
+ if (body && !fixed && (lost || this.caught(error, id))) {
808
+ if (!on) this._entries.push(entry);
809
+ try {
810
+ control.hook("after", key, entry);
811
+ } catch (thrown) {
812
+ this.halt("failed", thrown);
813
+ }
814
+ } else this.fault(frame._fixed || entry, !on && !frame._fixed, error);
815
+ } finally {
816
+ this.frames.delete(frame);
817
+ control.active = this.flight().at(-1)?.key;
818
+ for (const check of [...this._quiet]) check();
819
+ if (!this.flight().length) for (const resolve of this._drained.splice(0)) resolve();
820
+ }
821
+ }
822
+ fault(entry, push, error) {
823
+ if (push) this._entries.push(entry);
824
+ try {
825
+ this.control.hook("after", entry.key, entry);
826
+ } catch (thrown) {
827
+ error = thrown;
828
+ }
829
+ this.halt("failed", error);
830
+ }
831
+ reset(frame) {
832
+ this.restore(frame._prior, frame._log);
833
+ frame._armed++;
834
+ Object.assign(frame, {
835
+ _wrappers: /* @__PURE__ */ new Map(),
836
+ _log: [],
837
+ _changed: /* @__PURE__ */ new Set(),
838
+ _items: [],
839
+ _wrote: /* @__PURE__ */ new Set(),
840
+ _prior: {},
841
+ _on: [],
842
+ _violation: void 0,
843
+ _late: void 0
844
+ });
845
+ }
846
+ async end(item) {
847
+ this._ending = true;
848
+ await this.drain();
849
+ if (this._unwinding) await this._unwinding;
850
+ if (this._halted) return;
851
+ const open = this._entries.filter((entry) => this._on.has(entry.id) && !this._finished.has(entry.id) && !this._spent.has(entry.id));
852
+ for (const entry of open.reverse()) try {
853
+ this._finished.add(entry.id);
854
+ await this.fire(this._on.get(entry.id), "close", void 0, /* @__PURE__ */ new Set());
855
+ } catch (thrown) {
856
+ return this.halt("failed", thrown);
857
+ }
858
+ const { id } = item || this.node("end", []);
859
+ const entry = {
860
+ id,
861
+ key: "end",
862
+ args: [],
863
+ next: [],
864
+ wrote: [],
865
+ ...!item && { implicit: true }
866
+ };
867
+ this._entries.push(entry);
868
+ if (item) try {
869
+ this.control.hook("after", "end", entry);
870
+ } catch (thrown) {
871
+ return this.halt("failed", thrown);
872
+ }
873
+ this.finish();
874
+ }
875
+ drain() {
876
+ if (this.flight().length) return new Promise((resolve) => this._drained.push(resolve));
877
+ }
878
+ finish() {
879
+ const { _tail: tail } = this;
880
+ this._settling = true;
881
+ if (!tail) return this.settle(this._result);
882
+ this._await.resolve();
883
+ tail.then((value) => this.settle(value), (error) => this.broke(error));
884
+ }
885
+ broke(error) {
886
+ this.blame(error);
887
+ this.control.status = "failed";
888
+ this.reject();
889
+ }
890
+ settle(value) {
891
+ const { control } = this;
892
+ this.close();
893
+ control.status = "idle";
894
+ this._ticket.resolve(value);
895
+ control.advance();
896
+ }
897
+ close() {
898
+ const { control } = this;
899
+ control._current = void 0;
900
+ control.runs++;
901
+ this._entries = [];
902
+ this._held.clear();
903
+ this._on.clear();
904
+ this.frames.clear();
905
+ this.waits.clear();
906
+ }
907
+ halt(status, error) {
908
+ const { control } = this;
909
+ if (this._halted || this._settling) return;
910
+ this._halted = status;
911
+ control.status = status;
912
+ for (const frame of this.flight()) frame._abort.abort(status == "failed" ? void 0 : this._stopped);
913
+ if (status == "failed") {
914
+ this._error = error;
915
+ control.error = error;
916
+ }
917
+ this.release();
918
+ this.finalize(MODES[status]);
919
+ }
920
+ async finalize(mode) {
921
+ await this.drain();
922
+ if (this._unwinding) await this._unwinding;
923
+ const unwound = await this.unwind(this.reason(), mode);
924
+ this.refill(unwound);
925
+ if (unwound.failed) {
926
+ const { cause } = unwound.failed;
927
+ if (mode != "error") {
928
+ chain(cause, this._stopped);
929
+ this.control.status = "failed";
930
+ }
931
+ this.blame(cause);
932
+ }
933
+ const { _tail: tail } = this;
934
+ if (!tail) return this.reject();
935
+ this._await.reject(this.reason());
936
+ try {
937
+ this.salvage(await tail);
938
+ } catch (thrown) {
939
+ this.reject(thrown);
940
+ }
941
+ }
942
+ salvage(value) {
943
+ this.close();
944
+ this._ticket.resolve(value);
945
+ this.control.advance();
946
+ }
947
+ reason() {
948
+ const { _error: error } = this;
949
+ return error === void 0 ? this._stopped : error;
950
+ }
951
+ reject(reason = this.reason()) {
952
+ const { _ticket } = this;
953
+ let stopped = reason === this._stopped;
954
+ this.close();
955
+ _ticket.reject(reason);
956
+ if (!stopped) queueMicrotask(() => queueMicrotask(() => {
957
+ if (!_ticket.awaited) throw reason;
958
+ }));
959
+ this.control.advance();
960
+ }
961
+ async unwind(cause, mode, ids) {
962
+ const { control, _entries: entries } = this;
963
+ const pending = entries.filter((entry) => ids ? ids.has(entry.id) : !this._spent.has(entry.id));
964
+ const kept = /* @__PURE__ */ new Set();
965
+ const undone = [];
966
+ let halted = mode == "stop";
967
+ for (const entry of pending.reverse()) {
968
+ const on = this._on.get(entry.id);
969
+ const open = !this._finished.has(entry.id);
970
+ if (ids && this._halted) break;
971
+ if (entry.unwound == "failed") break;
972
+ if (!halted && (entry.irreversible || entry.committed)) {
973
+ entry.halted = halted = true;
974
+ if (entry.irreversible) console.warn(`${control._self} stopped unwinding at ${entry.id}: ${entry.irreversible.join(", ")} cannot be restored.`);
975
+ }
976
+ if (!on && "error" in entry) continue;
977
+ try {
978
+ if (open) await this.fire(on, "abort", mode == "error" ? cause : void 0, kept);
979
+ if (!halted && !this._reset.has(entry.id)) await this.fire(on, "reset", void 0, kept);
980
+ this._finished.add(entry.id);
981
+ if (open) await this.fire(on, "close", void 0, kept);
982
+ } catch (thrown) {
983
+ entry.unwound = "failed";
984
+ return {
985
+ undone,
986
+ kept,
987
+ failed: { cause: thrown }
988
+ };
989
+ }
990
+ this._spent.add(entry.id);
991
+ if (halted) continue;
992
+ if (on?.some((handler) => handler.kind == "reset") || this._held.has(entry.id) && entry.wrote.length) entry.unwound = true;
993
+ undone.push(entry);
994
+ }
995
+ return {
996
+ undone,
997
+ kept
998
+ };
999
+ }
1000
+ async compensate(item, cause, kept, bare) {
1001
+ const fn = this._inline.get(item.id);
1002
+ if (!fn) return this.undo([item], kept);
1003
+ const frame = this.frames.add({
1004
+ id: item.id,
1005
+ key: item.key,
1006
+ args: item.args,
1007
+ _undo: true,
1008
+ _owner: this._owner.get(item.id)
1009
+ });
1010
+ try {
1011
+ await (bare ? fn() : fn(cause));
1012
+ } finally {
1013
+ this.frames.delete(frame);
1014
+ for (const key of frame._wrote) kept.add(key);
1015
+ }
1016
+ await this.undo(frame._items, kept);
1017
+ }
1018
+ async undo(items, kept) {
1019
+ for (const item of items) if (item.group) await Promise.all(item.group.map((member) => this.undo([member], kept)));
1020
+ else {
1021
+ const frame = await this.invoke(item, void 0, true);
1022
+ for (const key of frame._wrote) kept.add(key);
1023
+ await this.undo(frame._items, kept);
1024
+ }
1025
+ }
1026
+ refill({ undone, kept }, into, all, within) {
1027
+ const self = this.control._self;
1028
+ const prior = /* @__PURE__ */ new Map();
1029
+ const log = [];
1030
+ for (const entry of undone) {
1031
+ const held = this._held.get(entry.id);
1032
+ if (!held) continue;
1033
+ for (const key in held.prior) prior.set(key, held.prior[key]);
1034
+ log.push(...[...held.log].reverse());
1035
+ }
1036
+ const since = (key) => !!within && !this.line(this._writer.get(key)).includes(within);
1037
+ for (const [key, value] of prior) {
1038
+ const wrote = into?._wrote.has(key);
1039
+ if (wrote) into._prior[key] = value;
1040
+ if (all || !wrote && !kept.has(key) && !since(key)) self[key] = value;
1041
+ }
1042
+ for (const [key, undo] of log) if (all || !into?._changed.has(key) && !since(key)) undo();
1043
+ }
1044
+ blame(cause) {
1045
+ this._error = cause;
1046
+ this.control.error = cause;
1047
+ }
1048
+ restore(prior, log) {
1049
+ const self = this.control._self;
1050
+ for (const key in prior) self[key] = prior[key];
1051
+ for (const [, undo] of [...log].reverse()) undo();
1052
+ }
1053
+ };
1054
+ var Frame = class extends Model {
1055
+ run = get(Run);
1056
+ id = "";
1057
+ key = "";
1058
+ args = [];
1059
+ _proxy;
1060
+ _owner = void 0;
1061
+ _view = void 0;
1062
+ _refs = /* @__PURE__ */ new Map();
1063
+ _abort = new AbortController();
1064
+ _items = [];
1065
+ _wrote = /* @__PURE__ */ new Set();
1066
+ _prior = {};
1067
+ _wrappers = /* @__PURE__ */ new Map();
1068
+ _log = [];
1069
+ _on = [];
1070
+ _changed = /* @__PURE__ */ new Set();
1071
+ _lost = /* @__PURE__ */ new Set();
1072
+ _wake = void 0;
1073
+ _undo = void 0;
1074
+ _retried = 0;
1075
+ _restore = void 0;
1076
+ _caught = void 0;
1077
+ _failed = void 0;
1078
+ _committed = void 0;
1079
+ _entry = void 0;
1080
+ _fixed = void 0;
1081
+ _violation = void 0;
1082
+ _late = void 0;
1083
+ _armed = 0;
1084
+ _settled = void 0;
1085
+ _point = void 0;
1086
+ new() {
1087
+ const { _self: self, _steps, _runner } = this.run.control;
1088
+ this._proxy = new Proxy(self, {
1089
+ get: (target, prop) => {
1090
+ const live = this._settled && this.live();
1091
+ if (live) return Reflect.get(live._proxy, prop);
1092
+ if (_steps.has(prop)) return this.ref(prop);
1093
+ const value = Reflect.get(target, prop, target);
1094
+ if (value === _runner) return this.view();
1095
+ return !this._undo && !this._settled && collection(value) ? this.wrap(prop, value) : value;
1096
+ },
1097
+ set: (target, prop, value) => {
1098
+ const key = String(prop);
1099
+ if (this._settled) {
1100
+ const live = this.live();
1101
+ return live ? Reflect.set(live._proxy, prop, value) : Reflect.set(target, prop, value, target);
1102
+ }
1103
+ if (!this._wrote.has(key)) {
1104
+ this._prior[key] = Reflect.get(target, prop, target);
1105
+ this._wrote.add(key);
1106
+ }
1107
+ if (this._items.length) this._violation ||= key;
1108
+ this.run._writer.set(key, this.id);
1109
+ return Reflect.set(target, prop, value, target);
1110
+ }
1111
+ });
1112
+ return () => this._abort.abort();
1113
+ }
1114
+ live() {
1115
+ const { run } = this;
1116
+ if (run.waits.any((wait) => wait._from === this)) return;
1117
+ const live = [...run.frames].filter((frame) => frame.within(this));
1118
+ return live.length == 1 ? live[0] : void 0;
1119
+ }
1120
+ within(owner) {
1121
+ for (let at = this._owner; at; at = at._owner) if (at === owner) return true;
1122
+ return false;
1123
+ }
1124
+ ref(key) {
1125
+ let ref = this._refs.get(key);
1126
+ if (!ref) this._refs.set(key, ref = (...args) => this._settled ? this.answer(key, args) : this.issue(key, args));
1127
+ return ref;
1128
+ }
1129
+ keyOf(fn) {
1130
+ for (const [key, ref] of this._refs) if (ref === fn) return key;
1131
+ }
1132
+ issue(key, args) {
1133
+ const { run } = this;
1134
+ if (this._undo && UNPLANNED.includes(key)) throw rule(`${named(key)} cannot run in an undo.`);
1135
+ if (this._settled) throw rule(`${this.key} has settled; ${key} cannot run.`);
1136
+ if (key == "run") throw rule("run is the entry - loop with a step: a step which schedules itself again.");
1137
+ const token = run.node(key, args);
1138
+ ISSUED.set(token, this);
1139
+ this._items.push(token);
1140
+ this.arm();
1141
+ return token;
1142
+ }
1143
+ answer(key, args) {
1144
+ const { run } = this;
1145
+ const wait = run.control._current === run && run.route(key, args, this);
1146
+ if (wait) return run.wake(wait, key, args);
1147
+ throw rule(`${this.key} has settled; ${key} cannot run.`);
1148
+ }
1149
+ async pause(pending) {
1150
+ const { signal } = this._abort;
1151
+ const abort = new Promise((resolve) => signal.addEventListener("abort", resolve, { once: true }));
1152
+ let failed;
1153
+ if (!signal.aborted) await Promise.race([pending, abort]).catch((cause) => failed = { cause });
1154
+ if (signal.aborted) throw signal.reason;
1155
+ return failed;
1156
+ }
1157
+ open(call) {
1158
+ const { run } = this;
1159
+ const body = Promise.resolve(call);
1160
+ body.catch(() => {});
1161
+ this.arm();
1162
+ return new Promise((resolve, reject) => {
1163
+ this._point = (done, fail) => {
1164
+ if (this._late) {
1165
+ const error = rule("run awaited work before its plan - it takes arguments, calls steps and awaits this.");
1166
+ fail(error);
1167
+ return reject(error);
1168
+ }
1169
+ run._await = {
1170
+ resolve: done,
1171
+ reject: fail
1172
+ };
1173
+ run._tail = body;
1174
+ resolve(void 0);
1175
+ };
1176
+ body.then((value) => {
1177
+ if (run._tail) return;
1178
+ run._result = value;
1179
+ resolve(void 0);
1180
+ }, (error) => {
1181
+ if (!run._tail) reject(error);
1182
+ });
1183
+ });
1184
+ }
1185
+ arm() {
1186
+ const armed = ++this._armed;
1187
+ let hops = SLACK;
1188
+ const tick = () => {
1189
+ if (this._settled || this._armed != armed) return;
1190
+ if (--hops) queueMicrotask(tick);
1191
+ else this._late = true;
1192
+ };
1193
+ queueMicrotask(tick);
1194
+ }
1195
+ view() {
1196
+ if (this._view) return this._view;
1197
+ const retry = () => this.retry();
1198
+ RETRY.add(retry);
1199
+ const view = (...args) => {
1200
+ const [items, each] = args;
1201
+ const fns = args.filter((arg) => typeof arg == "function");
1202
+ const refs = fns.map((fn) => this.keyOf(fn)).filter(Boolean);
1203
+ const tokens = args.filter((arg) => arg && typeof arg == "object");
1204
+ let timed = args.some(plain);
1205
+ let mapped = args.length == 2 && iterable(items) && typeof each == "function";
1206
+ let grouped = args.length && tokens.length == args.length;
1207
+ let waits = refs.length && refs.length == fns.length && refs.length + tokens.length == args.length;
1208
+ if (timed && args.length > 1) throw rule("then() takes a duration alone - this.then({ sec: 10 }).");
1209
+ if (timed) return this.delay(items);
1210
+ if (mapped) return this.group(Array.from(items, each));
1211
+ if (grouped) return this.group(args);
1212
+ if (waits) return this.wait(refs, tokens);
1213
+ if (refs.length) throw rule("then() takes steps uncalled to answer and called ones to race - not other functions.");
1214
+ if (fns.length == 2 && args.length == 2 && this._point && !this._settled) return this._point(fns[0], fns[1]);
1215
+ if (fns.length == 1) throw rule("then() takes steps, not a function - declare the work as a step.");
1216
+ throw rule("a step cannot await its own run.");
1217
+ };
1218
+ Object.setPrototypeOf(view, this.run.control._runner);
1219
+ Object.defineProperties(view, {
1220
+ then: { value: view },
1221
+ end: { value: (...value) => (valueless(value), this.issue("end", [])) },
1222
+ signal: { get: () => this._abort.signal },
1223
+ retried: { get: () => this._retried },
1224
+ retry: { value: retry },
1225
+ only: { value: (max, key) => this.slot(max, key) },
1226
+ on: { value: (handlers) => this.handle(handlers) },
1227
+ restore: { value: () => {
1228
+ if (!this._restore) throw rule("restore() must be called in a .catch handler.");
1229
+ this._restore(this, true);
1230
+ } },
1231
+ commit: { value: () => {
1232
+ if (this._settled) throw rule(`${this.key} has settled; commit cannot run.`);
1233
+ this._committed = true;
1234
+ } }
1235
+ });
1236
+ return this._view = view;
1237
+ }
1238
+ handle(handlers) {
1239
+ const { run } = this;
1240
+ const usage = "then.on() takes steps called for it - { reset: this.release(id) } - or a function.";
1241
+ if (this.id == run._root) throw rule("run cleans up in its tail - try/catch around await this.");
1242
+ if (this._undo) throw rule("then.on() cannot run in a handler step - use try/finally inside it.");
1243
+ if (this._settled) throw rule(`${this.key} has settled; on cannot run.`);
1244
+ if (!plain(handlers)) throw rule(usage);
1245
+ for (const [kind, value] of Object.entries(handlers)) {
1246
+ if (!KINDS.includes(kind)) throw rule(`then.on() takes reset, catch, abort and close - got ${kind}.`);
1247
+ if (value === void 0) continue;
1248
+ if (kind == "catch" && this._on.some((handler) => handler.kind == "catch")) throw rule("then.on() takes one catch per step.");
1249
+ const step = typeof value == "function" && this.keyOf(value);
1250
+ let node;
1251
+ if (step) node = run.node(step, []);
1252
+ else if (typeof value == "function") {
1253
+ node = run.node("then", []);
1254
+ run._inline.set(node.id, value);
1255
+ run._owner.set(node.id, this);
1256
+ } else [node] = this.claimed(Array.isArray(value) ? value : [value], usage, "then.on()");
1257
+ this._on.push({
1258
+ kind,
1259
+ node,
1260
+ bare: !!step
1261
+ });
1262
+ }
1263
+ }
1264
+ claimed(args, usage, verb = "then()") {
1265
+ const { _steps } = this.run.control;
1266
+ const fns = args.filter((arg) => typeof arg == "function");
1267
+ const steps = args.every((arg) => {
1268
+ const node = arg;
1269
+ return this._items.includes(node) && !UNPLANNED.includes(node.key) && (node.group || _steps.has(node.key));
1270
+ });
1271
+ if (fns.length && fns.length < args.length) throw rule(`${verb} takes called steps or step references, not both.`);
1272
+ if (!args.length || !steps) throw rule(usage);
1273
+ return this.claim([args.length > 1 ? this.group(args) : args[0]]);
1274
+ }
1275
+ claim(nodes) {
1276
+ this._items = this._items.filter((item) => !nodes.includes(item));
1277
+ if (this._items.length) this.arm();
1278
+ else {
1279
+ this._armed++;
1280
+ this._late = void 0;
1281
+ }
1282
+ return nodes;
1283
+ }
1284
+ rescue(token, handler) {
1285
+ const key = this.keyOf(handler);
1286
+ if (UNCAUGHT.includes(token.key)) throw rule(`${named(token.key)} cannot be caught.`);
1287
+ if (handler && typeof handler != "function") throw rule("catch() takes a step, a function or nothing.");
1288
+ if (RETRY.has(handler)) throw rule("catch() retries from a handler: .catch(this.rescan), rescan(error) { if (this.then.retried >= 3) throw error; this.then.retry(); }");
1289
+ token.rescue = handler ? key || "then" : true;
1290
+ this.run._catches.set(token.id, {
1291
+ key,
1292
+ fn: handler
1293
+ });
1294
+ return token;
1295
+ }
1296
+ retry() {
1297
+ const { run, _caught: caught } = this;
1298
+ if (!caught) throw rule("retry() must be called in a .catch handler.");
1299
+ if (this._settled) throw rule(`${this.key} has settled; retry cannot run.`);
1300
+ this._items.push(run.copy(caught));
1301
+ this.arm();
1302
+ }
1303
+ slot(max, key) {
1304
+ if (typeof max != "number" && typeof max != "function") throw rule("only() takes a count, or a function returning one.");
1305
+ const token = this.issue("gate", []);
1306
+ this.run._slots.set(token.id, {
1307
+ max,
1308
+ key
1309
+ });
1310
+ }
1311
+ delay(duration) {
1312
+ let ms = 0;
1313
+ if (!Object.keys(duration).length) throw rule("then() takes a duration of ms, sec, min, hr and day as numbers - got none.");
1314
+ for (const [unit, value] of Object.entries(duration)) if (!Object.hasOwn(UNITS, unit) || typeof value != "number" || !(value >= 0)) throw rule(`then() takes a duration of ms, sec, min, hr and day as numbers - got ${unit}.`);
1315
+ else ms += value * UNITS[unit];
1316
+ return Object.assign(this.issue("wait", [ms]), { answers: [] });
1317
+ }
1318
+ group(members) {
1319
+ for (const member of members) if (!this._items.includes(member)) throw rule(`then() takes steps called by ${this.key}.`);
1320
+ const group = Object.assign(this.run.node("then", []), { group: members });
1321
+ ISSUED.set(group, this);
1322
+ this.run._groups.add(group.id);
1323
+ this._items = this._items.filter((item) => !members.includes(item));
1324
+ this._items.push(group);
1325
+ this.arm();
1326
+ return group;
1327
+ }
1328
+ wait(answers, racers) {
1329
+ for (const key of answers) if (key == "run") throw rule("run cannot answer a wait.");
1330
+ const [race] = racers.length ? this.claimed(racers, "then() races steps called for it - this.then(this.allow, this.timeout(call)).") : [];
1331
+ const token = Object.assign(this.issue("wait", []), { answers }, race && { race });
1332
+ if (race) adopt(this.run._parent, [race], token.id);
1333
+ return token;
1334
+ }
1335
+ commit(out) {
1336
+ const { run, id, key, _items: items, _violation, _late, _wake, _lost, _on } = this;
1337
+ if (_violation) throw rule(`${key} wrote ${_violation} after scheduling ${items[0].key}.`);
1338
+ if (_late && !items.length) throw rule("run awaited work before its plan - it takes arguments, calls steps and awaits this.");
1339
+ if (_late) throw rule(`${key} kept working after scheduling ${items[0].key}.`);
1340
+ const root = id == run._root;
1341
+ if (!root && out !== void 0) throw rule(`${key} returned a value; a step returns nothing - its undo is this.then.on({ reset }).`);
1342
+ const end = items.findIndex((item) => item.key == "end");
1343
+ if (end >= 0 && end < items.length - 1) throw rule("end must be the last call in a step.");
1344
+ adopt(run._parent, items, id);
1345
+ const entry = {
1346
+ ...this.journal(),
1347
+ next: items
1348
+ };
1349
+ if (_wake) entry.wake = _wake;
1350
+ if (this._retried) entry.retried = this._retried;
1351
+ if (_on.length) {
1352
+ entry.on = grouped(_on);
1353
+ run._on.set(id, _on);
1354
+ }
1355
+ if (_lost.size && !_on.some((handler) => handler.kind == "reset")) entry.irreversible = [..._lost];
1356
+ if (!root) run._held.set(id, {
1357
+ prior: this._prior,
1358
+ log: this._log
1359
+ });
1360
+ if (this._undo) run._spent.add(id);
1361
+ run._entries.push(entry);
1362
+ this._entry = entry;
1363
+ return entry;
1364
+ }
1365
+ journal() {
1366
+ const { id, key, args, _wrote, _changed, _committed } = this;
1367
+ const entry = {
1368
+ id,
1369
+ key,
1370
+ args,
1371
+ next: [],
1372
+ wrote: [...new Set([..._wrote, ..._changed])]
1373
+ };
1374
+ if (_committed) entry.committed = true;
1375
+ return entry;
1376
+ }
1377
+ wrap(key, value) {
1378
+ let wrapper = this._wrappers.get(value);
1379
+ if (wrapper) return wrapper;
1380
+ const ops = mutators(value);
1381
+ wrapper = new Proxy(value, { get: (target, prop) => {
1382
+ const op = ops[prop];
1383
+ if (op) return (...args) => {
1384
+ const out = this.change(key, () => op(...args));
1385
+ return out === target ? wrapper : out;
1386
+ };
1387
+ const got = Reflect.get(target, prop, target);
1388
+ return typeof got == "function" ? got.bind(target) : got;
1389
+ } });
1390
+ this._wrappers.set(value, wrapper);
1391
+ return wrapper;
1392
+ }
1393
+ change(key, op) {
1394
+ const at = this._settled ? this.live() : this;
1395
+ if (!at) return op()[0];
1396
+ const [out, changed, undo, lost] = op();
1397
+ if (!changed) return out;
1398
+ if (at._items.length) at._violation ||= key;
1399
+ if (undo) at._log.push([key, undo]);
1400
+ if (lost) at._lost.add(key);
1401
+ at._changed.add(key);
1402
+ at.run._writer.set(key, at.id);
1403
+ return out;
1404
+ }
1405
+ };
1406
+ var Wait = class extends Model {
1407
+ run = get(Run);
1408
+ id = "";
1409
+ step = "";
1410
+ args = [];
1411
+ answers = [];
1412
+ until = void 0;
1413
+ _from;
1414
+ _park;
1415
+ _answer = void 0;
1416
+ _race = void 0;
1417
+ _open = true;
1418
+ new() {
1419
+ const { run, answers, until } = this;
1420
+ const { _self: self } = run.control;
1421
+ let timer;
1422
+ const arm = () => {
1423
+ const left = until - Date.now();
1424
+ timer = setTimeout(left > LONGEST ? arm : () => run.waits.delete(this), Math.min(left, LONGEST));
1425
+ };
1426
+ if (until !== void 0) arm();
1427
+ for (const key of answers) if (!run.accepts(key, this)) event(self, key);
1428
+ return () => {
1429
+ this._open = false;
1430
+ clearTimeout(timer);
1431
+ for (const key of answers) if (!run.accepts(key)) event(self, key);
1432
+ this._park(this._answer);
1433
+ };
1434
+ }
1435
+ };
1436
+ function chain(error, cause) {
1437
+ if (error instanceof Error && error.cause === void 0) error.cause = cause;
1438
+ }
1439
+ function grouped(on) {
1440
+ const out = {};
1441
+ for (const { kind, node } of on) (out[kind] ||= []).push(node);
1442
+ return out;
1443
+ }
1444
+ function serial(error) {
1445
+ if (!(error instanceof Error)) return error;
1446
+ const { name, message, cause } = error;
1447
+ return {
1448
+ name,
1449
+ message,
1450
+ ...cause !== void 0 && { cause }
1451
+ };
1452
+ }
1453
+ function rule(message) {
1454
+ const error = new Error(message);
1455
+ RULES.add(error);
1456
+ return error;
1457
+ }
1458
+ function named(key) {
1459
+ return key == "gate" ? "a gate" : `${key}()`;
1460
+ }
1461
+ function plain(value) {
1462
+ if (!value || typeof value != "object") return false;
1463
+ const proto = Object.getPrototypeOf(value);
1464
+ return proto === Object.prototype || proto === null;
1465
+ }
1466
+ function signal(key) {
1467
+ return typeof key != "boolean" && key !== null;
1468
+ }
1469
+ function iterable(value) {
1470
+ return value != null && typeof value[Symbol.iterator] == "function";
1471
+ }
1472
+ function valueless(args) {
1473
+ if (args.length) throw rule("end() takes no value; the result is what run returns.");
1474
+ }
1475
+ function outside(key, verb = "called") {
1476
+ throw rule(`${key} must be ${verb} inside a step.`);
1477
+ }
1478
+ var Handled = class extends Promise {
1479
+ static get [Symbol.species]() {
1480
+ return Promise;
1481
+ }
1482
+ then(...args) {
1483
+ this._ticket.awaited = true;
1484
+ return super.then(...args);
1485
+ }
1486
+ };
1487
+ function handle(ticket, executor) {
1488
+ const handled = new Handled(executor);
1489
+ handled._ticket = ticket;
1490
+ Promise.prototype.then.call(handled, void 0, () => {});
1491
+ return handled;
1492
+ }
1493
+ function gate(gates, key) {
1494
+ let gate = gates.get(key);
1495
+ if (!gate) gates.set(key, gate = {
1496
+ held: /* @__PURE__ */ new Set(),
1497
+ queue: []
1498
+ });
1499
+ return gate;
1500
+ }
1501
+ function pump(gate) {
1502
+ for (let next = gate.queue[0]; next && gate.held.size < next.max(); next = gate.queue[0]) {
1503
+ gate.queue.shift();
1504
+ gate.held.add(next.hold);
1505
+ next.grant();
1506
+ }
1507
+ }
1508
+ function adopt(parents, items, parent) {
1509
+ for (const item of items) {
1510
+ parents.set(item.id, parent);
1511
+ if (item.group) adopt(parents, item.group, item.id);
1512
+ }
1513
+ }
1514
+ function collection(value) {
1515
+ return value instanceof has.List || value instanceof has.Pool || value instanceof map.Managed;
1516
+ }
1517
+ function dead(value) {
1518
+ return value instanceof Model && value.get(null);
1519
+ }
1520
+ function mutators(value) {
1521
+ if (value instanceof has.List) return listOps(value);
1522
+ if (value instanceof has.Pool) return poolOps(value);
1523
+ return mapOps(value);
1524
+ }
1525
+ function listOps(list) {
1526
+ const at = (index, size) => index < 0 ? Math.max(size + index, 0) : Math.min(index, size);
1527
+ const unput = (start, values) => () => {
1528
+ for (let i = values.length - 1; i >= 0; i--) {
1529
+ const found = near(list, values[i], start + i);
1530
+ if (found >= 0) list.pop(found);
1531
+ }
1532
+ };
1533
+ return {
1534
+ push: (...values) => {
1535
+ const start = list.size;
1536
+ return [
1537
+ list.push(...values),
1538
+ values.length > 0,
1539
+ unput(start, values)
1540
+ ];
1541
+ },
1542
+ put: (index, ...values) => {
1543
+ const start = at(index, list.size);
1544
+ list.put(index, ...values);
1545
+ return [
1546
+ void 0,
1547
+ values.length > 0,
1548
+ unput(start, values)
1549
+ ];
1550
+ },
1551
+ pop: (index = -1, count) => {
1552
+ const before = list.get(0, list.size);
1553
+ const start = at(index, before.length);
1554
+ const removed = before.slice(start, start + (count ?? 1));
1555
+ return [
1556
+ count === void 0 ? list.pop(index) : list.pop(index, count),
1557
+ removed.length > 0,
1558
+ () => list.put(start, ...removed)
1559
+ ];
1560
+ },
1561
+ set: (index, value) => {
1562
+ const before = list.get(0, list.size);
1563
+ const start = index < 0 ? before.length + index : index;
1564
+ const prior = before[start];
1565
+ list.set(index, value);
1566
+ const restore = () => {
1567
+ const found = near(list, value, start);
1568
+ if (found >= 0) list.set(found, prior);
1569
+ };
1570
+ return [
1571
+ void 0,
1572
+ start >= 0 && start < before.length && prior !== value,
1573
+ restore
1574
+ ];
1575
+ },
1576
+ clear: () => {
1577
+ const removed = list.get(0, list.size);
1578
+ list.clear();
1579
+ return [
1580
+ void 0,
1581
+ removed.length > 0,
1582
+ () => list.put(0, ...removed)
1583
+ ];
1584
+ }
1585
+ };
1586
+ }
1587
+ function poolOps(pool) {
1588
+ const added = /* @__PURE__ */ new Set();
1589
+ const lost = (members) => members.filter((member) => !added.delete(member)).length > 0;
1590
+ return {
1591
+ add: (...args) => {
1592
+ const size = pool.size;
1593
+ const made = pool.add(...args);
1594
+ const fresh = pool.size > size;
1595
+ if (fresh) added.add(made);
1596
+ return [
1597
+ made,
1598
+ fresh,
1599
+ () => pool.delete(made)
1600
+ ];
1601
+ },
1602
+ delete: (member) => {
1603
+ const had = pool.delete(member);
1604
+ return [
1605
+ had,
1606
+ had,
1607
+ void 0,
1608
+ had && lost([member])
1609
+ ];
1610
+ },
1611
+ clear: () => {
1612
+ const members = [...pool];
1613
+ pool.clear();
1614
+ return [
1615
+ void 0,
1616
+ members.length > 0,
1617
+ void 0,
1618
+ lost(members)
1619
+ ];
1620
+ }
1621
+ };
1622
+ }
1623
+ function mapOps(map) {
1624
+ const added = /* @__PURE__ */ new Set();
1625
+ const removed = (entries) => {
1626
+ const kept = entries.filter(([, value]) => !dead(value));
1627
+ const lost = entries.some(([key, value]) => dead(value) && !added.has(key));
1628
+ const restore = () => {
1629
+ for (const [key, value] of kept) if (!map.has(key)) map.set(key, value);
1630
+ };
1631
+ return [
1632
+ void 0,
1633
+ entries.length > 0,
1634
+ restore,
1635
+ lost
1636
+ ];
1637
+ };
1638
+ return {
1639
+ set: (key, ...rest) => {
1640
+ const had = map.has(key);
1641
+ const prior = map.get(key);
1642
+ const own = !had || added.has(key);
1643
+ map.set(key, ...rest);
1644
+ const value = map.get(key);
1645
+ const gone = had && dead(prior);
1646
+ const restore = () => {
1647
+ if (map.get(key) !== value) return;
1648
+ if (had && !gone) map.set(key, prior);
1649
+ else if (own) map.delete(key);
1650
+ };
1651
+ added.add(key);
1652
+ return [
1653
+ map,
1654
+ !had || value !== prior,
1655
+ restore,
1656
+ gone && !own
1657
+ ];
1658
+ },
1659
+ delete: (key) => {
1660
+ const entry = [key, map.get(key)];
1661
+ const had = map.delete(key);
1662
+ const change = removed(had ? [entry] : []);
1663
+ change[0] = had;
1664
+ return change;
1665
+ },
1666
+ clear: () => {
1667
+ const entries = [...map.entries()];
1668
+ map.clear();
1669
+ return removed(entries);
1670
+ }
1671
+ };
1672
+ }
1673
+ function near(list, value, at) {
1674
+ let found = -1;
1675
+ list.get(0, list.size).forEach((item, index) => {
1676
+ if (item === value && (found < 0 || Math.abs(index - at) < Math.abs(found - at))) found = index;
1677
+ });
1678
+ return found;
1679
+ }
1680
+
1681
+ //#endregion
1682
+ export { Steps };
1683
+ //# sourceMappingURL=index.js.map