Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,47 @@ jobs:
working-directory: explorer
run: npm run build

dart:
name: Dart client (analyze + test)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable
with:
toolchain: stable

- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

# TODO: pin to a commit SHA to match the repo's action-pinning convention.
- uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Generate the Dart client from the Rust trait surface
run: |
cargo +nightly rustdoc -p truapi -- -Z unstable-options --output-format json
cargo run -p truapi-codegen -- \
--input target/doc/truapi.json \
--output "$RUNNER_TEMP/truapi-ts-throwaway" \
--dart-output dart/truapi/lib/src/generated \
--dart-host-output dart/truapi/lib/src/generated
cargo run -p truapi --example wire_vectors -- dart/truapi/test/wire_vectors.json

- name: Analyze + test
working-directory: dart/truapi
run: |
dart pub get
dart format --output=none --set-exit-if-changed lib test
dart analyze --fatal-infos
dart test

e2e:
name: E2E (playground inside dotli)
# Temporarily disabled while the dotli playground smoke test is stabilized.
Expand Down Expand Up @@ -279,7 +320,7 @@ jobs:
name: CI Status
if: always()
runs-on: ubuntu-latest
needs: [rust, licenses, codegen, ts-client, playground, explorer, e2e]
needs: [rust, licenses, codegen, ts-client, playground, explorer, dart, e2e]
steps:
- name: Check all jobs
run: |
Expand All @@ -290,6 +331,7 @@ jobs:
"${{ needs.ts-client.result }}"
"${{ needs.playground.result }}"
"${{ needs.explorer.result }}"
"${{ needs.dart.result }}"
"${{ needs.e2e.result }}"
)
for r in "${results[@]}"; do
Expand Down
16 changes: 14 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,28 @@ This repo is the single source of truth for the TrUAPI protocol. It vendors `dot
```
rust/crates/
truapi/ Rust trait + type definitions for protocol versions v0.1 and v0.2
truapi-codegen/ rustdoc JSON → TypeScript client + Rust dispatcher
truapi-codegen/ rustdoc JSON → TypeScript client + Dart host + Rust dispatcher
(ts.rs and dart.rs are sibling emitters over the same IR)
truapi-macros/ #[wire(id = N)] proc-macro
js/packages/
truapi/ @parity/truapi TS package; generated TS lives under ignored paths
dart/
truapi/ Dart/Flutter host; generated Dart under lib/src/generated (ignored)
playground/ Next.js interactive playground; deploys to truapi-playground.dot
hosts/dotli/ dotli submodule
docs/ design docs, RFCs, feature proposals
scripts/codegen.sh regenerate the TS client from the Rust crate
scripts/codegen.sh regenerate the TS client + Dart host from the Rust crate
```

The Dart package is **host-only** (products are web apps that use the TS/JS
client). `truapi-codegen` emits the shared types/wire-table (`--dart-output`) and
the host dispatcher (`--dart-host-output`); no Dart client facade is generated (see
`rust/crates/truapi-codegen/src/dart.rs`). The entry point is
`package:truapi/truapi.dart` (implement `TruapiHostHandlers`, wire with
`createTruapiServer`). Golden cross-language wire vectors are produced by `cargo run
-p truapi --example wire_vectors`. Run `make dart` to regenerate, refresh vectors,
analyze, and test. Design notes: `docs/design/dart-flutter-support.md`.

## Code style

- Every `pub` Rust item (functions, methods, types, traits, modules, constants) carries a doc comment (`///` or `//!`).
Expand Down
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
# Run `make help` for the list of targets.

.DEFAULT_GOAL := help
.PHONY: help setup build codegen test check playground dev matrix explorer
.PHONY: help setup build codegen test check playground dev matrix explorer dart dart-scaffold

TRUAPI_PKG := js/packages/truapi
PLAYGROUND := playground
EXPLORER := explorer
DART := dart/truapi
DOTLI := hosts/dotli

# `make dev DEBUG=1` runs dotli with VITE_APP_DEBUG=true to log every wire frame.
Expand Down Expand Up @@ -61,3 +62,15 @@ matrix: ## Regenerate the host compatibility matrix from explorer/diagnosis-repo

