@agoric/async-flow 0.1.1-dev-16095c5.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 (52) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +201 -0
  3. package/README.md +40 -0
  4. package/docs/async-flow-states.key +0 -0
  5. package/docs/async-flow-states.md +15 -0
  6. package/docs/async-flow-states.png +0 -0
  7. package/index.d.ts +2 -0
  8. package/index.d.ts.map +1 -0
  9. package/index.js +1 -0
  10. package/package.json +67 -0
  11. package/src/async-flow.d.ts +87 -0
  12. package/src/async-flow.d.ts.map +1 -0
  13. package/src/async-flow.js +502 -0
  14. package/src/bijection.d.ts +28 -0
  15. package/src/bijection.d.ts.map +1 -0
  16. package/src/bijection.js +132 -0
  17. package/src/convert.d.ts +5 -0
  18. package/src/convert.d.ts.map +1 -0
  19. package/src/convert.js +131 -0
  20. package/src/ephemera.d.ts +2 -0
  21. package/src/ephemera.d.ts.map +1 -0
  22. package/src/ephemera.js +35 -0
  23. package/src/equate.d.ts +2 -0
  24. package/src/equate.d.ts.map +1 -0
  25. package/src/equate.js +123 -0
  26. package/src/log-store.d.ts +25 -0
  27. package/src/log-store.d.ts.map +1 -0
  28. package/src/log-store.js +165 -0
  29. package/src/replay-membrane.d.ts +71 -0
  30. package/src/replay-membrane.d.ts.map +1 -0
  31. package/src/replay-membrane.js +435 -0
  32. package/src/type-guards.d.ts +4 -0
  33. package/src/type-guards.d.ts.map +1 -0
  34. package/src/type-guards.js +54 -0
  35. package/src/types.d.ts +70 -0
  36. package/src/types.d.ts.map +1 -0
  37. package/src/types.js +164 -0
  38. package/test/async-flow-crank.test.js +96 -0
  39. package/test/async-flow-no-this.js +59 -0
  40. package/test/async-flow.test.js +380 -0
  41. package/test/bad-host.test.js +205 -0
  42. package/test/bijection.test.js +118 -0
  43. package/test/convert.test.js +127 -0
  44. package/test/equate.test.js +116 -0
  45. package/test/log-store.test.js +112 -0
  46. package/test/prepare-test-env-ava.js +28 -0
  47. package/test/replay-membrane-settlement.test.js +154 -0
  48. package/test/replay-membrane-zombie.test.js +158 -0
  49. package/test/replay-membrane.test.js +271 -0
  50. package/tsconfig.build.json +11 -0
  51. package/tsconfig.json +13 -0
  52. package/typedoc.json +8 -0
