@agoric/vow 0.1.1-dev-4f70e66.0 → 0.1.1-dev-8edf902.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/vow",
3
- "version": "0.1.1-dev-4f70e66.0+4f70e66",
3
+ "version": "0.1.1-dev-8edf902.0+8edf902",
4
4
  "description": "Remote (shortening and disconnection-tolerant) Promise-likes",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -20,8 +20,8 @@
20
20
  "lint:types": "tsc"
21
21
  },
22
22
  "dependencies": {
23
- "@agoric/base-zone": "0.1.1-dev-4f70e66.0+4f70e66",
24
- "@agoric/internal": "0.3.3-dev-4f70e66.0+4f70e66",
23
+ "@agoric/base-zone": "0.1.1-dev-8edf902.0+8edf902",
24
+ "@agoric/internal": "0.3.3-dev-8edf902.0+8edf902",
25
25
  "@endo/env-options": "^1.1.4",
26
26
  "@endo/eventual-send": "^1.2.2",
27
27
  "@endo/pass-style": "^1.4.0",
@@ -54,5 +54,5 @@
54
54
  "typeCoverage": {
55
55
  "atLeast": 89.6
56
56
  },
57
- "gitHead": "4f70e66a2fa4f663e13fa9172e9b8efb1ad7a9d8"
57
+ "gitHead": "8edf90288c8a7b248ec4961d342063e2b8c52303"
58
58
  }
package/src/tools.d.ts CHANGED
@@ -2,12 +2,15 @@ export function prepareVowTools(zone: Zone, powers?: {
2
2
  isRetryableReason?: IsRetryableReason | undefined;
3
3
  } | undefined): {
4
4
  when: <T, TResult1 = import("./types.js").EUnwrap<T>, TResult2 = never>(specimenP: T, onFulfilled?: ((value: import("./types.js").EUnwrap<T>) => TResult1 | PromiseLike<TResult1>) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined) => Promise<TResult1 | TResult2>;
5
- watch: <T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]>(specimenP: import("./types.js").ERef<T | import("./types.js").Vow<T>>, watcher?: import("./types.js").Watcher<T, TResult1, TResult2, C> | undefined, ...watcherArgs: C) => import("./types.js").Vow<Exclude<TResult1, void> | Exclude<TResult2, void> extends never ? TResult1 : Exclude<TResult1, void> | Exclude<TResult2, void>>;
5
+ watch: <T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]>(specimenP: EVow<T>, watcher?: import("./types.js").Watcher<T, TResult1, TResult2, C> | undefined, ...watcherArgs: C) => import("./types.js").Vow<Exclude<TResult1, void> | Exclude<TResult2, void> extends never ? TResult1 : Exclude<TResult1, void> | Exclude<TResult2, void>>;
6
6
  makeVowKit: <T>() => import("./types.js").VowKit<T>;
7
- allVows: (vows: unknown[]) => import("./types.js").Vow<any[]>;
7
+ allVows: (maybeVows: EVow<unknown>[]) => import("./types.js").Vow<any[]>;
8
8
  asVow: <T extends unknown>(fn: (...args: any[]) => import("./types.js").Vow<Awaited<T>> | Awaited<T> | import("./types.js").PromiseVow<T>) => import("./types.js").Vow<Awaited<T>>;
9
+ asPromise: AsPromiseFunction;
9
10
  };
10
11
  export type VowTools = ReturnType<typeof prepareVowTools>;
11
12
  import type { Zone } from '@agoric/base-zone';
12
13
  import type { IsRetryableReason } from './types.js';
14
+ import type { EVow } from './types.js';
15
+ import type { AsPromiseFunction } from './types.js';
13
16
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["tools.js"],"names":[],"mappings":"AAeO,sCAJI,IAAI;;;;;;oBAiBF,OAAO,EAAE;oCASs0C,GAAG;EAJ91C;uBAGa,UAAU,CAAC,OAAO,eAAe,CAAC;0BA7BxB,mBAAmB;uCACN,YAAY"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["tools.js"],"names":[],"mappings":"AAiBO,sCAJI,IAAI;;;;;;yBAsBF,KAAK,OAAO,CAAC,EAAE;oCAaykC,GAAG;;EAJvmC;uBAGa,UAAU,CAAC,OAAO,eAAe,CAAC;0BAvCzB,mBAAmB;uCACmB,YAAY;0BAAZ,YAAY;uCAAZ,YAAY"}
package/src/tools.js CHANGED
@@ -5,8 +5,10 @@ import { prepareWatch } from './watch.js';
5
5
  import { prepareWatchUtils } from './watch-utils.js';