explorer: ## Run the explorer dev server standalone at http://localhost:5181.
cd $(EXPLORER) && npx vite --base / --port 5181

dart: ## Regenerate + analyze + test the Dart client/host (regen golden vectors too).
./scripts/codegen.sh
cargo run -p truapi --example wire_vectors -- $(DART)/test/wire_vectors.json
cd $(DART) && dart pub get && dart analyze && dart test

dart-scaffold: ## (Re)generate the one-shot host scaffold (overwrites example/host_scaffold.dart).
cargo +nightly rustdoc -p truapi -- -Z unstable-options --output-format json
cargo run -p truapi-codegen -- --input target/doc/truapi.json \
--output /tmp/truapi-ts-throwaway \
--dart-host-scaffold-output $(DART)/example/host_scaffold.dart
cd $(DART) && dart format example/host_scaffold.dart
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,22 @@ See [`js/packages/truapi/README.md`](js/packages/truapi/README.md) for the full
```
rust/crates/
truapi/ Rust trait and type definitions (v01, v02)
truapi-codegen/ rustdoc JSON to TypeScript client + Rust dispatcher
truapi-codegen/ rustdoc JSON to TypeScript client + Dart host + Rust dispatcher
truapi-macros/ #[wire(id = N)] proc-macro
js/packages/
truapi/ @parity/truapi TypeScript client
dart/
truapi/ Dart/Flutter host (generated surface under lib/src/generated)
playground/ Interactive Next.js playground (truapi-playground.dot)
hosts/dotli/ dotli host, vendored as a submodule
docs/ Design docs, RFCs, feature proposals
scripts/codegen.sh Regenerate the TS client from the Rust source
scripts/codegen.sh Regenerate the TS client + Dart host from the Rust source
```

A Dart/Flutter host is generated from the same Rust trait surface; see
[`dart/truapi/README.md`](dart/truapi/README.md) and the design doc
[`docs/design/dart-flutter-support.md`](docs/design/dart-flutter-support.md).

## How it works

1. The protocol is defined as Rust traits in [`rust/crates/truapi/`](rust/crates/truapi/), with each method tagged `#[wire(id = N)]` for a stable byte-level dispatch table. Every method's doc comment must carry a ` ```ts ` example, which codegen extracts into the playground's EXAMPLE tab; the build fails if any method is missing one.
Expand Down
12 changes: 12 additions & 0 deletions dart/truapi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Dart/pub
.dart_tool/
.packages
pubspec.lock
build/

# Generated by truapi-codegen (regenerate with scripts/codegen.sh).
lib/src/generated/

# Golden wire vectors generated from Rust (regenerate with `make dart` /
# `cargo run -p truapi --example wire_vectors`).
test/wire_vectors.json
140 changes: 140 additions & 0 deletions dart/truapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# truapi (Dart)

The Dart/Flutter **host** for the **TrUAPI** protocol — the typed, SCALE-encoded
API surface a Polkadot host exposes to the products running inside it.

This package is **host-only**: products are web apps and use the TS/JS
`@parity/truapi` client over the normal web route. The Dart side implements the
host that those products call.

The host surface is **generated from the Rust trait definitions** in this repo
(`rust/crates/truapi`) by `truapi-codegen`, so the Rust crate stays the single
source of truth. This package contributes the hand-written runtime (SCALE codec,
host dispatcher, providers) and the generated surface under `lib/src/generated/`.

> Prototype / reference implementation. Not audited. Use at your own risk.

## Layout

```
lib/
truapi.dart # host barrel: runtime + generated dispatcher + types
src/
scale.dart # SCALE codec primitives & combinators
result.dart # sealed Result<Ok, Err>
transport.dart # Provider, frame codec, wire-id types, SubscriptionInterrupted
providers/
loopback_provider.dart # in-memory channel for tests/local harnesses
host/
host_server.dart # host dispatcher runtime (createHostServer, CallContext, …)
generated/ # git-ignored — produced by truapi-codegen
types.dart # data classes, enums, sealed unions + codecs
wire_table.dart # per-method frame-id constants
host.dart # per-service handler interfaces, build*Entries, createTruapiServer
index.dart # types + wire table barrel
test/
scale_test.dart # SCALE vectors + round-trips
wire_vectors_test.dart # cross-language parity vs Rust golden vectors
host_server_test.dart # host dispatch over loopback, driven by raw wire frames
```

