@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/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,4 @@
1
+ export { basicE };
2
+ export function getVowPayload<T>(specimen: any): import("./types").VowPayload<T> | undefined;
3
+ import { E as basicE } from '@endo/eventual-send';
4
+ //# sourceMappingURL=vow-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vow-utils.d.ts","sourceRoot":"","sources":["vow-utils.js"],"names":[],"mappings":";AAoBO,2CAHI,GAAG,+CAgBb;4BAhC2B,qBAAqB"}
@@ -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
@@ -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"}