6
6
  import { makeAsVow } from './vow-utils.js';
7
7
 
8
- /** @import {Zone} from '@agoric/base-zone' */
9
- /** @import {IsRetryableReason} from './types.js' */
8
+ /**
9
+ * @import {Zone} from '@agoric/base-zone';
10
+ * @import {IsRetryableReason, AsPromiseFunction, EVow} from './types.js';
11
+ */
10
12
 
11
13
  /**
12
14
  * @param {Zone} zone
@@ -19,18 +21,27 @@ export const prepareVowTools = (zone, powers = {}) => {
19
21
  const makeVowKit = prepareVowKit(zone);
20
22
  const when = makeWhen(isRetryableReason);
21
23
  const watch = prepareWatch(zone, makeVowKit, isRetryableReason);
22
- const makeWatchUtils = prepareWatchUtils(zone, watch, makeVowKit);
24
+ const makeWatchUtils = prepareWatchUtils(zone, {
25
+ watch,
26
+ when,
27
+ makeVowKit,
28
+ isRetryableReason,
29
+ });
23
30
  const watchUtils = makeWatchUtils();
24
31
  const asVow = makeAsVow(makeVowKit);
25
32
 
26
33
  /**
27
34
  * Vow-tolerant implementation of Promise.all.
28
35
  *
29
- * @param {unknown[]} vows
36
+ * @param {EVow<unknown>[]} maybeVows
30
37
  */
31
- const allVows = vows => watchUtils.all(vows);
38
+ const allVows = maybeVows => watchUtils.all(maybeVows);
39
+
40
+ /** @type {AsPromiseFunction} */
41
+ const asPromise = (specimenP, ...watcherArgs) =>
42
+ watchUtils.asPromise(specimenP, ...watcherArgs);
32
43
 
33
- return harden({ when, watch, makeVowKit, allVows, asVow });
44
+ return harden({ when, watch, makeVowKit, allVows, asVow, asPromise });
34
45
  };
35
46
  harden(prepareVowTools);
36
47
 
package/src/types.d.ts CHANGED
@@ -8,6 +8,10 @@ export type IsRetryableReason = (reason: any, priorRetryValue: any) => any;
8
8
  */
9
9
  export type PromiseVow<T> = Promise<T | Vow<T>>;
10
10
  export type ERef<T> = T | PromiseLike<T>;
11
+ /**
12
+ * Eventually a value T or Vow for it.
13
+ */
14
+ export type EVow<T> = ERef<T | Vow<T>>;
11
15
  /**
12
16
  * Follow the chain of vow shortening to the end, returning the final value.
13
17
  * This is used within E, so we must narrow the type to its remote form.
@@ -44,6 +48,10 @@ export type Watcher<T = any, TResult1 = T, TResult2 = never, C extends any[] = a
44
48
  onFulfilled?: ((value: T, ...args: C) => Vow<TResult1> | PromiseVow<TResult1> | TResult1) | undefined;
45
49
  onRejected?: ((reason: any, ...args: C) => Vow<TResult2> | PromiseVow<TResult2> | TResult2) | undefined;
46
50
  };
51
+ /**
52
+ * Converts a vow or promise to a promise, ensuring proper handling of ephemeral promises.
53
+ */
54
+ export type AsPromiseFunction<T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]> = (specimenP: ERef<T | Vow<T>>, watcher?: Watcher<T, TResult1, TResult2, C> | undefined, watcherArgs?: C | undefined) => Promise<TResult1 | TResult2>;
47
55
  import type { RemotableObject } from '@endo/pass-style';
48
56
  import type { Remote } from '@agoric/internal';
