@agoric/vow 0.1.1-dev-c5284e4.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 +115 -0
- package/package.json +53 -0
- package/src/E.d.ts +156 -0
- package/src/E.d.ts.map +1 -0
- package/src/E.js +448 -0
- package/src/index.d.ts +4 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +6 -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 +9 -0
- package/src/tools.d.ts.map +1 -0
- package/src/tools.js +19 -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 +44 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +78 -0
- package/src/vow-utils.d.ts +4 -0
- package/src/vow-utils.d.ts.map +1 -0
- package/src/vow-utils.js +34 -0
- package/src/vow.d.ts +3 -0
- package/src/vow.d.ts.map +1 -0
- package/src/vow.js +138 -0
- package/src/watch-promise.d.ts +10 -0
- package/src/watch-promise.d.ts.map +1 -0
- package/src/watch-promise.js +61 -0
- package/src/watch.d.ts +3 -0
- package/src/watch.d.ts.map +1 -0
- package/src/watch.js +163 -0
- package/src/when.d.ts +3 -0
- package/src/when.d.ts.map +1 -0
- package/src/when.js +56 -0
package/src/tools.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function prepareVowTools(zone: import('@agoric/base-zone').Zone, powers?: {
|
|
2
|
+
isRetryableReason?: ((reason: any) => boolean) | undefined;
|
|
3
|
+
watchPromise?: ((p: PromiseLike<any>, watcher: import('./watch-promise.js').PromiseWatcher, ...args: unknown[]) => void) | undefined;
|
|
4
|
+
} | undefined): {
|
|
5
|
+
when: <T = any, TResult1 = import("./E.js").Unwrap<T>, TResult2 = never>(specimenP: T, onFulfilled?: ((value: import("./E.js").Unwrap<T>) => TResult1 | PromiseLike<TResult1>) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined) => Promise<TResult1 | TResult2>;
|
|
6
|
+
watch: <T_1 = any, TResult1_1 = T_1, TResult2_1 = T_1>(specimenP: import("./types.js").ERef<T_1 | import("./types.js").Vow<T_1>>, watcher?: import("./types.js").Watcher<T_1, TResult1_1, TResult2_1> | undefined, watcherContext?: unknown) => import("./types.js").Vow<TResult1_1 | TResult2_1>;
|
|
7
|
+
makeVowKit: <T_2>() => import("./types.js").VowKit<T_2>;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["tools.js"],"names":[],"mappings":"AAWO,sCALI,OAAO,mBAAmB,EAAE,IAAI;kCAEvB,GAAG,KAAK,OAAO;wBACpB,YAAY,GAAG,CAAC,WAAW,OAAO,oBAAoB,EAAE,cAAc,WAAW,OAAO,EAAE,KAAK,IAAI;;;;;EAQjH"}
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { makeWhen } from './when.js';
|
|
3
|
+
import { prepareVowKit } from './vow.js';
|
|
4
|
+
import { prepareWatch } from './watch.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
8
|
+
* @param {object} [powers]
|
|
9
|
+
* @param {(reason: any) => boolean} [powers.isRetryableReason]
|
|
10
|
+
* @param {(p: PromiseLike<any>, watcher: import('./watch-promise.js').PromiseWatcher, ...args: unknown[]) => void} [powers.watchPromise]
|
|
11
|
+
*/
|
|
12
|
+
export const prepareVowTools = (zone, powers = {}) => {
|
|
13
|
+
const { isRetryableReason = () => false, watchPromise } = powers;
|
|
14
|
+
const makeVowKit = prepareVowKit(zone, watchPromise);
|
|
15
|
+
const when = makeWhen(isRetryableReason);
|
|
16
|
+
const watch = prepareWatch(zone, makeVowKit, watchPromise, isRetryableReason);
|
|
17
|
+
return harden({ when, watch, makeVowKit });
|
|
18
|
+
};
|
|
19
|
+
harden(prepareVowTools);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function trackTurns<T extends TurnStarterFn[]>(funcs: T): T;
|
|
2
|
+
/**
|
|
3
|
+
* An optional function that is not this-sensitive, expected to be called at
|
|
4
|
+
* bottom of stack to start a new turn.
|
|
5
|
+
*/
|
|
6
|
+
export type TurnStarterFn = ((...args: any[]) => any) | undefined;
|
|
7
|
+
//# sourceMappingURL=track-turns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-turns.d.ts","sourceRoot":"","sources":["track-turns.js"],"names":[],"mappings":"AAyFO,mEAiBN;;;;;uCAMuB,GAAG,EAAE,KAAK,GAAG"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/* global globalThis */
|
|
3
|
+
import {
|
|
4
|
+
getEnvironmentOption,
|
|
5
|
+
environmentOptionsListHas,
|
|
6
|
+
} from '@endo/env-options';
|
|
7
|
+
|
|
8
|
+
// NOTE: We can't import these because they're not in scope before lockdown.
|
|
9
|
+
// import { assert, details as X, Fail } from '@agoric/assert';
|
|
10
|
+
|
|
11
|
+
// WARNING: Global Mutable State!
|
|
12
|
+
// This state is communicated to `assert` that makes it available to the
|
|
13
|
+
// causal console, which affects the console log output. Normally we
|
|
14
|
+
// regard the ability to see console log output as a meta-level privilege
|
|
15
|
+
// analogous to the ability to debug. Aside from that, this module should
|
|
16
|
+
// not have any observably mutable state.
|
|
17
|
+
|
|
18
|
+
let hiddenPriorError;
|
|
19
|
+
let hiddenCurrentTurn = 0;
|
|
20
|
+
let hiddenCurrentEvent = 0;
|
|
21
|
+
|
|
22
|
+
// Turn on if you seem to be losing error logging at the top of the event loop
|
|
23
|
+
const VERBOSE = environmentOptionsListHas('DEBUG', 'track-turns');
|
|
24
|
+
|
|
25
|
+
// Track-turns is disabled by default and can be enabled by an environment
|
|
26
|
+
// option.
|
|
27
|
+
const ENABLED =
|
|
28
|
+
getEnvironmentOption('TRACK_TURNS', 'disabled', ['enabled']) === 'enabled';
|
|
29
|
+
|
|
30
|
+
// We hoist the following functions out of trackTurns() to discourage the
|
|
31
|
+
// closures from holding onto 'args' or 'func' longer than necessary,
|
|
32
|
+
// which we've seen cause HandledPromise arguments to be retained for
|
|
33
|
+
// a surprisingly long time.
|
|
34
|
+
|
|
35
|
+
const addRejectionNote = detailsNote => reason => {
|
|
36
|
+
if (reason instanceof Error) {
|
|
37
|
+
assert.note(reason, detailsNote);
|
|
38
|
+
}
|
|
39
|
+
if (VERBOSE) {
|
|
40
|
+
console.log('REJECTED at top of event loop', reason);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const wrapFunction =
|
|
45
|
+
(func, sendingError, X) =>
|
|
46
|
+
(...args) => {
|
|
47
|
+
hiddenPriorError = sendingError;
|
|
48
|
+
hiddenCurrentTurn += 1;
|
|
49
|
+
hiddenCurrentEvent = 0;
|
|
50
|
+
try {
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = func(...args);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof Error) {
|
|
56
|
+
assert.note(
|
|
57
|
+
err,
|
|
58
|
+
X`Thrown from: ${hiddenPriorError}:${hiddenCurrentTurn}.${hiddenCurrentEvent}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (VERBOSE) {
|
|
62
|
+
console.log('THROWN to top of event loop', err);
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
// Must capture this now, not when the catch triggers.
|
|
67
|
+
const detailsNote = X`Rejection from: ${hiddenPriorError}:${hiddenCurrentTurn}.${hiddenCurrentEvent}`;
|
|
68
|
+
Promise.resolve(result).catch(addRejectionNote(detailsNote));
|
|
69
|
+
return result;
|
|
70
|
+
} finally {
|
|
71
|
+
hiddenPriorError = undefined;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Given a list of `TurnStarterFn`s, returns a list of `TurnStarterFn`s whose
|
|
77
|
+
* `this`-free call behaviors are not observably different to those that
|
|
78
|
+
* cannot see console output. The only purpose is to cause additional
|
|
79
|
+
* information to appear on the console.
|
|
80
|
+
*
|
|
81
|
+
* The call to `trackTurns` is itself a sending event, that occurs in some call
|
|
82
|
+
* stack in some turn number at some event number within that turn. Each call
|
|
83
|
+
* to any of the returned `TurnStartFn`s is a receiving event that begins a new
|
|
84
|
+
* turn. This sending event caused each of those receiving events.
|
|
85
|
+
*
|
|
86
|
+
* @template {TurnStarterFn[]} T
|
|
87
|
+
* @param {T} funcs
|
|
88
|
+
* @returns {T}
|
|
89
|
+
*/
|
|
90
|
+
export const trackTurns = funcs => {
|
|
91
|
+
if (!ENABLED || typeof globalThis === 'undefined' || !globalThis.assert) {
|
|
92
|
+
return funcs;
|
|
93
|
+
}
|
|
94
|
+
const { details: X } = assert;
|
|
95
|
+
|
|
96
|
+
hiddenCurrentEvent += 1;
|
|
97
|
+
const sendingError = Error(
|
|
98
|
+
`Event: ${hiddenCurrentTurn}.${hiddenCurrentEvent}`,
|
|
99
|
+
);
|
|
100
|
+
if (hiddenPriorError !== undefined) {
|
|
101
|
+
assert.note(sendingError, X`Caused by: ${hiddenPriorError}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return /** @type {T} */ (
|
|
105
|
+
funcs.map(func => func && wrapFunction(func, sendingError, X))
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* An optional function that is not this-sensitive, expected to be called at
|
|
111
|
+
* bottom of stack to start a new turn.
|
|
112
|
+
*
|
|
113
|
+
* @typedef {((...args: any[]) => any) | undefined} TurnStarterFn
|
|
114
|
+
*/
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type PromiseVow<T> = PromiseLike<T | Vow<T>>;
|
|
2
|
+
export type ERef<T> = import('./E').ERef<T>;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a type that accepts both near and marshalled references that were
|
|
5
|
+
* returned from `Remotable` or `Far`, and also promises for such references.
|
|
6
|
+
*/
|
|
7
|
+
export type FarRef<Primary, Local = import("./E").DataOnly<Primary>> = ERef<Local & import('@endo/eventual-send').RemotableBrand<Local, Primary>>;
|
|
8
|
+
/**
|
|
9
|
+
* The first version of the vow implementation
|
|
10
|
+
* object. CAVEAT: These methods must never be changed or added to, to provide
|
|
11
|
+
* forward/backward compatibility. Create a new object and bump its version
|
|
12
|
+
* number instead.
|
|
13
|
+
*/
|
|
14
|
+
export type VowV0<T = any> = {
|
|
15
|
+
/**
|
|
16
|
+
* Attempt to unwrap all vows in this
|
|
17
|
+
* promise chain, returning a promise for the final value. A rejection may
|
|
18
|
+
* indicate a temporary routing failure requiring a retry, otherwise that the
|
|
19
|
+
* decider of the terminal promise rejected it.
|
|
20
|
+
*/
|
|
21
|
+
shorten: () => Promise<T>;
|
|
22
|
+
};
|
|
23
|
+
export type VowPayload<T = any> = {
|
|
24
|
+
vowV0: import('@endo/eventual-send').FarRef<VowV0<T>>;
|
|
25
|
+
};
|
|
26
|
+
export type Vow<T = any> = import('@endo/pass-style').CopyTagged<'Vow', VowPayload<T>>;
|
|
27
|
+
export type VowKit<T = any> = {
|
|
28
|
+
vow: Vow<T>;
|
|
29
|
+
resolver: VowResolver<T>;
|
|
30
|
+
};
|
|
31
|
+
export type VowPromiseKit<T = any> = {
|
|
32
|
+
vow: Vow<T>;
|
|
33
|
+
resolver: VowResolver<T>;
|
|
34
|
+
promise: Promise<T>;
|
|
35
|
+
};
|
|
36
|
+
export type VowResolver<T = any> = {
|
|
37
|
+
resolve(value?: T | PromiseVow<T>): void;
|
|
38
|
+
reject(reason?: any): void;
|
|
39
|
+
};
|
|
40
|
+
export type Watcher<T = any, TResult1 = T, TResult2 = T> = {
|
|
41
|
+
onFulfilled?: ((value: T) => Vow<TResult1> | PromiseVow<TResult1> | TResult1) | undefined;
|
|
42
|
+
onRejected?: ((reason: any) => Vow<TResult2> | PromiseVow<TResult2> | TResult2) | undefined;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"4BAKa,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;sBAKvB,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;;;;;uEASrB,KAAK,KAAK,GAAG,OAAO,qBAAqB,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;;;;;;;;;;;;;;aAUzE,MAAM,QAAQ,CAAC,CAAC;;;WAShB,OAAO,qBAAqB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;;2BAK/C,OAAO,kBAAkB,EAAE,UAAU,CACjD,KAAS,EAAE,WAAW,CAAC,CAAC,CACrB;8BAKS;IACZ,GAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAChB,QAAY,EAAE,YAAY,CAAC,CAAC,CAAC;CAC1B;qCAKS;IACZ,GAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAChB,QAAY,EAAE,YAAY,CAAC,CAAC,CAAC;IAC7B,OAAW,EAAE,QAAQ,CAAC,CAAC,CAAA;CACpB;;oBAK2B,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,IAAI;oBAAkB,GAAG,GAAG,IAAI;;;2BAQ7D,CAAC,KAAK,IAAI,QAAQ,CAAC,GAAG,WAAW,QAAQ,CAAC,GAAG,QAAQ;2BACpD,GAAG,KAAK,IAAI,QAAQ,CAAC,GAAG,WAAW,QAAQ,CAAC,GAAG,QAAQ"}
|
package/src/types.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
export {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @template T
|
|
6
|
+
* @typedef {PromiseLike<T | Vow<T>>} PromiseVow
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @template T
|
|
11
|
+
* @typedef {import('./E').ERef<T>} ERef
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a type that accepts both near and marshalled references that were
|
|
16
|
+
* returned from `Remotable` or `Far`, and also promises for such references.
|
|
17
|
+
*
|
|
18
|
+
* @template Primary The type of the primary reference.
|
|
19
|
+
* @template [Local=import('./E').DataOnly<Primary>] The local properties of the object.
|
|
20
|
+
* @typedef {ERef<Local & import('@endo/eventual-send').RemotableBrand<Local, Primary>>} FarRef
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @template [T=any]
|
|
25
|
+
* @typedef {object} VowV0 The first version of the vow implementation
|
|
26
|
+
* object. CAVEAT: These methods must never be changed or added to, to provide
|
|
27
|
+
* forward/backward compatibility. Create a new object and bump its version
|
|
28
|
+
* number instead.
|
|
29
|
+
*
|
|
30
|
+
* @property {() => Promise<T>} shorten Attempt to unwrap all vows in this
|
|
31
|
+
* promise chain, returning a promise for the final value. A rejection may
|
|
32
|
+
* indicate a temporary routing failure requiring a retry, otherwise that the
|
|
33
|
+
* decider of the terminal promise rejected it.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @template [T=any]
|
|
38
|
+
* @typedef {object} VowPayload
|
|
39
|
+
* @property {import('@endo/eventual-send').FarRef<VowV0<T>>} vowV0
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @template [T=any]
|
|
44
|
+
* @typedef {import('@endo/pass-style').CopyTagged<
|
|
45
|
+
* 'Vow', VowPayload<T>
|
|
46
|
+
* >} Vow
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @template [T=any]
|
|
51
|
+
* @typedef {{
|
|
52
|
+
* vow: Vow<T>,
|
|
53
|
+
* resolver: VowResolver<T>,
|
|
54
|
+
* }} VowKit
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @template [T=any]
|
|
59
|
+
* @typedef {{
|
|
60
|
+
* vow: Vow<T>,
|
|
61
|
+
* resolver: VowResolver<T>,
|
|
62
|
+
* promise: Promise<T>
|
|
63
|
+
* }} VowPromiseKit
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @template [T=any]
|
|
68
|
+
* @typedef {{ resolve(value?: T | PromiseVow<T>): void, reject(reason?: any): void }} VowResolver
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @template [T=any]
|
|
73
|
+
* @template [TResult1=T]
|
|
74
|
+
* @template [TResult2=T]
|
|
75
|
+
* @typedef {object} Watcher
|
|
76
|
+
* @property {(value: T) => Vow<TResult1> | PromiseVow<TResult1> | TResult1} [onFulfilled]
|
|
77
|
+
* @property {(reason: any) => Vow<TResult2> | PromiseVow<TResult2> | TResult2} [onRejected]
|
|
78
|
+
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vow-utils.d.ts","sourceRoot":"","sources":["vow-utils.js"],"names":[],"mappings":";AAoBO,2CAHI,GAAG,+CAgBb;4BAhC2B,qBAAqB"}
|
package/src/vow-utils.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { E as basicE } from '@endo/eventual-send';
|
|
3
|
+
import { getTag, passStyleOf } from '@endo/pass-style';
|
|
4
|
+
|
|
5
|
+
// TODO: `isPassable` should come from @endo/pass-style
|
|
6
|
+
import { isPassable } from '@agoric/base-zone';
|
|
7
|
+
|
|
8
|
+
export { basicE };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A vow is a passable tagged as 'Vow'. Its payload is a record with
|
|
12
|
+
* API-versioned remotables. payload.vowV0 is the API for the `watch` and
|
|
13
|
+
* `when` operators to use for retriable shortening of the vow chain.
|
|
14
|
+
*
|
|
15
|
+
* If the specimen is a Vow, return its payload, otherwise undefined.
|
|
16
|
+
*
|
|
17
|
+
* @template T
|
|
18
|
+
* @param {any} specimen any value to verify as a vow
|
|
19
|
+
* @returns {import('./types').VowPayload<T> | undefined} undefined if specimen is not a vow, otherwise the vow's payload.
|
|
20
|
+
*/
|
|
21
|
+
export const getVowPayload = specimen => {
|
|
22
|
+
const isVow =
|
|
23
|
+
isPassable(specimen) &&
|
|
24
|
+
passStyleOf(specimen) === 'tagged' &&
|
|
25
|
+
getTag(specimen) === 'Vow';
|
|
26
|
+
if (!isVow) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const vow = /** @type {import('./types').Vow<T>} */ (
|
|
31
|
+
/** @type {unknown} */ (specimen)
|
|
32
|
+
);
|
|
33
|
+
return vow.payload;
|
|
34
|
+
};
|
package/src/vow.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export function prepareVowKit(zone: import('@agoric/base-zone').Zone, watchPromise?: ((p: Promise<any>, watcher: import("./watch-promise.js").PromiseWatcher, ...watcherArgs: unknown[]) => void) | undefined): <T>() => import("./types.js").VowKit<T>;
|
|
2
|
+
export type VowEphemera = Partial<import('@endo/promise-kit').PromiseKit<any>> & Pick<import('@endo/promise-kit').PromiseKit<any>, 'promise'>;
|
|
3
|
+
//# sourceMappingURL=vow.d.ts.map
|
package/src/vow.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vow.d.ts","sourceRoot":"","sources":["vow.js"],"names":[],"mappings":"AAkBO,oCAHI,OAAO,mBAAmB,EAAE,IAAI,oLAwH1C;0BA7HY,QAAQ,OAAO,mBAAmB,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,GAChE,KAAS,OAAO,mBAAmB,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC"}
|
package/src/vow.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { makePromiseKit } from '@endo/promise-kit';
|
|
3
|
+
import { M } from '@endo/patterns';
|
|
4
|
+
import { makeTagged } from '@endo/pass-style';
|
|
5
|
+
import { PromiseWatcherI, watchPromiseShim } from './watch-promise.js';
|
|
6
|
+
|
|
7
|
+
const sink = () => {};
|
|
8
|
+
harden(sink);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Partial<import('@endo/promise-kit').PromiseKit<any>> &
|
|
12
|
+
* Pick<import('@endo/promise-kit').PromiseKit<any>, 'promise'>} VowEphemera
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
17
|
+
* @param {typeof watchPromiseShim} [watchPromise]
|
|
18
|
+
*/
|
|
19
|
+
export const prepareVowKit = (zone, watchPromise = watchPromiseShim) => {
|
|
20
|
+
/** @type {WeakMap<import('./types.js').VowResolver, VowEphemera>} */
|
|
21
|
+
const resolverToEphemera = new WeakMap();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the current incarnation's promise kit associated with a vowV0.
|
|
25
|
+
*
|
|
26
|
+
* @param {import('./types.js').VowResolver} resolver
|
|
27
|
+
*/
|
|
28
|
+
const provideCurrentKit = resolver => {
|
|
29
|
+
let pk = resolverToEphemera.get(resolver);
|
|
30
|
+
if (pk) {
|
|
31
|
+
return pk;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pk = makePromiseKit();
|
|
35
|
+
pk.promise.catch(sink); // silence unhandled rejection
|
|
36
|
+
resolverToEphemera.set(resolver, pk);
|
|
37
|
+
return pk;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {import('./types.js').VowResolver} resolver
|
|
42
|
+
*/
|
|
43
|
+
const getPromiseKitForResolution = resolver => {
|
|
44
|
+
const kit = provideCurrentKit(resolver);
|
|
45
|
+
if (kit.resolve) {
|
|
46
|
+
// Resolution is a one-time event, so forget the resolve/reject functions.
|
|
47
|
+
resolverToEphemera.set(resolver, harden({ promise: kit.promise }));
|
|
48
|
+
}
|
|
49
|
+
return kit;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const makeVowInternalsKit = zone.exoClassKit(
|
|
53
|
+
'VowInternalsKit',
|
|
54
|
+
{
|
|
55
|
+
vowV0: M.interface('VowV0', {
|
|
56
|
+
shorten: M.call().returns(M.promise()),
|
|
57
|
+
}),
|
|
58
|
+
resolver: M.interface('VowResolver', {
|
|
59
|
+
resolve: M.call().optional(M.any()).returns(),
|
|
60
|
+
reject: M.call().optional(M.any()).returns(),
|
|
61
|
+
}),
|
|
62
|
+
watchNextStep: PromiseWatcherI,
|
|
63
|
+
},
|
|
64
|
+
() => ({
|
|
65
|
+
value: undefined,
|
|
66
|
+
// The stepStatus is null if the promise step hasn't settled yet.
|
|
67
|
+
stepStatus: /** @type {null | 'fulfilled' | 'rejected'} */ (null),
|
|
68
|
+
}),
|
|
69
|
+
{
|
|
70
|
+
vowV0: {
|
|
71
|
+
/**
|
|
72
|
+
* @returns {Promise<any>}
|
|
73
|
+
*/
|
|
74
|
+
async shorten() {
|
|
75
|
+
const { stepStatus, value } = this.state;
|
|
76
|
+
switch (stepStatus) {
|
|
77
|
+
case 'fulfilled':
|
|
78
|
+
return value;
|
|
79
|
+
case 'rejected':
|
|
80
|
+
throw value;
|
|
81
|
+
case null:
|
|
82
|
+
return provideCurrentKit(this.facets.resolver).promise;
|
|
83
|
+
default:
|
|
84
|
+
throw new TypeError(`unexpected stepStatus ${stepStatus}`);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
resolver: {
|
|
89
|
+
/**
|
|
90
|
+
* @param {any} [value]
|
|
91
|
+
*/
|
|
92
|
+
resolve(value) {
|
|
93
|
+
const { resolver } = this.facets;
|
|
94
|
+
const { promise, resolve } = getPromiseKitForResolution(resolver);
|
|
95
|
+
if (resolve) {
|
|
96
|
+
resolve(value);
|
|
97
|
+
watchPromise(promise, this.facets.watchNextStep);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* @param {any} [reason]
|
|
102
|
+
*/
|
|
103
|
+
reject(reason) {
|
|
104
|
+
const { resolver, watchNextStep } = this.facets;
|
|
105
|
+
const { reject } = getPromiseKitForResolution(resolver);
|
|
106
|
+
if (reject) {
|
|
107
|
+
reject(reason);
|
|
108
|
+
watchNextStep.onRejected(reason);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
watchNextStep: {
|
|
113
|
+
onFulfilled(value) {
|
|
114
|
+
this.state.stepStatus = 'fulfilled';
|
|
115
|
+
this.state.value = value;
|
|
116
|
+
},
|
|
117
|
+
onRejected(reason) {
|
|
118
|
+
this.state.stepStatus = 'rejected';
|
|
119
|
+
this.state.value = reason;
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @template T
|
|
127
|
+
* @returns {import('./types.js').VowKit<T>}
|
|
128
|
+
*/
|
|
129
|
+
const makeVowKit = () => {
|
|
130
|
+
const { resolver, vowV0 } = makeVowInternalsKit();
|
|
131
|
+
const vow = makeTagged('Vow', harden({ vowV0 }));
|
|
132
|
+
return harden({ resolver, vow });
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return makeVowKit;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
harden(prepareVowKit);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const PromiseWatcherI: import("@endo/patterns").InterfaceGuard<{
|
|
2
|
+
onFulfilled: import("@endo/patterns").MethodGuard;
|
|
3
|
+
onRejected: import("@endo/patterns").MethodGuard;
|
|
4
|
+
}>;
|
|
5
|
+
export function watchPromiseShim(p: Promise<any>, watcher: PromiseWatcher, ...watcherArgs: unknown[]): void;
|
|
6
|
+
export type PromiseWatcher = {
|
|
7
|
+
onFulfilled?: ((fulfilment: unknown, ...args: unknown[]) => void) | undefined;
|
|
8
|
+
onRejected?: ((reason: unknown, ...args: unknown[]) => void) | undefined;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=watch-promise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-promise.d.ts","sourceRoot":"","sources":["watch-promise.js"],"names":[],"mappings":"AASA;;;GAGG;AAuCI,oCALI,QAAQ,GAAG,CAAC,WACZ,cAAc,kBACX,OAAO,KACR,IAAI,CAUhB;;gCA3C0B,OAAO,WAAW,OAAO,EAAE,KAAK,IAAI;2BACxC,OAAO,WAAW,OAAO,EAAE,KAAK,IAAI"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { M } from '@endo/patterns';
|
|
3
|
+
|
|
4
|
+
import { basicE } from './vow-utils.js';
|
|
5
|
+
|
|
6
|
+
const { Fail } = assert;
|
|
7
|
+
|
|
8
|
+
const { apply } = Reflect;
|
|
9
|
+
|
|
10
|
+
export const PromiseWatcherI = M.interface('PromiseWatcher', {
|
|
11
|
+
onFulfilled: M.call(M.any()).rest(M.any()).returns(),
|
|
12
|
+
onRejected: M.call(M.any()).rest(M.any()).returns(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} PromiseWatcher
|
|
17
|
+
* @property {(fulfilment: unknown, ...args: unknown[]) => void} [onFulfilled]
|
|
18
|
+
* @property {(reason: unknown, ...args: unknown[]) => void} [onRejected]
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adapt a promise watcher method to E.when.
|
|
23
|
+
* @param {Record<PropertyKey, (...args: unknown[]) => unknown>} that
|
|
24
|
+
* @param {PropertyKey} prop
|
|
25
|
+
* @param {unknown[]} postArgs
|
|
26
|
+
*/
|
|
27
|
+
const callMeMaybe = (that, prop, postArgs) => {
|
|
28
|
+
const fn = that[prop];
|
|
29
|
+
if (!fn) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
assert.typeof(fn, 'function');
|
|
33
|
+
/**
|
|
34
|
+
* @param {unknown} arg value or reason
|
|
35
|
+
*/
|
|
36
|
+
const wrapped = arg => {
|
|
37
|
+
// Don't return a value, to prevent E.when from subscribing to a resulting
|
|
38
|
+
// promise.
|
|
39
|
+
apply(fn, that, [arg, ...postArgs]);
|
|
40
|
+
};
|
|
41
|
+
return wrapped;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Shim the promise watcher behaviour when VatData.watchPromise is not available.
|
|
46
|
+
*
|
|
47
|
+
* @param {Promise<any>} p
|
|
48
|
+
* @param {PromiseWatcher} watcher
|
|
49
|
+
* @param {...unknown} watcherArgs
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
export const watchPromiseShim = (p, watcher, ...watcherArgs) => {
|
|
53
|
+
Promise.resolve(p) === p || Fail`watchPromise only watches promises`;
|
|
54
|
+
const onFulfilled = callMeMaybe(watcher, 'onFulfilled', watcherArgs);
|
|
55
|
+
const onRejected = callMeMaybe(watcher, 'onRejected', watcherArgs);
|
|
56
|
+
onFulfilled ||
|
|
57
|
+
onRejected ||
|
|
58
|
+
Fail`promise watcher must implement at least one handler method`;
|
|
59
|
+
void basicE.when(p, onFulfilled, onRejected);
|
|
60
|
+
};
|
|
61
|
+
harden(watchPromiseShim);
|
package/src/watch.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export function prepareWatch(zone: import('@agoric/base-zone').Zone, makeVowKit: () => import('./types.js').VowKit<any>, watchPromise?: ((p: Promise<any>, watcher: import("./watch-promise.js").PromiseWatcher, ...watcherArgs: unknown[]) => void) | undefined, isRetryableReason?: ((reason: any) => boolean) | undefined): <T = any, TResult1 = T, TResult2 = T>(specimenP: import("./types.js").ERef<T | import("./types.js").Vow<T>>, watcher?: import("./types.js").Watcher<T, TResult1, TResult2> | undefined, watcherContext?: unknown) => import("./types.js").Vow<TResult1 | TResult2>;
|
|
2
|
+
export type Watch = ReturnType<typeof prepareWatch>;
|
|
3
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["watch.js"],"names":[],"mappings":"AAqHO,mCALI,OAAO,mBAAmB,EAAE,IAAI,cAChC,MAAM,OAAO,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,yKAE7B,GAAG,KAAK,OAAO,yNAqBtB,OAAO,mDAsBnB;oBAIa,WAAW,mBAAmB,CAAC"}
|