From 4e9b33d7bfa235253e104bc4d55d31cee94a1bb8 Mon Sep 17 00:00:00 2001 From: tjjfvi Date: Sun, 27 Nov 2022 18:26:46 +0000 Subject: [PATCH] sub0 docs --- Readme.md | 36 ++++---- docs/Configs.md | 70 ---------------- docs/Derivations.md | 1 - docs/Dynamic_Targets.md | 1 - docs/Effects.md | 175 --------------------------------------- docs/Events.md | 1 - docs/Future_Direction.md | 1 - docs/Patterns.md | 1 - docs/Principles.md | 47 ----------- docs/Quick_Start.md | 113 ------------------------- docs/Reading.md | 33 -------- docs/Readme.md | 15 ---- docs/Runtime_Upgrades.md | 0 docs/Subscribing.md | 1 - docs/Testing.md | 47 ----------- docs/Transacting.md | 1 - docs/Types.md | 2 + fluent/mod.ts | 6 ++ 18 files changed, 28 insertions(+), 523 deletions(-) delete mode 100644 docs/Configs.md delete mode 100644 docs/Derivations.md delete mode 100644 docs/Dynamic_Targets.md delete mode 100644 docs/Effects.md delete mode 100644 docs/Events.md delete mode 100644 docs/Future_Direction.md delete mode 100644 docs/Patterns.md delete mode 100644 docs/Principles.md delete mode 100644 docs/Quick_Start.md delete mode 100644 docs/Reading.md delete mode 100644 docs/Readme.md delete mode 100644 docs/Runtime_Upgrades.md delete mode 100644 docs/Subscribing.md delete mode 100644 docs/Testing.md delete mode 100644 docs/Transacting.md diff --git a/Readme.md b/Readme.md index 9ac118b66..07f16685b 100644 --- a/Readme.md +++ b/Readme.md @@ -4,33 +4,37 @@ Capi is a declarative, TypeScript-first toolkit for crafting interactions with Substrate-based chains. It consists of [FRAME](https://docs.substrate.io/v3/runtime/frame/) utilities and [a high-level functional effect system](https://github.com/paritytech/zones) and [effects](./effects), which facilitate multistep, multichain interactions without compromising either performance or safety. -- [Documentation →](./docs/Readme.md)
Materials for learning about Capi - [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 ## At a Glance -Generate chain-specific bindings. +```ts +import { System } from "https://capi.dev/proxy/wss:rpc.polkadot.io/pallets/mod.ts" -```sh -deno run -A -r https://deno.land/x/capi/codegen.ts \ - --src="wss://rpc.polkadot.io" \ - --out="polkadot" -``` +const key = System.Account.keys().first() -> ... or use **the Node equivalent**––`npx capi`--with the same arguments. +const value = System.Account.entry(key) -Make use of those bindings. +console.log(await value.run()) +``` -```ts -import * as C from "capi" -import { system } from "./polkadot/frame.ts" +> Note: although the codegen server is hosted on https://capi.dev, we encourage you to run it locally with +> +> ```sh +> deno run -A https://deno.land/x/capi/serve.ts +> ``` + +## Examples -// bind to the last inserted key -const key = system.account.keys.first +See [the `examples/` directory](./examples). -// bind to the corresponding value -const value = C.run(system.account.get(key)) +```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 ``` ## The Thesis diff --git a/docs/Configs.md b/docs/Configs.md deleted file mode 100644 index d1a796ba6..000000000 --- a/docs/Configs.md +++ /dev/null @@ -1,70 +0,0 @@ -# Configs - -Before interacting with a given chain, we must have a means of finding nodes of that chain. This means of discovery is called a "config." A config can also contain additional values and type information (more on this below). - -```ts -import { config as polkadot } from "@capi/polkadot" -``` - -Let's use the Polkadot config to read some storage. - -```ts -const result = await C.entry(polkadot, "Staking", "ActiveEra").read() -``` - -## Type Safety - -The static type of any config can describe accessible RPC server methods and FRAME metadata. This enables a narrowly-typed experience to flow through all usage of Capi. - -### FRAME Types - -What happens if––in the example above––we accidentally misspell a junction of the storage key? We get an immediate type error. - -```ts -const result = await C.entry(polkadot, "Stacking", "ActiveEra").read() -// ~~~~~~~~~~ -// ^ argument of type 'Stacking' is not assignable to parameter of type 'PolkadotPalletName'. -``` - -### RPC Methods - -The same is true for RPC method availability. - -```ts -const result = await C.rpcCall(myConfig, "nonexistent_method", []) -// ~~~~~~~~~~~~~~~~~~~~ -// ^ argument of type 'nonexistent_method' is not assignable to parameter of type 'existent_method'. -``` - -## Custom Configs - -To generate a config for an unknown chain, simply point the Capi CLI at the RPC URL of a node of the chain. - -```sh -deno run -A -r https://deno.land/x/capi/main.ts MyNamespace wss://xyz.network -``` - -> Note: running the CLI requires that you have the Deno toolchain installed locally - -Upon running this command, the CLI will generate a new directory in your current-working directory. By default, this directory is named `configs`, although you can modify this by supplying a `--dir` value. - -```diff -- deno run -A -r https://deno.land/x/capi/main.ts MyNamespace wss://xyz.network -+ deno run -A -r https://deno.land/x/capi/main.ts MyNamespace wss://xyz.network --out=my_configs -``` - -The generated directory will contain a root `mod.ts`, which re-exports the values contained within namespace-specific config definition files (a `my_configs/MyNamespace.ts`, for example). - -We can import and utilize these configs as we would from `capi/known`. - -```ts -import { config } from "./my_configs/mod.ts" -``` - -## Ecosystem Configs - -Proprietors and communities of a given chain may want to take ownership of their configs. Although Capi's typegen encodes all possible constraints from the FRAME metadata, there are further constraints from which users may benefit. - -```ts -import { config } from "https://deno.land/x/capi-xyz-chain/mod.ts" -``` diff --git a/docs/Derivations.md b/docs/Derivations.md deleted file mode 100644 index b88c379a3..000000000 --- a/docs/Derivations.md +++ /dev/null @@ -1 +0,0 @@ -# Derivations diff --git a/docs/Dynamic_Targets.md b/docs/Dynamic_Targets.md deleted file mode 100644 index ee59eb007..000000000 --- a/docs/Dynamic_Targets.md +++ /dev/null @@ -1 +0,0 @@ -# Dynamic Targets diff --git a/docs/Effects.md b/docs/Effects.md deleted file mode 100644 index 276f11af3..000000000 --- a/docs/Effects.md +++ /dev/null @@ -1,175 +0,0 @@ -# Effects - -"Effects" are a type-safe means by which we model and dispatch potentially-complex interactions that span many chains. To understand this programming model, let's discuss Effects beyond the context of blockchains and network resource management. - -Let's say we want to write a program that produces a random number. If the number is greater than `.5`, we want the program to fail (a strange little program, I'll admit). - -```ts -class GtPoint5Error extends Error { - constructor(value: number) { - super(`The random number \`${value}\` is greater than \`.5\`.`) - } -} - -function getRand(): number { - const rand = Math.random() - if (rand > .5) { - throw new GtPoint5Error(rand) - } - return rand -} -``` - -## Type-safe Errors - -How do we safeguard against `GtPoint5Error` causing trouble in other parts of our program? An unfortunate shortcoming of TypeScript is a lack of holistic error management (especially error types and their propagation through callers). In a language such as Rust, perhaps we could match error variants or trigger early escape. - -```rs -// Match and handle locally. -match getRand() { - Err(e) => handleErr(e), - Ok(o) => println!("{:?}", o), -} - -// Propagate to parent for handling. -fn getRandOrErr() -> Result { - getRand()? -} -``` - -In TypeScript, we don't have this luxury. So how might we tackle error handling? One solution would be to introduce a `Result` type. - -```diff -- function getRand(): number { -+ function getRand(): Result { - const rand = Math.random(); - if (rand > .5) { -- throw new GtPoint5(rand); -+ return new GtPoint5(rand); - } -- return rand; -+ return ok(rand); -}; -``` - -However, this introduces the complexity of propagating **all** error types as we compose our program. - -Let's say we want to represent the addition of two numeric result types. We parameterize the constraints of `a` and `b` and produce a result type, derived from the error types extracted from those constraints. This does the trick, but introduces much boilerplate. - -```ts -function add< - A extends Result, - B extends Result, ->( - a: A, - b: B, -): Result | Extract> { - if (a instanceof Error) { - return a - } else if (b instanceof Error) { - return b - } - return ok(a.ok + b.ok) -} -``` - -Because the error types of `A` and `B` are generic, we can never truly handle them within `add`. So, why do we even try? Ideally, the errors of `add`'s dependencies bubble up to the `add` caller's root, where the applied arguments' error types are accessible for type-safe handling. More on this later. - -This is precisely what Capi's effect system––[Zones](https://github.com/paritytech/zones)––enables. - -```ts -import * as Z from "zones" - -const rand = Z.call.fac(() => { - const rand = Math.random() - if (rand > .5) { - return new GtPoint5Error(rand) - } - return rand -}) - -const add = Z.call.fac((a: number, b: number) => { - return a + b -}) - -const root = add(rand(), 1) - -const result = Z.runtime().run(root) -``` - -In this example `result` carries the type `number | GtPoint5Error`, which allows us to discriminate with ease. - -```ts -if (result instanceof Error) { - // `result` is of type `GtPoint5Error` -} else { - // `result` is of type `number` -} -``` - -Although this may seem overly-complex for such a tiny amount of computation, it is dramatically-simple when composing interactions spanning many chains, each with their own types of errors. - -## Capi Effects - -In the context of Capi, Effects are used to represent on-chain constructs without actually performing any computation until necessary. As showcased within this project's readme, we can create an effect that represents a key in a map, and then use that effect to represent its corresponding value in the map. This all occurs without any network interaction whatsoever. - -```ts -import * as C from "capi" -import { system } from "./polkadot/frame.ts" - -const key = system.account.keys.first - -const value = system.account.get(key) -``` - -We can compose atomic effects such as these to create complex, multichain interactions that abstract over common use cases. Meanwhile, the underlying effect system appropriately determines the optimal path to execute effects. - -```ts -const result = await C.run(value) -``` - -In this example `result` carries a type representing a union of all possible errors (such as `StorageDne`). - -## Optimized Execution - -Let's go over one more pain point: optimizing execution. - -We don't want to accidentally allocate all the JS thread's processing to a blocking operation. We don't want to accidentally send duplicate requests or keep connections alive for longer than they are needed. These are just a few of the considerations that go into smooth network interactions. In practice, these considerations pose great difficulty. - -Imagine you're forming a derived request, wherein `requestC` accepts the result of `requestA` and `requestB`. - -```ts -const a = await requestA() -const b = await requestB() -const c = await requestC(a, b) -``` - -In this case, we accidentally block on `requestA`, when we could execute it in parallel with `requestB`. - -```ts -const a = requestA() -const b = requestB() -const c = await requestC(...await Promise.all([a, b])) -``` - -Or perhaps there are parts of our program which can produce repeat versions of a request. - -```ts -const makeD = (value: number) => requestD(value) -makeD(Math.random()) -makeD(Math.random()) -``` - -If `Math.random()` miraculously returns `.25` twice, do we need to send the request a second time? If `requestD` is idempotent, then no. - -The list of such situations goes on. The point is this: in an ideal world, the aforementioned considerations are offloaded from the developer. Enter, Capi's effect system. - -### Final Note about Effects - -Another advantage of this programming model is that developers can see performance improvements as we enhance the effect runtime. Today, a Capi effect may take 100ms to execute. Tomorrow, it may take 30ms. The day after, ... - -Onward to adventure. - ---- - -Now that we've gone over the basics of Capi's effect system, let's take a look at [how we read from different kinds of on-chain storage](./Reading.md). diff --git a/docs/Events.md b/docs/Events.md deleted file mode 100644 index 1f187df0f..000000000 --- a/docs/Events.md +++ /dev/null @@ -1 +0,0 @@ -# Events diff --git a/docs/Future_Direction.md b/docs/Future_Direction.md deleted file mode 100644 index fd15fda92..000000000 --- a/docs/Future_Direction.md +++ /dev/null @@ -1 +0,0 @@ -# Future Direction diff --git a/docs/Patterns.md b/docs/Patterns.md deleted file mode 100644 index 3c9daa330..000000000 --- a/docs/Patterns.md +++ /dev/null @@ -1 +0,0 @@ -# Patterns diff --git a/docs/Principles.md b/docs/Principles.md deleted file mode 100644 index 26f0d7db1..000000000 --- a/docs/Principles.md +++ /dev/null @@ -1,47 +0,0 @@ -# Principles - -> The following sections do not cover practical usage of Capi, but rather give background on underlying ideas. By all means **[skip to the next section](Testing.md) if your goal is to dive into building**. - -What principles guide the development of Capi? - -## Foresight - -Capi must adapt to future use cases and technical possibilities. - -### Multichain - -One of the major goals which motivates the development of Substrate is to support a multichain future. As we approach this future, there is increasing pressure on JavaScript clients to interact with multiple chains simultaneously. This is quite difficult in practice, as developers must manually instantiate and await connections, optimize disconnection and re-connection, and devise other means of preserving the limited resources of their JavaScript environments. - -Capi reduces this friction; developers declare their requirements as [Effects](Effects.md) and allow an executor to determine the most efficient route to fulfillment. The management and optimization of connections, and communication across those connections, is abstracted away from the developer. - -### Optimal Access Patterns - -Chain resources are intentionally-constrained by economic incentives; this limits a developer's ability to implement access patterns, which would otherwise benefit the end-user experience. Many such access patterns relate to data aggregation and preprocessing; doing this work on the client-side (in a browser, for example) consumes already-sparse resources. In other circumstances, the developer might move these tasks to less-constrained environments, such as that of a centralized server. Although Substrate developers can use centralized servers as chain proxies, this introduces points of failure. There is currently no obvious solution to avoiding trade-offs. - -Capi is designed to accommodate solutions––as they are devised––without requiring effort from developers. Standard effects can be made portable, such that they can be shared with other environments, which could assist in their fulfillment. We hope to make progress on realizing a [`chainHead_wasmQuery` RPC endpoint](https://github.com/paritytech/json-rpc-interface-spec/issues/4) as a first step towards addressing this principle. - -## Offer a Foundation - -Capi provides a foundation on top of which higher-level solutions can be built. - -### Effect System - -The basis of Capi is its effect system, with which developers describe chain interactions. This system is designed to make interactions optimally composable as to support an open source ecosystem of effects, ranging in level of abstraction. - -### Compatibility - -Capi is compatible with modern JavaScript environments; its behavior is consistent across browsers, [Node](https://nodejs.org/en/), [Deno](https://deno.land/) and even raw [V8](https://v8.dev/) (such is the case in [CloudFlare Workers](https://developers.cloudflare.com/workers/learning/how-workers-works#isolates)). Capi fits into the mold of popular build tools, such as [Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/guide/en/) and [Vite](https://vitejs.dev/). We aim to ensure that Capi fits into ESLint plugins, VSCode extensions, Vim/Neovim plugins and other tooling experiences where there is benefit to developers. - -## Safety - -### Testing - -The experience of testing Capi usage must be as unobtrusive as possible. We aim to gradually integrate with tools such as [Zombienet](https://github.com/paritytech/zombienet) which provide simple means of confirming behavioral expectations. Regarding ease of testing Capi: all dependencies of an effect are known before its execution; this metadata can be used within analysis and testing solutions. - -## Application Evolution - -Forkless upgrades are a defining feature of Substrate-based chains. Yet, this feature is also a point of failure for dependents of those chains. Following an upgrade, an app may form newly-invalid transactions or attempt to access newly-nonexistent storage. Capi provides a means for developers to prepare for runtime upgrades and seamlessly transition their apps with as little downtime as possible. - ---- - -These are just a few of the considerations that underlay Capi's design and development. Let's move onto [the next section](Testing.md), in which we'll spawn a local development node and use it to test Capi. diff --git a/docs/Quick_Start.md b/docs/Quick_Start.md deleted file mode 100644 index 350922712..000000000 --- a/docs/Quick_Start.md +++ /dev/null @@ -1,113 +0,0 @@ -# Quick Start - -## Setup - -If you're using [Deno](https://deno.land/), import Capi via its [`denoland/x`](https://deno.land/x) URI. - -```ts -import * as C from "https://deno.land/x/capi/mod.ts" -``` - -> Note: you may want to pin the version in the import specifier (`"https://deno.land/x/capi@x.x.x/mod.ts"`). - -If you're using [Node](https://nodejs.org/), install Capi from [NPM](https://www.npmjs.com/). - -```sh -npm install capi -``` - -Then import as follows. - -```ts -import * as C from "capi" -``` - -> The `capi` NPM package contains both ESM & CJS formats, alongside corresponding type definitions. - -This documentation will prefer the Node-style import for the sake of brevity, although Capi itself is a Deno-first toolkit. - -## Static vs. Dynamic - -**If we know the exact chain(s) with which we're going to interact, the "static" approach is preferable**. This approach offers minimal and type-safe bindings to specific chains. The benefits of this are far-reaching: compile-time validation, inference and autocompletion, symbol-bound ([TSDoc](https://tsdoc.org/)) comments, precompiled codecs and more. Given these DX gains, the static approach gets the majority of attention throughout this documentation. If, however, you do not have development-time knowledge of the target chain, [the "dynamic" approach](./Dynamic_Targets.md) is for you. - -## Generate Bindings - -We use Capi's codegen CLI to generate bindings to a given chain. - -```sh -deno run -A -r https://deno.land/x/capi/codegen.ts \ - --src="wss://rpc.polkadot.io" \ - --out="polkadot" -``` - -> Note: the `src` string can be a comma-separated list of proxy node URLs or even a relative path to chain spec on disk. -> -> Note: we can run this in CI for ongoing validation that our usage aligns with the latest runtime. - -## Read the Latest Block - -```ts -import * as C from "capi" -import { block } from "./polkadot/core.ts" - -const block = await C.run(block.latest) -``` - -## Reading From Storage - -Let's read from on-chain storage. - -```ts -import * as C from "capi" -import { system } from "./polkadot/frame.ts" - -// bind to the last inserted key -const key = system.account.keys.first - -// bind to the corresponding value -const value = C.run(system.account.get(key)) -``` - -## Transferring Some Funds - -In the following example, we create and sign an extrinsic that calls the Balance pallet's transfer method. - -```ts -import * as C from "capi" -import { balances } from "./polkadot/frame.ts" - -declare const aliceSigner: C.Signer - -const tx = balances.transfer({ - value: 12345n, - dest: C.MultiAddress.fromPublic(BOB_PUBLIC_KEY), -}) - .signed(aliceSigner) - .sent - .finalized - -const result = await C.run(tx) -``` - -### Observe Transfer Events - -Let's modify the code above so that we can observe corresponding events as they are emitted. - -```diff -const tx = balances.transfer({ - value: 12345n, - dest: C.MultiAddress.fromPublic(BOB_PUBLIC_KEY), -}) - .signed(aliceSigner) -- .sent -+ .watched((stop) => { -+ return (message) => { -+ // use `message` -+ } -+ }); -- .finalized; -``` - ---- - -At this point, we've generated chain-specific bindings, read the latest block, read from some on-chain storage and created, submitted and watched a transfer extrinsic. Now let's cover the API step by step, starting with notes on [principles](./Principles.md), including context on design decisions and long-term goals. diff --git a/docs/Reading.md b/docs/Reading.md deleted file mode 100644 index 3beaa5909..000000000 --- a/docs/Reading.md +++ /dev/null @@ -1,33 +0,0 @@ -# Reading - -```ts -import * as frame from "./generated/frame.ts" -``` - -## Items - -```ts -const result = await chain.timestamp.now -``` - -## Maps - -```ts -const result = await chain.system.account.get(PUBLIC_KEY) -``` - -## NMaps - -```ts -const result = await chain.staking.nominatorSlashInEra.get(123, PUBLIC_KEY) -``` - -### Keys - -```ts -const keyPage = await.chain.staking.nominatorSlashInEra.keys(5) -``` - -## Child Trie - -TODO diff --git a/docs/Readme.md b/docs/Readme.md deleted file mode 100644 index 019ef26cc..000000000 --- a/docs/Readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# Capi Docs (WIP) - -> Capi is under active development. Comprehensive, reliable documentation will follow a stable release. - -- [Quick Start](./Quick_Start.md) -- [Principles](./Principles.md) -- [Testing](./Testing.md) -- [Effects](./Effects.md) -- [Reading](./Reading.md) -- [Subscribing](./Subscribing.md) -- [Transacting](./Transacting.md) -- [Events](./Events.md) -- [Derivations](./Derivations.md) -- [Patterns](./Patterns.md) -- [Future Direction](./Future_Direction.md) diff --git a/docs/Runtime_Upgrades.md b/docs/Runtime_Upgrades.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/Subscribing.md b/docs/Subscribing.md deleted file mode 100644 index f3305d8c3..000000000 --- a/docs/Subscribing.md +++ /dev/null @@ -1 +0,0 @@ -# Subscribing diff --git a/docs/Testing.md b/docs/Testing.md deleted file mode 100644 index 42ba9c2f7..000000000 --- a/docs/Testing.md +++ /dev/null @@ -1,47 +0,0 @@ -# Testing - -> TODO: add more upon [Zombienet integration](https://github.com/paritytech/capi/issues/215). - -Before interacting with a live network, you may find it worthwhile to spawn a local chain and test out your interaction. - -## Run a Local Test Network - -> Note: the following makes use of [Polkadot](https://github.com/paritytech/polkadot), the installation instructions of which can be found [here](https://github.com/paritytech/polkadot#installation). - -## Map to Test Config - -Under the hood, a given "config" encapsulates the value(s) utilized to discover a given chain (RPC proxy URLs and chain specs). To utilize a test config, we can utilize an import map. - -Let's say you have the following usage. - -`example.ts` - -```ts -import * as C from "capi" -import { system } from "./polkadot/frame.ts" - -const result = await C.run(system.events) -``` - -Create an import map. - -`test_import_map.json` - -```json -{ - "./polkadot/config.ts": "capi/test_util/configs/polkadot.ts" -} -``` - -Specify the import map when you run your code. - -```diff -- deno run -A example.ts -+ deno run -A example.ts --import-map="test_import_map.json" -``` - -Under the hood, this usage will spawn a local dev chain and re-route to a corresponding config. - ---- - -Now that we've covered spawning and interacting with a local test network, let's cover [the effect system](./Effects.md), which enables us to model complex interactions spanning many chains and then optimally-execute those descriptions. diff --git a/docs/Transacting.md b/docs/Transacting.md deleted file mode 100644 index 89d9afcd4..000000000 --- a/docs/Transacting.md +++ /dev/null @@ -1 +0,0 @@ -# Transacting diff --git a/docs/Types.md b/docs/Types.md index 5e0efa35e..ef1d2894a 100644 --- a/docs/Types.md +++ b/docs/Types.md @@ -2,6 +2,8 @@ The types of the on-chain world are declared in the given 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 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; 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 Let's cover how to learn about a chain's types/properties. diff --git a/fluent/mod.ts b/fluent/mod.ts index be1706610..e33cd3c71 100644 --- a/fluent/mod.ts +++ b/fluent/mod.ts @@ -66,4 +66,10 @@ export class StorageKeys< ...rest, ).as() } + + first< + Rest extends [start?: C.Z.Ls$, blockHash?: C.Z.$], + >(...rest: Rest) { + return this.readPage(1, ...rest).access(0) + } }