49
57
  import type { CopyTagged } from '@endo/pass-style';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":";;;yCAaW,GAAG,mBACH,GAAG,KAED,GAAG;;;;;uBAKH,CAAC,IACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;iBAKnB,CAAC,IACD,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;;;;;oBAMlB,CAAC,IACD,CACZ,CAAK,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvC,CAAK,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC/C,CAAK,CACF;;;;;;;kBAIU,CAAC;;;;;;;aAMD,MAAM,OAAO,CAAC,CAAC,CAAC;;uBAOhB,CAAC;WAED,eAAe,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;;gBAIlC,CAAC,UACF,WAAW,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;mBAI/B,CAAC,UACF;IACZ,GAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAChB,QAAY,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAC1B;wBAIU,CAAC,UACF;IAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;CAAE;oBAIvE,CAAC,QACD,QAAQ,MACR,QAAQ,UACA,CAAC,SAAT,GAAG,EAAG;2BAEE,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ;2BAChE,GAAG,WAAW,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ;;qCAjFxD,kBAAkB;4BAC3B,kBAAkB;gCAFd,kBAAkB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":";;;yCAaW,GAAG,mBACH,GAAG,KAED,GAAG;;;;;uBAKH,CAAC,IACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;iBAKnB,CAAC,IACD,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;;;;iBAKlB,CAAC,IACD,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;;;;;oBAMhB,CAAC,IACD,CACZ,CAAK,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvC,CAAK,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC/C,CAAK,CACF;;;;;;;kBAIU,CAAC;;;;;;;aAMD,MAAM,OAAO,CAAC,CAAC,CAAC;;uBAOhB,CAAC;WAED,eAAe,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;;gBAIlC,CAAC,UACF,WAAW,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;mBAI/B,CAAC,UACF;IACZ,GAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAChB,QAAY,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAC1B;wBAIU,CAAC,UACF;IAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;CAAE;oBAIvE,CAAC,QACD,QAAQ,MACR,QAAQ,UACA,CAAC,SAAT,GAAG,EAAG;2BAEE,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ;2BAChE,GAAG,WAAW,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ;;;;;8BAM5E,CAAC,QACD,QAAQ,MACR,QAAQ,UACA,CAAC,SAAT,GAAG,EAAG,wBAET,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,2FAGd,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;qCArGP,kBAAkB;4BAC3B,kBAAkB;gCAFd,kBAAkB"}
package/src/types.js CHANGED
@@ -29,6 +29,12 @@ export {};
29
29
  * @typedef {T | PromiseLike<T>} ERef
30
30
  */
31
31
 
32
+ /**
33
+ * Eventually a value T or Vow for it.
34
+ * @template T
35
+ * @typedef {ERef<T | Vow<T>>} EVow
36
+ */
37
+
32
38
  /**
33
39
  * Follow the chain of vow shortening to the end, returning the final value.
34
40
  * This is used within E, so we must narrow the type to its remote form.
@@ -86,3 +92,17 @@ export {};
86
92
  * @property {(value: T, ...args: C) => Vow<TResult1> | PromiseVow<TResult1> | TResult1} [onFulfilled]
87
93
  * @property {(reason: any, ...args: C) => Vow<TResult2> | PromiseVow<TResult2> | TResult2} [onRejected]
88
94
  */
95
+
96
+ /**
97
+ * Converts a vow or promise to a promise, ensuring proper handling of ephemeral promises.
98
+ *
99
+ * @template [T=any]
100
+ * @template [TResult1=T]
101
+ * @template [TResult2=never]
102
+ * @template {any[]} [C=any[]]
103
+ * @callback AsPromiseFunction
104
+ * @param {ERef<T | Vow<T>>} specimenP
105
+ * @param {Watcher<T, TResult1, TResult2, C>} [watcher]
106
+ * @param {C} [watcherArgs]
107
+ * @returns {Promise<TResult1 | TResult2>}
108
+ */
package/src/vow.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vow.d.ts","sourceRoot":"","sources":["vow.js"],"names":[],"mappings":"AAuBO,oCAFI,IAAI,IA+HA,CAAC,OACD,OAAO,CAAC,CAAC,CASvB;0BA9IY,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,GACpC,IAAQ,CAAC,WAAW,GAAG,CAAC,EAAE,SAAS,CAAC;yBA8IvB,UAAU,CAAC,OAAO,aAAa,CAAC;0BAvJvB,mBAAmB;4BACJ,YAAY;gCAFrB,mBAAmB"}
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 CHANGED
@@ -4,10 +4,13 @@ import { M } from '@endo/patterns';
4
4
  import { makeTagged } from '@endo/pass-style';
