@agoric/vow 0.1.1-calypso-dev-84eb287.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/LICENSE +201 -0
- package/README.md +114 -0
- package/package.json +58 -0
- package/src/E.d.ts +165 -0
- package/src/E.d.ts.map +1 -0
- package/src/E.js +484 -0
- package/src/index.d.ts +6 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +16 -0
- package/src/message-breakpoints.d.ts +44 -0
- package/src/message-breakpoints.d.ts.map +1 -0
- package/src/message-breakpoints.js +180 -0
- package/src/tools.d.ts +25 -0
- package/src/tools.d.ts.map +1 -0
- package/src/tools.js +50 -0
- package/src/track-turns.d.ts +7 -0
- package/src/track-turns.d.ts.map +1 -0
- package/src/track-turns.js +114 -0
- package/src/types.d.ts +62 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +116 -0
- package/src/vow-e.d.ts +14 -0
- package/src/vow-e.d.ts.map +1 -0
- package/src/vow-e.js +101 -0
- package/src/vow-utils.d.ts +20 -0
- package/src/vow-utils.d.ts.map +1 -0
- package/src/vow-utils.js +112 -0
- package/src/vow.d.ts +7 -0
- package/src/vow.d.ts.map +1 -0
- package/src/vow.js +216 -0
- package/src/watch-utils.d.ts +20 -0
- package/src/watch-utils.d.ts.map +1 -0
- package/src/watch-utils.js +220 -0
- package/src/watch.d.ts +8 -0
- package/src/watch.d.ts.map +1 -0
- package/src/watch.js +199 -0
- package/src/when.d.ts +5 -0
- package/src/when.d.ts.map +1 -0
- package/src/when.js +76 -0
- package/vat.js +43 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { M } from '@endo/patterns';
|
|
4
|
+
import { PromiseWatcherI } from '@agoric/base-zone';
|
|
5
|
+
|
|
6
|
+
const { Fail, bare, details: X } = assert;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @import {MapStore} from '@agoric/store/src/types.js';
|
|
10
|
+
* @import {Zone} from '@agoric/base-zone';
|
|
11
|
+
* @import {Watch} from './watch.js';
|
|
12
|
+
* @import {When} from './when.js';
|
|
13
|
+
* @import {VowKit, AsPromiseFunction, IsRetryableReason, EVow} from './types.js';
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const VowShape = M.tagged(
|
|
17
|
+
'Vow',
|
|
18
|
+
harden({
|
|
19
|
+
vowV0: M.remotable('VowV0'),
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Like `provideLazy`, but accepts non-Passable values.
|
|
25
|
+
*
|
|
26
|
+
* @param {WeakMap} map
|
|
27
|
+
* @param {any} key
|
|
28
|
+
* @param {(key: any) => any} makeValue
|
|
29
|
+
*/
|
|
30
|
+
const provideLazyMap = (map, key, makeValue) => {
|
|
31
|
+
if (!map.has(key)) {
|
|
32
|
+
map.set(key, makeValue(key));
|
|
33
|
+
}
|
|
34
|
+
return map.get(key);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Zone} zone
|
|
39
|
+
* @param {object} powers
|
|
40
|
+
* @param {Watch} powers.watch
|
|
41
|
+
* @param {When} powers.when
|
|
42
|
+
* @param {() => VowKit<any>} powers.makeVowKit
|
|
43
|
+
* @param {IsRetryableReason} powers.isRetryableReason
|
|
44
|
+
*/
|
|
45
|
+
export const prepareWatchUtils = (
|
|
46
|
+
zone,
|
|
47
|
+
{ watch, when, makeVowKit, isRetryableReason },
|
|
48
|
+
) => {
|
|
49
|
+
const detached = zone.detached();
|
|
50
|
+
const utilsToNonStorableResults = new WeakMap();
|
|
51
|
+
|
|
52
|
+
const makeWatchUtilsKit = zone.exoClassKit(
|
|
53
|
+
'WatchUtils',
|
|
54
|
+
{
|
|
55
|
+
utils: M.interface('Utils', {
|
|
56
|
+
all: M.call(M.arrayOf(M.any())).returns(VowShape),
|
|
57
|
+
asPromise: M.call(M.raw()).rest(M.raw()).returns(M.promise()),
|
|
58
|
+
}),
|
|
59
|
+
watcher: M.interface('Watcher', {
|
|
60
|
+
onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
|
|
61
|
+
onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
|
|
62
|
+
}),
|
|
63
|
+
retryRejectionPromiseWatcher: PromiseWatcherI,
|
|
64
|
+
},
|
|
65
|
+
() => {
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {object} VowState
|
|
68
|
+
* @property {number} remaining
|
|
69
|
+
* @property {MapStore<number, any>} resultsMap
|
|
70
|
+
* @property {VowKit['resolver']} resolver
|
|
71
|
+
*/
|
|
72
|
+
/** @type {MapStore<bigint, VowState>} */
|
|
73
|
+
const idToVowState = detached.mapStore('idToVowState');
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
nextId: 0n,
|
|
77
|
+
idToVowState,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
utils: {
|
|
82
|
+
/**
|
|
83
|
+
* @param {EVow<unknown>[]} vows
|
|
84
|
+
*/
|
|
85
|
+
all(vows) {
|
|
86
|
+
const { nextId: id, idToVowState } = this.state;
|
|
87
|
+
/** @type {VowKit<any[]>} */
|
|
88
|
+
const kit = makeVowKit();
|
|
89
|
+
|
|
90
|
+
// Preserve the order of the vow results.
|
|
91
|
+
for (let index = 0; index < vows.length; index += 1) {
|
|
92
|
+
watch(vows[index], this.facets.watcher, {
|
|
93
|
+
id,
|
|
94
|
+
index,
|
|
95
|
+
numResults: vows.length,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (vows.length > 0) {
|
|
100
|
+
// Save the state until rejection or all fulfilled.
|
|
101
|
+
this.state.nextId += 1n;
|
|
102
|
+
idToVowState.init(
|
|
103
|
+
id,
|
|
104
|
+
harden({
|
|
105
|
+
resolver: kit.resolver,
|
|
106
|
+
remaining: vows.length,
|
|
107
|
+
resultsMap: detached.mapStore('resultsMap'),
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
const idToNonStorableResults = provideLazyMap(
|
|
111
|
+
utilsToNonStorableResults,
|
|
112
|
+
this.facets.utils,
|
|
113
|
+
() => new Map(),
|
|
114
|
+
);
|
|
115
|
+
idToNonStorableResults.set(id, new Map());
|
|
116
|
+
} else {
|
|
117
|
+
// Base case: nothing to wait for.
|
|
118
|
+
kit.resolver.resolve(harden([]));
|
|
119
|
+
}
|
|
120
|
+
return kit.vow;
|
|
121
|
+
},
|
|
122
|
+
/** @type {AsPromiseFunction} */
|
|
123
|
+
asPromise(specimenP, ...watcherArgs) {
|
|
124
|
+
// Watch the specimen in case it is an ephemeral promise.
|
|
125
|
+
const vow = watch(specimenP, ...watcherArgs);
|
|
126
|
+
const promise = when(vow);
|
|
127
|
+
// Watch the ephemeral result promise to ensure that if its settlement is
|
|
128
|
+
// lost due to upgrade of this incarnation, we will at least cause an
|
|
129
|
+
// unhandled rejection in the new incarnation.
|
|
130
|
+
zone.watchPromise(promise, this.facets.retryRejectionPromiseWatcher);
|
|
131
|
+
|
|
132
|
+
return promise;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
watcher: {
|
|
136
|
+
onFulfilled(value, { id, index, numResults }) {
|
|
137
|
+
const { idToVowState } = this.state;
|
|
138
|
+
if (!idToVowState.has(id)) {
|
|
139
|
+
// Resolution of the returned vow happened already.
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const { remaining, resultsMap, resolver } = idToVowState.get(id);
|
|
143
|
+
const idToNonStorableResults = provideLazyMap(
|
|
144
|
+
utilsToNonStorableResults,
|
|
145
|
+
this.facets.utils,
|
|
146
|
+
() => new Map(),
|
|
147
|
+
);
|
|
148
|
+
const nonStorableResults = provideLazyMap(
|
|
149
|
+
idToNonStorableResults,
|
|
150
|
+
id,
|
|
151
|
+
() => new Map(),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Capture the fulfilled value.
|
|
155
|
+
if (zone.isStorable(value)) {
|
|
156
|
+
resultsMap.init(index, value);
|
|
157
|
+
} else {
|
|
158
|
+
nonStorableResults.set(index, value);
|
|
159
|
+
}
|
|
160
|
+
const vowState = harden({
|
|
161
|
+
remaining: remaining - 1,
|
|
162
|
+
resultsMap,
|
|
163
|
+
resolver,
|
|
164
|
+
});
|
|
165
|
+
if (vowState.remaining > 0) {
|
|
166
|
+
idToVowState.set(id, vowState);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// We're done! Extract the array.
|
|
170
|
+
idToVowState.delete(id);
|
|
171
|
+
const results = new Array(numResults);
|
|
172
|
+
let numLost = 0;
|
|
173
|
+
for (let i = 0; i < numResults; i += 1) {
|
|
174
|
+
if (nonStorableResults.has(i)) {
|
|
175
|
+
results[i] = nonStorableResults.get(i);
|
|
176
|
+
} else if (resultsMap.has(i)) {
|
|
177
|
+
results[i] = resultsMap.get(i);
|
|
178
|
+
} else {
|
|
179
|
+
numLost += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (numLost > 0) {
|
|
183
|
+
resolver.reject(
|
|
184
|
+
assert.error(X`${numLost} unstorable results were lost`),
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
resolver.resolve(harden(results));
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
onRejected(value, { id, index: _index, numResults: _numResults }) {
|
|
191
|
+
const { idToVowState } = this.state;
|
|
192
|
+
if (!idToVowState.has(id)) {
|
|
193
|
+
// First rejection wins.
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const { resolver } = idToVowState.get(id);
|
|
197
|
+
idToVowState.delete(id);
|
|
198
|
+
resolver.reject(value);
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
retryRejectionPromiseWatcher: {
|
|
202
|
+
onFulfilled(_result) {},
|
|
203
|
+
onRejected(reason, failedOp) {
|
|
204
|
+
if (isRetryableReason(reason, undefined)) {
|
|
205
|
+
Fail`Pending ${bare(failedOp)} could not retry; ${reason}`;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const makeWatchUtil = () => {
|
|
213
|
+
const { utils } = makeWatchUtilsKit();
|
|
214
|
+
return harden(utils);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return makeWatchUtil;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
harden(prepareWatchUtils);
|
package/src/watch.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function prepareWatch(zone: Zone, makeVowKit: () => VowKit<any>, isRetryableReason?: ((reason: any, lastValue: any) => any) | undefined): <T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]>(specimenP: EVow<T>, watcher?: Watcher<T, TResult1, TResult2, C> | undefined, ...watcherArgs: C) => Vow<Exclude<TResult1, void> | Exclude<TResult2, void> extends never ? TResult1 : Exclude<TResult1, void> | Exclude<TResult2, void>>;
|
|
2
|
+
export type Watch = ReturnType<typeof prepareWatch>;
|
|
3
|
+
import type { Zone } from '@agoric/base-zone';
|
|
4
|
+
import type { VowKit } from './types.js';
|
|
5
|
+
import type { EVow } from './types.js';
|
|
6
|
+
import type { Watcher } from './types.js';
|
|
7
|
+
import type { Vow } from './types.js';
|
|
8
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["watch.js"],"names":[],"mappings":"AA2JO,mCAJI,IAAI,cACJ,MAAM,OAAO,GAAG,CAAC,gCACR,GAAG,aAAa,GAAG,KAAK,GAAG,iBAe/B,CAAC,QACD,QAAQ,MACR,QAAQ,UACA,CAAC,SAAT,GAAG,EAAG,qBACT,KAAK,CAAC,CAAC,2EAEP,CAAC,yIAoBb;oBAIa,UAAU,CAAC,OAAO,YAAY,CAAC;0BA/LJ,mBAAmB;4BACyB,YAAY;0BAAZ,YAAY;6BAAZ,YAAY;yBAAZ,YAAY"}
|
package/src/watch.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { PromiseWatcherI } from '@agoric/base-zone';
|
|
3
|
+
import { getVowPayload, basicE } from './vow-utils.js';
|
|
4
|
+
|
|
5
|
+
const { apply } = Reflect;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @import { PromiseWatcher, Zone } from '@agoric/base-zone';
|
|
9
|
+
* @import { ERef, EVow, IsRetryableReason, Vow, VowKit, VowResolver, Watcher } from './types.js';
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {Zone} zone
|
|
14
|
+
*/
|
|
15
|
+
const makeWatchNextStep =
|
|
16
|
+
zone =>
|
|
17
|
+
/**
|
|
18
|
+
* If the specimen is a vow, obtain a fresh shortened promise from it,
|
|
19
|
+
* otherwise coerce the non-vow specimen to a promise. Then, associate a
|
|
20
|
+
* (usually durable) watcher object with the promise.
|
|
21
|
+
*
|
|
22
|
+
* @param {any} specimen
|
|
23
|
+
* @param {PromiseWatcher} promiseWatcher
|
|
24
|
+
*/
|
|
25
|
+
(specimen, promiseWatcher) => {
|
|
26
|
+
let promise;
|
|
27
|
+
const payload = getVowPayload(specimen);
|
|
28
|
+
if (payload) {
|
|
29
|
+
promise = basicE(payload.vowV0).shorten();
|
|
30
|
+
} else {
|
|
31
|
+
promise = basicE.resolve(specimen);
|
|
32
|
+
}
|
|
33
|
+
zone.watchPromise(promise, promiseWatcher);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {VowResolver | undefined} resolver
|
|
38
|
+
* @param {Watcher<unknown, unknown, unknown> | undefined} watcher
|
|
39
|
+
* @param {keyof Required<Watcher>} wcb
|
|
40
|
+
* @param {unknown} value
|
|
41
|
+
* @param {unknown[]} [watcherArgs]
|
|
42
|
+
*/
|
|
43
|
+
const settle = (resolver, watcher, wcb, value, watcherArgs = []) => {
|
|
44
|
+
try {
|
|
45
|
+
let chainedValue = value;
|
|
46
|
+
const w = watcher && watcher[wcb];
|
|
47
|
+
if (w) {
|
|
48
|
+
chainedValue = apply(w, watcher, [value, ...watcherArgs]);
|
|
49
|
+
} else if (wcb === 'onRejected') {
|
|
50
|
+
throw value;
|
|
51
|
+
}
|
|
52
|
+
resolver && resolver.resolve(chainedValue);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
if (resolver) {
|
|
55
|
+
resolver.reject(e);
|
|
56
|
+
} else {
|
|
57
|
+
// for host's unhandled rejection handler to catch
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {Zone} zone
|
|
65
|
+
* @param {IsRetryableReason} isRetryableReason
|
|
66
|
+
* @param {ReturnType<typeof makeWatchNextStep>} watchNextStep
|
|
67
|
+
*/
|
|
68
|
+
const preparePromiseWatcher = (zone, isRetryableReason, watchNextStep) => {
|
|
69
|
+
// We use an ephemeral WeakSet for the previously seen vows in a watch operation
|
|
70
|
+
// While watch is durable, it suffices to detect the cycle in a single incarnation
|
|
71
|
+
/** @type {WeakMap<PromiseWatcher, WeakSet<any>>} */
|
|
72
|
+
const watcherSeenPayloads = new WeakMap();
|
|
73
|
+
|
|
74
|
+
/** @param {PromiseWatcher} watcher */
|
|
75
|
+
const getSeenPayloads = watcher => {
|
|
76
|
+
let seenPayloads = watcherSeenPayloads.get(watcher);
|
|
77
|
+
if (!seenPayloads) {
|
|
78
|
+
seenPayloads = new WeakSet();
|
|
79
|
+
watcherSeenPayloads.set(watcher, seenPayloads);
|
|
80
|
+
}
|
|
81
|
+
return seenPayloads;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return zone.exoClass(
|
|
85
|
+
'PromiseWatcher',
|
|
86
|
+
PromiseWatcherI,
|
|
87
|
+
/**
|
|
88
|
+
* @template [T=any]
|
|
89
|
+
* @template [TResult1=T]
|
|
90
|
+
* @template [TResult2=never]
|
|
91
|
+
* @param {VowResolver<TResult1 | TResult2>} resolver
|
|
92
|
+
* @param {Watcher<T, TResult1, TResult2>} [watcher]
|
|
93
|
+
* @param {unknown[]} [watcherArgs]
|
|
94
|
+
*/
|
|
95
|
+
(resolver, watcher, watcherArgs) => {
|
|
96
|
+
const state = {
|
|
97
|
+
vow: /** @type {unknown} */ (undefined),
|
|
98
|
+
priorRetryValue: /** @type {any} */ (undefined),
|
|
99
|
+
resolver,
|
|
100
|
+
watcher,
|
|
101
|
+
watcherArgs: harden(watcherArgs),
|
|
102
|
+
};
|
|
103
|
+
return /** @type {Partial<typeof state>} */ (state);
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
/** @type {Required<PromiseWatcher>['onFulfilled']} */
|
|
107
|
+
onFulfilled(value) {
|
|
108
|
+
const { watcher, watcherArgs, resolver } = this.state;
|
|
109
|
+
const payload = getVowPayload(value);
|
|
110
|
+
if (payload) {
|
|
111
|
+
const seenPayloads = getSeenPayloads(this.self);
|
|
112
|
+
// TODO: rely on endowed helper to get storable cap from payload
|
|
113
|
+
if (seenPayloads.has(payload.vowV0)) {
|
|
114
|
+
return this.self.onRejected(Error('Vow resolution cycle detected'));
|
|
115
|
+
}
|
|
116
|
+
seenPayloads.add(payload.vowV0);
|
|
117
|
+
// We've been shortened, so reflect our state accordingly, and go again.
|
|
118
|
+
this.state.vow = value;
|
|
119
|
+
watchNextStep(value, this.self);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
watcherSeenPayloads.delete(this.self);
|
|
123
|
+
this.state.priorRetryValue = undefined;
|
|
124
|
+
this.state.watcher = undefined;
|
|
125
|
+
this.state.resolver = undefined;
|
|
126
|
+
settle(resolver, watcher, 'onFulfilled', value, watcherArgs);
|
|
127
|
+
},
|
|
128
|
+
/** @type {Required<PromiseWatcher>['onRejected']} */
|
|
129
|
+
onRejected(reason) {
|
|
130
|
+
const { vow, watcher, watcherArgs, resolver, priorRetryValue } =
|
|
131
|
+
this.state;
|
|
132
|
+
if (vow) {
|
|
133
|
+
const retryValue = isRetryableReason(reason, priorRetryValue);
|
|
134
|
+
if (retryValue) {
|
|
135
|
+
// Retry the same specimen.
|
|
136
|
+
this.state.priorRetryValue = retryValue;
|
|
137
|
+
watchNextStep(vow, this.self);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
watcherSeenPayloads.delete(this.self);
|
|
142
|
+
this.state.priorRetryValue = undefined;
|
|
143
|
+
this.state.resolver = undefined;
|
|
144
|
+
this.state.watcher = undefined;
|
|
145
|
+
settle(resolver, watcher, 'onRejected', reason, watcherArgs);
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {Zone} zone
|
|
153
|
+
* @param {() => VowKit<any>} makeVowKit
|
|
154
|
+
* @param {(reason: any, lastValue: any) => any} [isRetryableReason]
|
|
155
|
+
*/
|
|
156
|
+
export const prepareWatch = (
|
|
157
|
+
zone,
|
|
158
|
+
makeVowKit,
|
|
159
|
+
isRetryableReason = (_reason, _lastValue) => undefined,
|
|
160
|
+
) => {
|
|
161
|
+
const watchNextStep = makeWatchNextStep(zone);
|
|
162
|
+
const makePromiseWatcher = preparePromiseWatcher(
|
|
163
|
+
zone,
|
|
164
|
+
isRetryableReason,
|
|
165
|
+
watchNextStep,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @template [T=any]
|
|
170
|
+
* @template [TResult1=T]
|
|
171
|
+
* @template [TResult2=never]
|
|
172
|
+
* @template {any[]} [C=any[]] watcher args
|
|
173
|
+
* @param {EVow<T>} specimenP
|
|
174
|
+
* @param {Watcher<T, TResult1, TResult2, C>} [watcher]
|
|
175
|
+
* @param {C} watcherArgs
|
|
176
|
+
*/
|
|
177
|
+
const watch = (specimenP, watcher, ...watcherArgs) => {
|
|
178
|
+
/** @typedef {Exclude<TResult1, void> | Exclude<TResult2, void>} Voidless */
|
|
179
|
+
/** @typedef {Voidless extends never ? TResult1 : Voidless} Narrowest */
|
|
180
|
+
/** @type {VowKit<Narrowest>} */
|
|
181
|
+
const { resolver, vow } = makeVowKit();
|
|
182
|
+
|
|
183
|
+
// Create a promise watcher to track vows, retrying upon rejection as
|
|
184
|
+
// controlled by `isRetryableReason`.
|
|
185
|
+
const promiseWatcher = makePromiseWatcher(resolver, watcher, watcherArgs);
|
|
186
|
+
|
|
187
|
+
// Coerce the specimen to a promise, and start the watcher cycle.
|
|
188
|
+
zone.watchPromise(basicE.resolve(specimenP), promiseWatcher);
|
|
189
|
+
|
|
190
|
+
return vow;
|
|
191
|
+
};
|
|
192
|
+
harden(watch);
|
|
193
|
+
|
|
194
|
+
return watch;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
harden(prepareWatch);
|
|
198
|
+
|
|
199
|
+
/** @typedef {ReturnType<typeof prepareWatch>} Watch */
|
package/src/when.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function makeWhen(isRetryableReason?: IsRetryableReason | undefined): <T, TResult1 = EUnwrap<T>, TResult2 = never>(specimenP: T, onFulfilled?: ((value: EUnwrap<T>) => TResult1 | PromiseLike<TResult1>) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined) => Promise<TResult1 | TResult2>;
|
|
2
|
+
export type When = ReturnType<typeof makeWhen>;
|
|
3
|
+
import type { IsRetryableReason } from './types.js';
|
|
4
|
+
import type { EUnwrap } from './types.js';
|
|
5
|
+
//# sourceMappingURL=when.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when.d.ts","sourceRoot":"","sources":["when.js"],"names":[],"mappings":"AAQO,8EAMQ,CAAC,EACA,QAAQ,eACR,QAAQ,qBACX,CAAC,yBACO,QAAQ,CAAC,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,sCAC9C,GAAG,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,kBAC/C,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAmD1C;mBAIa,UAAU,CAAC,OAAO,QAAQ,CAAC;uCAxEO,YAAY;6BAAZ,YAAY"}
|
package/src/when.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { getVowPayload, basicE } from './vow-utils.js';
|
|
3
|
+
|
|
4
|
+
/** @import { IsRetryableReason, EUnwrap } from './types.js' */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {IsRetryableReason} [isRetryableReason]
|
|
8
|
+
*/
|
|
9
|
+
export const makeWhen = (
|
|
10
|
+
isRetryableReason = /** @type {IsRetryableReason} */ (() => false),
|
|
11
|
+
) => {
|
|
12
|
+
/**
|
|
13
|
+
* Shorten `specimenP` until we achieve a final result.
|
|
14
|
+
*
|
|
15
|
+
* @template T
|
|
16
|
+
* @template [TResult1=EUnwrap<T>]
|
|
17
|
+
* @template [TResult2=never]
|
|
18
|
+
* @param {T} specimenP value to unwrap
|
|
19
|
+
* @param {(value: EUnwrap<T>) => TResult1 | PromiseLike<TResult1>} [onFulfilled]
|
|
20
|
+
* @param {(reason: any) => TResult2 | PromiseLike<TResult2>} [onRejected]
|
|
21
|
+
* @returns {Promise<TResult1 | TResult2>}
|
|
22
|
+
*/
|
|
23
|
+
const when = async (specimenP, onFulfilled, onRejected) => {
|
|
24
|
+
// Ensure we don't run until a subsequent turn.
|
|
25
|
+
await null;
|
|
26
|
+
|
|
27
|
+
// Ensure we have a presence that won't be disconnected later.
|
|
28
|
+
let result = await specimenP;
|
|
29
|
+
let payload = getVowPayload(result);
|
|
30
|
+
let priorRetryValue;
|
|
31
|
+
const seenPayloads = new WeakSet();
|
|
32
|
+
while (payload) {
|
|
33
|
+
// TODO: rely on endowed helpers for getting storable cap and performing
|
|
34
|
+
// shorten "next step"
|
|
35
|
+
const { vowV0 } = payload;
|
|
36
|
+
if (seenPayloads.has(vowV0)) {
|
|
37
|
+
throw Error('Vow resolution cycle detected');
|
|
38
|
+
}
|
|
39
|
+
result = await basicE(vowV0)
|
|
40
|
+
.shorten()
|
|
41
|
+
.then(
|
|
42
|
+
res => {
|
|
43
|
+
seenPayloads.add(vowV0);
|
|
44
|
+
priorRetryValue = undefined;
|
|
45
|
+
return res;
|
|
46
|
+
},
|
|
47
|
+
e => {
|
|
48
|
+
const nextValue = isRetryableReason(e, priorRetryValue);
|
|
49
|
+
if (nextValue) {
|
|
50
|
+
// Shorten the same specimen to try again.
|
|
51
|
+
priorRetryValue = nextValue;
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
throw e;
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
// Advance to the next vow.
|
|
58
|
+
payload = getVowPayload(result);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const unwrapped = /** @type {EUnwrap<T>} */ (result);
|
|
62
|
+
|
|
63
|
+
// We've extracted the final result.
|
|
64
|
+
if (onFulfilled == null && onRejected == null) {
|
|
65
|
+
return /** @type {TResult1} */ (unwrapped);
|
|
66
|
+
}
|
|
67
|
+
return basicE.resolve(unwrapped).then(onFulfilled, onRejected);
|
|
68
|
+
};
|
|
69
|
+
harden(when);
|
|
70
|
+
|
|
71
|
+
return when;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
harden(makeWhen);
|
|
75
|
+
|
|
76
|
+
/** @typedef {ReturnType<typeof makeWhen>} When */
|
package/vat.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file specialization of the `@agoric/vow` package for the vat disconnection rejections produced by
|
|
3
|
+
* the SwingSet kernel.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// @ts-check
|
|
7
|
+
import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
|
|
8
|
+
import { makeHeapZone } from '@agoric/base-zone/heap.js';
|
|
9
|
+
import { prepareVowTools as rawPrepareVowTools } from './src/index.js';
|
|
10
|
+
|
|
11
|
+
export { EVow$, OrVow$ } from './src/index.js';
|
|
12
|
+
|
|
13
|
+
/** @type {import('./src/types.js').IsRetryableReason} */
|
|
14
|
+
const isRetryableReason = (reason, priorRetryValue) => {
|
|
15
|
+
if (
|
|
16
|
+
isUpgradeDisconnection(reason) &&
|
|
17
|
+
(!priorRetryValue ||
|
|
18
|
+
reason.incarnationNumber > priorRetryValue.incarnationNumber)
|
|
19
|
+
) {
|
|
20
|
+
return reason;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const defaultPowers = harden({
|
|
26
|
+
isRetryableReason,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Produce SwingSet-compatible vowTools, with an arbitrary Zone type
|
|
31
|
+
*
|
|
32
|
+
* @type {typeof rawPrepareVowTools}
|
|
33
|
+
*/
|
|
34
|
+
export const prepareVowTools = (zone, powers = {}) =>
|
|
35
|
+
rawPrepareVowTools(zone, { ...defaultPowers, ...powers });
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `vowTools` that are not durable, but are useful in non-durable clients that
|
|
39
|
+
* need to consume vows from other SwingSet vats.
|
|
40
|
+
*/
|
|
41
|
+
export const heapVowTools = prepareVowTools(makeHeapZone());
|
|
42
|
+
|
|
43
|
+
export const { E: heapVowE } = heapVowTools;
|