Skip to content

Commit

Permalink
feat: add top-level client
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosala committed May 9, 2024
1 parent 44f7187 commit 2fdd952
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 0 deletions.
134 changes: 134 additions & 0 deletions docs/pages/client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# PolkadotClient

`PolkadotClient` interface shapes the top-level API for `polkadot-api`. Once we get a client using `createClient` function, we'll find the following:

```ts
interface PolkadotClient {
/**
* Retrieve the ChainSpecData as it comes from the
* [JSON-RPC spec](https://paritytech.github.io/json-rpc-interface-spec/api/chainSpec.html)
*/
getChainSpecData: () => Promise<ChainSpecData>

/**
* Observable that emits `BlockInfo` from the latest known finalized block.
* It's a multicast and stateful observable, that will synchronously replay
* its latest known state.
*/
finalizedBlock$: Observable<BlockInfo>
/**
* @returns Latest known finalized block.
*/
getFinalizedBlock: () => Promise<BlockInfo>

/**
* Observable that emits an Array of `BlockInfo`, being the first element the
* latest known best block, and the last element the latest known finalized
* block. It's a multicast and stateful observable, that will synchronously
* replay its latest known state. This array is an immutable data structure;
* i.e. a new array is emitted at every event but the reference to its
* children are stable if the children didn't change.
*
* Note that subscribing to this observable already supersedes the need of
* subscribing to `finalizedBlock$`, since the last element of the array will
* be the latest known finalized block.
*/
bestBlocks$: Observable<BlockInfo[]>
/**
* @returns Array of `BlockInfo`, being the first element the latest
* known best block, and the last element the latest known
* finalized block.
*/
getBestBlocks: () => Promise<BlockInfo[]>

/**
* Observable to watch Block Body.
*
* @param hash It can be a block hash, `"finalized"`, or `"best"`
* @returns Observable to watch a block body. There'll be just one event
* with the payload and the observable will complete.
*/
watchBlockBody: (hash: string) => Observable<HexString[]>
/**
* Get Block Body (Promise-based)
*
* @param hash It can be a block hash, `"finalized"`, or `"best"`
* @returns Block body.
*/
getBlockBody: (hash: string) => Promise<HexString[]>

/**
* Get Block Header (Promise-based)
*
* @param hash It can be a block hash, `"finalized"` (default), or
* `"best"`
* @returns Block hash.
*/
getBlockHeader: (hash?: string) => Promise<BlockHeader>

/**
* Broadcast a transaction (Promise-based)
*
* @param transaction SCALE-encoded tx to broadcast.
* @param at It can be a block hash, `"finalized"`, or `"best"`.
* That block will be used to verify the validity of
* the tx, retrieve the next nonce,
* and create the mortality taking that block into
* account.
*/
submit: (
transaction: HexString,
at?: HexString,
) => Promise<TxFinalizedPayload>
/**
* Broadcast a transaction and returns an Observable. The observable will
* complete as soon as the transaction is in a finalized block.
*
* @param transaction SCALE-encoded tx to broadcast.
* @param at It can be a block hash, `"finalized"`, or `"best"`.
* That block will be used to verify the validity of
* the tx, retrieve the next nonce,
* and create the mortality taking that block into
* account.
*/
submitAndWatch: (
transaction: HexString,
at?: HexString,
) => Observable<TxBroadcastEvent>

/**
* Returns an instance of a `TypedApi`
*
* @param descriptors Pass descriptors from `@polkadot-api/descriptors`
* generated by `papi` CLI.
*/
getTypedApi: <D extends Descriptors>(descriptors: D) => TypedApi<D>

/**
* This will `unfollow` the provider, disconnect and error every subscription.
* After calling it nothing can be done with the client.
*/
destroy: () => void

/**
* This API is meant as an "escape hatch" to allow access to debug endpoints
* such as `system_version`, and other useful endpoints that are not spec
* compliant.
*
* @example
*
* const systemVersion = await client._request<string>("system_version", [])
* const myFancyThhing = await client._request<
* { value: string },
* [id: number]
* >("very_fancy", [1714])
*
*/
_request: <Reply = any, Params extends Array<any> = any[]>(
method: string,
params: Params,
) => Promise<Reply>
}
```

As one can note, `PolkadotClient` heavily relies on rxjs' `Observable`, used as well under the hood of Promise-based methods. Every method is fairly straight-forward and already documented exhaustively, except for `getTypedApi`. Let's dive into it.
67 changes: 67 additions & 0 deletions docs/pages/typed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# TypedApi

The `TypedApi` allows to interact with the runtime metadata easily and with a great developer experience. It'll allow to make storage calls, create transactions, etc. It uses the descriptors generated by PAPI CLI (see [Codegen](/codegen) section for a deeper explanation) to generate the types used at devel time. `TypedApi` object looks like:

```ts
type TypedApi = {
query: StorageApi
tx: TxApi
event: EvApi
apis: RuntimeCallsApi
constants: ConstApi
runtime: RuntimeApi
}
```
Let's start with the simplest one, `runtime` field. It's just:
```ts
type RuntimeApi = Observable<Runtime> & {
latest: () => Promise<Runtime>
}
```
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<string, Record<string, ???>>`. 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!
## isCompatible
First of all, let's understand `isCompatible` 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. `isCompatible` enables you to check on runtime if there was a breaking upgrade that hit your particular method.
Let's see its interface, and an example.
```ts
interface IsCompatible {
(): Promise<boolean>
(runtime: Runtime): boolean
}
```

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 `isCompatible`.

```ts
const query = typedApi.query.System.Number
const runtime = await typedApi.runtime.latest() // we already learnt about it!

// in this case `isCompatible` returns a Promise<boolean>
if (await query.isCompatible()) {
// 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, `isCompatible` is sync, and returns a boolean
if (query.isCompatible(runtime)) {
// do your stuff, the query is compatible
} else {
// the call is not compatible!
// keep an eye on what you do
}
```

As you can see, `isCompatible` is really powerful since we can prepare for runtime upgrades seamlessly using PAPI. See [this recipe](/recipes/upgrade) for an example!

Let's continue with the rest of the fields!
19 changes: 19 additions & 0 deletions vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ export default defineConfig({
text: "Getting Started",
link: "/getting-started",
},
{
text: "Top-level client",
items: [
{
text: "PolkadotClient",
link: "/client",
},
{
text: "Typed API",
link: "/typed",
items: [
{
text: "Storage queries",
link: "/typed/queries",
},
],
},
],
},
{
text: "Providers",
link: "/providers",
Expand Down

0 comments on commit 2fdd952

Please sign in to comment.