5
5
  import { PromiseWatcherI } from '@agoric/base-zone';
6
6
 
7
+ const { details: X } = assert;
8
+
7
9
  /**
8
- * @import {PromiseKit} from '@endo/promise-kit'
9
- * @import {Zone} from '@agoric/base-zone'
10
- * @import {VowResolver, VowKit} from './types.js'
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';
11
14
  */
12
15
 
13
16
  const sink = () => {};
@@ -25,6 +28,9 @@ export const prepareVowKit = zone => {
25
28
  /** @type {WeakMap<VowResolver, VowEphemera>} */
26
29
  const resolverToEphemera = new WeakMap();
27
30
 
31
+ /** @type {WeakMap<VowResolver, any>} */
32
+ const resolverToNonStoredValue = new WeakMap();
33
+
28
34
  /**
29
35
  * Get the current incarnation's promise kit associated with a vowV0.
30
36
  *
@@ -61,17 +67,24 @@ export const prepareVowKit = zone => {
61
67
  shorten: M.call().returns(M.promise()),
62
68
  }),
63
69
  resolver: M.interface('VowResolver', {
64
- resolve: M.call().optional(M.any()).returns(),
65
- reject: M.call().optional(M.any()).returns(),
70
+ resolve: M.call().optional(M.raw()).returns(),
71
+ reject: M.call().optional(M.raw()).returns(),
66
72
  }),
67
73
  watchNextStep: PromiseWatcherI,
68
74
  },
69
75
  () => ({
70
- value: undefined,
76
+ value: /** @type {any} */ (undefined),
71
77
  // The stepStatus is null if the promise step hasn't settled yet.
72
78
  stepStatus: /** @type {null | 'pending' | 'fulfilled' | 'rejected'} */ (
73
79
  null
74
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,
75
88
  }),