## Usage

Implement one typed handler group per service and wire them to a `Provider`.
Handlers receive and return the inner protocol types; the generated dispatch
entries handle versioned wire wrapping, SCALE encode/decode, and the
request/subscription frame lifecycle.

```dart
import 'package:truapi/truapi.dart';

class MyAccountHandlers implements AccountHostHandlers {
@override
Future<Result<HostAccountGetResponse, HostAccountGetError>> getAccount(
CallContext ctx,
HostAccountGetRequest request,
) async {
final account = myStore.lookup(request.productAccountId);
return Ok(HostAccountGetResponse(account: account));
}

@override
Stream<HostAccountConnectionStatusSubscribeItem> connectionStatusSubscribe(
CallContext ctx,
) =>
myConnectionStatusStream; // each event is forwarded as a receive frame
// … the remaining AccountHostHandlers methods
}

// Implement TruapiHostHandlers (one getter per service) and start the server:
final server = createTruapiServer(provider, MyHostHandlers());
// … later: server.dispose();
```

A subscription handler returns a `Stream<Item>`. For a fallible
(`Result<Subscription>`) method, end the stream with a typed interrupt by adding
a `SubscriptionInterrupted<Reason>(reason)` error; otherwise the stream's normal
completion ends the subscription. To compose a server from a subset of services
(or add custom entries), use the public per-service `build<Service>Entries(...)`
builders with `createHostServer(provider, [...entries])`.

`Result<Ok, Err>` is a sealed type — pattern-match with `switch`, or use
`result.isOk` / `result.okOrNull` / `result.match(...)`.

### Scaffold

[`example/host_scaffold.dart`](example/host_scaffold.dart) is a ready-to-edit
`ScaffoldHostHandlers` implementing **every** service method with
`throw UnimplementedError(...)`, plus heuristic notes on which services are
likely backed by a light client (smoldot), the wallet, or host-local state.
Copy it into your host app and fill in each method. Regenerate it any time the
trait surface changes:

```bash
make dart-scaffold # overwrites example/host_scaffold.dart
```

## Type mapping

| Rust | Dart |
|---|---|
| `bool`, `u8`/`u16`/`u32`, `i8`/`i16`/`i32` | `bool`, `int` |
| `u64`/`u128`/`i64`/`i128`, `Compact<_>` | `BigInt` |
| `String` | `String` |
| `Vec<u8>`, `[u8; N]` | `Uint8List` |
| `Vec<T>` | `List<T>` |
| `Option<T>` | `T?` |
| `OptionBool` | `bool?` |
| tuple `(A, B)` | record `(A, B)` |
| struct | immutable class |
| unit-only enum | Dart `enum` |
| enum with payloads | `sealed class` + variant classes |
| `Result<Ok, Err>` | `Result<Ok, Err>` (`Ok` / `Err`) |
| `Subscription<T>` | `Stream<T>` |

## Transport

Everything above the `Provider` interface is transport-agnostic; only the
provider differs per host. This package ships `LoopbackChannel` (in-memory, for
tests). The real host implements `Provider` over the channel it exposes to its
products (e.g. a Flutter `BasicMessageChannel<ByteData>`, a local socket/WebSocket,
or stdio) — see the design doc's §6.1.

## Regenerate

From the repo root:

```bash
make dart # codegen + golden vectors + analyze + test
# or just the codegen step:
./scripts/codegen.sh
```

## Develop

```bash
dart pub get
dart analyze
dart test
dart format .
```
17 changes: 17 additions & 0 deletions dart/truapi/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
include: package:lints/recommended.yaml

analyzer:
language:
strict-casts: true
strict-raw-types: true
errors:
# Generated code is held to the same bar; real issues still surface.
todo: ignore
exclude:
- lib/src/generated/**

linter:
rules:
- prefer_final_locals
- prefer_const_constructors
- avoid_print
Loading