Skip to content

Commit

Permalink
chore(vow): retriable -> retryable
Browse files Browse the repository at this point in the history
  • Loading branch information
mhofman committed Oct 1, 2024
1 parent 37bd361 commit 9a4f166
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ Generated by [AVA](https://avajs.dev).
},
},
vows: {
AdminRetriableFlow_kindHandle: 'Alleged: kind',
AdminRetriableFlow_singleton: 'Alleged: AdminRetriableFlow',
AdminRetryableFlow_kindHandle: 'Alleged: kind',
AdminRetryableFlow_singleton: 'Alleged: AdminRetryableFlow',
PromiseWatcher_kindHandle: 'Alleged: kind',
VowInternalsKit_kindHandle: 'Alleged: kind',
WatchUtils_kindHandle: 'Alleged: kind',
retriableFlowForOutcomeVow: {},
retryableFlowForOutcomeVow: {},
},
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,11 @@ Generated by [AVA](https://avajs.dev).
},
},
vows: {
AdminRetriableFlow_kindHandle: 'Alleged: kind',
AdminRetriableFlow_singleton: 'Alleged: AdminRetriableFlow',
AdminRetryableFlow_kindHandle: 'Alleged: kind',
AdminRetryableFlow_singleton: 'Alleged: AdminRetryableFlow',
PromiseWatcher_kindHandle: 'Alleged: kind',
VowInternalsKit_kindHandle: 'Alleged: kind',
WatchUtils_kindHandle: 'Alleged: kind',
retriableFlowForOutcomeVow: {},
retryableFlowForOutcomeVow: {},
},
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,11 @@ Generated by [AVA](https://avajs.dev).
},
},
vows: {
AdminRetriableFlow_kindHandle: 'Alleged: kind',
AdminRetriableFlow_singleton: 'Alleged: AdminRetriableFlow',
AdminRetryableFlow_kindHandle: 'Alleged: kind',
AdminRetryableFlow_singleton: 'Alleged: AdminRetryableFlow',
PromiseWatcher_kindHandle: 'Alleged: kind',
VowInternalsKit_kindHandle: 'Alleged: kind',
WatchUtils_kindHandle: 'Alleged: kind',
retriableFlowForOutcomeVow: {},
retryableFlowForOutcomeVow: {},
},
}
Binary file not shown.
4 changes: 2 additions & 2 deletions packages/vow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ Here is an (oversimplified) algorithm that `watch` and `when` use to obtain a
final result:

```js
// Directly await the non-retriable original specimen.
// This is non-retriable because we don't know how our caller obtained
// Directly await the non-retryable original specimen.
// This is non-retryable because we don't know how our caller obtained
// it in the first place, since it is an application-specific detail
// that may not be side-effect free.
let result = await specimenP;
Expand Down
86 changes: 43 additions & 43 deletions packages/vow/src/retriable.js → packages/vow/src/retryable.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,28 @@ import { makeAsVow, toPassableCap, VowShape } from './vow-utils.js';
*/

/**
* @typedef {(...args: Passable[]) => Promise<any>} RetriableFunc
* @typedef {(...args: Passable[]) => Promise<any>} RetryableFunc
*/

const { defineProperties } = Object;

const RetriableFlowIKit = harden({
const RetryableFlowIKit = harden({
flow: M.interface('Flow', {
restart: M.call().returns(),
getOutcome: M.call().returns(VowShape),
}),
resultWatcher: PromiseWatcherI,
});

const AdminRetriableFlowI = M.interface('RetriableFlowAdmin', {
const AdminRetryableFlowI = M.interface('RetryableFlowAdmin', {
getFlowForOutcomeVow: M.call(VowShape).returns(M.opt(M.remotable('flow'))),
});

/**
* @param {Zone} outerZone
* @param {PreparationOptions} outerOptions
*/
export const prepareRetriableTools = (outerZone, outerOptions) => {
export const prepareRetryableTools = (outerZone, outerOptions) => {
const { makeVowKit, isRetryableReason } = outerOptions;

const asVow = makeAsVow(makeVowKit);
Expand All @@ -48,8 +48,8 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
* for their activations later.
*/
const flowForOutcomeVowKey =
/** @type {MapStore<PassableCap, RetriableFlow>} */ (
outerZone.mapStore('retriableFlowForOutcomeVow', {
/** @type {MapStore<PassableCap, RetryableFlow>} */ (
outerZone.mapStore('retryableFlowForOutcomeVow', {
keyShape: M.remotable('toPassableCap'),
valueShape: M.remotable('flow'), // isDone === false
})
Expand All @@ -58,20 +58,20 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
/**
* @param {Zone} zone
* @param {string} tag
* @param {RetriableFunc} retriableFunc
* @param {RetryableFunc} retryableFunc
*/
const prepareRetriableFlowKit = (zone, tag, retriableFunc) => {
typeof retriableFunc === 'function' ||
Fail`retriableFunc must be a callable function ${retriableFunc}`;
const prepareRetryableFlowKit = (zone, tag, retryableFunc) => {
typeof retryableFunc === 'function' ||
Fail`retryableFunc must be a callable function ${retryableFunc}`;

const internalMakeRetriableFlowKit = zone.exoClassKit(
const internalMakeRetryableFlowKit = zone.exoClassKit(
tag,
RetriableFlowIKit,
RetryableFlowIKit,
activationArgs => {
harden(activationArgs);

return {
activationArgs, // restarting the retriable function uses the original args
activationArgs, // restarting the retryable function uses the original args
outcomeKit: makeVowKit(), // outcome of activation as vow
lastRetryReason: undefined,
runs: 0n,
Expand All @@ -81,8 +81,8 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
{
flow: {
/**
* Calls the retriable function, either for the initial run or when
* the result of the previous run fails with a retriable reason.
* Calls the retryable function, either for the initial run or when
* the result of the previous run fails with a retryable reason.
*/
restart() {
const { state, facets } = this;
Expand All @@ -91,14 +91,14 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {

!isDone ||
// separate line so I can set a breakpoint
Fail`Cannot restart a done retriable flow ${flow}`;
Fail`Cannot restart a done retryable flow ${flow}`;

const runId = state.runs + 1n;
state.runs = runId;

let resultP;
try {
resultP = Promise.resolve(retriableFunc(...activationArgs));
resultP = Promise.resolve(retryableFunc(...activationArgs));
} catch (err) {
resultP = Promise.reject(err);
}
Expand All @@ -117,7 +117,7 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
const { runs, outcomeKit } = state;
if (runId !== runs) return;
!state.isDone ||
Fail`Cannot resolve a done retriable flow ${this.facets.flow}`;
Fail`Cannot resolve a done retryable flow ${this.facets.flow}`;
outcomeKit.resolver.resolve(value);
flowForOutcomeVowKey.delete(toPassableCap(outcomeKit.vow));
state.isDone = true;
Expand All @@ -127,7 +127,7 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
const { runs, outcomeKit } = state;
if (runId !== runs) return;
!state.isDone ||
Fail`Cannot reject a done retriable flow ${this.facets.flow}`;
Fail`Cannot reject a done retryable flow ${this.facets.flow}`;
const retryReason = isRetryableReason(
reason,
state.lastRetryReason,
Expand All @@ -144,47 +144,47 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
},
},
);
const makeRetriableFlowKit = activationArgs => {
const retriableKit = internalMakeRetriableFlowKit(activationArgs);
const { flow } = retriableKit;
const makeRetryableFlowKit = activationArgs => {
const retryableKit = internalMakeRetryableFlowKit(activationArgs);
const { flow } = retryableKit;

const vow = flow.getOutcome();
flowForOutcomeVowKey.init(toPassableCap(vow), flow);
flow.restart();
return retriableKit;
return retryableKit;
};
return harden(makeRetriableFlowKit);
return harden(makeRetryableFlowKit);
};

/**
* @type {VowTools['retriable']}
* @type {VowTools['retryable']}
*/
const retriable = (zone, tag, retriableFunc) => {
const makeRetriableKit = prepareRetriableFlowKit(zone, tag, retriableFunc);
const wrapperFuncName = `${tag}_retriable`;
const retryable = (zone, tag, retryableFunc) => {
const makeRetryableKit = prepareRetryableFlowKit(zone, tag, retryableFunc);
const wrapperFuncName = `${tag}_retryable`;

const wrapperFunc = {
/** @param {any[]} args */
[wrapperFuncName](...args) {
// Make sure any error results in a rejected vow
return asVow(() => {
zone.isStorable(harden(args)) ||
Fail`retriable arguments must be storable ${args}`;
const { flow } = makeRetriableKit(args);
Fail`retryable arguments must be storable ${args}`;
const { flow } = makeRetryableKit(args);
return flow.getOutcome();
});
},
}[wrapperFuncName];
defineProperties(wrapperFunc, {
length: { value: retriableFunc.length },
length: { value: retryableFunc.length },
});
// @ts-expect-error inferred generic func
return harden(wrapperFunc);
};

const adminRetriableFlow = outerZone.exo(
'AdminRetriableFlow',
AdminRetriableFlowI,
const adminRetryableFlow = outerZone.exo(
'AdminRetryableFlow',
AdminRetryableFlowI,
{
/**
* @param {Vow} outcomeVow
Expand All @@ -196,29 +196,29 @@ export const prepareRetriableTools = (outerZone, outerOptions) => {
);

return harden({
prepareRetriableFlowKit,
adminRetriableFlow,
retriable,
prepareRetryableFlowKit,
adminRetryableFlow,
retryable,
});
};
harden(prepareRetriableTools);
harden(prepareRetryableTools);

/**
* @typedef {ReturnType<prepareRetriableTools>} RetriableTools
* @typedef {ReturnType<prepareRetryableTools>} RetryableTools
*/

/**
* @typedef {RetriableTools['adminRetriableFlow']} AdminRetriableFlow
* @typedef {RetryableTools['adminRetryableFlow']} AdminRetryableFlow
*/

/**
* @typedef {ReturnType<RetriableTools['prepareRetriableFlowKit']>} MakeRetriableFlowKit
* @typedef {ReturnType<RetryableTools['prepareRetryableFlowKit']>} MakeRetryableFlowKit
*/

/**
* @typedef {ReturnType<MakeRetriableFlowKit>} RetriableFlowKit
* @typedef {ReturnType<MakeRetryableFlowKit>} RetryableFlowKit
*/

/**
* @typedef {RetriableFlowKit['flow']} RetriableFlow
* @typedef {RetryableFlowKit['flow']} RetryableFlow
*/
7 changes: 4 additions & 3 deletions packages/vow/src/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { makeAsVow } from './vow-utils.js';
import { prepareVowKit } from './vow.js';
import { prepareWatchUtils } from './watch-utils.js';
import { prepareWatch } from './watch.js';
import { prepareRetriableTools } from './retriable.js';
import { prepareRetryableTools } from './retryable.js';
import { makeWhen } from './when.js';

/**
Expand Down Expand Up @@ -36,7 +36,7 @@ export const prepareBasicVowTools = (zone, powers = {}) => {
const watchUtils = makeWatchUtils();
const asVow = makeAsVow(makeVowKit);

const { retriable } = prepareRetriableTools(zone, {
const { retryable } = prepareRetryableTools(zone, {
makeVowKit,
isRetryableReason,
});
Expand Down Expand Up @@ -81,7 +81,8 @@ export const prepareBasicVowTools = (zone, powers = {}) => {
allSettled,
asVow,
asPromise,
retriable,
retryable,
retriable: retryable, // For temporary backwards compat with alpha implementation
});
};
harden(prepareBasicVowTools);
38 changes: 24 additions & 14 deletions packages/vow/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ export type AsPromiseFunction<
watcherArgs?: C | undefined,
) => Promise<TResult1 | TResult2>;

export interface RetryableTool {
/**
* Create a function that retries the given function if the underlying
* async function rejects due to an upgrade disconnection. The return value
* of the created function is a vow that settles to the final retry result.
*
* The retried function should be idempotent.
*
* @param fnZone the zone for the named function
* @param name base name to use in the zone
* @param fn the retried function
*/
<F extends (...args: any[]) => Promise<any>>(
fnZone: Zone,
name: string,
fn: F,
): F extends (...args: infer Args) => Promise<infer R>
? (...args: Args) => Vow<R>
: never;
}

export type VowTools = {
/**
* Vow-tolerant implementation of Promise.all that takes an iterable of vows
Expand Down Expand Up @@ -142,22 +163,11 @@ export type VowTools = {
fn: (...args: any[]) => Vow<Awaited<T>> | Awaited<T> | PromiseVow<T>,
) => Vow<Awaited<T>>;
makeVowKit: <T>() => VowKit<T>;
retryable: RetryableTool;
/**
* Create a function that retries the given function if the underlying
* async function rejects due to an upgrade disconnection. The return value
* of the created function is a vow that settles to the final retry result.
*
* @param fnZone the zone for the named function
* @param name base name to use in the zone
* @param fn the retried function
* @deprecated use `retryable`
*/
retriable: <F extends (...args: any[]) => Promise<any>>(
fnZone: Zone,
name: string,
fn: F,
) => F extends (...args: infer Args) => Promise<infer R>
? (...args: Args) => Vow<R>
: never;
retriable: RetryableTool;
watch: <T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]>(
specimenP: EVow<T>,
watcher?: Watcher<T, TResult1, TResult2, C> | undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/vow/src/vow-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ harden(isVow);
/**
* A vow is a passable tagged as 'Vow'. Its payload is a record with
* API-versioned remotables. payload.vowV0 is the API for the `watch` and
* `when` operators to use for retriable shortening of the vow chain.
* `when` operators to use for retryable shortening of the vow chain.
*
* If the specimen is a Vow, return its payload, otherwise undefined.
*
Expand Down
Loading

0 comments on commit 9a4f166

Please sign in to comment.