From 1e97dc8826d327332597c0f507d9b84508c4584e Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Fri, 19 Jul 2024 11:18:17 +0200 Subject: [PATCH] feat: explain compatibilityToken --- docs/pages/recipes/upgrade.md | 2 +- docs/pages/typed.md | 44 ++++++++++++++--------------------- docs/pages/typed/constants.md | 10 ++++---- docs/pages/typed/tx.md | 10 ++++---- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/docs/pages/recipes/upgrade.md b/docs/pages/recipes/upgrade.md index 9ef28f18..982ec8a0 100644 --- a/docs/pages/recipes/upgrade.md +++ b/docs/pages/recipes/upgrade.md @@ -61,4 +61,4 @@ Furthermore, the runtime upgrade might happen while the dApp is running, and thi As a note, `getCompatibilityLevel` is a function available on every interaction on the typedApi (queries, apis, constants, events, transactions). If used without any parameter it will return a `Promise`, because it needs to wait for the runtime to be loaded before it can tell whether it's compatible or not. -If you have multiple `getCompatibilityLevel` checks that you don't want to wait for each one of them, you can first wait for the runtime to be loaded with `await dotApi.runtime.latest()`, and then pass this to `getCompatibilityLevel` as a paramter. This will make `getCompatibilityLevel` return synchronously. +If you have multiple `getCompatibilityLevel` checks and don't want to wait for each one individually, you can first wait for the descriptors to be loaded with `await dotApi.compatibilityToken`, and then pass this result to `getCompatibilityLevel` as a parameter, allowing it to return synchronously. See [TypedApi getCompatibilityLevel](/typed#getcompatibilitylevel) for a deeper explanation. diff --git a/docs/pages/typed.md b/docs/pages/typed.md index 4e8ed105..32f2fe29 100644 --- a/docs/pages/typed.md +++ b/docs/pages/typed.md @@ -9,25 +9,15 @@ type TypedApi = { event: EvApi apis: RuntimeCallsApi constants: ConstApi - runtime: RuntimeApi + compatibilityToken: Promise } ``` -Let's start with the simplest one, `runtime` field. It's just: - -```ts -type RuntimeApi = Observable & { - latest: () => Promise -} -``` - -It's an observable that holds the current runtime information for that specific client, with a `latest` function to be able to wait for the runtime to load (it'll be helpful for some functions that need a `Runtime`, see [this recipe](/recipes/upgrade)). - -All the other fields are a `Record>`. The first index defines the pallet that we're looking for, and the second one defines which query/tx/event/api/constant are we looking for inside that pallet. Let's see, one by one, what do we find inside of it! +Every field except for `compatibilityToken` is a `Record>`. The first index defines the pallet, and the second one defines which query/tx/event/api/constant within that pallet. Each one of them will be described in the following pages, but let's focus on the compatibility check, which is common for all of them. ## getCompatibilityLevel -First of all, let's understand `getCompatibilityLevel` field. It's under each query/tx/event/api/constant in any runtime. After generating the descriptors (see [Codegen](/codegen) section), we have a typed interface to every interaction with the chain. Nevertheless, breaking runtime upgrades might hit the runtime between developing and the runtime execution of your app. `getCompatibilityLevel` enables you to check on runtime if there was a breaking upgrade that hit your particular method. +The `getCompatibilityLevel` is under each query/tx/event/api/constant. After generating the descriptors (see [Codegen](/codegen) section), we have a typed interface to every interaction with the chain. Nevertheless, breaking runtime upgrades might hit the runtime between developing and the runtime execution of your app. `getCompatibilityLevel` enables you to check on runtime if there was a breaking upgrade that hit your particular method. The enum `CompatibilityLevel` defines 4 levels of compatibility: @@ -52,37 +42,39 @@ On the other hand, a `CompatibilityLevel.BackwardsCompatible`, means that the op A backwards-compatible change also happens in structs. For instance, if an input struct removes one of their properties, those operations are still compatible. -It needs the runtime and the descriptors to be loaded, so it has two overloads, one where it will wait for them to be loaded, returning a promise, or another that returns synchronously if you already have a reference to the `Runtime` object from `typedApi.runtime.latest()`. +This compatibility check needs to have the runtime from the current connection, but also the descriptors that were generated from the CLI and are lazy loaded. This means that by default, `getCompatibilityLevel` is asynchronous, because it potentially needs to wait until that is loaded before it can run the check. + +This is where `compatibilityToken` comes into play. This is a promise that will resolve when both the connection to the current runtime and the descriptors have fully loaded. So if you want `getCompatibilityLevel` to be synchronous, then you can `await` the compatibilityToken just once (e.g. on dApp initialization), and then pass it as a parameter to `getCompatibilityLevel` to have it synchronously. This is because the token internally has all the information needed to run the compatibility check. ```ts interface GetCompatibilityLevel { (): Promise - (runtime: Runtime): CompatibilityLevel + (compatibilityToken: CompatibilityToken): CompatibilityLevel } ``` -For example, let's use `typedApi.query.System.Number`. It's a simple query, we'll see in the next pages how to interact with it. We're only interested on `getCompatibilityLevel`. +For example, let's use `typedApi.query.System.Number`. It's a simple query, we'll see in the next pages how to interact with it. In this example we'll focus on `getCompatibilityLevel`. ```ts const query = typedApi.query.System.Number -const runtime = await typedApi.runtime.latest() // we already learnt about it! + +// Set our threshold for compatibility +const isCompatible = (level: CompatibilityLevel) => + level >= CompatibilityLevel.BackwardsCompatible // in this case `getCompatibilityLevel` returns a Promise -if ( - (await query.getCompatibilityLevel()) >= - CompatibilityLevel.BackwardsCompatible -) { +if (isCompatible(await query.getCompatibilityLevel())) { // do your stuff, the query is compatible } else { // the call is not compatible! // keep an eye on what you do } -// another option would be to use the already loaded runtime -// in this case, `getCompatibilityLevel` is sync, and returns a boolean -if ( - query.getCompatibilityLevel(runtime) >= CompatibilityLevel.BackwardsCompatible -) { +// Alternatively, we can await just once the compatibilityToken +const compatibilityToken = await typedApi.compatibilityToken + +// And later on we can use it, so that `getCompatibilityLevel` is sync +if (isCompatible(query.getCompatibilityLevel(compatibilityToken))) { // do your stuff, the query is compatible } else { // the call is not compatible! diff --git a/docs/pages/typed/constants.md b/docs/pages/typed/constants.md index 9ffe4a59..711725dc 100644 --- a/docs/pages/typed/constants.md +++ b/docs/pages/typed/constants.md @@ -2,8 +2,10 @@ Constants are the simplest structure that we find inside the `TypedApi`. Constants are hard-coded key-value pairs that are embedded in the runtime metadata. In PAPI their structure is just a simple function that return its decoded value, with two alternatives. As explained in [the previous section](/typed#getcompatibilitylevel) for `getCompatibility Level`, we have two options to get the value: -- Promise-based call, without passing the runtime -- Synchronous return, passing the runtime previously awaited for +- Promise-based call, without passing the compatibility token +- Synchronous return, passing the compatibility token previously awaited for + +This is because, like every other chain interaction, constants are also ran through the compatibility check in case any type has changed. Let's use `typedApi.constants.System.Version`. See in this example how it's used: @@ -11,7 +13,7 @@ Let's use `typedApi.constants.System.Version`. See in this example how it's used // in this case the function is asynchronous const versionAsync = await typedApi.constants.System.Version() -const runtime = await typedApi.runtime.latest() // we already learnt about it! +const compatibilityToken = await typedApi.compatibilityToken // we already learnt about it! // in this case it's sync -const versionSync = typedApi.constants.System.Version(runtime) +const versionSync = typedApi.constants.System.Version(compatibilityToken) ``` diff --git a/docs/pages/typed/tx.md b/docs/pages/typed/tx.md index 95de625f..ce997ff7 100644 --- a/docs/pages/typed/tx.md +++ b/docs/pages/typed/tx.md @@ -65,13 +65,13 @@ const proxyTx = typedApi.tx.Proxy.proxy({ ## `getEncodedData` -`getEncodedData`, instead, packs the call data (without signed extensions, of course!) as a SCALE-encoded blob. It requires a `Runtime` field (like `getCompatibilityLevel`). You can call without it, and it'll be a `Promise`-based call, or pass the runtime and it'll answer synchronously. Let's see an example: +`getEncodedData`, instead, packs the call data (without signed extensions, of course!) as a SCALE-encoded blob. It also runs the compatibility check, so it needs the runtime and descriptors loaded. As we've seen with `getCompatibilityLevel`, if you call it directly it'll be a `Promise`-based call, or you can pass in a `compatibilityToken` you've previously awaited for and it'll answer synchronously. Let's see an example: ```ts // `getEncodedData` has this interface interface TxCall { (): Promise - (runtime: Runtime): Binary + (compatibilityToken: CompatibilityToken): Binary } import { MultiAddress } from "@polkadot-api/descriptors" @@ -84,9 +84,9 @@ const tx: Transaction = typedApi.tx.Balances.transfer_keep_alive({ // without argument it's async! const encodedTx = await tx.getEncodedData() -// with runtime argument it's sync! -const runtime = await typedApi.runtime.latest() -const encodedTx = tx.getEncodedData(runtime) +// with compatibilityToken argument it's sync! +const compatibilityToken = await typedApi.compatibilityToken +const encodedTx = tx.getEncodedData(compatibilityToken) ``` ## `TxOptions`