diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index ac8cde3b6f0..ca0b4fb5909 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -1,15 +1,13 @@ import { makeStateRecord } from '@agoric/async-flow'; import { AmountShape } from '@agoric/ertp'; import { heapVowE } from '@agoric/vow/vat.js'; -import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; +import { Fail } from '@endo/errors'; import { E } from '@endo/far'; -import { M, mustMatch } from '@endo/patterns'; -import { makeResumableAgoricNamesHack } from '../exos/agoric-names-tools.js'; +import { M } from '@endo/patterns'; import { CosmosChainInfoShape } from '../typeGuards.js'; import { withOrchestration } from '../utils/start-helper.js'; - -const { entries } = Object; +import { orchestrationFns } from './sendAnywhereFlows.js'; /** * @import {TimerService} from '@agoric/time'; @@ -19,9 +17,7 @@ const { entries } = Object; * @import {Zone} from '@agoric/zone'; * @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api'; * @import {CosmosInterchainService} from '../exos/cosmos-interchain-service.js'; - * @import {Orchestrator} from '../types.js' * @import {OrchestrationTools} from '../utils/start-helper.js'; - * @import {OrchestrationAccount} from '../orchestration-api.js' */ /** @@ -34,54 +30,6 @@ const { entries } = Object; * }} OrchestrationPowers */ -/** - * @param {Orchestrator} orch - * @param {object} ctx - * @param {ZCF} ctx.zcf - * @param {any} ctx.agoricNamesTools TODO Give this a better type - * @param {{ account: OrchestrationAccount | undefined }} ctx.contractState - * @param {ZCFSeat} seat - * @param {object} offerArgs - * @param {string} offerArgs.chainName - * @param {string} offerArgs.destAddr - */ -const sendItFn = async ( - orch, - { zcf, agoricNamesTools, contractState }, - seat, - offerArgs, -) => { - mustMatch(offerArgs, harden({ chainName: M.scalar(), destAddr: M.string() })); - const { chainName, destAddr } = offerArgs; - const { give } = seat.getProposal(); - const [[kw, amt]] = entries(give); - const { denom } = await agoricNamesTools.findBrandInVBank(amt.brand); - const chain = await orch.getChain(chainName); - - if (!contractState.account) { - const agoricChain = await orch.getChain('agoric'); - contractState.account = await agoricChain.makeAccount(); - console.log('contractState.account', contractState.account); - } - - const info = await chain.getChainInfo(); - console.log('info', info); - const { chainId } = info; - assert(typeof chainId === 'string', 'bad chainId'); - const { [kw]: pmtP } = await withdrawFromSeat(zcf, seat, give); - // #9212 types for chain account helpers - // @ts-expect-error LCA should have .deposit() method - await E.when(pmtP, pmt => contractState.account?.deposit(pmt)); - await contractState.account?.transfer( - { denom, value: amt.value }, - { - value: destAddr, - encoding: 'bech32', - chainId, - }, - ); -}; - export const SingleAmountRecord = M.and( M.recordOf(M.string(), AmountShape, { numPropertiesLimit: 1, @@ -103,26 +51,36 @@ const contract = async ( zcf, privateArgs, zone, - { chainHub, orchestrate, vowTools }, + { chainHub, orchestrateAll, vowTools, zoeTools }, ) => { - const agoricNamesTools = makeResumableAgoricNamesHack(zone, { - agoricNames: privateArgs.agoricNames, - vowTools, - }); - const contractState = makeStateRecord( /** @type {{ account: OrchestrationAccount | undefined }} */ { account: undefined, }, ); - /** @type {OfferHandler} */ - const sendIt = orchestrate( - 'sendIt', - { zcf, agoricNamesTools, contractState }, - sendItFn, + // TODO should be a provided helper + const findBrandInVBank = vowTools.retriable( + zone, + 'findBrandInVBank', + /** @param {Brand} brand */ + async brand => { + const { agoricNames } = privateArgs; + const assets = await E(E(agoricNames).lookup('vbankAsset')).values(); + const it = assets.find(a => a.brand === brand); + it || Fail`brand ${brand} not in agoricNames.vbankAsset`; + return it; + }, ); + // orchestrate uses the names on orchestrationFns to do a "prepare" of the associated behavior + const orchFns = orchestrateAll(orchestrationFns, { + zcf, + contractState, + localTransfer: zoeTools.localTransfer, + findBrandInVBank, + }); + const publicFacet = zone.exo( 'Send PF', M.interface('Send PF', { @@ -131,7 +89,7 @@ const contract = async ( { makeSendInvitation() { return zcf.makeInvitation( - sendIt, + orchFns.sendIt, 'send', undefined, M.splitRecord({ give: SingleAmountRecord }), diff --git a/packages/orchestration/src/examples/sendAnywhereFlows.js b/packages/orchestration/src/examples/sendAnywhereFlows.js new file mode 100644 index 00000000000..c6d34c31912 --- /dev/null +++ b/packages/orchestration/src/examples/sendAnywhereFlows.js @@ -0,0 +1,59 @@ +import { M, mustMatch } from '@endo/patterns'; + +/** + * @import {Orchestrator, OrchestrationAccount} from '../types.js'; + */ + +const { entries } = Object; + +// in guest file (the orchestration functions) +// the second argument is all the endowments provided + +export const orchestrationFns = harden({ + /** + * @param {Orchestrator} orch + * @param {object} ctx + * @param {{ account: OrchestrationAccount }} ctx.contractState + * @param {any} ctx.localTransfer + * @param {any} ctx.findBrandInVBank + * @param {ZCFSeat} seat + * @param {{ chainName: string; destAddr: string }} offerArgs + */ + async sendIt( + orch, + { contractState, localTransfer, findBrandInVBank }, + seat, + offerArgs, + ) { + mustMatch( + offerArgs, + harden({ chainName: M.scalar(), destAddr: M.string() }), + ); + const { chainName, destAddr } = offerArgs; + // NOTE the proposal shape ensures that the `give` is a single asset + const { give } = seat.getProposal(); + const [[_kw, amt]] = entries(give); + const { denom } = await findBrandInVBank(amt.brand); + const chain = await orch.getChain(chainName); + + if (!contractState.account) { + const agoricChain = await orch.getChain('agoric'); + contractState.account = await agoricChain.makeAccount(); + } + + const info = await chain.getChainInfo(); + const { chainId } = info; + assert(typeof chainId === 'string', 'bad chainId'); + + await localTransfer(seat, contractState.account, give); + + await contractState.account.transfer( + { denom, value: amt.value }, + { + value: destAddr, + encoding: 'bech32', + chainId, + }, + ); + }, +}); diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index eb3a93da608..944f72be366 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -1,13 +1,11 @@ import { StorageNodeShape } from '@agoric/internal'; import { TimerServiceShape } from '@agoric/time'; -import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; -import { deeplyFulfilled } from '@endo/marshal'; -import { M, objectMap } from '@endo/patterns'; +import { M } from '@endo/patterns'; import { orcUtils } from '../utils/orc.js'; import { withOrchestration } from '../utils/start-helper.js'; /** - * @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js' + * @import {Orchestrator, CosmosValidatorAddress} from '../types.js' * @import {TimerService} from '@agoric/time'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {Remote} from '@agoric/internal'; @@ -20,13 +18,13 @@ import { withOrchestration } from '../utils/start-helper.js'; /** * @param {Orchestrator} orch * @param {object} ctx - * @param {ZCF} ctx.zcf + * @param {OrchestrationTools['zoeTools']['localTransfer']} ctx.localTransfer * @param {ZCFSeat} seat * @param {object} offerArgs * @param {Amount<'nat'>} offerArgs.staked * @param {CosmosValidatorAddress} offerArgs.validator */ -const stackAndSwapFn = async (orch, { zcf }, seat, offerArgs) => { +const stackAndSwapFn = async (orch, { localTransfer }, seat, offerArgs) => { const { give } = seat.getProposal(); const omni = await orch.getChain('omniflixhub'); @@ -40,13 +38,9 @@ const stackAndSwapFn = async (orch, { zcf }, seat, offerArgs) => { const omniAddress = omniAccount.getAddress(); // deposit funds from user seat to LocalChainAccount - const payments = await withdrawFromSeat(zcf, seat, give); - await deeplyFulfilled( - objectMap(payments, payment => - // @ts-expect-error payment is ERef which happens to work but isn't officially supported - localAccount.deposit(payment), - ), - ); + // TODO localTransfer type returns vow but in the guest context it should be a promise + // @ts-expect-error XXX localAccount type + await localTransfer(seat, localAccount, give); seat.exit(); // build swap instructions with orcUtils library @@ -104,7 +98,7 @@ export const makeNatAmountShape = (brand, min) => * @param {Zone} zone * @param {OrchestrationTools} tools */ -const contract = async (zcf, privateArgs, zone, { orchestrate }) => { +const contract = async (zcf, privateArgs, zone, { orchestrate, zoeTools }) => { const { brands } = zcf.getTerms(); /** deprecated historical example */ @@ -114,7 +108,11 @@ const contract = async (zcf, privateArgs, zone, { orchestrate }) => { * { staked: Amount<'nat'>; validator: CosmosValidatorAddress } * >} */ - const swapAndStakeHandler = orchestrate('LSTTia', { zcf }, stackAndSwapFn); + const swapAndStakeHandler = orchestrate( + 'LSTTia', + { zcf, localTransfer: zoeTools.localTransfer }, + stackAndSwapFn, + ); const publicFacet = zone.exo('publicFacet', undefined, { makeSwapAndStakeInvitation() { diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index 97fca59363d..3fc2b80134f 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -139,3 +139,11 @@ export const prepareOrchestratorKit = ( }, ); harden(prepareOrchestratorKit); +/** + * Host side of the Orchestrator interface. (Methods return vows instead of + * promises as the interface within the guest function.) + * + * @typedef {ReturnType< + * ReturnType + * >['orchestrator']} HostOrchestrator + */ diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index f9cb821140c..667d7fa8290 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,23 +1,17 @@ -/** @file Orchestration service */ +/** @file Orchestration facade */ import { Fail } from '@endo/errors'; -import { pickFacet } from '@agoric/vat-data'; -import { prepareOrchestratorKit } from './exos/orchestrator.js'; /** * @import {AsyncFlowTools} from '@agoric/async-flow'; * @import {Zone} from '@agoric/zone'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {TimerService} from '@agoric/time'; - * @import {IBCConnectionID} from '@agoric/vats'; - * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. + * @import {HostOrchestrator} from './exos/orchestrator.js'; * @import {Remote} from '@agoric/internal'; * @import {CosmosInterchainService} from './exos/cosmos-interchain-service.js'; * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from './types.js'; - * @import {MakeLocalChainFacade} from './exos/local-chain-facade.js'; - * @import {MakeRemoteChainFacade} from './exos/remote-chain-facade.js'; - * @import {MakeLocalOrchestrationAccountKit} from './exos/local-orchestration-account.js'; */ /** @@ -27,13 +21,8 @@ import { prepareOrchestratorKit } from './exos/orchestrator.js'; * zcf: ZCF; * storageNode: Remote; * orchestrationService: Remote; - * localchain: Remote; - * chainHub: import('./exos/chain-hub.js').ChainHub; - * makeLocalOrchestrationAccountKit: MakeLocalOrchestrationAccountKit; * makeRecorderKit: MakeRecorderKit; - * makeCosmosOrchestrationAccount: any; - * makeLocalChainFacade: MakeLocalChainFacade; - * makeRemoteChainFacade: MakeRemoteChainFacade; + * makeOrchestrator: () => HostOrchestrator; * vowTools: VowTools; * asyncFlowTools: AsyncFlowTools; * }} powers @@ -44,12 +33,8 @@ export const makeOrchestrationFacade = ({ zcf, storageNode, orchestrationService, - localchain, - chainHub, - makeLocalOrchestrationAccountKit, makeRecorderKit, - makeLocalChainFacade, - makeRemoteChainFacade, + makeOrchestrator, vowTools, asyncFlowTools, }) => { @@ -59,73 +44,75 @@ export const makeOrchestrationFacade = ({ storageNode && orchestrationService && // @ts-expect-error type says defined but double check - makeLocalOrchestrationAccountKit && - // @ts-expect-error type says defined but double check makeRecorderKit && // @ts-expect-error type says defined but double check - makeRemoteChainFacade && + makeOrchestrator && asyncFlowTools) || Fail`params missing`; - const makeOrchestratorKit = prepareOrchestratorKit(zone, { - asyncFlowTools, - chainHub, - localchain, - makeRecorderKit, - makeLocalChainFacade, - makeRemoteChainFacade, - orchestrationService, - storageNode, - timerService, - vowTools, - zcf, - }); - const makeOrchestrator = pickFacet(makeOrchestratorKit, 'orchestrator'); - const { prepareEndowment, asyncFlow, adminAsyncFlow } = asyncFlowTools; const { when } = vowTools; - return { - /** - * @template GuestReturn - * @template HostReturn - * @template GuestContext - * @template HostContext - * @template {any[]} GuestArgs - * @template {any[]} HostArgs - * @param {string} durableName - the orchestration flow identity in the zone - * (to resume across upgrades) - * @param {HostContext} hostCtx - values to pass through the async flow - * membrane - * @param {( - * guestOrc: Orchestrator, - * guestCtx: GuestContext, - * ...args: GuestArgs - * ) => Promise} guestFn - * @returns {(...args: HostArgs) => Promise} TODO returns a - * Promise for now for compat before use of asyncFlow. But really should - * be `Vow` - */ - orchestrate(durableName, hostCtx, guestFn) { - const subZone = zone.subZone(durableName); + /** + * @template GuestReturn + * @template HostReturn + * @template GuestContext + * @template HostContext + * @template {any[]} GuestArgs + * @template {any[]} HostArgs + * @param {string} durableName - the orchestration flow identity in the zone + * (to resume across upgrades) + * @param {HostContext} hostCtx - values to pass through the async flow + * membrane + * @param {( + * guestOrc: Orchestrator, + * guestCtx: GuestContext, + * ...args: GuestArgs + * ) => Promise} guestFn + * @returns {(...args: HostArgs) => Promise} TODO returns a + * Promise for now for compat before use of asyncFlow. But really should be + * `Vow` + */ + const orchestrate = (durableName, hostCtx, guestFn) => { + const subZone = zone.subZone(durableName); - const hostOrc = makeOrchestrator(); + const hostOrc = makeOrchestrator(); - const [wrappedOrc, wrappedCtx] = prepareEndowment(subZone, 'endowments', [ - hostOrc, - hostCtx, - ]); + const [wrappedOrc, wrappedCtx] = prepareEndowment(subZone, 'endowments', [ + hostOrc, + hostCtx, + ]); - const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); + const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); - const orcFn = (...args) => - // TODO remove the `when` after fixing the return type - // to `Vow` - when(hostFn(wrappedOrc, wrappedCtx, ...args)); - return harden(orcFn); - }, + const orcFn = (...args) => + // TODO remove the `when` after fixing the return type + // to `Vow` + when(hostFn(wrappedOrc, wrappedCtx, ...args)); + return harden(orcFn); + }; + + /** + * Orchestrate all the guest functions. + * + * NOTE multiple calls to this with the same guestFn name will fail + * + * @param {{ [durableName: string]: (...args: any[]) => any }} guestFns + * @param {any} hostCtx + */ + const orchestrateAll = (guestFns, hostCtx) => + Object.fromEntries( + Object.entries(guestFns).map(([name, guestFn]) => [ + name, + orchestrate(name, hostCtx, guestFn), + ]), + ); + + return { adminAsyncFlow, + orchestrate, + orchestrateAll, }; }; harden(makeOrchestrationFacade); diff --git a/packages/orchestration/src/utils/start-helper.js b/packages/orchestration/src/utils/start-helper.js index a25de447666..9f9b9ac417f 100644 --- a/packages/orchestration/src/utils/start-helper.js +++ b/packages/orchestration/src/utils/start-helper.js @@ -1,13 +1,16 @@ import { prepareAsyncFlowTools } from '@agoric/async-flow'; +import { pickFacet } from '@agoric/vat-data'; import { prepareVowTools } from '@agoric/vow'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; -import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; -import { makeOrchestrationFacade } from '../facade.js'; import { makeChainHub } from '../exos/chain-hub.js'; -import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js'; import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js'; import { prepareLocalChainFacade } from '../exos/local-chain-facade.js'; +import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; +import { prepareOrchestratorKit } from '../exos/orchestrator.js'; +import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js'; +import { makeOrchestrationFacade } from '../facade.js'; +import { makeZoeTools } from './zoe-tools.js'; /** * @import {PromiseKit} from '@endo/promise-kit' @@ -34,6 +37,8 @@ import { prepareLocalChainFacade } from '../exos/local-chain-facade.js'; * Helper that a contract start function can use to set up the objects needed * for orchestration. * + * TODO strip problematic operations from ZCF (e.g., getPayouts) + * * @param {ZCF} zcf * @param {Baggage} baggage * @param {OrchestrationPowers} remotePowers @@ -58,6 +63,7 @@ export const provideOrchestration = ( contract: zone.subZone('contract'), orchestration: zone.subZone('orchestration'), vows: zone.subZone('vows'), + zoe: zone.subZone('zoe'), }; })(); @@ -67,6 +73,8 @@ export const provideOrchestration = ( const chainHub = makeChainHub(agoricNames, vowTools); + const zoeTools = makeZoeTools(zones.zoe, { zcf, vowTools }); + const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( zones.orchestration, @@ -106,20 +114,38 @@ export const provideOrchestration = ( vowTools, }); - const facade = makeOrchestrationFacade({ - zcf, - zone: zones.orchestration, + const makeOrchestratorKit = prepareOrchestratorKit(zones.orchestration, { + asyncFlowTools, chainHub, - makeLocalOrchestrationAccountKit, + localchain: remotePowers.localchain, makeRecorderKit, - makeCosmosOrchestrationAccount, makeLocalChainFacade, makeRemoteChainFacade, + storageNode: remotePowers.storageNode, + orchestrationService: remotePowers.orchestrationService, + timerService, + vowTools, + zcf, + }); + + const makeOrchestrator = pickFacet(makeOrchestratorKit, 'orchestrator'); + + const facade = makeOrchestrationFacade({ + zcf, + zone: zones.orchestration, + makeRecorderKit, + makeOrchestrator, asyncFlowTools, vowTools, ...remotePowers, }); - return { ...facade, chainHub, vowTools, zone: zones.contract }; + return { + ...facade, + chainHub, + vowTools, + zoeTools, + zone: zones.contract, + }; }; harden(provideOrchestration); diff --git a/packages/orchestration/src/utils/zoe-tools.js b/packages/orchestration/src/utils/zoe-tools.js new file mode 100644 index 00000000000..0f756fd1360 --- /dev/null +++ b/packages/orchestration/src/utils/zoe-tools.js @@ -0,0 +1,64 @@ +import { Fail } from '@endo/errors'; +import { atomicTransfer } from '@agoric/zoe/src/contractSupport/index.js'; + +/** + * @import {LocalOrchestrationAccountKit} from '../exos/local-orchestration-account.js'; + * @import {Vow, VowTools} from '@agoric/vow'; + * @import {Zone} from '@agoric/zone'; + * @import {OrchestrationAccount} from '../orchestration-api.js' + */ + +/** + * @param {Zone} zone + * @param {{ zcf: ZCF; vowTools: VowTools }} io + */ +export const makeZoeTools = (zone, { zcf, vowTools }) => { + /** + * Transfer the `give` a seat to a local account. + */ + const localTransfer = vowTools.retriable( + zone, + 'localTransfer', + /** + * @type {( + * srcSeat: ZCFSeat, + * localAccount: LocalOrchestrationAccountKit['holder'], + * give: AmountKeywordRecord, + * ) => Promise} + */ + async (srcSeat, localAccount, give) => { + !srcSeat.hasExited() || Fail`The seat cannot have exited.`; + const { zcfSeat: tempSeat, userSeat: userSeatP } = zcf.makeEmptySeatKit(); + const userSeat = await userSeatP; + atomicTransfer(zcf, srcSeat, tempSeat, give); + tempSeat.exit(); + // TODO get the userSeat into baggage so it's at least recoverable + // const userSeat = await subzone.makeOnce( + // 'localTransferHelper', + // async () => { + // const { zcfSeat: tempSeat, userSeat: userSeatP } = + // zcf.makeEmptySeatKit(); + // const uSeat = await userSeatP; + // // TODO how do I store in the place for this retriable? + // atomicTransfer(zcf, srcSeat, tempSeat, give); + // tempSeat.exit(); + // return uSeat; + // }, + // ); + + // Now all the `give` are accessible, so we can move them to the localAccount` + + const promises = Object.entries(give).map(async ([kw, _amount]) => { + const pmt = await userSeat.getPayout(kw); + // TODO arrange recovery on upgrade of pmt? + return localAccount.deposit(pmt); + }); + await Promise.all(promises); + // TODO remove userSeat from baggage + }, + ); + + return harden({ + localTransfer, + }); +}; diff --git a/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.md b/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.md index b26f76cdb5a..a63bd488ab3 100644 --- a/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.md @@ -25,12 +25,10 @@ Generated by [AVA](https://avajs.dev). unwrapMap: 'Alleged: weakMapStore', }, contract: { - ResumableAgoricNamesHack_kindHandle: 'Alleged: kind', 'Send CF_kindHandle': 'Alleged: kind', 'Send CF_singleton': 'Alleged: Send CF', 'Send PF_kindHandle': 'Alleged: kind', 'Send PF_singleton': 'Alleged: Send PF', - vbankAssetsByBrand: {}, }, orchestration: { 'Local Orchestration Account Kit_kindHandle': 'Alleged: kind', @@ -44,6 +42,10 @@ Generated by [AVA](https://avajs.dev). 1: { contractState_kindHandle: 'Alleged: kind', contractState_singleton: 'Alleged: contractState', + findBrandInVBank_kindHandle: 'Alleged: kind', + findBrandInVBank_singleton: 'Alleged: findBrandInVBank', + localTransfer_kindHandle: 'Alleged: kind', + localTransfer_singleton: 'Alleged: localTransfer', }, }, }, @@ -53,4 +55,5 @@ Generated by [AVA](https://avajs.dev). VowInternalsKit_kindHandle: 'Alleged: kind', WatchUtils_kindHandle: 'Alleged: kind', }, + zoe: {}, } diff --git a/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.snap b/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.snap index 68b8203f347..923f7c29cf8 100644 Binary files a/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.snap and b/packages/orchestration/test/examples/snapshots/sendAnywhere.test.ts.snap differ diff --git a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md b/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md index 5a872f186dd..6d3674e4d49 100644 --- a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md @@ -43,4 +43,5 @@ Generated by [AVA](https://avajs.dev). VowInternalsKit_kindHandle: 'Alleged: kind', WatchUtils_kindHandle: 'Alleged: kind', }, + zoe: {}, } diff --git a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap b/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap index eb1faeb51a1..68062a520ee 100644 Binary files a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap and b/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap differ diff --git a/packages/vow/src/tools.js b/packages/vow/src/tools.js index 19b5a2390f8..26e8dca10e7 100644 --- a/packages/vow/src/tools.js +++ b/packages/vow/src/tools.js @@ -10,6 +10,22 @@ import { makeWhen } from './when.js'; * @import {IsRetryableReason, AsPromiseFunction, EVow, Vow, ERef} from './types.js'; */ +// TODO find a good home, DRY with orchestration package. +/** + * Converts a function type that returns a Promise to a function type that + * returns a Vow. If the input is not a function returning a Promise, it + * preserves the original type. + * + * @template T - The type to transform + * @typedef {T extends ( + * ...args: infer Args + * ) => Promise + * ? (...args: Args) => Vow + * : T extends (...args: infer Args) => infer R + * ? (...args: Args) => R + * : T} PromiseToVow + */ + /** * NB: Not to be used in a Vat. It doesn't know what an upgrade is. For that you * need `prepareVowTools` from `vat.js`. @@ -40,14 +56,15 @@ export const prepareVowTools = (zone, powers = {}) => { * * The internal functions * - * @template T + * @template {(...args: any[]) => any} F * @param {Zone} fnZone - the zone for the named function * @param {string} name - * @param {(...args: unknown[]) => ERef} fn - * @returns {(...args: unknown[]) => Vow} + * @param {F} fn + * @returns {PromiseToVow} */ const retriable = (fnZone, name, fn) => + // @ts-expect-error cast (...args) => { return watch(fn(...args)); };