@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/src/vow-e.js ADDED
@@ -0,0 +1,101 @@
1
+ // @ts-check
2
+ /* global globalThis */
3
+ import { PromiseWatcherI } from '@agoric/base-zone';
4
+ import makeE from './E.js';
5
+
6
+ /**
7
+ * @import {PromiseWatcher} from '@agoric/base-zone'
8
+ */
9
+
10
+ const { Fail, bare } = assert;
11
+
12
+ /**
13
+ * @param {import('@agoric/base-zone').Zone} zone
14
+ * @param {object} powers
15
+ * @param {import('./when.js').When} powers.when
16
+ * @param {import('./watch.js').Watch} powers.watch
17
+ * @param {import('./types.js').IsRetryableReason} powers.isRetryableReason
18
+ */
19
+ export const makeVowE = (zone, { when, watch, isRetryableReason }) => {
20
+ /** @type {import('@agoric/base-zone').PromiseWatcher} */
21
+ const retryRejectionPromiseWatcher = zone.exo(
22
+ 'rejectRetriesPromiseWatcher',
23
+ PromiseWatcherI,
24
+ {
25
+ onFulfilled(_result) {},
26
+ onRejected(reason, failedOp) {
27
+ if (isRetryableReason(reason)) {
28
+ Fail`Pending ${bare(failedOp)} could not retry; ${reason}`;
29
+ }
30
+ },
31
+ },
32
+ );
33
+
34
+ /**
35
+ * @param {unknown} specimenP
36
+ * @param {string} opDescription
37
+ * @param {import('./E.js').EOptions} [opts]
38
+ * @returns {Promise<any>}
39
+ */
40
+ const unwrap = (specimenP, opDescription, opts = {}) => {
41
+ const { watch: optWatch = false } = opts;
42
+
43
+ // Watch the specimen in case it is an ephemeral promise.
44
+ /** @type {Promise<any>} */
45
+ let promise;
46
+
47
+ if (optWatch) {
48
+ /** @type {import('./types.js').Vow<any>} */
49
+ let vow;
50
+ if (Array.isArray(optWatch)) {
51
+ // Arguments for the watch.
52
+ vow = watch(specimenP, ...optWatch);
53
+ } else if (
54
+ Object(optWatch) === optWatch &&
55
+ typeof optWatch === 'object'
56
+ ) {
57
+ // Just a watcher.
58
+ vow = watch(specimenP, optWatch);
59
+ } else {
60
+ vow = watch(specimenP);
61
+ }
62
+
63
+ // Make a prompt ephemeral promise for a (potentially durable) vow.
64
+ promise = HandledPromise.resolve(vow);
65
+ } else {
66
+ // Resolve to an non-prompt ephemeral promise.
67
+ promise = when(specimenP);
68
+ }
69
+
70
+ // Watch the ephemeral result promise to ensure that if its settlement is
71
+ // lost due to upgrade of this incarnation, we will at least cause an
72
+ // unhandled rejection in the new incarnation.
73
+ zone.watchPromise(
74
+ harden(promise),
75
+ retryRejectionPromiseWatcher,
76
+ opDescription,
77
+ );
78
+
79
+ return promise;
80
+ };
81
+
82
+ /**
83
+ * A convenient vow-shortening E, which returns platform result promises. It
84
+ * can cause problems when used in a durable context, because the resulting
85
+ * promises are ephemeral and likely not promptly resolved.
86
+ *
87
+ * Instead of this `vowE`, when manipulating durable vows, use
88
+ * `vowTools.watch` prepared in a durable zone with the standard,
89
+ * non-unwrapping `import('@endo/far').E`. This avoids the creation of
90
+ * promise/vow chains containing non-prompt ephemeral promises.
91
+ */
92
+ const vowE = makeE(globalThis.HandledPromise, {
93
+ unwrap,
94
+ additional: {
95
+ when,
96
+ watch,
97
+ },
98
+ });
99
+
100
+ return vowE;
101
+ };
@@ -0,0 +1,20 @@
1
+ export { basicE };
2
+ /**
3
+ * The least possibly restrictive guard for a `watch` watcher's `onFulfilled` or
4
+ * `onRejected` reaction
5
+ */
6
+ export const ReactionGuard: import("@endo/patterns").MethodGuard;
7
+ export const VowShape: import("@endo/patterns").Matcher;
8
+ export function OrVow$(shape: any): import("@endo/patterns").Matcher;
9
+ export function EVow$(shape: any): import("@endo/patterns").Matcher;
10
+ export function isVow(specimen: unknown): specimen is Vow;
11
+ export function getVowPayload<T>(specimen: any): VowPayload<T> | undefined;
12
+ export function toPassableCap(k: PassableCap | Vow): PassableCap;
13
+ export function makeAsVow(makeVowKit: MakeVowKit): <T extends unknown>(fn: (...args: any[]) => Vow<Awaited<T>> | Awaited<T> | PromiseVow<T>) => Vow<Awaited<T>>;
14
+ import { E as basicE } from '@endo/eventual-send';
15
+ import type { Vow } from './types.js';
16
+ import type { VowPayload } from './types.js';
17
+ import type { PassableCap } from '@endo/pass-style';
18
+ import type { MakeVowKit } from './vow.js';
19
+ import type { PromiseVow } from './types.js';
20
+ //# sourceMappingURL=vow-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vow-utils.d.ts","sourceRoot":"","sources":["vow-utils.js"],"names":[],"mappings":";AAaA;;;GAGG;AACH,iEAAgF;AAEhF,wDAKE;AAEK,qEAA6C;AAC7C,oEAAkE;AAMlE,gCAHI,OAAO,GACL,QAAQ,IAAI,GAAG,CAGyB;AAc9C,8BAJM,CAAC,YACH,GAAG,GACD,WAAW,CAAC,CAAC,GAAG,SAAS,CASrC;AAoBM,iCAHI,WAAW,GAAG,GAAG,GACf,WAAW,CAUvB;AAIM,sCADK,UAAU,IAKD,CAAC,sBACT,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,KAC9D,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAiB7B;4BA7G2B,qBAAqB;yBAMH,YAAY;gCAAZ,YAAY;iCAD5B,kBAAkB;gCAEnB,UAAU;gCADO,YAAY"}
@@ -0,0 +1,112 @@
1
+ // @ts-check
2
+ import { E as basicE } from '@endo/eventual-send';
3
+ import { isPassable } from '@endo/pass-style';
4
+ import { M, matches } from '@endo/patterns';
5
+
6
+ /**
7
+ * @import {PassableCap} from '@endo/pass-style';
8
+ * @import {VowPayload, Vow, PromiseVow} from './types.js';
9
+ * @import {MakeVowKit} from './vow.js';
10
+ */
11
+
12
+ export { basicE };
13
+
14
+ /**
15
+ * The least possibly restrictive guard for a `watch` watcher's `onFulfilled` or
16
+ * `onRejected` reaction
17
+ */
18
+ export const ReactionGuard = M.call(M.any()).optional(M.any()).returns(M.any());
19
+
20
+ export const VowShape = M.tagged(
21
+ 'Vow',
22
+ M.splitRecord({
23
+ vowV0: M.remotable('VowV0'),
24
+ }),
25
+ );
26
+
27
+ export const OrVow$ = shape => M.or(shape, VowShape);
28
+ export const EVow$ = shape => M.or(OrVow$(shape), M.promise(/* shape */));
29
+
30
+ /**
31
+ * @param {unknown} specimen
32
+ * @returns {specimen is Vow}
33
+ */
34
+ export const isVow = specimen =>
35
+ isPassable(specimen) && matches(specimen, VowShape);
36
+ harden(isVow);
37
+
38
+ /**
39
+ * A vow is a passable tagged as 'Vow'. Its payload is a record with
40
+ * API-versioned remotables. payload.vowV0 is the API for the `watch` and
41
+ * `when` operators to use for retriable shortening of the vow chain.
42
+ *
43
+ * If the specimen is a Vow, return its payload, otherwise undefined.
44
+ *
45
+ * @template T
46
+ * @param {any} specimen any value to verify as a vow
47
+ * @returns {VowPayload<T> | undefined} undefined if specimen is not a vow, otherwise the vow's payload.
48
+ */
49
+ export const getVowPayload = specimen => {
50
+ if (!isVow(specimen)) {
51
+ return undefined;
52
+ }
53
+
54
+ const vow = /** @type {Vow<T>} */ (/** @type {unknown} */ (specimen));
55
+ return vow.payload;
56
+ };
57
+ harden(getVowPayload);
58
+
59
+ /**
60
+ * For when you have a Vow or a `PassableCap` (`RemotableObject` or
61
+ * passable `Promise`) and you need `PassableCap`,
62
+ * typically to serve as a key in a `Map`, `WeakMap`, `Store`, or `WeakStore`.
63
+ *
64
+ * Relies on, and encapsulates, the current "V0" representation of a vow
65
+ * as containing a unique remotable shortener.
66
+ *
67
+ * Note: if `k` is not a `Vow`, `toPassableCap` does no enforcement that `k`
68
+ * is already a `PassableCap`. Rather, it just acts as an identity function
69
+ * returning `k` without further checking. The types only describe the
70
+ * intended use. (If warranted, we may later add such enforcement, so please
71
+ * do not rely on either the presence or absence of such enforcement.)
72
+ *
73
+ * @param {PassableCap | Vow} k
74
+ * @returns {PassableCap}
75
+ */
76
+ export const toPassableCap = k => {
77
+ const payload = getVowPayload(k);
78
+ if (payload === undefined) {
79
+ return /** @type {PassableCap} */ (k);
80
+ }
81
+ const { vowV0 } = payload;
82
+ // vowMap.set(vowV0, h);
83
+ return vowV0;
84
+ };
85
+ harden(toPassableCap);
86
+
87
+ /** @param {MakeVowKit} makeVowKit */
88
+ export const makeAsVow = makeVowKit => {
89
+ /**
90
+ * Helper function that coerces the result of a function to a Vow. Helpful
91
+ * for scenarios like a synchronously thrown error.
92
+ * @template {any} T
93
+ * @param {(...args: any[]) => Vow<Awaited<T>> | Awaited<T> | PromiseVow<T>} fn
94
+ * @returns {Vow<Awaited<T>>}
95
+ */
96
+ const asVow = fn => {
97
+ let result;
98
+ try {
99
+ result = fn();
100
+ } catch (e) {
101
+ result = Promise.reject(e);
102
+ }
103
+ if (isVow(result)) {
104
+ return result;
105
+ }
106
+ const kit = makeVowKit();
107
+ kit.resolver.resolve(result);
108
+ return kit.vow;
109
+ };
110
+ return harden(asVow);
111
+ };
112
+ harden(makeAsVow);
package/src/vow.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export function prepareVowKit(zone: Zone): <T>() => VowKit<T>;
2
+ export type VowEphemera = Partial<PromiseKit<any>> & Pick<PromiseKit<any>, "promise">;
3
+ export type MakeVowKit = ReturnType<typeof prepareVowKit>;
4
+ import type { Zone } from '@agoric/base-zone';
5
+ import type { VowKit } from './types.js';
6
+ import type { PromiseKit } from '@endo/promise-kit';
7
+ //# sourceMappingURL=vow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vow.d.ts","sourceRoot":"","sources":["vow.js"],"names":[],"mappings":"AA0BO,oCAFI,IAAI,IAkLA,CAAC,OACD,OAAO,CAAC,CAAC,CASvB;0BAjMY,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,GACpC,IAAQ,CAAC,WAAW,GAAG,CAAC,EAAE,SAAS,CAAC;yBAiMvB,UAAU,CAAC,OAAO,aAAa,CAAC;0BA3MvB,mBAAmB;4BAEJ,YAAY;gCAHrB,mBAAmB"}
package/src/vow.js ADDED
@@ -0,0 +1,216 @@
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 } from '@agoric/base-zone';
6
+
7
+ const { details: X } = assert;
8
+
9
+ /**
10
+ * @import {PromiseKit} from '@endo/promise-kit';
11
+ * @import {Zone} from '@agoric/base-zone';
12
+ * @import {MapStore} from '@agoric/store';
13
+ * @import {VowResolver, VowKit} from './types.js';
14
+ */
15
+
16
+ const sink = () => {};
17
+ harden(sink);
18
+
19
+ /**
20
+ * @typedef {Partial<PromiseKit<any>> &
21
+ * Pick<PromiseKit<any>, 'promise'>} VowEphemera
22
+ */
23
+
24
+ /**
25
+ * @param {Zone} zone
26
+ */
27
+ export const prepareVowKit = zone => {
28
+ /** @type {WeakMap<VowResolver, VowEphemera>} */
29
+ const resolverToEphemera = new WeakMap();
30
+
31
+ /** @type {WeakMap<VowResolver, any>} */
32
+ const resolverToNonStoredValue = new WeakMap();
33
+
34
+ /**
35
+ * Get the current incarnation's promise kit associated with a vowV0.
36
+ *
37
+ * @param {VowResolver} resolver
38
+ */
39
+ const provideCurrentKit = resolver => {
40
+ let pk = resolverToEphemera.get(resolver);
41
+ if (pk) {
42
+ return pk;
43
+ }
44
+
45
+ pk = makePromiseKit();
46
+ pk.promise.catch(sink); // silence unhandled rejection
47
+ resolverToEphemera.set(resolver, pk);
48
+ return pk;
49
+ };
50
+
51
+ /**
52
+ * @param {VowResolver} resolver
53
+ */
54
+ const getPromiseKitForResolution = resolver => {
55
+ const kit = provideCurrentKit(resolver);
56
+ if (kit.resolve) {
57
+ // Resolution is a one-time event, so forget the resolve/reject functions.
58
+ resolverToEphemera.set(resolver, harden({ promise: kit.promise }));
59
+ }
60
+ return kit;
61
+ };
62
+
63
+ const makeVowInternalsKit = zone.exoClassKit(
64
+ 'VowInternalsKit',
65
+ {
66
+ vowV0: M.interface('VowV0', {
67
+ shorten: M.call().returns(M.promise()),
68
+ }),
69
+ resolver: M.interface('VowResolver', {
70
+ resolve: M.call().optional(M.raw()).returns(),
71
+ reject: M.call().optional(M.raw()).returns(),
72
+ }),
73
+ watchNextStep: PromiseWatcherI,
74
+ },
75
+ () => ({
76
+ value: /** @type {any} */ (undefined),
77
+ // The stepStatus is null if the promise step hasn't settled yet.
78
+ stepStatus: /** @type {null | 'pending' | 'fulfilled' | 'rejected'} */ (
79
+ null
80
+ ),
81
+ isStoredValue: /** @type {boolean} */ (false),
82
+ /**
83
+ * Map for future properties that aren't in the schema.
84
+ * UNTIL https://github.com/Agoric/agoric-sdk/issues/7407
85
+ * @type {MapStore<any, any> | undefined}
86
+ */
87
+ extra: undefined,
88
+ }),
89
+ {
90
+ vowV0: {
91
+ /**
92
+ * @returns {Promise<any>}
93
+ */
94
+ async shorten() {
95
+ const { stepStatus, isStoredValue, value } = this.state;
96
+ const { resolver } = this.facets;
97
+
98
+ switch (stepStatus) {
99
+ case 'fulfilled': {
100
+ if (isStoredValue) {
101
+ // Always return a stored fulfilled value.
102
+ return value;
103
+ } else if (resolverToNonStoredValue.has(resolver)) {
104
+ // Non-stored value is available.
105
+ return resolverToNonStoredValue.get(resolver);
106
+ }
107
+ // We can't recover the non-stored value, so throw the
108
+ // explanation.
109
+ throw value;
110
+ }
111
+ case 'rejected': {
112
+ if (!isStoredValue && resolverToNonStoredValue.has(resolver)) {
113
+ // Non-stored reason is available.
114
+ throw resolverToNonStoredValue.get(resolver);
115
+ }
116
+ // Always throw a stored rejection reason.
117
+ throw value;
118
+ }
119
+ case null:
120
+ case 'pending':
121
+ return provideCurrentKit(this.facets.resolver).promise;
122
+ default:
123
+ throw new TypeError(`unexpected stepStatus ${stepStatus}`);
124
+ }
125
+ },
126
+ },
127
+ resolver: {
128
+ /**
129
+ * @param {any} [value]
130
+ */
131
+ resolve(value) {
132
+ const { resolver } = this.facets;
133
+ const { stepStatus } = this.state;
134
+ const { resolve } = getPromiseKitForResolution(resolver);
135
+ if (resolve) {
136
+ resolve(value);
137
+ }
138
+ if (stepStatus === null) {
139
+ this.state.stepStatus = 'pending';
140
+ zone.watchPromise(
141
+ HandledPromise.resolve(value),
142
+ this.facets.watchNextStep,
143
+ );
144
+ }
145
+ },
146
+ /**
147
+ * @param {any} [reason]
148
+ */
149
+ reject(reason) {
150
+ const { resolver, watchNextStep } = this.facets;
151
+ const { stepStatus } = this.state;
152
+ const { reject } = getPromiseKitForResolution(resolver);
153
+ if (reject) {
154
+ reject(reason);
155
+ }
156
+ if (stepStatus === null) {
157
+ watchNextStep.onRejected(reason);
158
+ }
159
+ },
160
+ },
161
+ watchNextStep: {
162
+ onFulfilled(value) {
163
+ const { resolver } = this.facets;
164
+ const { resolve } = getPromiseKitForResolution(resolver);
165
+ harden(value);
166
+ if (resolve) {
167
+ resolve(value);
168
+ }
169
+ this.state.stepStatus = 'fulfilled';
170
+ this.state.isStoredValue = zone.isStorable(value);
171
+ if (this.state.isStoredValue) {
172
+ this.state.value = value;
173
+ } else {
174
+ resolverToNonStoredValue.set(resolver, value);
175
+ this.state.value = assert.error(
176
+ X`Vow fulfillment value was not stored: ${value}`,
177
+ );
178
+ }
179
+ },
180
+ onRejected(reason) {
181
+ const { resolver } = this.facets;
182
+ const { reject } = getPromiseKitForResolution(resolver);
183
+ harden(reason);
184
+ if (reject) {
185
+ reject(reason);
186
+ }
187
+ this.state.stepStatus = 'rejected';
188
+ this.state.isStoredValue = zone.isStorable(reason);
189
+ if (this.state.isStoredValue) {
190
+ this.state.value = reason;
191
+ } else {
192
+ resolverToNonStoredValue.set(resolver, reason);
193
+ this.state.value = assert.error(
194
+ X`Vow rejection reason was not stored: ${reason}`,
195
+ );
196
+ }
197
+ },
198
+ },
199
+ },
200
+ );
201
+
202
+ /**
203
+ * @template T
204
+ * @returns {VowKit<T>}
205
+ */
206
+ const makeVowKit = () => {
207
+ const { resolver, vowV0 } = makeVowInternalsKit();
208
+ const vow = makeTagged('Vow', harden({ vowV0 }));
209
+ return harden({ resolver, vow });
210
+ };
211
+
212
+ return makeVowKit;
213
+ };
214
+ /** @typedef {ReturnType<typeof prepareVowKit>} MakeVowKit */
215
+
216
+ harden(prepareVowKit);
@@ -0,0 +1,20 @@
1
+ export function prepareWatchUtils(zone: Zone, { watch, when, makeVowKit, isRetryableReason }: {
2
+ watch: Watch;
3
+ when: When;
4
+ makeVowKit: () => VowKit<any>;
5
+ isRetryableReason: IsRetryableReason;
6
+ }): () => import("@endo/exo").Guarded<{
7
+ /**
8
+ * @param {EVow<unknown>[]} vows
9
+ */
10
+ all(vows: EVow<unknown>[]): import("./types.js").Vow<any[]>;
11
+ /** @type {AsPromiseFunction} */
12
+ asPromise(specimenP: any, watcher: import("./types.js").Watcher<any, any, never, any[]> | undefined, watcherArgs: any[] | undefined): Promise<any | never>;
13
+ }>;
14
+ import type { Zone } from '@agoric/base-zone';
15
+ import type { Watch } from './watch.js';
16
+ import type { When } from './when.js';
17
+ import type { VowKit } from './types.js';
18
+ import type { IsRetryableReason } from './types.js';
19
+ import type { EVow } from './types.js';
20
+ //# sourceMappingURL=watch-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-utils.d.ts","sourceRoot":"","sources":["watch-utils.js"],"names":[],"mappings":"AA4CO,wCAPI,IAAI,kDAEZ;IAAsB,KAAK,EAAnB,KAAK;IACQ,IAAI,EAAjB,IAAI;IACsB,UAAU,EAApC,MAAM,OAAO,GAAG,CAAC;IACS,iBAAiB,EAA3C,iBAAiB;CAC3B;IAsCO;;OAEG;cADQ,KAAK,OAAO,CAAC,EAAE;IAuC1B,gCAAgC;;GAgGvC;0BAhNsB,mBAAmB;2BAClB,YAAY;0BACb,WAAW;4BACmC,YAAY;uCAAZ,YAAY;0BAAZ,YAAY"}