76
89
  {
77
90
  vowV0: {
@@ -79,12 +92,30 @@ export const prepareVowKit = zone => {
79
92
  * @returns {Promise<any>}
80
93
  */
81
94
  async shorten() {
82
- const { stepStatus, value } = this.state;
95
+ const { stepStatus, isStoredValue, value } = this.state;
96
+ const { resolver } = this.facets;
97
+
83
98
  switch (stepStatus) {
84
- case 'fulfilled':
85
- return value;
86
- case 'rejected':
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.
87
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
+ }
88
119
  case null:
89
120
  case 'pending':
90
121
  return provideCurrentKit(this.facets.resolver).promise;
@@ -131,15 +162,38 @@ export const prepareVowKit = zone => {
131
162
  onFulfilled(value) {
132
163
  const { resolver } = this.facets;
133
164
  const { resolve } = getPromiseKitForResolution(resolver);
165
+ harden(value);
134
166
  if (resolve) {
135
167
  resolve(value);
136
168
  }
137
169
  this.state.stepStatus = 'fulfilled';
138
- this.state.value = value;
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
+ }
139
179
  },
140
180
  onRejected(reason) {
181
+ const { resolver } = this.facets;
182
+ const { reject } = getPromiseKitForResolution(resolver);
183
+ harden(reason);
184
+ if (reject) {
185
+ reject(reason);
186
+ }
141
187
  this.state.stepStatus = 'rejected';
142
- this.state.value = reason;
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
+ }
143
197
  },
144
198
  },
145
199
  },
@@ -1,10 +1,20 @@
1
- export function prepareWatchUtils(zone: Zone, watch: Watch, makeVowKit: () => VowKit<any>): () => import("@endo/exo").Guarded<{
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<{
2
7
  /**
3
- * @param {unknown[]} vows
8
+ * @param {EVow<unknown>[]} vows
4
9
  */
5
- all(vows: unknown[]): import("./types.js").Vow<any[]>;
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>;
6
13
  }>;
7
14
  import type { Zone } from '@agoric/base-zone';
8
15
  import type { Watch } from './watch.js';
16
+ import type { When } from './when.js';
9
17
  import type { VowKit } from './types.js';
18
+ import type { IsRetryableReason } from './types.js';
19
+ import type { EVow } from './types.js';
10
20
  //# sourceMappingURL=watch-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"watch-utils.d.ts","sourceRoot":"","sources":["watch-utils.js"],"names":[],"mappings":"AAuBO,wCAJI,IAAI,SACJ,KAAK,cACL,MAAM,OAAO,GAAG,CAAC;IAgCpB;;OAEG;cADQ,OAAO,EAAE;GA+E3B;0BA/HwB,mBAAmB;2BAClB,YAAY;4BACb,YAAY"}
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"}
@@ -1,12 +1,16 @@
1
1
  // @ts-check
2
2
 
3
3
  import { M } from '@endo/patterns';
4
+ import { PromiseWatcherI } from '@agoric/base-zone';
5
+
6
+ const { Fail, bare, details: X } = assert;
4
7
 
5
8
  /**
6
- * @import {MapStore} from '@agoric/store/src/types.js'
7
- * @import { Zone } from '@agoric/base-zone'
8
- * @import { Watch } from './watch.js'
9
- * @import {VowKit} from './types.js'
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';
10
14
  */
11
15
 
12
16
  const VowShape = M.tagged(
@@ -16,23 +20,47 @@ const VowShape = M.tagged(
16
20
  }),
17
21
  );
18
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
+
19
37
  /**
20
38
  * @param {Zone} zone
21
- * @param {Watch} watch
22
- * @param {() => VowKit<any>} makeVowKit
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
23
44
  */
24
- export const prepareWatchUtils = (zone, watch, makeVowKit) => {
45
+ export const prepareWatchUtils = (
46
+ zone,
47
+ { watch, when, makeVowKit, isRetryableReason },
48
+ ) => {
25
49
  const detached = zone.detached();
50
+ const utilsToNonStorableResults = new WeakMap();
51
+
26
52
  const makeWatchUtilsKit = zone.exoClassKit(
27
53
  'WatchUtils',
28
54
  {
29
55
  utils: M.interface('Utils', {
30
56
  all: M.call(M.arrayOf(M.any())).returns(VowShape),
57
+ asPromise: M.call(M.raw()).rest(M.raw()).returns(M.promise()),
31
58
  }),
32
59
  watcher: M.interface('Watcher', {
33
60
  onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
34
61
  onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
35
62
  }),
63
+ retryRejectionPromiseWatcher: PromiseWatcherI,
36
64
  },
37
65
  () => {
38
66
  /**
@@ -52,7 +80,7 @@ export const prepareWatchUtils = (zone, watch, makeVowKit) => {
52
80
  {
53
81
  utils: {
54
82
  /**
55
- * @param {unknown[]} vows
83
+ * @param {EVow<unknown>[]} vows
56
84
  */
57
85
  all(vows) {
58
86
  const { nextId: id, idToVowState } = this.state;
@@ -60,40 +88,75 @@ export const prepareWatchUtils = (zone, watch, makeVowKit) => {
60
88
  const kit = makeVowKit();
61
89
 
62
90
  // Preserve the order of the vow results.
63
- let index = 0;
64
- for (const vow of vows) {
65
- watch(vow, this.facets.watcher, { id, index });
66
- index += 1;
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
+ });
67
97
  }
68
98
 
69
- if (index > 0) {
99
+ if (vows.length > 0) {
70
100
  // Save the state until rejection or all fulfilled.
71
101
  this.state.nextId += 1n;
72
102
  idToVowState.init(
73
103
  id,
74
104
  harden({
75
105
  resolver: kit.resolver,
76
- remaining: index,
106
+ remaining: vows.length,
77
107
  resultsMap: detached.mapStore('resultsMap'),
78
108
  }),
79
109
  );
110
+ const idToNonStorableResults = provideLazyMap(
111
+ utilsToNonStorableResults,
112
+ this.facets.utils,
113
+ () => new Map(),
114
+ );
115
+ idToNonStorableResults.set(id, new Map());
80
116
  } else {
81
117
  // Base case: nothing to wait for.
82
118
  kit.resolver.resolve(harden([]));
83
119
  }
84
120
  return kit.vow;
85
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
+ },
86
134
  },
87
135
  watcher: {
88
- onFulfilled(value, { id, index }) {
136
+ onFulfilled(value, { id, index, numResults }) {
89
137
  const { idToVowState } = this.state;
90
138
  if (!idToVowState.has(id)) {
91
139
  // Resolution of the returned vow happened already.
92
140
  return;
93
141
  }
94
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
+
95
154
  // Capture the fulfilled value.
96
- resultsMap.init(index, value);
155
+ if (zone.isStorable(value)) {
156
+ resultsMap.init(index, value);
157
+ } else {
158
+ nonStorableResults.set(index, value);
159
+ }
97
160
  const vowState = harden({
98
161
  remaining: remaining - 1,
99
162
  resultsMap,
@@ -105,13 +168,26 @@ export const prepareWatchUtils = (zone, watch, makeVowKit) => {
105
168
  }
106
169
  // We're done! Extract the array.
107
170
  idToVowState.delete(id);
108
- const results = new Array(resultsMap.getSize());
109
- for (const [i, val] of resultsMap.entries()) {
110
- results[i] = val;
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));
111
188
  }
112
- resolver.resolve(harden(results));
113
189
  },
114
- onRejected(value, { id, index: _index }) {
190
+ onRejected(value, { id, index: _index, numResults: _numResults }) {
115
191
  const { idToVowState } = this.state;
116
192
  if (!idToVowState.has(id)) {
117
193
  // First rejection wins.
@@ -122,6 +198,14 @@ export const prepareWatchUtils = (zone, watch, makeVowKit) => {
122
198
  resolver.reject(value);
123
199
  },
124
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
+ },
125
209
  },
126
210
  );
127
211
 
package/src/watch.d.ts CHANGED
@@ -1,8 +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: ERef<T | Vow<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>>;
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
2
  export type Watch = ReturnType<typeof prepareWatch>;
3
3
  import type { Zone } from '@agoric/base-zone';
4
4
  import type { VowKit } from './types.js';
5
- import type { Vow } from './types.js';
6
- import type { ERef } from './types.js';
5
+ import type { EVow } from './types.js';
7
6
  import type { Watcher } from './types.js';
7
+ import type { Vow } from './types.js';
8
8
  //# sourceMappingURL=watch.d.ts.map
@@ -1 +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,GAAG,IAAI,CAAC,CAAC,CAAC,2EAEhB,CAAC,yIAoBb;oBAIa,UAAU,CAAC,OAAO,YAAY,CAAC;0BA/LJ,mBAAmB;4BACmB,YAAY;yBAAZ,YAAY;0BAAZ,YAAY;6BAAZ,YAAY"}
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 CHANGED
@@ -6,7 +6,7 @@ const { apply } = Reflect;
6
6
 
7
7
  /**
8
8
  * @import { PromiseWatcher, Zone } from '@agoric/base-zone';
9
- * @import { ERef, IsRetryableReason, Vow, VowKit, VowResolver, Watcher } from './types.js';
9
+ * @import { ERef, EVow, IsRetryableReason, Vow, VowKit, VowResolver, Watcher } from './types.js';
10
10
  */
11
11
 
12
12
  /**
@@ -170,7 +170,7 @@ export const prepareWatch = (
170
170
  * @template [TResult1=T]
171
171
  * @template [TResult2=never]
172
172
  * @template {any[]} [C=any[]] watcher args
173
- * @param {ERef<T | Vow<T>>} specimenP
173
+ * @param {EVow<T>} specimenP
174
174
  * @param {Watcher<T, TResult1, TResult2, C>} [watcher]
175
175
  * @param {C} watcherArgs
176
176
  */