diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 420a0d6e9..a05b69125 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,8 +60,8 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -. -All complaints will be reviewed and investigated promptly and fairly. +. All complaints will be reviewed and investigated promptly and +fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8528c37ae..dce5810e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,30 +1,41 @@ # Contributing -When contributing to this repository, please check our open issues and whether there is already an issue related to your idea. Please first discuss the change you wish to make in a GitHub issue and wait for a reply from the maintainers of this repository before making a change. +When contributing to this repository, please check our open issues and whether +there is already an issue related to your idea. Please first discuss the change +you wish to make in a GitHub issue and wait for a reply from the maintainers of +this repository before making a change. -We have a [code of conduct](CODE_OF_CONDUCT.md); please follow it in all your interactions relating to the project. +We have a [code of conduct](CODE_OF_CONDUCT.md); please follow it in all your +interactions relating to the project. ## Environment setup ### Container environment setup -Develop locally using the [VSCode Remote Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension and [Docker](https://docs.docker.com/get-docker/). [![Open in Remote - Containers](https://img.shields.io/badge/Remote_--_Container-Open-blue?logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/paritytech/capi) +Develop locally using the +[VSCode Remote Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) +extension and [Docker](https://docs.docker.com/get-docker/). +[![Open in Remote - Containers](https://img.shields.io/badge/Remote_--_Container-Open-blue?logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/paritytech/capi) ### Local environment setup -To develop on your machine, install the following (and please submit issues if errors crop up) +To develop on your machine, install the following (and please submit issues if +errors crop up) - [Deno](https://deno.land/manual@v1.19.3/getting_started/installation) - [Docker](https://docs.docker.com/get-docker/) -- [NodeJS](https://nodejs.org/) (only necessary if you're going to run [the dnt task](./_/tasks/dnt.ts)) +- [NodeJS](https://nodejs.org/) (only necessary if you're going to run + [the dnt task](./_/tasks/dnt.ts)) - [dprint](https://dprint.dev/) ## Rules There are a few basic ground-rules for contributors: -1. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors -2. Contributors should attempt to adhere to the prevailing [code-style](#code-style) +1. **All modifications** must be made in a **pull-request** to solicit feedback + from other contributors +2. Contributors should attempt to adhere to the prevailing + [code-style](#code-style) ## Pull requests @@ -34,25 +45,35 @@ There are a few basic ground-rules for contributors: :white_check_mark:   Have one approving review -:white_check_mark:   Have the PR title follow [conventional commit](https://www.conventionalcommits.org/) +:white_check_mark:   Have the PR title follow +[conventional commit](https://www.conventionalcommits.org/) **Ideally, a good pull request should:** :clock3:   Take less than 15 minutes to review -:open_book:   Have a meaningful description (describes the problem being solved) +:open_book:   Have a meaningful description (describes the problem being +solved) -:one:   Introduce one feature or solve one bug at a time, for which an open issue already exists. In case of a project wide refactoring, a larger PR is to be expected, but the reviewer should be more carefully guided through it +:one:   Introduce one feature or solve one bug at a time, for which an open +issue already exists. In case of a project wide refactoring, a larger PR is to +be expected, but the reviewer should be more carefully guided through it -:jigsaw:   Issues that seem too big for a PR that can be reviewed in 15 minutes or PRs that need to touch other issues should be discussed and probably split differently before starting any development +:jigsaw:   Issues that seem too big for a PR that can be reviewed in 15 +minutes or PRs that need to touch other issues should be discussed and probably +split differently before starting any development -:dart:   Handle renaming, moving files, linting and formatting separately (not alongside features or bug fixes) +:dart:   Handle renaming, moving files, linting and formatting separately +(not alongside features or bug fixes) :test_tube:   Add tests for new functionality -**Draft pull requests for early feedback are welcome and do not need to adhere to any guidelines.** +**Draft pull requests for early feedback are welcome and do not need to adhere +to any guidelines.** -When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in: +When reviewing a pull request, the end-goal is to suggest useful changes to the +author. Reviews should finish with approval unless there are issues that would +result in: :x:   Buggy behavior @@ -60,21 +81,25 @@ When reviewing a pull request, the end-goal is to suggest useful changes to the :x:   Measurable performance issues -:x:   Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on) +:x:   Feature reduction (i.e. it removes some aspect of functionality that +a significant minority of users rely on) -:x:   Uselessness (i.e. it does not strictly add a feature or fix a known issue) +:x:   Uselessness (i.e. it does not strictly add a feature or fix a known +issue) :x:   Disabling a compiler feature to introduce code that wouldn't compile ## Code style -We use the following tools to enforce linting rules, formatting and spell checking +We use the following tools to enforce linting rules, formatting and spell +checking - [`deno lint`](https://deno.land/manual/tools/linter) - [`dprint`](https://dprint.dev/) - [`cspell`](https://cspell.org/) -We encourage adding the [recommended](.vscode/extensions.json) (or similar) extensions to your IDE. +We encourage adding the [recommended](.vscode/extensions.json) (or similar) +extensions to your IDE. To run a project wide check you can use: @@ -90,15 +115,20 @@ Declaring formal releases remains the prerogative of the project maintainer(s). ## License -By contributing to project, you agree that your contributions will be licensed under its [Apache license](LICENSE). +By contributing to project, you agree that your contributions will be licensed +under its [Apache license](LICENSE). ## Changes to this arrangement -This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. +This is an experiment and feedback is welcome! This document may also be subject +to pull-requests or changes by contributors where you believe you have something +valuable to add or change. ## Heritage These contributing guidelines are modified from -- the "Substrate Project" guidelines https://github.com/paritytech/substrate/blob/master/docs/CONTRIBUTING.adoc -- the "Substrate Contracts UI" guidelines https://github.com/paritytech/contracts-ui/blob/master/CONTRIBUTING.md +- the "Substrate Project" guidelines + https://github.com/paritytech/substrate/blob/master/docs/CONTRIBUTING.adoc +- the "Substrate Contracts UI" guidelines + https://github.com/paritytech/contracts-ui/blob/master/CONTRIBUTING.md diff --git a/Readme.md b/Readme.md index cac749cf6..3618d8ec1 100644 --- a/Readme.md +++ b/Readme.md @@ -1,77 +1,86 @@ # Capi -> Capi is a work in progress. The documentation may not reflect the current implementation. **Expect a stable release and proper documentation in early 2023**. +> Capi is a work in progress. The documentation may not reflect the current +> implementation. **Expect a stable release and proper documentation in early +> 2023**. -Capi is a declarative, TypeScript-first toolkit for crafting interactions with Substrate-based chains. It consists of [FRAME](https://docs.substrate.io/reference/glossary/#frame) utilities, a functional effect system (Rune) and a fluent API, which facilitates multistep, multichain interactions without compromising either performance or safety. +Capi is a framework for crafting interactions with Substrate chains. It consists +of a development server and fluent API, which facilitates multistep, multichain +interactions without compromising either performance or ease of use. +- [Docs →](https://docs.capi.dev)
Guides for Capi developers and + pattern library developers - [Examples →](./examples)
SHOW ME THE CODE -- [API Reference →](https://deno.land/x/capi/mod.ts)
A generated API reference, based on type signatures and in-source comments. -- [Type Conversion Guide →](./docs/Types.md)
Guide for Capi's conversion of types from Rust to TypeScript +- [API Reference →](https://deno.land/x/capi/mod.ts)
A generated API + reference, based on type signatures and TSDocs. ## At a Glance -Run the local server. - -```sh -deno run -A https://deno.land/x/capi/main.ts -``` - -Then, open your IDE and import pallet-corresponding modules from the local server. - -```ts -import { System } from "http://localhost:4646/frame/wss/rpc.polkadot.io/@latest/mod.ts" - -const key = System.Account.keys().first() - -const value = System.Account.entry(key) - -console.log(await value.run()) -``` - -### Import Mapping - -For simplicity, we recommend aliasing import specifiers via import maps. +Create an import map with the specifier corresponding to your target. `import_map.json` ```json { "imports": { - "#polkadot/": "http://localhost:4646/frame/wss/rpc.polkadot.io/@latest/" + "@capi/polkadot/": "https://capi.dev/frame/wss/rpc.polkadot.io/@latest/" } } ``` -```diff -- import { System } from "http://localhost:4646/frame/wss/rpc.polkadot.io/@latest/mod.ts" -+ import { System } from "#polkadot/mod.ts" +Then, open your editor and import from the mapped chain module. + +```ts +import { System } from "@capi/polkadot/mod.ts" + +const key = System.Account + .keyPage(1) + .access(0) + .unhandle(undefined) + +const value = System.Account.value(key) + +console.log(await value.run()) ``` -## Examples +## Running Examples -See [the `examples/` directory](./examples). +Within a fresh clone of the repository... + + ```sh -git clone https://github.com/partitytech/capi -cd capi -deno task run codegen # host the server locally and cache all codegen -deno task run examples/.ts +deno task run examples/.ts ``` -## The Thesis +## Rationale -In a likely future of specialized, interoperable chains, developers will need to make use of on-chain programs to satisfy varying use cases; the expertise required to interact with these on-chain programs is currently greater than that which _should_ be expected of app developers. Does this mean that app developers must forgo integrating with this blossoming infrastructure? We think not; **the open source community can use Capi to abstract over the atomics of the on-chain world**. An interaction spanning several chains and dozens of methods can be described with a single effect. +In a likely future of specialized, interoperable chains, developers will need to +make use of on-chain programs to satisfy varying use cases; the expertise +required to interact with these on-chain programs is currently greater than that +which _should_ be expected of app developers. Does this mean that app developers +must forgo integrating with this blossoming infrastructure? We think not; **the +open source community can use Capi to abstract over the atomics of the on-chain +world**. An interaction spanning several chains and dozens of methods can be +described with a single Rune[^1]. -As you read through this documentation, please consider use cases over which you might like to abstract; if you wish to add your use case to [Capi's standard library](effects), please [submit an issue](https://github.com/paritytech/capi/issues/new). +As you read through this documentation, please consider use cases over which you +might like to abstract; if you wish to add your use case to +[Capi's standard library](patterns), please +[submit an issue](https://github.com/paritytech/capi/issues/new?title=pattern%20idea:%20). ## Code of Conduct -Everyone interacting in this repo is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). +Everyone interacting in this repo is expected to follow the +[code of conduct](CODE_OF_CONDUCT.md). ## Contributing -Contributions are welcome and appreciated! Check out the [contributing guide](CONTRIBUTING.md) before you dive in. +Contributions are welcome and appreciated! Check out the +[contributing guide](CONTRIBUTING.md) before you dive in. ## License Capi is [Apache licensed](LICENSE). + +[^1]: Rune is the unit of composition with which we model Capi programs. diff --git a/docs/Types.md b/docs/Types.md index 68618146a..5133ed6b8 100644 --- a/docs/Types.md +++ b/docs/Types.md @@ -1,8 +1,26 @@ # Types -The types of the on-chain world are declared in a given [Substrate](https://substrate.io/)-based chain's (Rust) source code. While many types may remain consistent across chains, many may differ. On one chain, `AccountData` may be defined with fields describing fungible assets; on another (hypothetical) chain, perhaps `AccountData` references non-fungible assets, reputation, linked accounts or something else entirely. Although [FRAME](https://docs.substrate.io/reference/glossary/#frame) certainly helps to standardize chain properties, those properties can be customized to the extent that we cannot make assumptions regarding shapes of data across chains. Additionally, types can change upon [runtime upgrades](https://docs.substrate.io/build/upgrade-the-runtime/); your assumptions about the shape of a type may become invalid; to interact with these highly-dynamic on-chain environments––and to do so from a JavaScript environment––poses inherent difficulty. We JS developers must (A) think in terms of Rust data types and (B) keep a lookout for breaking changes to chain runtimes. This document does not provide a silver-bullet solution to this complexity, but it should provide the background necessary for you to address types for your specific use cases. - -If you just want to see how Rust types are transformed by Capi, [skip to the conversion table](#rust-⬌-typescript). +The types of the on-chain world are declared in a given +[Substrate](https://substrate.io/)-based chain's (Rust) source code. While many +types may remain consistent across chains, many may differ. On one chain, +`AccountData` may be defined with fields describing fungible assets; on another +(hypothetical) chain, perhaps `AccountData` references non-fungible assets, +reputation, linked accounts or something else entirely. Although +[FRAME](https://docs.substrate.io/reference/glossary/#frame) certainly helps to +standardize chain properties, those properties can be customized to the extent +that we cannot make assumptions regarding shapes of data across chains. +Additionally, types can change upon +[runtime upgrades](https://docs.substrate.io/build/upgrade-the-runtime/); your +assumptions about the shape of a type may become invalid; to interact with these +highly-dynamic on-chain environments––and to do so from a JavaScript +environment––poses inherent difficulty. We JS developers must (A) think in terms +of Rust data types and (B) keep a lookout for breaking changes to chain +runtimes. This document does not provide a silver-bullet solution to this +complexity, but it should provide the background necessary for you to address +types for your specific use cases. + +If you just want to see how Rust types are transformed by Capi, +[skip to the conversion table](#rust-⬌-typescript). ## Learning About Types @@ -12,7 +30,8 @@ Let's cover how to learn about a chain's types/properties. ### FRAME Metadata -Every FRAME chain exposes metadata about its types and capabilities. This metadata is called the "FRAME Metadata." Let's retrieve and inspect it. +Every FRAME chain exposes metadata about its types and capabilities. This +metadata is called the "FRAME Metadata." Let's retrieve and inspect it. ```ts import { client } from "http://localhost:4646/frame/wss/rpc.polkadot.io/@v0.9.36/mod.ts" @@ -20,7 +39,8 @@ import { client } from "http://localhost:4646/frame/wss/rpc.polkadot.io/@v0.9.36 > To run the Capi dev server, run `deno run -A https://deno.land/x/capi/main.ts` -Let's connect to Polkadot and fetch the chain's metadata for the present highest block. +Let's connect to Polkadot and fetch the chain's metadata for the present highest +block. ```ts // ... @@ -30,9 +50,15 @@ const metadata = await C.metadata(client)() console.log(await metadata.run()) ``` -If we index into `metadata.pallets`, we'll see a list of all pallet metadata. Each element of this list contains a complete description of the given pallet's storage entries, as well as constants, callables (for creating extrinsics), errors and events. Some fields––such as a pallet's `call` field––point to an index in `metadata.tys`, which contains a complete description of the chain's type-level context. +If we index into `metadata.pallets`, we'll see a list of all pallet metadata. +Each element of this list contains a complete description of the given pallet's +storage entries, as well as constants, callables (for creating extrinsics), +errors and events. Some fields––such as a pallet's `call` field––point to an +index in `metadata.tys`, which contains a complete description of the chain's +type-level context. -Let's say we want to learn about the types associated with the `Balances` pallet's `Account` storage. +Let's say we want to learn about the types associated with the `Balances` +pallet's `Account` storage. ```ts // ... @@ -44,7 +70,8 @@ const accountsStorage = C.entryMetadata(balancesPallet, "Account") console.log(await accountsStorage.run()) ``` -On chains using the `Balances` pallet, `accountsStorage` will look _similar_ to the following. +On chains using the `Balances` pallet, `accountsStorage` will look _similar_ to +the following. ```ts { @@ -78,11 +105,14 @@ On chains using the `Balances` pallet, `accountsStorage` will look _similar_ to } ``` -- `type` tells us that this storage is that of a map, not a plain entry (standalone value) -- `key` tells us what type of value(s) we need to use in order to index into the map +- `type` tells us that this storage is that of a map, not a plain entry + (standalone value) +- `key` tells us what type of value(s) we need to use in order to index into the + map - `value` tells us what type of value we can expect to retrieve from the map -In this example, the `key`'s `id` is `0`. Let's take a look at this type (within the top-level `metadata`'s `tys`). +In this example, the `key`'s `id` is `0`. Let's take a look at this type (within +the top-level `metadata`'s `tys`). ```ts const keyType = metadata.tys[accountsStorage.key] @@ -101,7 +131,9 @@ const keyType = metadata.tys[accountsStorage.key] }; ``` -If we index again into `metadata.tys` with `1` (as specified in the first field), we'll see the inner types (in this case a 32-element tuple of `u8`s). From these descriptions, we can roughly deduce the JS equivalent. +If we index again into `metadata.tys` with `1` (as specified in the first +field), we'll see the inner types (in this case a 32-element tuple of `u8`s). +From these descriptions, we can roughly deduce the JS equivalent. ```ts namespace sp_core { @@ -117,7 +149,8 @@ We can instantiate this as we would any other JS-land value. const accountId32 = new Uint8Array(RAW_ADDR_BYTES) ``` -We'll cover the TypeScript <-> Rust conversions more in depth [in a later section](#typescript---rust). +We'll cover the TypeScript <-> Rust conversions more in depth +[in a later section](#typescript---rust). Let's now utilize our `accountId32` definition to read a balance. @@ -135,7 +168,8 @@ const account = await account.run() What value does this retrieve? How can we deduce this from the FRAME metadata? -We can do the same as before, but this time index into `metadata.tys` with the `accountsStorage.value`. +We can do the same as before, but this time index into `metadata.tys` with the +`accountsStorage.value`. ```ts const valueType = metadata.tys[accountsStorage.value] @@ -159,7 +193,9 @@ This should give us something along the following lines: }; ``` -When we follow type `6` (metadata.tys[6]), we see that it represents a `u128`. In TypeScript, this translates to a `bigint`. Therefore, the complete JS-land structure looks as follows. +When we follow type `6` (metadata.tys[6]), we see that it represents a `u128`. +In TypeScript, this translates to a `bigint`. Therefore, the complete JS-land +structure looks as follows. ```ts namespace pallet_balances { @@ -172,7 +208,8 @@ namespace pallet_balances { } ``` -> Note: we can ignore the `params` field of the `AccountData` metadata, as the type param is already applied to the field metadata. +> Note: we can ignore the `params` field of the `AccountData` metadata, as the +> type param is already applied to the field metadata. ## Rust ⬌ TypeScript @@ -249,9 +286,12 @@ type E1 = ## Runtime Validation -What happens if we ever specify an invalid value to an untyped effect? Capi will return a validation error with a detailed description of the type incompatibility. +What happens if we ever specify an invalid value to an untyped effect? Capi will +return a validation error with a detailed description of the type +incompatibility. -For instance, the aforementioned system accounts map is keyed with a `Uint8Array`. What happens if we try to key into it with `"HELLO T6"`? +For instance, the aforementioned system accounts map is keyed with a +`Uint8Array`. What happens if we try to key into it with `"HELLO T6"`? ```ts const account = C.entryRead(client)("System", "Account", ["HELLO T6"]) @@ -276,9 +316,13 @@ Let's look at the same example from before: reading some `AccountData`. const result = await C.entryRead(client)("System", "Account", [key]).run() ``` -In this storage read example, `result` is typed as the successfully-retrieved value (container) unioned with all possible errors. +In this storage read example, `result` is typed as the successfully-retrieved +value (container) unioned with all possible errors. -There are several ways to "unwrap" the inner `value`. The recommended path is to first check for and handle all possible errors, which may encapsulate error specific data (as do [SCALE](https://github.com/paritytech/scale-ts) validation errors). +There are several ways to "unwrap" the inner `value`. The recommended path is to +first check for and handle all possible errors, which may encapsulate error +specific data (as do [SCALE](https://github.com/paritytech/scale-ts) validation +errors). ```ts if (account instanceof Error) { diff --git a/dprint.json b/dprint.json index 425c343f6..873ecb8dc 100644 --- a/dprint.json +++ b/dprint.json @@ -7,6 +7,10 @@ "arrowFunction.useParentheses": "force", "semiColons": "asi" }, + "markdown": { + "lineWidth": 80, + "textWrap": "always" + }, "includes": ["**.{dockerfile,json,md,toml,ts,tsx}"], "excludes": ["target", "util/tsFormatter.ts"], "plugins": [ diff --git a/rune/rune.md b/rune/rune.md index 5bc4e7e68..b1e226d99 100644 --- a/rune/rune.md +++ b/rune/rune.md @@ -211,7 +211,8 @@ We can still achieve the same behavior in both, but in RxJS, we had to use a different combinator. (`combineLatest` wouldn't have worked in the first example as it would've given `a` inconsistent values). -As the timing gets even more complex, though, it becomes infeasible to handle correctly in RxJS: +As the timing gets even more complex, though, it becomes infeasible to handle +correctly in RxJS: ```ts import { combineLatest, map, timer, zip } from "npm:rxjs"