package/src/types.js ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @import {PromiseKit} from '@endo/promise-kit'
3
+ * @import {Passable} from '@endo/pass-style'
4
+ * @import {Zone} from '@agoric/base-zone'
5
+ * @import {Vow, VowTools} from '@agoric/vow'
6
+ * @import {LogStore} from './log-store.js'
7
+ * @import {Bijection} from './bijection.js'
8
+ * @import {ReplayMembrane} from './replay-membrane.js'
9
+ */
10
+
11
+ /**
12
+ * @typedef {'Running' |
13
+ * 'Sleeping' |
14
+ * 'Replaying' |
15
+ * 'Failed' |
16
+ * 'Done'
17
+ * } FlowState
18
+ */
19
+
20
+ /**
21
+ * @template {Passable} [T=Passable]
22
+ * @typedef {T} Guest
23
+ */
24
+
25
+ /**
26
+ * @template {Passable} [T=Passable]
27
+ * @typedef {T} Host
28
+ */
29
+
30
+ /**
31
+ * A HostVow must be durably storable. It corresponds to an
32
+ * ephemeral guest promise.
33
+ *
34
+ * @template {Passable} [T=Passable]
35
+ * @typedef {Host<Vow<T>>} HostVow
36
+ */
37
+
38
+ /**
39
+ * @typedef {(...activationArgs: Guest[]) => Guest<Promise>} GuestAsyncFunc
40
+ */
41
+
42
+ /**
43
+ * @typedef {(...activationArgs: Host[]) => HostVow} HostAsyncFuncWrapper
44
+ */
45
+
46
+ /**
47
+ * @typedef {object} PreparationOptions
48
+ * @property {VowTools} [vowTools]
49
+ * @property {() => LogStore} [makeLogStore]
50
+ * @property {() => Bijection} [makeBijection]
51
+ */
52
+
53
+ /**
54
+ * @typedef {'return'|'throw'} OutcomeKind
55
+ */
56
+
57
+ /**
58
+ * @typedef {{kind: 'return', result: any}
59
+ * | {kind: 'throw', problem: any}
60
+ * } Outcome
61
+ */
62
+
63
+ /**
64
+ * @template {WeakKey} [S=WeakKey]
65
+ * @template {any} [V=any]
66
+ * @typedef {object} Ephemera
67
+ * @property {(self: S) => V} for
68
+ * @property {(self: S) => void} resetFor
69
+ */
70
+
71
+ /**
72
+ * This is the typedef for the membrane log entries we currently implement.
73
+ * See comment below for the commented-out typedef for the full
74
+ * membrane log entry, which we do not yet support.
75
+ *
76
+ * @typedef {[ // ///////////////// From Host to Guest /////////////////////////
77
+ * op: 'doFulfill',
78
+ * vow: HostVow,
79
+ * fulfillment: Host,
80
+ * ] | [
81
+ * op: 'doReject',
82
+ * vow: HostVow,
83
+ * reason: Host,
84
+ * ] | [
85
+ * op: 'doReturn',
86
+ * callIndex: number,
87
+ * result: Host,
88
+ * ] | [
89
+ * op: 'doThrow',
90
+ * callIndex: number,
91
+ * problem: Host,
92
+ * ] | [ // ///////////////////// From Guest to Host /////////////////////////
93
+ * op: 'checkCall',
94
+ * target: Host,
95
+ * optVerb: PropertyKey|undefined,
96
+ * args: Host[],
97
+ * callIndex: number
98
+ * ]} LogEntry
99
+ */
100
+
101
+ /**
102
+ * This would be the typedef for the full membrane log, if we supported
103
+ * - the guest sending guest-promises and guest-remotables to the host
104
+ * - the guest using `E` to eventual-send to guest wrappers of host
105
+ * vows and remotables.
106
+ *
107
+ * at-typedef {[ // ///////////////// From Host to Guest ///////////////////////
108
+ * op: 'doFulfill',
109
+ * vow: HostVow,
110
+ * fulfillment: Host,
111
+ * ] | [
112
+ * op: 'doReject',
113
+ * vow: HostVow,
114
+ * reason: Host,
115
+ * ] | [
116
+ * op: 'doCall',
117
+ * target: Host,
118
+ * optVerb: PropertyKey|undefined,
119
+ * args: Host[],
120
+ * callIndex: number
121
+ * ] | [
122
+ * op: 'doSend',
123
+ * target: Host,
124
+ * optVerb: PropertyKey|undefined,
125
+ * args: Host[],
126
+ * callIndex: number
127
+ * ] | [
128
+ * op: 'doReturn',
129
+ * callIndex: number,
130
+ * result: Host,
131
+ * ] | [
132
+ * op: 'doThrow',
133
+ * callIndex: number,
134
+ * problem: Host,
135
+ * ] | [ // ///////////////////// From Guest to Host /////////////////////////
136
+ * op: 'checkFulfill',
137
+ * vow: HostVow,
138
+ * fulfillment: Host,
139
+ * ] | [
140
+ * op: 'checkReject',
141
+ * vow: HostVow,
142
+ * reason: Host,
143
+ * ] | [
144
+ * op: 'checkCall',
145
+ * target: Host,
146
+ * optVerb: PropertyKey|undefined,
147
+ * args: Host[],
148
+ * callIndex: number
149
+ * ] | [
150
+ * op: 'checkSend',
151
+ * target: Host,
152
+ * optVerb: PropertyKey|undefined,
153
+ * args: Host[],
154
+ * callIndex: number
155
+ * ] | [
156
+ * op: 'checkReturn',
157
+ * callIndex: number,
158
+ * result: Host,
159
+ * ] | [
160
+ * op: 'checkThrow',
161
+ * callIndex: number,
162
+ * problem: Host,
163
+ * ]} LogEntry
164
+ */
@@ -0,0 +1,96 @@
1
+ // The purpose of this test file is to demonstrate
2
+ // https://github.com/Agoric/agoric-sdk/issues/9377
3
+ // as the `test.serial.failing` test at the end.
4
+
5
+ // eslint-disable-next-line import/order
6
+ import {
7
+ test,
8
+ getBaggage,
9
+ annihilate,
10
+ nextLife,
11
+ } from './prepare-test-env-ava.js';
12
+
13
+ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
14
+ import { prepareVowTools } from '@agoric/vow';
15
+ import { makeDurableZone } from '@agoric/zone/durable.js';
16
+
17
+ import { prepareAsyncFlowTools } from '../src/async-flow.js';
18
+
19
+ const neverSettlesP = new Promise(() => {});
20
+
21
+ /**
22
+ * @param {any} t
23
+ * @param {Zone} zone
24
+ */
25
+ const testPlay1 = async (t, zone) => {
26
+ const vowTools = prepareVowTools(zone);
27
+ const { asyncFlow } = prepareAsyncFlowTools(zone, {
28
+ vowTools,
29
+ });
30
+
31
+ const guestFunc = async () => neverSettlesP;
32
+
33
+ const wrapperFunc = asyncFlow(zone, 'guestFunc', guestFunc);
34
+
35
+ wrapperFunc();
36
+ t.pass();
37
+ };
38
+
39
+ /**
40
+ * @param {any} t
41
+ * @param {Zone} zone
42
+ */
43
+ const testPlay2 = async (t, zone) => {
44
+ const vowTools = prepareVowTools(zone);
45
+ const { asyncFlow } = prepareAsyncFlowTools(zone, {
46
+ vowTools,
47
+ });
48
+
49
+ const guestFunc = async () => neverSettlesP;
50
+
51
+ t.notThrows(() => asyncFlow(zone, 'guestFunc', guestFunc));
52
+ };
53
+
54
+ /**
55
+ * @param {any} t
56
+ * @param {Zone} zone
57
+ */
58
+ const testPlay3 = async (t, zone) => {
59
+ const vowTools = prepareVowTools(zone);
60
+ const { asyncFlow, allWokenP } = prepareAsyncFlowTools(zone, {
61
+ vowTools,
62
+ });
63
+
64
+ await null;
65
+
66
+ const guestFunc = async () => neverSettlesP;
67
+ t.notThrows(() => asyncFlow(zone, 'guestFunc', guestFunc));
68
+ t.notThrowsAsync(
69
+ () => allWokenP,
70
+ 'will actually throw due to crank bug #9377',
71
+ );
72
+ };
73
+
74
+ test.serial('test durable first-crank hazard 1', async t => {
75
+ annihilate();
76
+ const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
77
+ await testPlay1(t, zone1);
78
+
79
+ await eventLoopIteration();
80
+ });
81
+
82
+ test.serial('test durable first-crank hazard 2', async t => {
83
+ nextLife();
84
+ const zone2 = makeDurableZone(getBaggage(), 'durableRoot');
85
+ await testPlay2(t, zone2);
86
+
87
+ await eventLoopIteration();
88
+ });
89
+
90
+ test.serial.failing('test durable first-crank hazard 3', async t => {
91
+ nextLife();
92
+ const zone3 = makeDurableZone(getBaggage(), 'durableRoot');
93
+ await testPlay3(t, zone3);
94
+
95
+ return eventLoopIteration();
96
+ });
@@ -0,0 +1,59 @@
1
+ // eslint-disable-next-line import/order
2
+ import { test, getBaggage, annihilate } from './prepare-test-env-ava.js';
3
+
4
+ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
5
+ import { prepareVowTools } from '@agoric/vow';
6
+ import { makeHeapZone } from '@agoric/zone/heap.js';
7
+ import { makeVirtualZone } from '@agoric/zone/virtual.js';
8
+ import { makeDurableZone } from '@agoric/zone/durable.js';
9
+
10
+ import { prepareAsyncFlowTools } from '../src/async-flow.js';
11
+
12
+ const { apply } = Reflect;
13
+
14
+ /**
15
+ * @param {any} t
16
+ * @param {Zone} zone
17
+ */
18
+ const testPlay = async (t, zone) => {
19
+ const vowTools = prepareVowTools(zone);
20
+ const { asyncFlow } = prepareAsyncFlowTools(zone, {
21
+ vowTools,
22
+ });
23
+
24
+ const { guestFunc } = {
25
+ async guestFunc() {
26
+ t.is(this, undefined);
27
+ },
28
+ };
29
+
30
+ const wrapperFunc = asyncFlow(zone, 'guestFunc', guestFunc);
31
+
32
+ // Demonstrates that even if something is passed as `this` to the wrapperFunc,
33
+ // the guestFunc will run with `this` bound to `undefined`.
34
+ apply(wrapperFunc, 'bogus', []);
35
+ };
36
+
37
+ test.serial('test heap no guest this', async t => {
38
+ annihilate();
39
+ const zone = makeHeapZone('heapRoot');
40
+ await testPlay(t, zone);
41
+
42
+ await eventLoopIteration();
43
+ });
44
+
45
+ test.serial('test virtual no guest this', async t => {
46
+ annihilate();
47
+ const zone = makeVirtualZone('virtualRoot');
48
+ await testPlay(t, zone);
49
+
50
+ await eventLoopIteration();
51
+ });
52
+
53
+ test.serial('test durable no guest this', async t => {
54
+ annihilate();
55
+ const zone = makeDurableZone(getBaggage(), 'durableRoot');
56
+ await testPlay(t, zone);
57
+
58
+ await eventLoopIteration();
59
+ });
@@ -0,0 +1,380 @@
1
+ // eslint-disable-next-line import/order
2
+ import {
3
+ test,
4
+ getBaggage,
5
+ annihilate,
6
+ nextLife,
7
+ } from './prepare-test-env-ava.js';
8
+
9
+ import { Fail } from '@endo/errors';
10
+ import { passStyleOf } from '@endo/pass-style';
11
+ import { makeCopyMap } from '@endo/patterns';
12
+ import { makePromiseKit } from '@endo/promise-kit';
13
+ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
14
+ import { isVow } from '@agoric/vow/src/vow-utils.js';
15
+ import { prepareVowTools } from '@agoric/vow';
16
+ import { makeHeapZone } from '@agoric/zone/heap.js';
17
+ import { makeVirtualZone } from '@agoric/zone/virtual.js';
18
+ import { makeDurableZone } from '@agoric/zone/durable.js';
19
+
20
+ import { prepareAsyncFlowTools } from '../src/async-flow.js';
21
+
22
+ /**
23
+ * @import {AsyncFlow} from '../src/async-flow.js'
24
+ */
25
+
26
+ /**
27
+ * @param {Zone} zone
28
+ * @param {number} [k]
29
+ */
30
+ const prepareOrchestra = (zone, k = 1) =>
31
+ zone.exoClass(
32
+ 'Orchestra',
33
+ undefined,
34
+ (factor, vow, resolver) => ({ factor, vow, resolver }),
35
+ {
36
+ scale(n) {
37
+ const { state } = this;
38
+ return k * state.factor * n;
39
+ },
40
+ vow() {
41
+ const { state } = this;
42
+ return state.vow;
43
+ },
44
+ resolve(x) {
45
+ const { state } = this;
46
+ state.resolver.resolve(x);
47
+ },
48
+ },
49
+ );
50
+
51
+ /** @typedef {ReturnType<ReturnType<prepareOrchestra>>} Orchestra */
52
+
53
+ const firstLogLen = 7;
54
+
55
+ /**
56
+ * @param {any} t
57
+ * @param {Zone} zone
58
+ */
59
+ const testFirstPlay = async (t, zone) => {
60
+ t.log('firstPlay started');
61
+ const vowTools = prepareVowTools(zone);
62
+ const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
63
+ vowTools,
64
+ });
65
+ const makeOrchestra = prepareOrchestra(zone);
66
+ const { makeVowKit } = vowTools;
67
+
68
+ const { vow: v1, resolver: r1 } = zone.makeOnce('v1', () => makeVowKit());
69
+ const { vow: v2, resolver: r2 } = zone.makeOnce('v2', () => makeVowKit());
70
+ const { vow: v3, resolver: _r3 } = zone.makeOnce('v3', () => makeVowKit());
71
+ const hOrch7 = zone.makeOnce('hOrch7', () => makeOrchestra(7, v2, r2));
72
+
73
+ // purposely violate rule that guestMethod is closed.
74
+ const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
75
+
76
+ const { guestMethod } = {
77
+ async guestMethod(gOrch7, g1, _p3) {
78
+ t.log(' firstPlay about to await g1');
79
+ t.is(await g1, 'x');
80
+ const p2 = gOrch7.vow();
81
+ const prod = gOrch7.scale(3);
82
+ t.is(prod, 21);
83
+
84
+ let gErr;
85
+ try {
86
+ gOrch7.scale(9n);
87
+ } catch (e) {
88
+ gErr = e;
89
+ }
90
+ t.is(gErr.name, 'TypeError');
91
+
92
+ resolveStep(true);
93
+ t.log(' firstPlay to hang awaiting p2');
94
+ // awaiting a promise that won't be resolved until next incarnation
95
+ await p2;
96
+ t.fail('must not reach here in first incarnation');
97
+ },
98
+ };
99
+
100
+ const wrapperFunc = asyncFlow(zone, 'AsyncFlow1', guestMethod);
101
+
102
+ const outcomeV = zone.makeOnce('outcomeV', () => wrapperFunc(hOrch7, v1, v3));
103
+
104
+ t.true(isVow(outcomeV));
105
+ r1.resolve('x');
106
+
107
+ const flow = zone.makeOnce('flow', () =>
108
+ adminAsyncFlow.getFlowForOutcomeVow(outcomeV),
109
+ );
110
+ t.is(passStyleOf(flow), 'remotable');
111
+
112
+ await promiseStep;
113
+
114
+ const logDump = flow.dump();
115
+ t.is(logDump.length, firstLogLen);
116
+ t.deepEqual(logDump, [
117
+ ['doFulfill', v1, 'x'],
118
+ ['checkCall', hOrch7, 'vow', [], 1],
119
+ ['doReturn', 1, v2],
120
+ ['checkCall', hOrch7, 'scale', [3], 3],
121
+ ['doReturn', 3, 21],
122
+ ['checkCall', hOrch7, 'scale', [9n], 5],
123
+ [
124
+ 'doThrow',
125
+ 5,
126
+ TypeError('Cannot mix BigInt and other types, use explicit conversions'),
127
+ ],
128
+ ]);
129
+ t.log('firstPlay done');
130
+ };
131
+
132
+ /**
133
+ * @param {any} t
134
+ * @param {Zone} zone
135
+ */
136
+ const testBadReplay = async (t, zone) => {
137
+ t.log('badReplay started');
138
+ const vowTools = prepareVowTools(zone);
139
+ const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
140
+ vowTools,
141
+ });
142
+ prepareOrchestra(zone);
143
+ const { when } = vowTools;
144
+ const hOrch7 = /** @type {Orchestra} */ (
145
+ zone.makeOnce('hOrch7', () => Fail`need hOrch7`)
146
+ );
147
+ // purposely violate rule that guestMethod is closed.
148
+ const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
149
+
150
+ const { guestMethod } = {
151
+ async guestMethod(gOrch7, g1, _p3) {
152
+ t.log(' badReplay about to await g1');
153
+ await g1;
154
+ gOrch7.vow();
155
+ resolveStep(true);
156
+ // This is a replay error
157
+ gOrch7.scale(4);
158
+ t.fail('badReplay must not reach here');
159
+ },
160
+ };
161
+
162
+ // `asyncFlow` can be used simply to re-prepare the guest function
163
+ // by ignoring the returned wrapper function. If the wrapper function is
164
+ // invoked, that would be a *new* activation with a new outcome and
165
+ // flow, and would have nothing to do with the existing one.
166
+ asyncFlow(zone, 'AsyncFlow1', guestMethod);
167
+
168
+ const outcomeV = /** @type {Vow} */ (
169
+ zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
170
+ );
171
+ const flow = /** @type {AsyncFlow} */ (
172
+ zone.makeOnce('flow', () => Fail`need flow`)
173
+ );
174
+ const flow1 = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
175
+ t.is(flow, flow1);
176
+ t.is(passStyleOf(flow), 'remotable');
177
+
178
+ // This unblocks `await p2;` but only after the replay failure is fixed in
179
+ // the next incarnation.
180
+ hOrch7.resolve('y');
181
+ await promiseStep;
182
+ t.is(await when(hOrch7.vow()), 'y');
183
+
184
+ const logDump = flow.dump();
185
+ t.is(logDump.length, firstLogLen);
186
+
187
+ const replayProblem = flow.getOptFatalProblem();
188
+ t.true(replayProblem instanceof Error);
189
+
190
+ t.deepEqual(
191
+ adminAsyncFlow.getFailures(),
192
+ makeCopyMap([[flow, replayProblem]]),
193
+ );
194
+
195
+ t.log(' badReplay failures', flow.getOptFatalProblem());
196
+ t.log('badReplay done');
197
+ };
198
+
199
+ /**
200
+ * @param {any} t
201
+ * @param {Zone} zone
202
+ */
203
+ const testGoodReplay = async (t, zone) => {
204
+ t.log('goodReplay started');
205
+ const vowTools = prepareVowTools(zone);
206
+ const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
207
+ vowTools,
208
+ });
209
+ prepareOrchestra(zone, 2); // Note change in new behavior
210
+ const { when } = vowTools;
211
+ const hOrch7 = /** @type {Orchestra} */ (
212
+ zone.makeOnce('hOrch7', () => Fail`need hOrch7`)
213
+ );
214
+ // purposely violate rule that guestMethod is closed.
215
+ const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
216
+
217
+ const { guestMethod } = {
218
+ async guestMethod(gOrch7, g1, p3) {
219
+ t.log(' goodReplay about to await g1');
220
+ await g1;
221
+ const p2 = gOrch7.vow();
222
+ const prod = gOrch7.scale(3);
223
+ t.is(prod, 21);
224
+
225
+ let gErr;
226
+ try {
227
+ gOrch7.scale(9n);
228
+ } catch (e) {
229
+ gErr = e;
230
+ }
231
+ t.is(gErr.name, 'TypeError');
232
+
233
+ resolveStep(true);
234
+ t.log(' goodReplay about to await p2');
235
+ // awaiting a promise that won't be resolved until this incarnation
236
+ await p2;
237
+ t.log(' goodReplay woke up!');
238
+ const prod2 = gOrch7.scale(3);
239
+ // same question. different answer
240
+ t.is(prod2, 42);
241
+ t.log('about to await p3');
242
+ await p3;
243
+ t.log('p3 settled');
244
+ },
245
+ };
246
+
247
+ // `asyncFlow` can be used simply to re-prepare the guest function
248
+ // by ignoring the returned wrapper function. If the wrapper function is
249
+ // invoked, that would be a *new* activation with a new outcome and
250
+ // flow, and would have nothing to do with the existing one.
251
+ asyncFlow(zone, 'AsyncFlow1', guestMethod);
252
+
253
+ const outcomeV = /** @type {Vow} */ (
254
+ zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
255
+ );
256
+ const flow = /** @type {AsyncFlow} */ (
257
+ zone.makeOnce('flow', () => Fail`need flow`)
258
+ );
259
+ const flow1 = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
260
+ t.is(flow, flow1);
261
+ t.is(passStyleOf(flow), 'remotable');
262
+
263
+ const v2 = hOrch7.vow();
264
+ t.is(await when(v2), 'y');
265
+ await eventLoopIteration();
266
+
267
+ await promiseStep;
268
+
269
+ const { vow: v1 } = zone.makeOnce('v1', () => Fail`need v1`);
270
+ const { resolver: r3 } = zone.makeOnce('v3', () => Fail`need v3`);
271
+
272
+ const logDump = flow.dump();
273
+ t.is(logDump.length, firstLogLen + 3);
274
+ t.deepEqual(logDump, [
275
+ ['doFulfill', v1, 'x'],
276
+ ['checkCall', hOrch7, 'vow', [], 1],
277
+ ['doReturn', 1, v2],
278
+ ['checkCall', hOrch7, 'scale', [3], 3],
279
+ ['doReturn', 3, 21],
280
+ ['checkCall', hOrch7, 'scale', [9n], 5],
281
+ [
282
+ 'doThrow',
283
+ 5,
284
+ TypeError('Cannot mix BigInt and other types, use explicit conversions'),
285
+ ],
286
+ // new stuff
287
+ ['doFulfill', v2, 'y'],
288
+ ['checkCall', hOrch7, 'scale', [3], firstLogLen + 1],
289
+ // same question. different answer
290
+ ['doReturn', firstLogLen + 1, 42],
291
+ ]);
292
+
293
+ // @ts-expect-error TS doesn't know it is a resolver
294
+ r3.resolve('done');
295
+ await eventLoopIteration();
296
+
297
+ t.is(await when(outcomeV), undefined);
298
+ t.deepEqual(flow.dump(), []);
299
+
300
+ t.log('goodReplay done', await promiseStep);
301
+ };
302
+
303
+ /**
304
+ * @param {any} t
305
+ * @param {Zone} zone
306
+ */
307
+ const testAfterPlay = async (t, zone) => {
308
+ t.log('testAfterPlay started');
309
+ const vowTools = prepareVowTools(zone);
310
+ const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
311
+ vowTools,
312
+ });
313
+ prepareOrchestra(zone);
314
+
315
+ const { guestMethod } = {
316
+ async guestMethod(_gOrch7, _gP, _p3) {
317
+ t.fail('Must not replay this');
318
+ },
319
+ };
320
+
321
+ // `asyncFlow` can be used simply to re-prepare the guest function
322
+ // by ignoring the returned wrapper function. If the wrapper function is
323
+ // invoked, that would be a *new* activation with a new outcome and
324
+ // flow, and would have nothing to do with the existing one.
325
+ asyncFlow(zone, 'AsyncFlow1', guestMethod);
326
+
327
+ const outcomeV = /** @type {Vow} */ (
328
+ zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
329
+ );
330
+ const flow = /** @type {AsyncFlow} */ (
331
+ zone.makeOnce('flow', () => Fail`need flow`)
332
+ );
333
+ t.is(passStyleOf(flow), 'remotable');
334
+
335
+ t.throws(() => adminAsyncFlow.getFlowForOutcomeVow(outcomeV), {
336
+ message:
337
+ 'key "[Alleged: VowInternalsKit vowV0]" not found in collection "flowForOutcomeVow"',
338
+ });
339
+
340
+ // Even this doesn't wake it up.
341
+ flow.wake();
342
+ await eventLoopIteration();
343
+
344
+ t.log('testAfterDoneReplay done');
345
+ };
346
+
347
+ test.serial('test heap async-flow', async t => {
348
+ const zone = makeHeapZone('heapRoot');
349
+ return testFirstPlay(t, zone);
350
+ });
351
+
352
+ test.serial('test virtual async-flow', async t => {
353
+ annihilate();
354
+ const zone = makeVirtualZone('virtualRoot');
355
+ return testFirstPlay(t, zone);
356
+ });
357
+
358
+ test.serial('test durable async-flow', async t => {
359
+ annihilate();
360
+ const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
361
+ await testFirstPlay(t, zone1);
362
+
363
+ await eventLoopIteration();
364
+
365
+ nextLife();
366
+ const zone2 = makeDurableZone(getBaggage(), 'durableRoot');
367
+ await testBadReplay(t, zone2);
368
+
369
+ await eventLoopIteration();
370
+
371
+ nextLife();
372
+ const zone3 = makeDurableZone(getBaggage(), 'durableRoot');
373
+ await testGoodReplay(t, zone3);
374
+
375
+ await eventLoopIteration();
376
+
377
+ nextLife();
378
+ const zone4 = makeDurableZone(getBaggage(), 'durableRoot');
379
+ return testAfterPlay(t, zone4);
380
+ });