From bc688f1e6b5d61b735a85eca6da96d7c90c2f1d7 Mon Sep 17 00:00:00 2001 From: T6 Date: Thu, 15 Jun 2023 14:06:10 -0400 Subject: [PATCH] feat: scope (#1084) Co-authored-by: Harry Solovay --- Readme.md | 5 +- examples/blocks.eg.ts | 4 +- examples/dev/metadata.eg.ts | 3 +- examples/dev/storage_sizes.eg.ts | 4 +- examples/dynamic.eg.ts | 4 +- examples/ink/deploy.eg.ts | 4 +- examples/ink/interact.eg.ts | 10 +-- examples/misc/identity.eg.ts | 7 +- examples/misc/indices.eg.ts | 10 +-- examples/multisig/basic.eg.ts | 17 ++--- examples/multisig/stash.eg.ts | 13 ++-- examples/multisig/virtual.eg.ts | 15 ++-- examples/nfts.eg.ts | 18 ++--- examples/paginate.eg.ts | 8 ++- examples/raw_rpc/call.eg.ts | 4 +- examples/raw_rpc/subscription.eg.ts | 4 +- examples/read/account_info.eg.ts | 4 +- examples/read/era_reward_points.eg.ts | 4 +- examples/read/now.eg.ts | 4 +- examples/read/para_heads.eg.ts | 4 +- examples/rune/collections.eg.ts | 6 +- examples/rune/intro.eg.ts | 10 +-- examples/rune/subclassing.eg.ts | 8 ++- examples/rune/u_track.eg.ts | 12 ++-- examples/sign/ed25519.eg.ts | 11 +-- examples/sign/offline.eg.ts | 9 +-- examples/sign/pjs.eg.ts | 5 +- examples/smoldot/fetch_chainspec.eg.ts | 3 +- examples/smoldot/smoldot.eg.ts | 4 +- examples/tx/balances_transfer.eg.ts | 9 +-- examples/tx/handle_errors.eg.ts | 4 +- examples/tx/utility_batch.eg.ts | 10 +-- examples/tx/weights_and_estimates.eg.ts | 6 +- examples/watch.eg.ts | 4 +- examples/xcm/asset_teleportation.eg.ts | 12 ++-- examples/xcm/reserve_transfer.eg.ts | 24 ++++--- fluent/ConnectionRune.ts | 10 +-- rpc/Connection.ts | 3 + rune/MetaRune.ts | 14 ++-- rune/Rune.test.ts | 64 ++++++++--------- rune/Rune.ts | 73 +++++++++++--------- rune/ValueRune.ts | 92 ++++++++++++------------- 42 files changed, 293 insertions(+), 246 deletions(-) diff --git a/Readme.md b/Readme.md index 2c05c893d..3594e9096 100644 --- a/Readme.md +++ b/Readme.md @@ -97,8 +97,11 @@ Retrieve the first 10 entries from a storage map of Polkadot. ```ts import { polkadot } from "@capi/polkadot" +import { Scope } from "capi" -const accounts = await polkadot.System.Account.entries({ limit: 10 }).run() +const accounts = await polkadot.System.Account + .entries({ limit: 10 }) + .run(new Scope()) ``` ## Development Networks diff --git a/examples/blocks.eg.ts b/examples/blocks.eg.ts index 48e7de69e..5a91dd573 100644 --- a/examples/blocks.eg.ts +++ b/examples/blocks.eg.ts @@ -7,7 +7,7 @@ */ import { $eventRecord, metadata, polkadot } from "@capi/polkadot" -import { $, $extrinsic, known, Rune } from "capi" +import { $, $extrinsic, known, Rune, Scope } from "capi" import { babeBlockAuthor } from "capi/patterns/consensus" /// Reference the latest block hash. @@ -38,7 +38,7 @@ const collection = await Rune events, author, }) - .run() + .run(new Scope()) /// Ensure that collection contains the expected shape of data. console.log("Collection:", collection) diff --git a/examples/dev/metadata.eg.ts b/examples/dev/metadata.eg.ts index d50a7aab0..fc0c7e6fd 100644 --- a/examples/dev/metadata.eg.ts +++ b/examples/dev/metadata.eg.ts @@ -12,8 +12,9 @@ */ import { polkadotDev } from "@capi/polkadot-dev" +import { Scope } from "capi" /// Execute the metadata Rune. -const metadata = await polkadotDev.metadata.run() +const metadata = await polkadotDev.metadata.run(new Scope()) console.log("Metadata:", metadata) diff --git a/examples/dev/storage_sizes.eg.ts b/examples/dev/storage_sizes.eg.ts index 6002b0e13..d1889d38b 100644 --- a/examples/dev/storage_sizes.eg.ts +++ b/examples/dev/storage_sizes.eg.ts @@ -8,11 +8,11 @@ */ import { polkadotDev } from "@capi/polkadot-dev" -import { $ } from "capi" +import { $, Scope } from "capi" import { storageSizes } from "capi/patterns/storage_sizes" /// Use the storageSizes factory to produce a Rune. Then execute it. -const sizes = await storageSizes(polkadotDev).run() +const sizes = await storageSizes(polkadotDev).run(new Scope()) /// Ensure `sizes` is of the expected shape. console.log("Sizes:", sizes) diff --git a/examples/dynamic.eg.ts b/examples/dynamic.eg.ts index 9cf0ef13c..2a24356ba 100644 --- a/examples/dynamic.eg.ts +++ b/examples/dynamic.eg.ts @@ -13,7 +13,7 @@ * 3. Chain-specifics are untyped (be wary to supply the correct data, as the checker is on vacation). */ -import { $, ChainRune, WsConnection } from "capi" +import { $, ChainRune, Scope, WsConnection } from "capi" /// We could also initialize a `ChainRune` with `WsConnection` and an RPC node WebSocket URL. const chain = ChainRune.from(WsConnection.bind("wss://rpc.polkadot.io")) @@ -26,7 +26,7 @@ const Account = System.storage("Account") /// Read the first ten entries of the `Account` storage map. /// Note how the lack of partial key is communicated via `null`. -const entries = await Account.entries({ limit: 10 }).run() +const entries = await Account.entries({ limit: 10 }).run(new Scope()) /// The result should contain a `[Uint8Array, AccountInfo]` tuple of length 10. console.log("Entries page:", entries) diff --git a/examples/ink/deploy.eg.ts b/examples/ink/deploy.eg.ts index 008453606..3446020ee 100644 --- a/examples/ink/deploy.eg.ts +++ b/examples/ink/deploy.eg.ts @@ -7,7 +7,7 @@ */ import { contractsDev } from "@capi/contracts-dev" -import { $, createDevUsers, hex, Sr25519, ss58 } from "capi" +import { $, createDevUsers, hex, Scope, Sr25519, ss58 } from "capi" import { InkMetadataRune } from "capi/patterns/ink" import { signature } from "capi/patterns/signature/polkadot" @@ -36,7 +36,7 @@ const events = await metadata .sent() .dbgStatus("Instantiation:") .inBlockEvents() - .run() + .run(new Scope()) /// > Note: we're using `inBlockEvents` and not `finalizedEvents` because our provider /// > is configured with instant finality. This is optimal for testing, but not production. diff --git a/examples/ink/interact.eg.ts b/examples/ink/interact.eg.ts index bcd964dd3..95c72f9e3 100644 --- a/examples/ink/interact.eg.ts +++ b/examples/ink/interact.eg.ts @@ -8,10 +8,12 @@ import { contractsDev } from "@capi/contracts-dev" import { assert } from "asserts" -import { $, createDevUsers, hex } from "capi" +import { $, createDevUsers, hex, Scope } from "capi" import { InkMetadataRune } from "capi/patterns/ink" import { signature } from "capi/patterns/signature/polkadot" +const scope = new Scope() + /// Get two test users. Alexa will deploy, Billy will be the recipient of an erc20 /// token transfer. const { alexa, billy } = await createDevUsers() @@ -37,7 +39,7 @@ const state = contract.call({ }) /// Retrieve the initial state. -const initialState = await state.run() +const initialState = await state.run(scope) console.log("Alexa initial balance:", initialState) /// Use the `flip` method to *flip* the contract instance state. @@ -52,7 +54,7 @@ const events = await contract .dbgStatus("Transfer:") .inBlockEvents() .pipe(contract.emittedEvents) - .run() + .run(scope) /// Ensure the emitted events are of the expected shape. /// In this case, we expect only a `Transfer` event. @@ -70,7 +72,7 @@ $.assert( console.log(events) /// Retrieve the final state. -const finalState = await state.run() +const finalState = await state.run(scope) console.log("Alexa final balance:", finalState) assert(finalState < initialState) diff --git a/examples/misc/identity.eg.ts b/examples/misc/identity.eg.ts index 015876ecf..bbbbbc001 100644 --- a/examples/misc/identity.eg.ts +++ b/examples/misc/identity.eg.ts @@ -7,11 +7,12 @@ */ import { polkadotDev } from "@capi/polkadot-dev" -import { $, createDevUsers, is } from "capi" +import { $, createDevUsers, is, Scope } from "capi" import { IdentityInfoTranscoders } from "capi/patterns/identity" import { signature } from "capi/patterns/signature/polkadot" const { alexa } = await createDevUsers() +const scope = new Scope() /// Initialize an `IdentityInfoTranscoders` of shape `{ stars: number }`. const transcoders = new IdentityInfoTranscoders({ stars: $.u8 }) @@ -29,7 +30,7 @@ await polkadotDev.Identity .sent() .dbgStatus("Set identity:") .finalized() - .run() + .run(scope) /// Retrieve and decode the identity info. const infoDecoded = await polkadotDev.Identity.IdentityOf @@ -37,7 +38,7 @@ const infoDecoded = await polkadotDev.Identity.IdentityOf .unhandle(is(undefined)) .access("info") .pipe((raw) => transcoders.decode(raw)) - .run() + .run(scope) console.log("identity info:", infoDecoded) $.assert($.u8, infoDecoded.additional.stars) diff --git a/examples/misc/indices.eg.ts b/examples/misc/indices.eg.ts index c05ee0261..0bb506fbc 100644 --- a/examples/misc/indices.eg.ts +++ b/examples/misc/indices.eg.ts @@ -7,12 +7,14 @@ import { polkadotDev } from "@capi/polkadot-dev" import { assertEquals } from "asserts" -import { createDevUsers, is } from "capi" +import { createDevUsers, is, Scope } from "capi" import { signature } from "capi/patterns/signature/polkadot" const { alexa } = await createDevUsers() +const scope = new Scope() -const index = 254 +/// Generate a random (but reasonably large) index. +const index = (crypto.getRandomValues(new Uint32Array([0]))[0]! | 4646) >>> 0 /// Claim the index. const hash = await polkadotDev.Indices @@ -21,14 +23,14 @@ const hash = await polkadotDev.Indices .sent() .dbgStatus("Claim index:") .finalized() - .run() + .run(scope) /// Use the index to key into the indices accounts map. const mapped = await polkadotDev.Indices.Accounts .value(index, hash) .unhandle(is(undefined)) .access(0) - .run() + .run(scope) /// The retrieved mapped account id should be Alexa's. console.log(`Index ${index} Mapped to:`, mapped) diff --git a/examples/multisig/basic.eg.ts b/examples/multisig/basic.eg.ts index 87879cb75..ce94b99c0 100644 --- a/examples/multisig/basic.eg.ts +++ b/examples/multisig/basic.eg.ts @@ -7,11 +7,12 @@ import { polkadotDev } from "@capi/polkadot-dev" import { assert } from "asserts" -import { $, createDevUsers, is } from "capi" +import { $, createDevUsers, is, Scope } from "capi" import { MultisigRune } from "capi/patterns/multisig" import { signature } from "capi/patterns/signature/polkadot" const { alexa, billy, carol, david } = await createDevUsers() +const scope = new Scope() /// Initialize the `MultisigRune` with Alexa, Billy and Carol. Set the passing threshold to 2. const multisig = MultisigRune.from(polkadotDev, { @@ -26,7 +27,7 @@ const davidFree = polkadotDev.System.Account .access("data", "free") /// Execute the `davidFree` Rune. -const davidFreeInitial = await davidFree.run() +const davidFreeInitial = await davidFree.run(scope) console.log("David free initial:", davidFreeInitial) /// Transfer initial funds to the multisig (existential deposit). @@ -36,7 +37,7 @@ await multisig .sent() .dbgStatus("Existential deposit:") .finalized() - .run() + .run(scope) /// Describe the call we wish to dispatch from the multisig. const call = polkadotDev.Balances.transferKeepAlive({ @@ -51,17 +52,17 @@ await multisig .sent() .dbgStatus("Proposal:") .finalized() - .run() + .run(scope) /// Check whether the call has been proposed. -const isProposed = await multisig.isProposed(call.callHash).run() +const isProposed = await multisig.isProposed(call.callHash).run(scope) console.log("Is proposed:", isProposed) assert(isProposed) const { approvals } = await multisig .proposal(call.callHash) .unhandle(is(undefined)) - .run() + .run(scope) /// `approvals` should be a list of the approvers (account ids). console.log("Approvals:", approvals) @@ -74,10 +75,10 @@ await multisig .sent() .dbgStatus("Final approval:") .finalized() - .run() + .run(scope) /// Check to see whether David's balance has in fact changed -const davidFreeFinal = await davidFree.run() +const davidFreeFinal = await davidFree.run(scope) console.log("David free final:", davidFreeFinal) /// The final balance should be greater than the initial. diff --git a/examples/multisig/stash.eg.ts b/examples/multisig/stash.eg.ts index d17260cd3..18b3310a0 100644 --- a/examples/multisig/stash.eg.ts +++ b/examples/multisig/stash.eg.ts @@ -7,12 +7,13 @@ import { MultiAddress, polkadotDev } from "@capi/polkadot-dev" import { assert } from "asserts" -import { createDevUsers, is } from "capi" +import { createDevUsers, is, Scope } from "capi" import { MultisigRune } from "capi/patterns/multisig" import { filterPureCreatedEvents } from "capi/patterns/proxy" import { signature } from "capi/patterns/signature/polkadot" const { alexa, billy, carol } = await createDevUsers() +const scope = new Scope() /// Initialize the `MultisigRune` with Alexa, Billy and Carol. Set the passing threshold to 2. const multisig = MultisigRune.from(polkadotDev, { @@ -27,7 +28,7 @@ await multisig .sent() .dbgStatus("Existential deposit:") .finalized() - .run() + .run(scope) /// Describe the call which we wish to dispatch from the multisig account: /// the creation of the stash / pure proxy, belonging to the multisig account itself. @@ -44,7 +45,7 @@ await multisig .sent() .dbgStatus("Proposal:") .finalized() - .run() + .run(scope) /// Approve the stash creation call and extract the pure creation event, which should /// contain its account id. @@ -56,7 +57,7 @@ const stashAccountId = await multisig .finalizedEvents() .pipe(filterPureCreatedEvents) .access(0, "pure") - .run() + .run(scope) /// Send funds to the stash (existential deposit). await polkadotDev.Balances @@ -68,14 +69,14 @@ await polkadotDev.Balances .sent() .dbgStatus("Fund Stash:") .finalized() - .run() + .run(scope) /// Ensure that the funds arrived successfully. const stashFree = await polkadotDev.System.Account .value(stashAccountId) .unhandle(is(undefined)) .access("data", "free") - .run() + .run(scope) /// The stash's free should be greater than zero. console.log("Stash free:", stashFree) diff --git a/examples/multisig/virtual.eg.ts b/examples/multisig/virtual.eg.ts index 6b14ebc47..b7f507f39 100644 --- a/examples/multisig/virtual.eg.ts +++ b/examples/multisig/virtual.eg.ts @@ -19,12 +19,13 @@ import { MultiAddress, polkadotDev } from "@capi/polkadot-dev" import { assert } from "asserts" -import { $, createDevUsers, is, Rune, Sr25519 } from "capi" +import { $, createDevUsers, is, Rune, Scope, Sr25519 } from "capi" import { VirtualMultisigRune } from "capi/patterns/multisig" import { signature } from "capi/patterns/signature/polkadot" import { parse } from "../../deps/std/flags.ts" const { alexa, billy, carol, david } = await createDevUsers() +const scope = new Scope() /// To reference a virtual multisig, one must have several pieces of data, including /// the member->proxy account id lookup, the threshold and the stash account id. @@ -40,7 +41,7 @@ if (!state) { deployer: alexa.address, }, signature({ sender: alexa })) .hex - .run() + .run(scope) } console.log("State:", state) @@ -59,7 +60,7 @@ await polkadotDev.Balances .sent() .dbgStatus("Fund stash:") .finalized() - .run() + .run(scope) /// Reference David's free balance. const davidFree = polkadotDev.System.Account @@ -68,7 +69,7 @@ const davidFree = polkadotDev.System.Account .access("data", "free") /// Retrieve David's initial free. -const davidFreeInitial = await davidFree.run() +const davidFreeInitial = await davidFree.run(scope) console.log("David free initial:", davidFreeInitial) /// Describe the call we wish to dispatch from the virtual multisig's stash. @@ -78,11 +79,11 @@ const call = polkadotDev.Balances.transfer({ }) /// Fund Billy and Carol's proxy accounts (existential deposits). -await fundAndRatify("billy", billy).run() -await fundAndRatify("carol", carol).run() +await fundAndRatify("billy", billy).run(scope) +await fundAndRatify("carol", carol).run(scope) /// Retrieve David's final balance. -const davidFreeFinal = await davidFree.run() +const davidFreeFinal = await davidFree.run(scope) console.log("David free final:", davidFreeFinal) // David's final balance should be greater than the initial. diff --git a/examples/nfts.eg.ts b/examples/nfts.eg.ts index ebe5ea01f..1669f7fb5 100644 --- a/examples/nfts.eg.ts +++ b/examples/nfts.eg.ts @@ -15,10 +15,12 @@ import { RuntimeEvent, } from "@capi/rococo-dev-westmint" import { assertEquals } from "asserts" -import { $, createDevUsers, is, Rune } from "capi" +import { $, createDevUsers, is, Rune, Scope } from "capi" import { DefaultCollectionSetting, DefaultItemSetting } from "capi/patterns/nfts" import { signature } from "capi/patterns/signature/statemint" +const scope = new Scope() + /// Create two dev users. Alexa will mint and list the NFT. Billy will purchase it. const { alexa, billy } = await createDevUsers() @@ -38,7 +40,7 @@ const createEvents = await rococoDevWestmint.Nfts .sent() .dbgStatus("Create collection:") .finalizedEvents() - .run() + .run(scope) /// Extract the collection's id from emitted events. const collection = (() => { @@ -68,7 +70,7 @@ await rococoDevWestmint.Nfts .sent() .dbgStatus("Mint the NFT:") .finalized() - .run() + .run(scope) const owner = rococoDevWestmint.Nfts.Item .value([collection, item]) @@ -76,7 +78,7 @@ const owner = rococoDevWestmint.Nfts.Item .access("owner") /// Retrieve the final owner. -const initialOwner = await owner.run() +const initialOwner = await owner.run(scope) /// Ensure Alexa is the initial owner. console.log("Initial owner:", initialOwner) @@ -100,14 +102,14 @@ await rococoDevWestmint.Utility .sent() .dbgStatus("Sale prep:") .finalized() - .run() + .run(scope) /// Retrieve the price of the NFT. const bidPrice = await rococoDevWestmint.Nfts.ItemPriceOf .value([collection, item]) .unhandle(is(undefined)) .access(0) - .run() + .run(scope) /// Ensure the `bidPrice` is the expected value. console.log(bidPrice) @@ -120,10 +122,10 @@ await rococoDevWestmint.Nfts .sent() .dbgStatus("Purchase:") .finalized() - .run() + .run(scope) /// Retrieve the final owner. -const finalOwner = await owner.run() +const finalOwner = await owner.run(scope) /// Ensure Billy is the final owner. console.log("Final owner:", finalOwner) diff --git a/examples/paginate.eg.ts b/examples/paginate.eg.ts index 0909559de..2c0d60698 100644 --- a/examples/paginate.eg.ts +++ b/examples/paginate.eg.ts @@ -5,17 +5,19 @@ */ import { $accountInfo, polkadotDev } from "@capi/polkadot-dev" -import { $ } from "capi" +import { $, Scope } from "capi" + +const scope = new Scope() // Reference the first 10 keys of a polkadot dev chain's system account map. -const accountKeys = await polkadotDev.System.Account.keys({ limit: 10 }).run() +const accountKeys = await polkadotDev.System.Account.keys({ limit: 10 }).run(scope) /// Each key should be of type `Uint8Array`. console.log("Account keys:", accountKeys) $.assert($.sizedArray($.uint8Array, 10), accountKeys) /// Reference the first 10 key-value pairs of a polkadot dev chain's system account map. -const accountEntries = await polkadotDev.System.Account.entries({ limit: 10 }).run() +const accountEntries = await polkadotDev.System.Account.entries({ limit: 10 }).run(scope) /// Each entry should be of type `[Uint8Array, AccountInfo]` console.log("Account entries:", accountEntries) diff --git a/examples/raw_rpc/call.eg.ts b/examples/raw_rpc/call.eg.ts index 62ce8a5c0..d2bc01bbf 100644 --- a/examples/raw_rpc/call.eg.ts +++ b/examples/raw_rpc/call.eg.ts @@ -5,12 +5,12 @@ */ import { polkadotDev } from "@capi/polkadot-dev" -import { $ } from "capi" +import { $, Scope } from "capi" /// Make a call. const hash = await polkadotDev.connection .call("chain_getFinalizedHead") - .run() + .run(new Scope()) /// Ensure the result is a block hash. $.assert($.str, hash) diff --git a/examples/raw_rpc/subscription.eg.ts b/examples/raw_rpc/subscription.eg.ts index 8ab0a7304..656115d69 100644 --- a/examples/raw_rpc/subscription.eg.ts +++ b/examples/raw_rpc/subscription.eg.ts @@ -5,12 +5,12 @@ */ import { polkadotDev } from "@capi/polkadot-dev" -import { $, known } from "capi" +import { $, known, Scope } from "capi" /// Get an async iterator, which yields subscription events. const headerIter = polkadotDev.connection .subscribe("chain_subscribeFinalizedHeads", "chain_unsubscribeAllHeads") - .iter() + .iter(new Scope()) /// Create a simple counter so that we can break iteration at 3. let i = 0 diff --git a/examples/read/account_info.eg.ts b/examples/read/account_info.eg.ts index 9dbd14b97..3df1be8b5 100644 --- a/examples/read/account_info.eg.ts +++ b/examples/read/account_info.eg.ts @@ -5,12 +5,12 @@ */ import { $accountInfo, polkadotDev } from "@capi/polkadot-dev" -import { $, createDevUsers } from "capi" +import { $, createDevUsers, Scope } from "capi" const { alexa } = await createDevUsers() /// Retrieve Alexa's account info. -const accountInfo = await polkadotDev.System.Account.value(alexa.publicKey).run() +const accountInfo = await polkadotDev.System.Account.value(alexa.publicKey).run(new Scope()) /// Ensure that the account info is of the expected shape. console.log("Account info:", accountInfo) diff --git a/examples/read/era_reward_points.eg.ts b/examples/read/era_reward_points.eg.ts index 33b4a2b02..982f4c194 100644 --- a/examples/read/era_reward_points.eg.ts +++ b/examples/read/era_reward_points.eg.ts @@ -7,7 +7,7 @@ */ import { $eraRewardPoints, westend } from "@capi/westend" -import { $, is } from "capi" +import { $, is, Scope } from "capi" /// Reference the active era index. const idx = westend.Staking.ActiveEra @@ -16,7 +16,7 @@ const idx = westend.Staking.ActiveEra .access("index") /// Retrieve the reward points corresponding to `idx`. -const points = await westend.Staking.ErasRewardPoints.value(idx).run() +const points = await westend.Staking.ErasRewardPoints.value(idx).run(new Scope()) /// Ensure the era reward points is of the correct shape. console.log("Era reward points:", points) diff --git a/examples/read/now.eg.ts b/examples/read/now.eg.ts index c3130c9fa..78fdd3f64 100644 --- a/examples/read/now.eg.ts +++ b/examples/read/now.eg.ts @@ -5,10 +5,10 @@ */ import { polkadot } from "@capi/polkadot" -import { $ } from "capi" +import { $, Scope } from "capi" /// Retrieve the chain's current recorded time. -const now = await polkadot.Timestamp.Now.value().run() +const now = await polkadot.Timestamp.Now.value().run(new Scope()) /// Ensure `now` is of the correct shape. console.log("Now:", now) diff --git a/examples/read/para_heads.eg.ts b/examples/read/para_heads.eg.ts index 1f2f06253..fe8b92ffa 100644 --- a/examples/read/para_heads.eg.ts +++ b/examples/read/para_heads.eg.ts @@ -7,7 +7,7 @@ */ import { polkadot } from "@capi/polkadot" -import { $, ArrayRune, is, ValueRune } from "capi" +import { $, ArrayRune, is, Scope, ValueRune } from "capi" /// Retrieve the head for each id in the parachains storage. const heads = await polkadot.Paras.Parachains @@ -17,7 +17,7 @@ const heads = await polkadot.Paras.Parachains .mapArray((id) => polkadot.Paras.Heads.value(id).unhandle(is(undefined))) .into(ValueRune) .rehandle(is(undefined)) - .run() + .run(new Scope()) /// Ensure `heads` is of the expected shape. console.log("Parachain heads:", heads) diff --git a/examples/rune/collections.eg.ts b/examples/rune/collections.eg.ts index 885380f39..982ce4871 100644 --- a/examples/rune/collections.eg.ts +++ b/examples/rune/collections.eg.ts @@ -5,7 +5,9 @@ */ import { assertEquals } from "asserts" -import { Rune } from "capi" +import { Rune, Scope } from "capi" + +const scope = new Scope() /// Begin with three arbitrary Runes. These could come from anywhere. /// In this case, we initialize them from string constants. @@ -21,7 +23,7 @@ const e = Rune.object({ c, d }) /// Execute `e`. Note the resolution of inner Runes is parallelized, /// much like a `Promise.all` call. -const result = await e.run() +const result = await e.run(scope) /// Ensure the result is of the expected shape. console.log(result) diff --git a/examples/rune/intro.eg.ts b/examples/rune/intro.eg.ts index 5e6b63c2d..b7378d090 100644 --- a/examples/rune/intro.eg.ts +++ b/examples/rune/intro.eg.ts @@ -7,13 +7,15 @@ */ import { assertEquals } from "asserts" -import { Rune, RunicArgs } from "capi" +import { Rune, RunicArgs, Scope } from "capi" + +const scope = new Scope() /// Lift a constant value into a Rune. const value = Rune.constant(3) /// Map over `value` and multiply it by itself. Then, run it. -const a = await value.map((value) => value * value).run() +const a = await value.map((value) => value * value).run(scope) /// Ensure `a` is as expected. console.log("a:", a) @@ -29,9 +31,9 @@ function exp(...[n]: RunicArgs) { /// Utilize the `exp` factory and run the resulting Rune. Note we can /// supply either a resolved value `3` or another Rune, which resolves /// to the factory's parameter type. -const b = await exp(3).run() +const b = await exp(3).run(scope) console.log("b:", b) assertEquals(b, 9) -const c = await exp(value).run() +const c = await exp(value).run(scope) console.log("c:", c) assertEquals(c, 9) diff --git a/examples/rune/subclassing.eg.ts b/examples/rune/subclassing.eg.ts index 9d988f33e..06b03f371 100644 --- a/examples/rune/subclassing.eg.ts +++ b/examples/rune/subclassing.eg.ts @@ -6,7 +6,9 @@ */ import { assertEquals } from "asserts" -import { Rune, RunicArgs } from "capi" +import { Rune, RunicArgs, Scope } from "capi" + +const scope = new Scope() /// The following is a Rune of `number`. const num = Rune.constant(46) @@ -25,7 +27,7 @@ class MyNumRune extends Rune { const myNum = num.into(MyNumRune) /// This allows us to operate on `num` with the `MyNumRune`-defined fluent API. -const result0 = await myNum.add(100).run() +const result0 = await myNum.add(100).run(scope) /// Ensure that `result0` is equal to 146. console.log(result0) @@ -47,6 +49,6 @@ class MyRuneWithArgs extends Rune { const myNumWithArgs = num.into(MyRuneWithArgs, 100) /// Ensure that `result1` is equal to 146. -const result1 = await myNumWithArgs.added().run() +const result1 = await myNumWithArgs.added().run(scope) console.log(result1) assertEquals(result1, 146) diff --git a/examples/rune/u_track.eg.ts b/examples/rune/u_track.eg.ts index 73f91a0be..8b68f8101 100644 --- a/examples/rune/u_track.eg.ts +++ b/examples/rune/u_track.eg.ts @@ -7,7 +7,9 @@ */ import { assert, assertEquals, assertInstanceOf } from "asserts" -import { is, Rune, Unhandled } from "capi" +import { is, Rune, Scope, Unhandled } from "capi" + +const scope = new Scope() /// Define a custom error, potentially with a constructor accepting /// some error-specific data (here we'll leave as is). @@ -28,7 +30,7 @@ const start = initial.map((value) => Math.random() > .5 ? new MyError() : value) /// Rune's execution will throw an `Unhandled`, within which the intercepted `MyError` /// instance resides. The other half of the time, we'll get our initial message. try { - const unhandled = await start.unhandle(is(MyError)).run() + const unhandled = await start.unhandle(is(MyError)).run(scope) console.log("Unhandled:", unhandled) assertEquals(unhandled, INITIAL_MSG) } catch (e) { @@ -40,7 +42,7 @@ try { /// A better solution might be to `handle` the error. We can use `handle` along with /// the error constructor or a type guard to specify some alternative execution. const RECOVERY_MSG = "Smooth recovery" -const handled = await start.handle(is(MyError), () => Rune.constant(RECOVERY_MSG)).run() +const handled = await start.handle(is(MyError), () => Rune.constant(RECOVERY_MSG)).run(scope) console.log("Handled:", handled) assert(handled === INITIAL_MSG || handled === RECOVERY_MSG) @@ -49,7 +51,7 @@ const unReHandled = await start .unhandle(is(MyError)) .map((msg) => `**${msg}**`) .rehandle(is(MyError)) - .run() + .run(scope) console.log("(Un|Re)handled:", unReHandled) assert(unReHandled === `**${INITIAL_MSG}**` || unReHandled instanceof MyError) @@ -57,6 +59,6 @@ assert(unReHandled === `**${INITIAL_MSG}**` || unReHandled instanceof MyError) const unReHandledWithFallback = await start .unhandle(is(MyError)) .rehandle(is(MyError), () => Rune.constant(RECOVERY_MSG)) - .run() + .run(scope) console.log("(Un|Re)handled with fallback:", unReHandledWithFallback) assert(unReHandledWithFallback === INITIAL_MSG || unReHandledWithFallback === RECOVERY_MSG) diff --git a/examples/sign/ed25519.eg.ts b/examples/sign/ed25519.eg.ts index 9d3f62cbf..ffd8387ec 100644 --- a/examples/sign/ed25519.eg.ts +++ b/examples/sign/ed25519.eg.ts @@ -6,11 +6,12 @@ import { MultiAddress, westendDev } from "@capi/westend-dev" import { assert } from "asserts" -import { createDevUsers, ExtrinsicSender, is } from "capi" +import { createDevUsers, ExtrinsicSender, is, Scope } from "capi" import { signature } from "capi/patterns/signature/polkadot" import * as ed from "../../deps/ed25519.ts" const { alexa, billy } = await createDevUsers() +const scope = new Scope() /// Reference Billy's free balance for later use. const billyFree = westendDev.System.Account @@ -18,7 +19,7 @@ const billyFree = westendDev.System.Account .unhandle(is(undefined)) .access("data", "free") -const billyFreeInitial = await billyFree.run() +const billyFreeInitial = await billyFree.run(scope) console.log("Billy free initial:", billyFreeInitial) /// Initialize a secret with the `crypto.getRandomValues` builtin. @@ -45,7 +46,7 @@ await westendDev.Balances .sent() .dbgStatus("Existential deposit:") .finalized() - .run() + .run(scope) /// Execute a transfer from the derived user to Billy. We utilize our /// derived ed25519 `sign` function for this. @@ -58,10 +59,10 @@ await westendDev.Balances .sent() .dbgStatus("Transfer:") .finalizedEvents() - .run() + .run(scope) /// Retrieve Billy's final free. -const billyFreeFinal = await billyFree.run() +const billyFreeFinal = await billyFree.run(scope) console.log("Billy free final:", billyFreeFinal) /// Ensure that the final is greater than the initial. diff --git a/examples/sign/offline.eg.ts b/examples/sign/offline.eg.ts index d791ada85..7ef4234d0 100644 --- a/examples/sign/offline.eg.ts +++ b/examples/sign/offline.eg.ts @@ -6,10 +6,11 @@ */ import { $runtimeCall, westendDev } from "@capi/westend-dev" -import { $, createDevUsers, SignedExtrinsicRune } from "capi" +import { $, createDevUsers, Scope, SignedExtrinsicRune } from "capi" import { signature } from "capi/patterns/signature/polkadot" const { alexa, billy } = await createDevUsers() +const scope = new Scope() /// Create and sign the extrinsic. Extract the hex. const hex = await westendDev.Balances @@ -19,7 +20,7 @@ const hex = await westendDev.Balances }) .signed(signature({ sender: alexa })) .hex() - .run() + .run(scope) /// Save `hex` however you'd like (potentially sending to a relayer service, /// writing to disk, etc.). @@ -29,7 +30,7 @@ save(hex) const signedExtrinsic = SignedExtrinsicRune.fromHex(westendDev, hex) /// Get an `ExtrinsicRune` (resolves to call data) from the `SignedExtrinsicRune`. -const call = await signedExtrinsic.call().run() +const call = await signedExtrinsic.call().run(scope) /// Ensure the call data is what we expect. console.log(call) @@ -41,7 +42,7 @@ const hash = await SignedExtrinsicRune .sent() .dbgStatus("Tx status:") .finalized() - .run() + .run(scope) /// Ensure the extrinsic has been finalized. $.assert($.str, hash) diff --git a/examples/sign/pjs.eg.ts b/examples/sign/pjs.eg.ts index c523daf3d..f8076e055 100644 --- a/examples/sign/pjs.eg.ts +++ b/examples/sign/pjs.eg.ts @@ -6,13 +6,14 @@ */ import { polkadotDev } from "@capi/polkadot-dev" -import { createDevUsers, ss58 } from "capi" +import { createDevUsers, Scope, ss58 } from "capi" import { pjsSender, PjsSigner } from "capi/patterns/compat/pjs_sender" import { signature } from "capi/patterns/signature/polkadot" import { createPair } from "https://deno.land/x/polkadot@0.2.38/keyring/mod.ts" import { TypeRegistry } from "https://deno.land/x/polkadot@0.2.38/types/mod.ts" const { alexa, billy } = await createDevUsers() +const scope = new Scope() /// Here, we manually create a signer. Chances are that **you should not do this**. /// Instead, it's likely that you'll want to access a signer from a wallet browser @@ -52,4 +53,4 @@ await polkadotDev.Balances .sent() .dbgStatus("Transfer:") .finalized() - .run() + .run(scope) diff --git a/examples/smoldot/fetch_chainspec.eg.ts b/examples/smoldot/fetch_chainspec.eg.ts index 9ef93d19c..cb0833154 100644 --- a/examples/smoldot/fetch_chainspec.eg.ts +++ b/examples/smoldot/fetch_chainspec.eg.ts @@ -7,9 +7,10 @@ */ import { polkadot } from "@capi/polkadot" +import { Scope } from "capi" /// We'll connect to the polkadot wss server to get the chainspec. -const chainSpec = await polkadot.connection.call("sync_state_genSyncSpec", true).run() +const chainSpec = await polkadot.connection.call("sync_state_genSyncSpec", true).run(new Scope()) /// We'll print out the chainspec here. This can be written into a file for later use. console.log(chainSpec) diff --git a/examples/smoldot/smoldot.eg.ts b/examples/smoldot/smoldot.eg.ts index 47e9d2c8b..3861a4414 100644 --- a/examples/smoldot/smoldot.eg.ts +++ b/examples/smoldot/smoldot.eg.ts @@ -7,7 +7,7 @@ */ import { PolkadotRune } from "@capi/polkadot" -import { $, known, SmoldotConnection } from "capi" +import { $, known, Scope, SmoldotConnection } from "capi" /// Bring the chainspec into scope. Here, we'll import it from `fetch_chainspec.eg.ts`. import { relayChainSpec } from "./fetch_chainspec.eg.ts" @@ -16,7 +16,7 @@ import { relayChainSpec } from "./fetch_chainspec.eg.ts" const polkadot = PolkadotRune.from(SmoldotConnection.bind({ relayChainSpec })) // Utilize the smoldot-connected `PolkadotRune` instance. -const { block } = await polkadot.blockHash().block().run() +const { block } = await polkadot.blockHash().block().run(new Scope()) /// Ensure the block is of the expected shape. console.log(block) diff --git a/examples/tx/balances_transfer.eg.ts b/examples/tx/balances_transfer.eg.ts index 2bf6bf11a..5f4c40aae 100644 --- a/examples/tx/balances_transfer.eg.ts +++ b/examples/tx/balances_transfer.eg.ts @@ -6,11 +6,12 @@ import { westendDev } from "@capi/westend-dev" import { assert } from "asserts" -import { createDevUsers, is } from "capi" +import { createDevUsers, is, Scope } from "capi" import { signature } from "capi/patterns/signature/polkadot" /// Create two dev users. Alexa will send the funds to Billy. const { alexa, billy } = await createDevUsers() +const scope = new Scope() /// Reference Billy's free balance. const billyFree = westendDev.System.Account @@ -19,7 +20,7 @@ const billyFree = westendDev.System.Account .access("data", "free") /// Read the initial free. -const initialFree = await billyFree.run() +const initialFree = await billyFree.run(scope) console.log("Billy free initial:", initialFree) // Create and submit the transaction. @@ -32,10 +33,10 @@ await westendDev.Balances .sent() .dbgStatus("Transfer:") .finalized() - .run() + .run(scope) /// Read the final free. -const finalFree = await billyFree.run() +const finalFree = await billyFree.run(scope) console.log("Billy free final:", finalFree) /// The final free should be greater than the initial. diff --git a/examples/tx/handle_errors.eg.ts b/examples/tx/handle_errors.eg.ts index a4594a330..5e66e137d 100644 --- a/examples/tx/handle_errors.eg.ts +++ b/examples/tx/handle_errors.eg.ts @@ -7,7 +7,7 @@ import { contractsDev } from "@capi/contracts-dev" import { assertInstanceOf } from "asserts" -import { createDevUsers, ExtrinsicError, is } from "capi" +import { createDevUsers, ExtrinsicError, is, Scope } from "capi" import { signature } from "capi/patterns/signature/polkadot" const { alexa, billy } = await createDevUsers() @@ -24,7 +24,7 @@ const extrinsicError = await contractsDev.Balances .inBlockEvents() .unhandleFailed() .rehandle(is(ExtrinsicError)) - .run() + .run(new Scope()) /// Ensure `extrinsicError` is in fact an instance of `ExtrinsicError` console.log("The unhandled extrinsic error:", extrinsicError) diff --git a/examples/tx/utility_batch.eg.ts b/examples/tx/utility_batch.eg.ts index 4b2d7631e..665b902f2 100644 --- a/examples/tx/utility_batch.eg.ts +++ b/examples/tx/utility_batch.eg.ts @@ -6,9 +6,11 @@ import { westendDev } from "@capi/westend-dev" import { assert } from "asserts" -import { createDevUsers, is, Rune } from "capi" +import { createDevUsers, is, Rune, Scope } from "capi" import { signature } from "capi/patterns/signature/polkadot" +const scope = new Scope() + /// Create four dev users, one of whom will be the batch sender. The other /// three will be recipients of balance transfers described in the batch. const [sender, ...recipients] = await createDevUsers(4) @@ -21,7 +23,7 @@ const frees = Rune.tuple( ) /// Retrieve the initial free balances of the recipients. -const initialFrees = await frees.run() +const initialFrees = await frees.run(scope) console.log("Initial frees:", initialFrees) /// Create and submit the batch call. Not how we must utilize `Rune.tuple` in @@ -39,10 +41,10 @@ await westendDev.Utility .sent() .dbgStatus("Batch:") .finalized() - .run() + .run(scope) /// Retrieve the final free balances of the recipients. -const finalFrees = await frees.run() +const finalFrees = await frees.run(scope) console.log("Final frees:", finalFrees) /// Ensure that the final balances are greater than the initial ones. diff --git a/examples/tx/weights_and_estimates.eg.ts b/examples/tx/weights_and_estimates.eg.ts index 71d23311f..4153271b7 100644 --- a/examples/tx/weights_and_estimates.eg.ts +++ b/examples/tx/weights_and_estimates.eg.ts @@ -5,7 +5,9 @@ */ import { $weight, westend } from "@capi/westend" -import { $, alice, Rune } from "capi" +import { $, alice, Rune, Scope } from "capi" + +const scope = new Scope() /// Create the call data. const call = westend.Balances @@ -20,7 +22,7 @@ const collection = await Rune weight: call.weight(), estimate: call.estimate(), }) - .run() + .run(scope) /// Ensure the data is of the expected shape. console.log(collection) diff --git a/examples/watch.eg.ts b/examples/watch.eg.ts index d7d7523fc..96e31b2e1 100644 --- a/examples/watch.eg.ts +++ b/examples/watch.eg.ts @@ -7,7 +7,7 @@ */ import { polkadot } from "@capi/polkadot" -import { $ } from "capi" +import { $, Scope } from "capi" /// Specifying `chain.latestBlockHash` indicates that (A) this Rune tree /// can be treated as reactive and (B) is a dependent of a "timeline" associated @@ -19,7 +19,7 @@ let i = 0 /// Use the `watch` method to retrieve an async iterable, which will /// gather and yield the `collection`-described data upon new blocks. -for await (const item of now.iter()) { +for await (const item of now.iter(new Scope())) { console.log(item) $.assert($.u64, item) if (++i === 3) break diff --git a/examples/xcm/asset_teleportation.eg.ts b/examples/xcm/asset_teleportation.eg.ts index 180ac6641..e16a0e643 100644 --- a/examples/xcm/asset_teleportation.eg.ts +++ b/examples/xcm/asset_teleportation.eg.ts @@ -26,9 +26,11 @@ import { RuntimeEvent, } from "@capi/rococo-dev-westmint" import { assert } from "asserts" -import { createDevUsers, is, Rune } from "capi" +import { createDevUsers, is, Rune, Scope } from "capi" import { signature } from "capi/patterns/signature/polkadot" +const scope = new Scope() + const { alexa } = await createDevUsers() /// Reference Alexa's free balance. @@ -38,7 +40,7 @@ const alexaBalance = rococoDevWestmint.System.Account .access("data", "free") /// Read the initial free. -const alexaFreeInitial = await alexaBalance.run() +const alexaFreeInitial = await alexaBalance.run(scope) console.log("Alexa initial free:", alexaFreeInitial) /// Execute the teleportation without blocking. @@ -77,7 +79,7 @@ rococoDev.XcmPallet .sent() .dbgStatus("Teleportation:") .finalized() - .run() + .run(scope) /// Iterate over the parachain events until receiving a downward message processed event, /// at which point we can read alexa's free balance, which should be greater than the initial. @@ -85,7 +87,7 @@ outer: for await ( const e of rococoDevWestmint.System.Events .value(undefined, rococoDevWestmint.latestBlockHash) - .iter() + .iter(scope) ) { if (e) { for (const { event } of e) { @@ -93,7 +95,7 @@ for await ( RuntimeEvent.isParachainSystem(event) && CumulusPalletParachainSystemEvent.isDownwardMessagesProcessed(event.value) ) { - const alexaFreeFinal = await alexaBalance.run() + const alexaFreeFinal = await alexaBalance.run(scope) console.log("Alexa final free:", alexaFreeFinal) assert(alexaFreeFinal > alexaFreeInitial) break outer diff --git a/examples/xcm/reserve_transfer.eg.ts b/examples/xcm/reserve_transfer.eg.ts index 1885976e9..113557454 100644 --- a/examples/xcm/reserve_transfer.eg.ts +++ b/examples/xcm/reserve_transfer.eg.ts @@ -27,12 +27,13 @@ import { } from "@capi/rococo-dev-xcm-statemine" import { rococoDevXcmTrappist } from "@capi/rococo-dev-xcm-trappist" import { assert, assertNotEquals } from "asserts" -import { $, alice as root, createDevUsers, is, Rune, ValueRune } from "capi" +import { $, alice as root, createDevUsers, is, Rune, Scope, ValueRune } from "capi" import { $siblId } from "capi/patterns/para_id" import { signature } from "capi/patterns/signature/statemint" import { retry } from "../../deps/std/async.ts" const { alexa, billy } = await createDevUsers() +const scope = new Scope() /// Define some constants for later use. const RESERVE_ASSET_ID = 1 @@ -77,11 +78,12 @@ await rococoDevXcm.Sudo .sent() .dbgStatus("Rococo(root) > Statemine(root): Create asset") .finalized() - .run() + .run(scope) /// Wait for the asset to be recorded in storage. const assetDetails = await retry( - () => rococoDevXcmStatemine.Assets.Asset.value(RESERVE_ASSET_ID).unhandle(is(undefined)).run(), + () => + rococoDevXcmStatemine.Assets.Asset.value(RESERVE_ASSET_ID).unhandle(is(undefined)).run(scope), retryOptions, ) @@ -100,14 +102,14 @@ await rococoDevXcmStatemine.Assets .sent() .dbgStatus("Statemine(Alexa): Mint reserve asset to Billy") .finalized() - .run() + .run(scope) const billyStatemintBalance = rococoDevXcmStatemine.Assets.Account .value([RESERVE_ASSET_ID, billy.publicKey]) .unhandle(is(undefined)) .access("balance") -const billyStatemintBalanceInitial = await billyStatemintBalance.run() +const billyStatemintBalanceInitial = await billyStatemintBalance.run(scope) console.log("Statemine(Billy): asset balance", billyStatemintBalanceInitial) $.assert($.u128, billyStatemintBalanceInitial) @@ -125,7 +127,7 @@ await rococoDevXcmTrappist.Sudo .sent() .dbgStatus("Trappist(root): Create derived asset") .finalized() - .run() + .run(scope) const assetsPalletId = rococoDevXcmStatemine.Assets.into(ValueRune).access("id") @@ -148,7 +150,7 @@ await rococoDevXcmTrappist.Sudo .sent() .dbgStatus("Trappist(root): Register AssetId to Reserve AssetId") .finalized() - .run() + .run(scope) /// Reserve transfer asset id on reserve parachain to Trappist parachain. const events = await rococoDevXcmStatemine.PolkadotXcm @@ -185,7 +187,7 @@ const events = await rococoDevXcmStatemine.PolkadotXcm .sent() .dbgStatus("Statemine(Billy): Reserve transfer to Trappist") .finalizedEvents() - .run() + .run(scope) for (const { event } of events) { if ( @@ -199,7 +201,7 @@ const { balance: billyTrappistBalance } = await retry( rococoDevXcmTrappist.Assets.Account .value([TRAPPIST_ASSET_ID, billy.publicKey]) .unhandle(is(undefined)) - .run(), + .run(scope), retryOptions, ) @@ -208,7 +210,7 @@ console.log("Trappist(Billy): asset balance:", billyTrappistBalance) assert(billyTrappistBalance > 0) /// Retrieve Billy's balance on statemint. -const billyStatemintBalanceFinal = await billyStatemintBalance.run() +const billyStatemintBalanceFinal = await billyStatemintBalance.run(scope) /// Ensure the balance is different from the initial. console.log("Statemine(Billy): asset balance:", billyStatemintBalanceFinal) @@ -219,7 +221,7 @@ const statemintSovereignAccountBalance = await rococoDevXcmStatemine.Assets.Acco .value([RESERVE_ASSET_ID, $siblId.encode(TRAPPIST_CHAIN_ID)]) .unhandle(is(undefined)) .access("balance") - .run() + .run(scope) /// Ensure the balance is greater than zero. console.log("Statemine(TrappistSovereignAccount): asset balance", statemintSovereignAccountBalance) diff --git a/fluent/ConnectionRune.ts b/fluent/ConnectionRune.ts index 07d48104a..8e96d8ad5 100644 --- a/fluent/ConnectionRune.ts +++ b/fluent/ConnectionRune.ts @@ -1,13 +1,13 @@ import { Calls, Subscription, Subscriptions } from "../rpc/known/mod.ts" import { Connection, ConnectionError, RpcSubscriptionMessage, ServerError } from "../rpc/mod.ts" -import { Batch, is, MetaRune, Run, Rune, RunicArgs, RunStream } from "../rune/mod.ts" +import { is, MetaRune, Run, Rune, RunicArgs, RunStream, Scope } from "../rune/mod.ts" class RunConnection extends Run { constructor( - ctx: Batch, + scope: Scope, readonly initConnection: (signal: AbortSignal) => Connection | Promise, ) { - super(ctx) + super(scope) } connection?: Connection @@ -62,13 +62,13 @@ export class ConnectionRune extends Rune { class RunRpcSubscription extends RunStream { constructor( - ctx: Batch, + scope: Scope, connection: Connection, params: unknown[], subscribeMethod: string, unsubscribeMethod: string, ) { - super(ctx) + super(scope) connection.subscription( subscribeMethod, unsubscribeMethod, diff --git a/rpc/Connection.ts b/rpc/Connection.ts index bd7ad837e..6eee28eec 100644 --- a/rpc/Connection.ts +++ b/rpc/Connection.ts @@ -34,10 +34,13 @@ export abstract class Connection { return connection } + alive = true ref(signal: AbortSignal) { + if (!this.alive) throw new Error("Cannot reference dead connection") this.references++ signal.addEventListener("abort", () => { if (!--this.references) { + this.alive = false this.#controller.abort() this.close() } diff --git a/rune/MetaRune.ts b/rune/MetaRune.ts index 8e61caddc..3024b3ebd 100644 --- a/rune/MetaRune.ts +++ b/rune/MetaRune.ts @@ -1,4 +1,4 @@ -import { Batch, Run, Rune } from "./Rune.ts" +import { Run, Rune, Scope } from "./Rune.ts" import { Receipt } from "./Timeline.ts" import { ValueRune } from "./ValueRune.ts" @@ -14,10 +14,10 @@ export class MetaRune extends Rune, U2> { class RunFlat extends Run { child - constructor(batch: Batch, child: Rune, U2>, indirect?: Rune) { - super(batch) - this.child = batch.prime(child, this.signal) - if (indirect) batch.prime(indirect, this.signal) + constructor(scope: Scope, child: Rune, U2>, indirect?: Rune) { + super(scope) + this.child = scope.prime(child, this.signal) + if (indirect) scope.prime(indirect, this.signal) } lastChildReceipt = new Receipt() @@ -31,8 +31,8 @@ class RunFlat extends Run { if (receipt.novel) { this.innerController.abort() this.innerController = new AbortController() - // const innerBatch = new Batch(this.batch.timeline, this.batch) - this.currentInner = this.batch.prime(rune, this.innerController.signal) + // const innerScope = new Scope(this.scope.timeline, this.scope) + this.currentInner = this.scope.prime(rune, this.innerController.signal) } const _receipt = new Receipt() try { diff --git a/rune/Rune.test.ts b/rune/Rune.test.ts index a661bf2fc..c2e8df93e 100644 --- a/rune/Rune.test.ts +++ b/rune/Rune.test.ts @@ -1,12 +1,12 @@ import { assertEquals } from "../deps/std/testing/asserts.ts" import { Clock } from "../util/clock.ts" -import { is, MetaRune, Rune, RunicArgs } from "./mod.ts" +import { is, MetaRune, Rune, RunicArgs, Scope } from "./mod.ts" Deno.test("constant", async () => { assertEquals( await Rune .constant(123) - .run(), + .run(new Scope()), 123, ) }) @@ -16,7 +16,7 @@ Deno.test("pipe", async () => { await Rune .constant(1) .map((x) => x + 1) - .run(), + .run(new Scope()), 2, ) assertEquals( @@ -25,22 +25,22 @@ Deno.test("pipe", async () => { .unhandle(is(Error)) .map(() => 123) .rehandle(is(Error), (x) => x) - .run(), + .run(new Scope()), new Error(), ) }) Deno.test("ls", async () => { - assertEquals(await Rune.tuple([1, 2]).run(), [1, 2]) + assertEquals(await Rune.tuple([1, 2]).run(new Scope()), [1, 2]) }) const count = Rune.asyncIter(() => iter([1, 2, 3])) Deno.test("stream", async () => { - assertEquals(await count.run(), 1) - assertEquals(await _collect(count.iter()), [1, 2, 3]) - assertEquals(await count.collect().run(), [1, 2, 3]) - assertEquals(await count.map((x) => x + "").collect().run(), ["1", "2", "3"]) + assertEquals(await count.run(new Scope()), 1) + assertEquals(await _collect(count.iter(new Scope())), [1, 2, 3]) + assertEquals(await count.collect().run(new Scope()), [1, 2, 3]) + assertEquals(await count.map((x) => x + "").collect().run(new Scope()), ["1", "2", "3"]) async function _collect(iter: AsyncIterable) { const values = [] @@ -60,30 +60,30 @@ const sum = (...[...args]: RunicArgs) => { } Deno.test("add", async () => { - assertEquals(await add(1, 2).run(), 3) + assertEquals(await add(1, 2).run(new Scope()), 3) assertEquals( await add(1, Rune.constant(new Error()).unhandle(is(Error))) - .rehandle(is(Error), (x) => x).run(), + .rehandle(is(Error), (x) => x).run(new Scope()), new Error(), ) assertEquals( - await add(count, 10).run(), + await add(count, 10).run(new Scope()), 11, ) assertEquals( - await add(count, 10).collect().run(), + await add(count, 10).collect().run(new Scope()), [11, 12, 13], ) assertEquals( - await add(count, count.map((x) => x * 10)).collect().run(), + await add(count, count.map((x) => x * 10)).collect().run(new Scope()), [11, 22, 33], ) }) Deno.test("sum", async () => { - assertEquals(await sum(1, 2, 3, 4, 5).run(), 15) - assertEquals(await sum(count, count, count, count).run(), 4) - assertEquals(await sum(count, count, count, count).collect().run(), [4, 8, 12]) + assertEquals(await sum(1, 2, 3, 4, 5).run(new Scope()), 15) + assertEquals(await sum(count, count, count, count).run(new Scope()), 4) + assertEquals(await sum(count, count, count, count).collect().run(new Scope()), [4, 8, 12]) }) const divide = ( @@ -95,7 +95,7 @@ const divide = ( } Deno.test("divide", async () => { - assertEquals(await divide({ numerator: 3, denominator: 2 }).run(), 1.5) + assertEquals(await divide({ numerator: 3, denominator: 2 }).run(new Scope()), 1.5) }) Deno.test("multi stream", async () => { @@ -130,27 +130,27 @@ Deno.test("multi stream", async () => { // d: * * 11 12 * 23 * 44 // e: * 11 * 22 * 33 * 43 assertEquals( - await a.map((v) => [clock.time, v]).collect().run(), + await a.map((v) => [clock.time, v]).collect().run(new Scope()), [[1, 1], [2, 2], [5, 3], [8, 4]], ) clock.reset() assertEquals( - await b.map((v) => [clock.time, v]).collect().run(), + await b.map((v) => [clock.time, v]).collect().run(new Scope()), [[3, 10], [4, 20], [6, 30], [7, 40]], ) clock.reset() assertEquals( - await c.map((v) => [clock.time, v]).collect().run(), + await c.map((v) => [clock.time, v]).collect().run(new Scope()), [[3, 11], [3, 12], [4, 22], [5, 23], [6, 33], [7, 43], [8, 44]], ) clock.reset() assertEquals( - await d.map((v) => [clock.time, v]).collect().run(), + await d.map((v) => [clock.time, v]).collect().run(new Scope()), [[3, 11], [3, 12], [5, 23], [8, 44]], ) clock.reset() assertEquals( - await e.map((v) => [clock.time, v]).collect().run(), + await e.map((v) => [clock.time, v]).collect().run(new Scope()), [[3, 11], [4, 22], [6, 33], [7, 43]], ) clock.reset() @@ -191,29 +191,29 @@ Deno.test("multi stream 2", async () => { // g: * 11 * 22 32 (due to lazy, b is not triggered until 5, causing 2 to be pushed late at 8) // h: * 11 assertEquals( - await d.map((v) => [clock.time, v]).collect().run(), + await d.map((v) => [clock.time, v]).collect().run(new Scope()), [[2, 11], [3, 12], [5, 22], [8, 32]], ) clock.reset() - assertEquals(await e.collect().run(), [11, 12, 22, 32]) + assertEquals(await e.collect().run(new Scope()), [11, 12, 22, 32]) clock.reset() assertEquals( - await e.map((v) => [clock.time, v]).collect().run(), + await e.map((v) => [clock.time, v]).collect().run(new Scope()), [[4, 11], [7, 12], [7, 22], [8, 32]], ) clock.reset() assertEquals( - await f.map((v) => [clock.time, v]).collect().run(), + await f.map((v) => [clock.time, v]).collect().run(new Scope()), [[4, 11], [7, 12]], ) clock.reset() assertEquals( - await g.map((v) => [clock.time, v]).collect().run(), + await g.map((v) => [clock.time, v]).collect().run(new Scope()), [[4, 11], [8, 22], [8, 32]], ) clock.reset() assertEquals( - await h.map((v) => [clock.time, v]).collect().run(), + await h.map((v) => [clock.time, v]).collect().run(new Scope()), [[4, 11]], ) clock.reset() @@ -241,7 +241,7 @@ Deno.test("derived stream", async () => { }) ).into(MetaRune).flat() assertEquals( - await b.map((v) => [clock.time, v]).collect().run(), + await b.map((v) => [clock.time, v]).collect().run(new Scope()), [[2, 1], [3, 1], [5, 2], [6, 2], [8, 3], [9, 3], [11, 3]], ) }) @@ -265,7 +265,7 @@ Deno.test("match abc", async () => { .when(is("a"), (x) => x.access("a")) .when(is("b"), (x) => x.access("b")) .else((x) => x.access("c")) - ).run() + ).run(new Scope()) assertEquals(+result, 0) } }) @@ -285,7 +285,7 @@ Deno.test("match u", async () => { ) ) .rehandle(is(String)) - .run(), + .run(new Scope()), "hello", ) }) diff --git a/rune/Rune.ts b/rune/Rune.ts index e6bad2516..4367331ab 100644 --- a/rune/Rune.ts +++ b/rune/Rune.ts @@ -7,10 +7,10 @@ import { Trace } from "./Trace.ts" // @deno-types="./_empty.d.ts" import * as _ from "./_empty.js" -export class Batch { +export class Scope { constructor( readonly timeline = new Timeline(), - readonly parent?: Batch, + readonly parent?: Scope, readonly wrapParent: (rune: Run) => Run = (x) => x, ) {} @@ -27,7 +27,11 @@ export class Batch { const prevTrace = this._currentTrace this._currentTrace = rune._trace try { - return rune._prime(this) + const primed = rune._prime(this) + primed.signal.addEventListener("abort", () => { + this.primed.delete(rune._prime) + }) + return primed } finally { this._currentTrace = prevTrace } @@ -44,13 +48,16 @@ export class Batch { if (parent) { const wrapped = this.wrapParent(parent) this.primed.set(rune._prime, wrapped) + wrapped.signal.addEventListener("abort", () => { + this.primed.delete(rune._prime) + }) return wrapped } return undefined } spawn(time: number, receipt: Receipt) { - return new Batch(new Timeline(), this, (x) => new RunProxy(this, x, time, receipt)) + return new Scope(new Timeline(), this, (x) => new RunProxy(this, x, time, receipt)) } } @@ -73,27 +80,27 @@ export class Rune { declare private _ _trace: Trace - constructor(readonly _prime: (batch: Batch) => Run) { + constructor(readonly _prime: (scope: Scope) => Run) { this._trace = new Trace(`execution of the ${new.target.name} instantiated`) } static new( - ctor: new(batch: Batch, ...args: A) => Run, + ctor: new(scope: Scope, ...args: A) => Run, ...args: A ) { - return new Rune((batch) => new ctor(batch, ...args)) + return new Rune((scope) => new ctor(scope, ...args)) } - async run(batch = new Batch()): Promise { - for await (const value of this.iter(batch)) { + async run(scope: Scope): Promise { + for await (const value of this.iter(scope)) { return value } throw new Error("Rune did not yield any values") } - async *iter(batch = new Batch()) { + async *iter(scope: Scope) { const abortController = new AbortController() - const primed = batch.prime(this, abortController.signal) + const primed = scope.prime(this, abortController.signal) let time = 0 try { while (time !== Infinity) { @@ -189,7 +196,7 @@ export class Rune { } into>( - ctor: new(_prime: (batch: Batch) => Run>, ...args: A) => C, + ctor: new(_prime: (scope: Scope) => Run>, ...args: A) => C, ...args: A ): C { const rune = new ctor(this._prime, ...args) @@ -197,7 +204,7 @@ export class Rune { return rune } - as(this: R, _ctor: new(_prime: (batch: Batch) => Run, ...args: any) => R): R { + as(this: R, _ctor: new(_prime: (scope: Scope) => Run, ...args: any) => R): R { return this } @@ -216,9 +223,9 @@ export abstract class Run { abortController = new AbortController() signal = this.abortController.signal - constructor(readonly batch: Batch) { + constructor(readonly scope: Scope) { this.signal.addEventListener("abort", () => this.cleanup()) - this.trace = batch._currentTrace + this.trace = scope._currentTrace ?? new Trace(`execution of the ${new.target.name} instantiated`) } @@ -263,12 +270,12 @@ export abstract class Run { class RunProxy extends Run { constructor( - batch: Batch, + scope: Scope, readonly inner: Run, readonly innerTime: number, readonly innerReceipt: Receipt, ) { - super(batch) + super(scope) } _evaluate(): Promise { @@ -278,8 +285,8 @@ class RunProxy extends Run { } class RunConstant extends Run { - constructor(batch: Batch, readonly value: T) { - super(batch) + constructor(scope: Scope, readonly value: T) { + super(scope) } _evaluate() { @@ -289,9 +296,9 @@ class RunConstant extends Run { class RunLs extends Run { children - constructor(batch: Batch, children: Rune[]) { - super(batch) - this.children = children.map((child) => batch.prime(child, this.signal)) + constructor(scope: Scope, children: Rune[]) { + super(scope) + this.children = children.map((child) => scope.prime(child, this.signal)) } _evaluate(time: number, receipt: Receipt) { @@ -302,15 +309,15 @@ class RunLs extends Run { export abstract class RunStream extends Run { initPromise = deferred() valueQueue: [number, T][] = [] - eventSource = new EventSource(this.batch.timeline) + eventSource = new EventSource(this.scope.timeline) first = true done = false curIter = new AbortController() lastValue: T = null! - constructor(batch: Batch) { - super(batch) + constructor(scope: Scope) { + super(scope) } async _evaluate(time: number, receipt: Receipt): Promise { @@ -347,8 +354,8 @@ export abstract class RunStream extends Run { } class RunAsyncIter extends RunStream { - constructor(batch: Batch, fn: (signal: AbortSignal) => AsyncIterable) { - super(batch) + constructor(scope: Scope, fn: (signal: AbortSignal) => AsyncIterable) { + super(scope) ;(async () => { for await (const value of fn(this.signal)) { this.push(value) @@ -366,9 +373,9 @@ class RunPlaceholder extends Run { class RunBubbleUnhandled extends Run { child - constructor(batch: Batch, child: Rune, readonly symbol: symbol) { - super(batch) - this.child = batch.prime(child, this.signal) + constructor(scope: Scope, child: Rune, readonly symbol: symbol) { + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -385,9 +392,9 @@ class RunBubbleUnhandled extends Run { class RunCaptureUnhandled extends Run { child - constructor(batch: Batch, child: Rune, readonly symbol: symbol) { - super(batch) - this.child = batch.prime(child, this.signal) + constructor(scope: Scope, child: Rune, readonly symbol: symbol) { + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt) { diff --git a/rune/ValueRune.ts b/rune/ValueRune.ts index 5ba10bdd9..ea5331835 100644 --- a/rune/ValueRune.ts +++ b/rune/ValueRune.ts @@ -1,5 +1,5 @@ import { Guard, SmartExclude } from "./is.ts" -import { Batch, Run, Rune, RunicArgs, Unhandled } from "./Rune.ts" +import { Run, Rune, RunicArgs, Scope, Unhandled } from "./Rune.ts" import { Receipt } from "./Timeline.ts" type NonIndexSignatureKeys = T extends T ? keyof { @@ -27,10 +27,10 @@ type EnsurePath = never extends P ? P extends [infer K, ...infer Q] ? export class ValueRune extends Rune { static override new( - ctor: new(batch: Batch, ...args: A) => Run, + ctor: new(scope: Scope, ...args: A) => Run, ...args: A ) { - return new ValueRune((batch) => new ctor(batch, ...args)) + return new ValueRune((scope) => new ctor(scope, ...args)) } map(fn: (value: T) => T2 | Promise): ValueRune { @@ -181,14 +181,14 @@ class RunMatch extends Run { value conditions constructor( - batch: Batch, + scope: Scope, child: Rune, conditions: [(x: M) => boolean, ValueRune][], ) { - super(batch) - this.value = batch.prime(child, this.signal) + super(scope) + this.value = scope.prime(child, this.signal) this.conditions = conditions.map(([cond, val]) => - [cond, batch.prime(val, this.signal)] as const + [cond, scope.prime(val, this.signal)] as const ) } @@ -206,12 +206,12 @@ class RunMatch extends Run { class RunMap extends Run { child constructor( - batch: Batch, + scope: Scope, child: Rune, readonly fn: (value: T1) => T2 | Promise, ) { - super(batch) - this.child = batch.prime(child, this.signal) + super(scope) + this.child = scope.prime(child, this.signal) } lastValue: T2 = null! @@ -229,14 +229,14 @@ class RunHandle extends Run | T3, U | child alt constructor( - batch: Batch, + scope: Scope, child: Rune, readonly guard: Guard, alt: Rune, ) { - super(batch) - this.child = batch.prime(child, this.signal) - this.alt = batch.prime(alt, this.signal) + super(scope) + this.child = scope.prime(child, this.signal) + this.alt = scope.prime(alt, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -250,12 +250,12 @@ class RunHandle extends Run | T3, U | class RunUnhandle extends Run { child constructor( - batch: Batch, + scope: Scope, child: Rune, readonly guard: Guard, ) { - super(batch) - this.child = batch.prime(child, this.signal) + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -268,12 +268,12 @@ class RunUnhandle extends Run { class RunThrows extends Run { child constructor( - batch: Batch, + scope: Scope, child: Rune, readonly guards: Array>, ) { - super(batch) - this.child = batch.prime(child, this.signal) + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -290,9 +290,9 @@ class RunThrows extends Run { class RunGetUnhandled extends Run | null, never> { child - constructor(batch: Batch, child: Rune) { - super(batch) - this.child = batch.prime(child, this.signal) + constructor(scope: Scope, child: Rune) { + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -312,14 +312,14 @@ class RunRehandle extends Run, readonly fn: (value: U1) => value is U2, alt: Rune, ) { - super(batch) - this.child = batch.prime(child, this.signal) - this.alt = batch.prime(alt, this.signal) + super(scope) + this.child = scope.prime(child, this.signal) + this.alt = scope.prime(alt, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -336,9 +336,9 @@ class RunRehandle extends Run extends Run { child - constructor(batch: Batch, child: Rune) { - super(batch) - this.child = batch.prime(child, this.signal) + constructor(scope: Scope, child: Rune) { + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, _receipt: Receipt): Promise { @@ -348,9 +348,9 @@ class RunLazy extends Run { class RunFilter extends Run { child - constructor(batch: Batch, child: Rune, readonly fn: (value: T) => boolean) { - super(batch) - this.child = batch.prime(child, this.signal) + constructor(scope: Scope, child: Rune, readonly fn: (value: T) => boolean) { + super(scope) + this.child = scope.prime(child, this.signal) } first = true @@ -377,9 +377,9 @@ class RunFilter extends Run { class RunFinal extends Run { child - constructor(batch: Batch, child: Rune) { - super(batch) - this.child = batch.prime(child, this.signal) + constructor(scope: Scope, child: Rune) { + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt): Promise { @@ -393,13 +393,13 @@ class RunFinal extends Run { class RunReduce extends Run { child constructor( - batch: Batch, + scope: Scope, child: Rune, public lastValue: T2, readonly fn: (last: T2, value: T1) => T2 | Promise, ) { - super(batch) - this.child = batch.prime(child, this.signal) + super(scope) + this.child = scope.prime(child, this.signal) } async _evaluate(time: number, receipt: Receipt) { @@ -411,13 +411,13 @@ class RunReduce extends Run { } class RunSingular extends Run { - constructor(batch: Batch, readonly child: Rune) { - super(batch) + constructor(scope: Scope, readonly child: Rune) { + super(scope) } result?: Promise _evaluate(time: number, receipt: Receipt): Promise { - return this.result ??= this.child.run(this.batch.spawn(time, receipt)) + return this.result ??= this.child.run(this.scope.spawn(time, receipt)) } } @@ -425,13 +425,13 @@ class RunChain extends Run { first second constructor( - batch: Batch, + scope: Scope, first: Rune, second: Rune, ) { - super(batch) - this.first = batch.prime(first, this.signal) - this.second = batch.prime(second, this.signal) + super(scope) + this.first = scope.prime(first, this.signal) + this.second = scope.prime(second, this.signal) } lastValue: T2 = null!