@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.
- package/CHANGELOG.md +1 -0
- package/LICENSE +201 -0
- package/README.md +40 -0
- package/docs/async-flow-states.key +0 -0
- package/docs/async-flow-states.md +15 -0
- package/docs/async-flow-states.png +0 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -0
- package/index.js +1 -0
- package/package.json +67 -0
- package/src/async-flow.d.ts +87 -0
- package/src/async-flow.d.ts.map +1 -0
- package/src/async-flow.js +502 -0
- package/src/bijection.d.ts +28 -0
- package/src/bijection.d.ts.map +1 -0
- package/src/bijection.js +132 -0
- package/src/convert.d.ts +5 -0
- package/src/convert.d.ts.map +1 -0
- package/src/convert.js +131 -0
- package/src/ephemera.d.ts +2 -0
- package/src/ephemera.d.ts.map +1 -0
- package/src/ephemera.js +35 -0
- package/src/equate.d.ts +2 -0
- package/src/equate.d.ts.map +1 -0
- package/src/equate.js +123 -0
- package/src/log-store.d.ts +25 -0
- package/src/log-store.d.ts.map +1 -0
- package/src/log-store.js +165 -0
- package/src/replay-membrane.d.ts +71 -0
- package/src/replay-membrane.d.ts.map +1 -0
- package/src/replay-membrane.js +435 -0
- package/src/type-guards.d.ts +4 -0
- package/src/type-guards.d.ts.map +1 -0
- package/src/type-guards.js +54 -0
- package/src/types.d.ts +70 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +164 -0
- package/test/async-flow-crank.test.js +96 -0
- package/test/async-flow-no-this.js +59 -0
- package/test/async-flow.test.js +380 -0
- package/test/bad-host.test.js +205 -0
- package/test/bijection.test.js +118 -0
- package/test/convert.test.js +127 -0
- package/test/equate.test.js +116 -0
- package/test/log-store.test.js +112 -0
- package/test/prepare-test-env-ava.js +28 -0
- package/test/replay-membrane-settlement.test.js +154 -0
- package/test/replay-membrane-zombie.test.js +158 -0
- package/test/replay-membrane.test.js +271 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +13 -0
- package/typedoc.json +8 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/* eslint-disable no-use-before-define */
|
|
2
|
+
import { Fail, b, q } from '@endo/errors';
|
|
3
|
+
import { Far, Remotable, getInterfaceOf } from '@endo/pass-style';
|
|
4
|
+
import { E } from '@endo/eventual-send';
|
|
5
|
+
import { getMethodNames } from '@endo/eventual-send/utils.js';
|
|
6
|
+
import { makePromiseKit } from '@endo/promise-kit';
|
|
7
|
+
import { makeEquate } from './equate.js';
|
|
8
|
+
import { makeConvertKit } from './convert.js';
|
|
9
|
+
|
|
10
|
+
const { fromEntries, defineProperties } = Object;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {LogStore} log
|
|
14
|
+
* @param {Bijection} bijection
|
|
15
|
+
* @param {VowTools} vowTools
|
|
16
|
+
* @param {(vowish: Promise | Vow) => void} watchWake
|
|
17
|
+
* @param {(problem: Error) => never} panic
|
|
18
|
+
*/
|
|
19
|
+
export const makeReplayMembrane = (
|
|
20
|
+
log,
|
|
21
|
+
bijection,
|
|
22
|
+
vowTools,
|
|
23
|
+
watchWake,
|
|
24
|
+
panic,
|
|
25
|
+
) => {
|
|
26
|
+
const { when } = vowTools;
|
|
27
|
+
|
|
28
|
+
const equate = makeEquate(bijection);
|
|
29
|
+
|
|
30
|
+
const guestPromiseMap = new WeakMap();
|
|
31
|
+
|
|
32
|
+
let stopped = false;
|
|
33
|
+
|
|
34
|
+
// ////////////// Host or Interpreter to Guest ///////////////////////////////
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* When replaying, this comes from interpreting the log.
|
|
38
|
+
* Otherwise, it is triggered by a watcher watching hostVow,
|
|
39
|
+
* that must also log it.
|
|
40
|
+
*
|
|
41
|
+
* @param {HostVow} hostVow
|
|
42
|
+
* @param {Host} hostFulfillment
|
|
43
|
+
*/
|
|
44
|
+
const doFulfill = (hostVow, hostFulfillment) => {
|
|
45
|
+
const guestPromise = hostToGuest(hostVow);
|
|
46
|
+
const status = guestPromiseMap.get(guestPromise);
|
|
47
|
+
if (!status || status === 'settled') {
|
|
48
|
+
Fail`doFulfill should only be called on a registered unresolved promise`;
|
|
49
|
+
}
|
|
50
|
+
const guestFulfillment = hostToGuest(hostFulfillment);
|
|
51
|
+
status.resolve(guestFulfillment);
|
|
52
|
+
guestPromiseMap.set(guestPromise, 'settled');
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* When replaying, this comes from interpreting the log.
|
|
57
|
+
* Otherwise, it is triggered by a watcher watching hostVow,
|
|
58
|
+
* that must also log it.
|
|
59
|
+
*
|
|
60
|
+
* @param {HostVow} hostVow
|
|
61
|
+
* @param {Host} hostReason
|
|
62
|
+
*/
|
|
63
|
+
const doReject = (hostVow, hostReason) => {
|
|
64
|
+
const guestPromise = hostToGuest(hostVow);
|
|
65
|
+
const status = guestPromiseMap.get(guestPromise);
|
|
66
|
+
if (!status || status === 'settled') {
|
|
67
|
+
Fail`doReject should only be called on a registered unresolved promise`;
|
|
68
|
+
}
|
|
69
|
+
const guestReason = hostToGuest(hostReason);
|
|
70
|
+
status.reject(guestReason);
|
|
71
|
+
guestPromiseMap.set(guestPromise, 'settled');
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* When replaying, after the guest thinks it has called a host method,
|
|
76
|
+
* triggering `checkCall`, that host method emulator consumes one of
|
|
77
|
+
* these entries from the log to return what it is supposed to.
|
|
78
|
+
* It returns an Outcome describing either a throw or return, because we
|
|
79
|
+
* reserve the actual throw channels for replay errors and internal
|
|
80
|
+
* errors.
|
|
81
|
+
*
|
|
82
|
+
* @param {number} callIndex
|
|
83
|
+
* @param {Host} hostResult
|
|
84
|
+
* @returns {Outcome}
|
|
85
|
+
*/
|
|
86
|
+
const doReturn = (callIndex, hostResult) => {
|
|
87
|
+
unnestInterpreter(callIndex);
|
|
88
|
+
const guestResult = hostToGuest(hostResult);
|
|
89
|
+
return harden({
|
|
90
|
+
kind: 'return',
|
|
91
|
+
result: guestResult,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* When replaying, after the guest thinks it has called a host method,
|
|
97
|
+
* triggering `checkCall`, that host method emulator consumes one of
|
|
98
|
+
* these entries from the log to return what it is supposed to.
|
|
99
|
+
* It returns an Outcome describing either a throw or return, because we
|
|
100
|
+
* reserve the actual throw channels for replay errors and internal
|
|
101
|
+
* errors.
|
|
102
|
+
*
|
|
103
|
+
* @param {number} callIndex
|
|
104
|
+
* @param {Host} hostProblem
|
|
105
|
+
* @returns {Outcome}
|
|
106
|
+
*/
|
|
107
|
+
const doThrow = (callIndex, hostProblem) => {
|
|
108
|
+
unnestInterpreter(callIndex);
|
|
109
|
+
const guestProblem = hostToGuest(hostProblem);
|
|
110
|
+
return harden({
|
|
111
|
+
kind: 'throw',
|
|
112
|
+
problem: guestProblem,
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ///////////// Guest to Host or consume log ////////////////////////////////
|
|
117
|
+
|
|
118
|
+
const performCall = (hostTarget, optVerb, hostArgs, callIndex) => {
|
|
119
|
+
let hostResult;
|
|
120
|
+
try {
|
|
121
|
+
hostResult = optVerb
|
|
122
|
+
? hostTarget[optVerb](...hostArgs)
|
|
123
|
+
: hostTarget(...hostArgs);
|
|
124
|
+
// Try converting here just to route the error correctly
|
|
125
|
+
hostToGuest(hostResult, `converting ${optVerb || 'host'} result`);
|
|
126
|
+
} catch (hostProblem) {
|
|
127
|
+
return logDo(nestDispatch, harden(['doThrow', callIndex, hostProblem]));
|
|
128
|
+
}
|
|
129
|
+
return logDo(nestDispatch, harden(['doReturn', callIndex, hostResult]));
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const guestCallsHost = (guestTarget, optVerb, guestArgs, callIndex) => {
|
|
133
|
+
if (stopped || !bijection.hasGuest(guestTarget)) {
|
|
134
|
+
// This happens in a delayed guest-to-host call from a previous run.
|
|
135
|
+
// In that case, the bijection was reset and all guest caps
|
|
136
|
+
// created in the previous run were unregistered,
|
|
137
|
+
// including guestTarget.
|
|
138
|
+
// Throwing an error back to the old guest caller may cause
|
|
139
|
+
// it to proceed in all sorts of crazy ways. But that old run
|
|
140
|
+
// should now be isolated and unable to cause any observable effects.
|
|
141
|
+
// Well, except for resource exhaustion including infinite loops,
|
|
142
|
+
// which would be a genuine problem.
|
|
143
|
+
//
|
|
144
|
+
// Console logging of unhandled rejections, errors thrown to the top
|
|
145
|
+
// of the event loop, or anything else are not problematic effects.
|
|
146
|
+
// At this level of abstraction, we don't consider console logging
|
|
147
|
+
// activity to be observable. Thus, it is also ok for the guest
|
|
148
|
+
// function, which should otherwise be closed, to
|
|
149
|
+
// capture (lexically "close over") the `console`.
|
|
150
|
+
const extraDiagnostic =
|
|
151
|
+
callStack.length === 0
|
|
152
|
+
? ''
|
|
153
|
+
: // This case should only happen when the callStack is empty
|
|
154
|
+
` with non-empty callstack ${q(callStack)};`;
|
|
155
|
+
Fail`Called from a previous run: ${guestTarget}${b(extraDiagnostic)}`;
|
|
156
|
+
}
|
|
157
|
+
/** @type {Outcome} */
|
|
158
|
+
let outcome;
|
|
159
|
+
try {
|
|
160
|
+
const guestEntry = harden([
|
|
161
|
+
'checkCall',
|
|
162
|
+
guestTarget,
|
|
163
|
+
optVerb,
|
|
164
|
+
guestArgs,
|
|
165
|
+
callIndex,
|
|
166
|
+
]);
|
|
167
|
+
if (log.isReplaying()) {
|
|
168
|
+
const entry = log.nextEntry();
|
|
169
|
+
equate(
|
|
170
|
+
guestEntry,
|
|
171
|
+
entry,
|
|
172
|
+
`replay ${callIndex}:
|
|
173
|
+
${q(guestEntry)}
|
|
174
|
+
vs ${q(entry)}
|
|
175
|
+
`,
|
|
176
|
+
);
|
|
177
|
+
outcome = /** @type {Outcome} */ (nestInterpreter(callIndex));
|
|
178
|
+
} else {
|
|
179
|
+
const entry = guestToHost(guestEntry);
|
|
180
|
+
log.pushEntry(entry);
|
|
181
|
+
const [_, ...args] = entry;
|
|
182
|
+
nestInterpreter(callIndex);
|
|
183
|
+
outcome = performCall(...args);
|
|
184
|
+
}
|
|
185
|
+
} catch (fatalError) {
|
|
186
|
+
throw panic(fatalError);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
switch (outcome.kind) {
|
|
190
|
+
case 'return': {
|
|
191
|
+
return outcome.result;
|
|
192
|
+
}
|
|
193
|
+
case 'throw': {
|
|
194
|
+
throw outcome.problem;
|
|
195
|
+
}
|
|
196
|
+
default: {
|
|
197
|
+
// @ts-expect-error TS correctly knows this case would be outside
|
|
198
|
+
// the type. But that's what we want to check.
|
|
199
|
+
throw Fail`unexpected outcome kind ${q(outcome.kind)}`;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// //////////////// Converters ///////////////////////////////////////////////
|
|
205
|
+
|
|
206
|
+
const makeGuestForHostRemotable = hRem => {
|
|
207
|
+
// Nothing here that captures `hRem` should make any use of it after the
|
|
208
|
+
// `makeGuestForHostRemotable` returns. This invariant enables
|
|
209
|
+
// `makeGuestForHostRemotable` to clear the `hRem` variable just before
|
|
210
|
+
// it returns, so any implementation-level capture of the variable does
|
|
211
|
+
// not inadvertently retain the host remotable which was the original
|
|
212
|
+
// value of the `hRem` variable.
|
|
213
|
+
let gRem;
|
|
214
|
+
/** @param {PropertyKey} [optVerb] */
|
|
215
|
+
const makeGuestMethod = (optVerb = undefined) => {
|
|
216
|
+
const guestMethod = (...guestArgs) => {
|
|
217
|
+
const callIndex = log.getIndex();
|
|
218
|
+
return guestCallsHost(gRem, optVerb, guestArgs, callIndex);
|
|
219
|
+
};
|
|
220
|
+
if (optVerb) {
|
|
221
|
+
defineProperties(guestMethod, {
|
|
222
|
+
name: { value: String(hRem[optVerb].name || optVerb) },
|
|
223
|
+
length: { value: Number(hRem[optVerb].length || 0) },
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
defineProperties(guestMethod, {
|
|
227
|
+
name: { value: String(hRem.name || 'anon') },
|
|
228
|
+
length: { value: Number(hRem.length || 0) },
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return guestMethod;
|
|
232
|
+
};
|
|
233
|
+
const iface = String(getInterfaceOf(hRem) || 'remotable');
|
|
234
|
+
const guestIface = `${iface} guest wrapper`; // just for debugging clarity
|
|
235
|
+
if (typeof hRem === 'function') {
|
|
236
|
+
// NOTE: Assumes that a far function has no "static" methods. This
|
|
237
|
+
// is the current marshal design, but revisit this if we change our
|
|
238
|
+
// minds.
|
|
239
|
+
gRem = Remotable(guestIface, undefined, makeGuestMethod());
|
|
240
|
+
// NOTE: If we ever do support that, probably all we need
|
|
241
|
+
// to do is remove the following `throw Fail` line.
|
|
242
|
+
throw Fail`host far functions not yet passable`;
|
|
243
|
+
} else {
|
|
244
|
+
const methodNames = getMethodNames(hRem);
|
|
245
|
+
const guestMethods = methodNames.map(name => [
|
|
246
|
+
name,
|
|
247
|
+
makeGuestMethod(name),
|
|
248
|
+
]);
|
|
249
|
+
// TODO in order to support E *well*,
|
|
250
|
+
// use HandledPromise to make gRem a remote presence for hRem
|
|
251
|
+
gRem = Remotable(guestIface, undefined, fromEntries(guestMethods));
|
|
252
|
+
}
|
|
253
|
+
// See note at the top of the function to see why clearing the `hRem`
|
|
254
|
+
// variable is safe, and what invariant the above code needs to maintain so
|
|
255
|
+
// that it remains safe.
|
|
256
|
+
hRem = undefined;
|
|
257
|
+
return gRem;
|
|
258
|
+
};
|
|
259
|
+
harden(makeGuestForHostRemotable);
|
|
260
|
+
|
|
261
|
+
const makeGuestForHostVow = hVow => {
|
|
262
|
+
// TODO in order to support E *well*,
|
|
263
|
+
// use HandledPromise to make `promise` a handled promise for hVow
|
|
264
|
+
const { promise, resolve, reject } = makePromiseKit();
|
|
265
|
+
guestPromiseMap.set(promise, harden({ resolve, reject }));
|
|
266
|
+
|
|
267
|
+
watchWake(hVow);
|
|
268
|
+
|
|
269
|
+
// The replay membrane is the only component inserting entries into
|
|
270
|
+
// the log. In particular, the flow's vow durable watcher does not log the
|
|
271
|
+
// settlement outcome, and instead it's the responsibility of the
|
|
272
|
+
// membrane's ephemeral handler. Because of this, the membrane's handler
|
|
273
|
+
// must be careful to:
|
|
274
|
+
// - Be added to the vow if the settlement has not yet been recorded in
|
|
275
|
+
// the log.
|
|
276
|
+
// - Insert a single settlement outcome in the log for the given vow.
|
|
277
|
+
//
|
|
278
|
+
// In practice the former is accomplished by a handler always being
|
|
279
|
+
// added to the host vow when creating a guest promise, and the
|
|
280
|
+
// handler checking after replay is complete, whether the guest promise
|
|
281
|
+
// is already settled (by the log replay) or not. The latter is
|
|
282
|
+
// accomplished by checking that the membrane has not been stopped
|
|
283
|
+
// before updating the log.
|
|
284
|
+
|
|
285
|
+
void when(
|
|
286
|
+
hVow,
|
|
287
|
+
async hostFulfillment => {
|
|
288
|
+
await log.promiseReplayDone(); // should never reject
|
|
289
|
+
if (!stopped && guestPromiseMap.get(promise) !== 'settled') {
|
|
290
|
+
/** @type {LogEntry} */
|
|
291
|
+
const entry = harden(['doFulfill', hVow, hostFulfillment]);
|
|
292
|
+
log.pushEntry(entry);
|
|
293
|
+
try {
|
|
294
|
+
interpretOne(topDispatch, entry);
|
|
295
|
+
} catch {
|
|
296
|
+
// interpretOne does its own try/catch/panic, so failure would
|
|
297
|
+
// already be registered. Here, just return to avoid the
|
|
298
|
+
// Unhandled rejection.
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async hostReason => {
|
|
303
|
+
await log.promiseReplayDone(); // should never reject
|
|
304
|
+
if (!stopped && guestPromiseMap.get(promise) !== 'settled') {
|
|
305
|
+
/** @type {LogEntry} */
|
|
306
|
+
const entry = harden(['doReject', hVow, hostReason]);
|
|
307
|
+
log.pushEntry(entry);
|
|
308
|
+
try {
|
|
309
|
+
interpretOne(topDispatch, entry);
|
|
310
|
+
} catch {
|
|
311
|
+
// interpretOne does its own try/catch/panic, so failure would
|
|
312
|
+
// already be registered. Here, just return to avoid the
|
|
313
|
+
// Unhandled rejection.
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
return promise;
|
|
319
|
+
};
|
|
320
|
+
harden(makeGuestForHostVow);
|
|
321
|
+
|
|
322
|
+
const { guestToHost, hostToGuest } = makeConvertKit(
|
|
323
|
+
bijection,
|
|
324
|
+
makeGuestForHostRemotable,
|
|
325
|
+
makeGuestForHostVow,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// /////////////////////////////// Interpreter ///////////////////////////////
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* These are the only ones that are driven from the interpreter loop
|
|
332
|
+
*/
|
|
333
|
+
const topDispatch = harden({
|
|
334
|
+
doFulfill,
|
|
335
|
+
doReject,
|
|
336
|
+
// doCall, // unimplemented in the current plan
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* These are the only ones that are driven from the interpreter loop
|
|
341
|
+
*/
|
|
342
|
+
const nestDispatch = harden({
|
|
343
|
+
// doCall, // unimplemented in the current plan
|
|
344
|
+
doReturn,
|
|
345
|
+
doThrow,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const interpretOne = (dispatch, [op, ...args]) => {
|
|
349
|
+
try {
|
|
350
|
+
op in dispatch ||
|
|
351
|
+
// separate line so I can set a breakpoint
|
|
352
|
+
Fail`unexpected dispatch op: ${q(op)}`;
|
|
353
|
+
return dispatch[op](...args);
|
|
354
|
+
} catch (problem) {
|
|
355
|
+
throw panic(problem);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const logDo = (dispatch, entry) => {
|
|
360
|
+
log.pushEntry(entry);
|
|
361
|
+
return interpretOne(dispatch, entry);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const callStack = [];
|
|
365
|
+
|
|
366
|
+
let unnestFlag = false;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* @param {number} callIndex
|
|
370
|
+
* @returns {Outcome | undefined}
|
|
371
|
+
*/
|
|
372
|
+
const nestInterpreter = callIndex => {
|
|
373
|
+
callStack.push(callIndex);
|
|
374
|
+
while (log.isReplaying() && !stopped) {
|
|
375
|
+
const entry = log.nextEntry();
|
|
376
|
+
const optOutcome = interpretOne(nestDispatch, entry);
|
|
377
|
+
if (unnestFlag) {
|
|
378
|
+
optOutcome ||
|
|
379
|
+
// separate line so I can set a breakpoint
|
|
380
|
+
Fail`only unnest with an outcome: ${q(entry[0])}`;
|
|
381
|
+
unnestFlag = false;
|
|
382
|
+
return optOutcome;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
unnestFlag = false;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* @param {number} callIndex
|
|
390
|
+
*/
|
|
391
|
+
const unnestInterpreter = callIndex => {
|
|
392
|
+
!stopped ||
|
|
393
|
+
Fail`This membrane stopped. Restart with new membrane ${replayMembrane}`;
|
|
394
|
+
callStack.length >= 1 ||
|
|
395
|
+
// separate line so I can set a breakpoint
|
|
396
|
+
Fail`Unmatched unnest: ${q(callIndex)}`;
|
|
397
|
+
const i = callStack.pop();
|
|
398
|
+
i === callIndex ||
|
|
399
|
+
// separate line so I can set a breakpoint
|
|
400
|
+
Fail`Unexpected unnest: ${q(callIndex)} vs ${q(i)}`;
|
|
401
|
+
unnestFlag = true;
|
|
402
|
+
if (callStack.length === 0) {
|
|
403
|
+
void E.when(undefined, wake);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const wake = () => {
|
|
408
|
+
while (log.isReplaying() && !stopped) {
|
|
409
|
+
callStack.length === 0 ||
|
|
410
|
+
Fail`wake only with empty callStack: ${q(callStack)}`;
|
|
411
|
+
const entry = log.peekEntry();
|
|
412
|
+
const op = entry[0];
|
|
413
|
+
if (!(op in topDispatch)) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
void log.nextEntry();
|
|
417
|
+
interpretOne(topDispatch, entry);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const stop = () => {
|
|
422
|
+
stopped = true;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const replayMembrane = Far('replayMembrane', {
|
|
426
|
+
hostToGuest,
|
|
427
|
+
guestToHost,
|
|
428
|
+
wake,
|
|
429
|
+
stop,
|
|
430
|
+
});
|
|
431
|
+
return replayMembrane;
|
|
432
|
+
};
|
|
433
|
+
harden(makeReplayMembrane);
|
|
434
|
+
|
|
435
|
+
/** @typedef {ReturnType<makeReplayMembrane>} ReplayMembrane */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["type-guards.js"],"names":[],"mappings":"AAGA,8DAME;AAEF,gEAA6D;AAE7D,6DAwCE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { M } from '@endo/patterns';
|
|
2
|
+
import { VowShape } from '@agoric/vow';
|
|
3
|
+
|
|
4
|
+
export const FlowStateShape = M.or(
|
|
5
|
+
'Running',
|
|
6
|
+
'Sleeping',
|
|
7
|
+
'Replaying',
|
|
8
|
+
'Failed',
|
|
9
|
+
'Done',
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const PropertyKeyShape = M.or(M.string(), M.symbol());
|
|
13
|
+
|
|
14
|
+
export const LogEntryShape = M.or(
|
|
15
|
+
// ////////////////////////////// From Host to Guest /////////////////////////
|
|
16
|
+
['doFulfill', VowShape, M.any()],
|
|
17
|
+
['doReject', VowShape, M.any()],
|
|
18
|
+
// [
|
|
19
|
+
// 'doCall',
|
|
20
|
+
// M.remotable('host wrapper of guest target'),
|
|
21
|
+
// M.opt(PropertyKeyShape),
|
|
22
|
+
// M.arrayOf(M.any()),
|
|
23
|
+
// M.number(),
|
|
24
|
+
// ],
|
|
25
|
+
// [
|
|
26
|
+
// 'doSend',
|
|
27
|
+
// M.or(M.remotable('host wrapper of guest target'), VowShape),
|
|
28
|
+
// M.opt(PropertyKeyShape),
|
|
29
|
+
// M.arrayOf(M.any()),
|
|
30
|
+
// M.number(),
|
|
31
|
+
// ],
|
|
32
|
+
['doReturn', M.number(), M.any()],
|
|
33
|
+
['doThrow', M.number(), M.any()],
|
|
34
|
+
|
|
35
|
+
// ////////////////////////////// From Guest to Host /////////////////////////
|
|
36
|
+
// ['checkFulfill', VowShape, M.any()],
|
|
37
|
+
// ['checkReject', VowShape, M.any()],
|
|
38
|
+
[
|
|
39
|
+
'checkCall',
|
|
40
|
+
M.remotable('host target'),
|
|
41
|
+
M.opt(PropertyKeyShape),
|
|
42
|
+
M.arrayOf(M.any()),
|
|
43
|
+
M.number(),
|
|
44
|
+
],
|
|
45
|
+
// [
|
|
46
|
+
// 'checkSend',
|
|
47
|
+
// M.or(M.remotable('host target'), VowShape),
|
|
48
|
+
// M.opt(PropertyKeyShape),
|
|
49
|
+
// M.arrayOf(M.any()),
|
|
50
|
+
// M.number(),
|
|
51
|
+
// ],
|
|
52
|
+
// ['checkReturn', M.number(), M.any()],
|
|
53
|
+
// ['checkThrow', M.number(), M.any()],
|
|
54
|
+
);
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type FlowState = "Running" | "Sleeping" | "Replaying" | "Failed" | "Done";
|
|
2
|
+
type Guest<T extends Passable = Passable> = T;
|
|
3
|
+
type Host<T extends Passable = Passable> = T;
|
|
4
|
+
/**
|
|
5
|
+
* A HostVow must be durably storable. It corresponds to an
|
|
6
|
+
* ephemeral guest promise.
|
|
7
|
+
*/
|
|
8
|
+
type HostVow<T extends Passable = Passable> = import("@endo/pass-style").PassStyled<"tagged", "Vow"> & {
|
|
9
|
+
payload: import("@agoric/vow").VowPayload<T>;
|
|
10
|
+
};
|
|
11
|
+
type GuestAsyncFunc = (...activationArgs: Guest[]) => Guest<Promise<any>>;
|
|
12
|
+
type HostAsyncFuncWrapper = (...activationArgs: Host[]) => HostVow;
|
|
13
|
+
type PreparationOptions = {
|
|
14
|
+
vowTools?: {
|
|
15
|
+
when: <T, TResult1 = import("@agoric/vow").Unwrap<T>, TResult2 = never>(specimenP: T, onFulfilled?: ((value: import("@agoric/vow").Unwrap<T>) => TResult1 | PromiseLike<TResult1>) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined) => Promise<TResult1 | TResult2>;
|
|
16
|
+
watch: <T_1 = unknown, TResult1_1 = T_1, TResult2_1 = T_1, C = unknown>(specimenP: import("@agoric/vow").ERef<T_1 | Vow<T_1>>, watcher?: import("@agoric/vow").Watcher<T_1, TResult1_1, TResult2_1> | undefined, watcherContext?: C | undefined) => Vow<TResult1_1 | TResult2_1>;
|
|
17
|
+
makeVowKit: <T_2>() => import("@agoric/vow").VowKit<T_2>;
|
|
18
|
+
allVows: (vows: any) => Vow<any>;
|
|
19
|
+
} | undefined;
|
|
20
|
+
makeLogStore?: (() => import("@endo/exo").Guarded<{
|
|
21
|
+
reset(): void;
|
|
22
|
+
dispose(): void;
|
|
23
|
+
getIndex(): number;
|
|
24
|
+
getLength(): number;
|
|
25
|
+
isReplaying(): boolean;
|
|
26
|
+
peekEntry(): LogEntry;
|
|
27
|
+
nextEntry(): LogEntry;
|
|
28
|
+
pushEntry(entry: any): number;
|
|
29
|
+
dump(): ([op: "doFulfill", vow: Vow<Passable>, fulfillment: Passable] | [op: "doReject", vow: Vow<Passable>, reason: Passable] | [op: "doReturn", callIndex: number, result: Passable] | [op: "doThrow", callIndex: number, problem: Passable] | [op: "checkCall", target: Passable, optVerb: PropertyKey | undefined, args: Passable[], callIndex: number])[];
|
|
30
|
+
promiseReplayDone(): Promise<undefined>;
|
|
31
|
+
}>) | undefined;
|
|
32
|
+
makeBijection?: (() => import("@endo/exo").Guarded<{
|
|
33
|
+
reset(): void;
|
|
34
|
+
init(g: any, h: any): void;
|
|
35
|
+
hasGuest(g: any): boolean;
|
|
36
|
+
hasHost(h: any): boolean;
|
|
37
|
+
has(g: any, h: any): boolean;
|
|
38
|
+
guestToHost(g: any): any;
|
|
39
|
+
hostToGuest(h: any): any;
|
|
40
|
+
}>) | undefined;
|
|
41
|
+
};
|
|
42
|
+
type OutcomeKind = "return" | "throw";
|
|
43
|
+
type Outcome = {
|
|
44
|
+
kind: "return";
|
|
45
|
+
result: any;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "throw";
|
|
48
|
+
problem: any;
|
|
49
|
+
};
|
|
50
|
+
type Ephemera<S extends WeakKey = WeakKey, V extends unknown = any> = {
|
|
51
|
+
for: (self: S) => V;
|
|
52
|
+
resetFor: (self: S) => void;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* This is the typedef for the membrane log entries we currently implement.
|
|
56
|
+
* See comment below for the commented-out typedef for the full
|
|
57
|
+
* membrane log entry, which we do not yet support.
|
|
58
|
+
*/
|
|
59
|
+
type LogEntry = [// ///////////////// From Host to Guest /////////////////////////
|
|
60
|
+
op: "doFulfill", vow: HostVow, fulfillment: Host] | [op: "doReject", vow: HostVow, reason: Host] | [op: "doReturn", callIndex: number, result: Host] | [op: "doThrow", callIndex: number, problem: Host] | [// ///////////////////// From Guest to Host /////////////////////////
|
|
61
|
+
op: "checkCall", target: Host, optVerb: PropertyKey | undefined, args: Host[], callIndex: number];
|
|
62
|
+
import type { PromiseKit } from '@endo/promise-kit';
|
|
63
|
+
import type { Passable } from '@endo/pass-style';
|
|
64
|
+
import type { Zone } from '@agoric/base-zone';
|
|
65
|
+
import type { Vow } from '@agoric/vow';
|
|
66
|
+
import type { VowTools } from '@agoric/vow';
|
|
67
|
+
import type { LogStore } from './log-store.js';
|
|
68
|
+
import type { Bijection } from './bijection.js';
|
|
69
|
+
import type { ReplayMembrane } from './replay-membrane.js';
|
|
70
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"iBAWa,SAAS,GACrB,UAAsB,GACtB,WAAuB,GACvB,QAAoB,GACpB,MAAkB;WAMN,CAAC,gCAAD,CAAC;UAKD,CAAC,gCAAD,CAAC;;;;;aAQQ,CAAC;;;sBAIV,CAAC,GAAG,cAAc,EAAE,KAAK,EAAE,KAAK,KAAK,cAAS;4BAI9C,CAAC,GAAG,cAAc,EAAE,IAAI,EAAE,KAAK,OAAO;;;2FA7BlC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAwCL,QAAQ,GAAC,OAAO;eAIhB;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAA;CAAC;cAStB,CAAC,4BADK,CAAC;SAAd,CAAC,IAAI,EACE,CAAC,AADA,KAAK,CAAC;cACd,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI;;;;;;;gBAQlB,CAAE,iEAAiE;AAC/E,EAAQ,EAAE,WAAW,EACrB,GAAS,EAAE,OAAO,EAClB,WAAiB,EAAE,IAAI,CAClB,GAAG,CACR,EAAQ,EAAE,UAAU,EACpB,GAAS,EAAE,OAAO,EAClB,MAAY,EAAE,IAAI,CACb,GAAG,CACR,EAAQ,EAAE,UAAU,EACpB,SAAe,EAAE,MAAM,EACvB,MAAY,EAAE,IAAI,CACb,GAAG,CACR,EAAQ,EAAE,SAAS,EACnB,SAAe,EAAE,MAAM,EACvB,OAAa,EAAE,IAAI,CACd,GAAG,CAAE,qEAAqE;AAC/E,EAAQ,EAAE,WAAW,EACrB,MAAY,EAAE,IAAI,EAClB,OAAa,EAAE,WAAW,GAAC,SAAS,EACpC,IAAU,EAAE,IAAI,EAAE,EAClB,SAAe,EAAE,MAAM,CAClB;gCAhGuB,mBAAmB;8BACrB,kBAAkB;0BACtB,mBAAmB;yBACV,aAAa;8BAAb,aAAa;8BAClB,gBAAgB;+BACf,gBAAgB;oCACX,sBAAsB"}
|