From baef28083779f2354c42ea0912631c478b3ac5d3 Mon Sep 17 00:00:00 2001 From: fran domovic Date: Tue, 27 Jan 2026 16:05:50 +0100 Subject: [PATCH] feat: js sdk, run local network, monitoring, meroctl --- docs/builder-directory/js-sdk-guide.md | 564 ++++++++------------- docs/builder-directory/sdk-guide.md | 72 ++- docs/getting-started/index.md | 16 +- docs/operator-track/monitor-and-debug.md | 57 ++- docs/operator-track/run-a-local-network.md | 87 +++- docs/tools-apis/meroctl-cli.md | 37 +- 6 files changed, 433 insertions(+), 400 deletions(-) diff --git a/docs/builder-directory/js-sdk-guide.md b/docs/builder-directory/js-sdk-guide.md index 058e0d1..b8d49a9 100644 --- a/docs/builder-directory/js-sdk-guide.md +++ b/docs/builder-directory/js-sdk-guide.md @@ -12,6 +12,7 @@ The JavaScript SDK (`calimero-sdk-js`) consists of two main packages: - **`@calimero-network/calimero-cli-js`** - Build toolchain (Rollup โ†’ QuickJS โ†’ WASM) **Key features:** + - Write services in TypeScript/JavaScript instead of Rust - Same CRDT collections as Rust SDK - Automatic conflict resolution @@ -52,7 +53,8 @@ flowchart LR style RUNTIME fill:#000000,stroke:#00ff00,stroke-width:4px,color:#ffffff ``` -**How it works:** +**How the SDK works:** + - Your TypeScript code runs inside QuickJS (a lightweight JavaScript engine) - CRDT operations call host functions that interact with Rust storage - State is serialized and synchronized across the network @@ -66,21 +68,111 @@ flowchart LR - `pnpm` โ‰ฅ 8 (or npm/yarn) - Access to a Calimero node (`merod`) and CLI (`meroctl`) +### TypeScript Project setup + +> **NOTE**: There is also an npx command `npx create-mero-app my-app` which generates a template for Rust or Tyepscript application including frontend, application logic, scripts and workflows, see [Getting started](../getting-started/index.md/#__tabbed_3_1){:target="_blank"} for more information. + +```bash +$: mkdir my-calimero-js-app +$: cd my-calimero-js-app +$: pnpm init -y +> Wrote to /Users/X/Desktop/my-calimero-js-app/package.json +> +> { +> "name": "my-calimero-js-app", +> "version": "1.0.0", +> "description": "", +> "main": "index.js", +> "scripts": { +> "test": "echo \"Error: no test specified\" && exit 1" +> }, +> "keywords": [], +> "author": "", +> "license": "ISC" +> } + + +#Add following build script which will be used later to generate .WASM from the typescript application. +# package.json +{ + ..., + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "calimero-sdk build src/index.ts -o build/my-calimero-js-app.wasm" + }, + ... +} + +$: pnpm add typescript && npx tsc --init +> + +> Progress: resolved 4, reused 4, downloaded 0, added 1, done +> +> dependencies: +> + typescript 5.9.3 +> +> Done in 3s +> +> Created a new tsconfig.json +``` + +Update tsconfig.json with following settings +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020"], + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "./build", + "rootDir": "./src", + "composite": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*"] +} + +``` + ### Installation ```bash -pnpm add @calimero-network/calimero-sdk-js -pnpm add -D @calimero-network/calimero-cli-js typescript +$: pnpm add @calimero-network/calimero-sdk-js +> ... +> Progress: resolved 213, reused 145, downloaded 44, added 189, done +> node_modules/.pnpm/@calimero-network+calimero-sdk-js@0.2.3_typescript@5.9.3/node_modules/@calimero-network/calimero-sdk-js: Running postinstall script... +> dependencies: +> + @calimero-network/calimero-sdk-js 0.2.3 + +$: pnpm add -D @calimero-network/calimero-cli-js tslib +> ... +> devDependencies: +> + @calimero-network/calimero-cli-js ^0.3.0 +> + tslib 2.8.1 + +# Use following command if post install doesn't trigger deps installation +$: cd node_modules/@calimero-network/calimero-cli-js && pnpm install-deps +> ๐Ÿ”ง Installing Calimero SDK build dependencies... +> ... + +# Create index file +$: mkdir src && touch src/index.ts ``` **NPM Resources:** -- [calimero-sdk-js](https://www.npmjs.com/package/@calimero-network/calimero-sdk-js){:target="_blank"} -- [calimero-cli-js](https://www.npmjs.com/package/@calimero-network/calimero-cli-js){:target="_blank"} +- Calimero SDK JS - [NPM package](https://www.npmjs.com/package/@calimero-network/calimero-sdk-js){:target="_blank"} +- Calimero CLI JS - [NPM package](https://www.npmjs.com/package/@calimero-network/calimero-cli-js){:target="_blank"} -### Minimal Service +### Minimal Application Example ```typescript +// index.ts import { State, Logic, Init, View } from '@calimero-network/calimero-sdk-js'; import { Counter } from '@calimero-network/calimero-sdk-js/collections'; import * as env from '@calimero-network/calimero-sdk-js/env'; @@ -114,21 +206,76 @@ export class CounterLogic extends CounterApp { ```bash # Build WASM from TypeScript -npx calimero-sdk build src/index.ts -o build/service.wasm - -# Install on node -meroctl --node node1 app install \ - --path build/service.wasm \ - --context-id +$: pnpm build +> my-calimero-js-app@1.0.0 build /Users/X/Desktop/my-calimero-js-app +> calimero-sdk build src/index.ts -o build/my-calimero-js-app.wasm + +> [build] โ€บ โ€ฆ awaiting Extracting service methods... +> [build] โ€บ โœ” success Contract built successfully: build/my-calimero-js-app.wasm (1456.35 KB) + +# Install application wasm on the node +$: meroctl --node node1 app install \ + --path build/my-calimero-js-app.wasm +> โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +> โ”‚ Application Installed โ”‚ +> โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก +> โ”‚ Successfully installed application 'EdQAQGNLHBpM8atH18re56RmxL676WCJZEZvCPdXQbbw' โ”‚ +> โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +# Create a context for installed application +$: $: meroctl --node node1 context create \ + --protocol near --application-id EdQAQGNLHBpM8atH18re56RmxL676WCJZEZvCPdXQbbw +> +------------------------------+ +> | Context Created | +> +==============================+ +> | Successfully created context | +> +------------------------------+ + +# View context ID and context identity +$: meroctl --node node1 context ls +> +----------------------------------------------+----------------------------------------------+------------------------------------------------------+ +> | Context ID | Application ID | Root Hash | +> +====================================================================================================================================================+ +> | 9MYohRkkpT1QXtBGAcXYeB7yTtWNeFrVieK47tV4TSx9 | EdQAQGNLHBpM8atH18re56RmxL676WCJZEZvCPdXQbbw | Hash("J6XbiySdR1cdcBYkHfxpdP4hFQ9aLvLSe2knvPW7SJxG") | +> +----------------------------------------------+----------------------------------------------+------------------------------------------------------+ + +$: meroctl --node node1 context identity list --context 9MYohRkkpT1QXtBGAcXYeB7yTtWNeFrVieK47tV4TSx9 +> +----------------------------------------------+------------------+ +> | Identity | Type | +> +=================================================================+ +> | E9X7upjmMoZB5FL79JSuxUY2i873DvZ5f7QkLcSat89e | Context Identity | +> +----------------------------------------------+------------------+ # Call methods -meroctl --node node1 call \ - --context-id \ - --method increment - -meroctl --node node1 call \ - --context-id \ - --method getCount +# Calling getCount to verify that the counter value is 0 +$: meroctl --node node1 call getCount \ + --context 9MYohRkkpT1QXtBGAcXYeB7yTtWNeFrVieK47tV4TSx9 \ + --as E9X7upjmMoZB5FL79JSuxUY2i873DvZ5f7QkLcSat89e +> ... +> "result": { +> "output": "0" +> } +> ... + +# Calling increment which will increase the counter value to 1 +$: meroctl --node node1 call increment \ + --context 9MYohRkkpT1QXtBGAcXYeB7yTtWNeFrVieK47tV4TSx9 \ + --as E9X7upjmMoZB5FL79JSuxUY2i873DvZ5f7QkLcSat89e +> +-------------------+---------+ +> | Response | Status | +> +=============================+ +> | JSON-RPC Response | Success | +> +-------------------+---------+ + +# Callin getCount to verify that the counter value is 1 +$: meroctl --node node1 call getCount \ + --context 9MYohRkkpT1QXtBGAcXYeB7yTtWNeFrVieK47tV4TSx9 \ + --as E9X7upjmMoZB5FL79JSuxUY2i873DvZ5f7QkLcSat89e +> ... +> "result": { +> "output": "1" +> } +> ... ``` ## Core Concepts @@ -151,6 +298,7 @@ export class MyApp { ``` **Key points:** + - State is persisted and synchronized across nodes - Initialize CRDT fields inline (runtime reuses persisted IDs) - Don't use regular JavaScript objects for synchronized state @@ -160,7 +308,7 @@ export class MyApp { Marks a class as application logic (methods): ```typescript -import { Logic, Init } from '@calimero-network/calimero-sdk-js'; +import { Logic, Init, Logic } from '@calimero-network/calimero-sdk-js'; @Logic(MyApp) export class MyAppLogic extends MyApp { @@ -176,13 +324,14 @@ export class MyAppLogic extends MyApp { // View method (read-only) @View() - getItem(key: string): string | undefined { + getItem(key: string): string | null { return this.items.get(key); } } ``` **Key points:** + - Logic class extends State class - `@Init` marks the initialization method (called once on context creation) - Methods without `@View()` are mutations (generate deltas) @@ -195,11 +344,6 @@ Marks read-only methods: ```typescript @Logic(MyApp) export class MyAppLogic extends MyApp { - // Mutation - generates delta - setValue(value: string): void { - this.register.set(value); - } - // View - read-only, no delta @View() getValue(): string { @@ -225,9 +369,16 @@ export class ItemAdded { constructor( public key: string, public value: string, - public timestamp: number ) {} } + +// Emit an event inside a function +... +addItem(key: string, value: string): void { + emit(new ItemAdded(key, value)); + this.items.set(key, value); +} +... ``` #### @Init @@ -245,6 +396,7 @@ export class MyAppLogic extends MyApp { ``` **Requirements:** + - Must be static - Must return an instance of State class - Called once when context is created @@ -321,10 +473,6 @@ counter.incrementBy(5); // Get value const total = counter.value(); // bigint - -// Decrement -counter.decrement(); -counter.decrementBy(2); ``` #### LwwRegister @@ -379,7 +527,7 @@ CRDTs can be nested arbitrarily: @State export class TeamMetrics { // Map of member โ†’ Map of metric โ†’ Counter - memberMetrics: UnorderedMap>; + memberMetrics: UnorderedMap>>; // Map of team โ†’ Set of members teams: UnorderedMap>; @@ -443,12 +591,14 @@ export class MyAppLogic extends MyApp { ``` **Event lifecycle:** + 1. Emitted during method execution 2. Included in delta broadcast to all peers 3. Handlers execute on peer nodes (not author node) 4. Handlers can update state or trigger side effects **Handler requirements:** + - **Commutative**: Order-independent operations - **Independent**: No shared mutable state between handlers - **Idempotent**: Safe to retry @@ -477,324 +627,12 @@ secrets.modify( ``` **Key properties:** + - Never replicated across nodes - Stored via `storage_read` / `storage_write` directly - Never included in CRDT deltas - Only accessible on the executing node -## Build Pipeline - -### Development Setup - -```bash -# Create project -mkdir my-calimero-service -cd my-calimero-service -pnpm init - -# Install dependencies -pnpm add @calimero-network/calimero-sdk-js -pnpm add -D @calimero-network/calimero-cli-js typescript @types/node - -# Create TypeScript config -cat > tsconfig.json << EOF -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "lib": ["ES2020"], - "moduleResolution": "node", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src/**/*"] -} -EOF - -# Create source directory -mkdir src -``` - -### Build Process - -```bash -# Build WASM from TypeScript -npx calimero-sdk build src/index.ts -o build/service.wasm - -# With verbose output -npx calimero-sdk build src/index.ts -o build/service.wasm --verbose - -# Validate only (no build) -npx calimero-sdk validate src/index.ts -``` - -### Build Configuration - -The CLI automatically handles: -- TypeScript compilation -- Dependency bundling (Rollup) -- QuickJS bytecode generation -- WASM compilation (WASI-SDK) -- Optimization (wasm-opt) - -**Output:** -- `service.wasm` - Deployable WASM binary (~500KB with QuickJS overhead) - -## Common Patterns - -### Pattern 1: Simple Key-Value Store - -```typescript -import { State, Logic, Init, View } from '@calimero-network/calimero-sdk-js'; -import { UnorderedMap, LwwRegister } from '@calimero-network/calimero-sdk-js/collections'; - -@State -export class KvStore { - items: UnorderedMap>; - - constructor() { - this.items = new UnorderedMap(); - } -} - -@Logic(KvStore) -export class KvStoreLogic extends KvStore { - @Init - static init(): KvStore { - return new KvStore(); - } - - set(key: string, value: string): void { - const register = this.items.get(key) ?? new LwwRegister(); - register.set(value); - this.items.set(key, register); - } - - @View() - get(key: string): string | null { - const register = this.items.get(key); - return register ? register.get() : null; - } - - remove(key: string): void { - this.items.remove(key); - } -} -``` - -### Pattern 2: Metrics with Counters - -```typescript -import { State, Logic, Init, View } from '@calimero-network/calimero-sdk-js'; -import { UnorderedMap, Counter } from '@calimero-network/calimero-sdk-js/collections'; - -@State -export class Metrics { - pageViews: UnorderedMap; - - constructor() { - this.pageViews = new UnorderedMap(); - } -} - -@Logic(Metrics) -export class MetricsLogic extends Metrics { - @Init - static init(): Metrics { - return new Metrics(); - } - - trackPageView(page: string): void { - const counter = this.pageViews.get(page) ?? new Counter(); - counter.increment(); - this.pageViews.set(page, counter); - } - - @View() - getViews(page: string): bigint { - const counter = this.pageViews.get(page); - return counter ? counter.value() : 0n; - } -} -``` - -### Pattern 3: Event-Driven Updates - -```typescript -import { State, Logic, Init, Event, emitWithHandler } from '@calimero-network/calimero-sdk-js'; -import { UnorderedMap, Counter } from '@calimero-network/calimero-sdk-js/collections'; -import * as env from '@calimero-network/calimero-sdk-js/env'; - -@Event -export class ItemAdded { - constructor(public key: string, public value: string) {} -} - -@State -export class StoreWithEvents { - items: UnorderedMap; - eventCount: Counter; - - constructor() { - this.items = new UnorderedMap(); - this.eventCount = new Counter(); - } -} - -@Logic(StoreWithEvents) -export class StoreWithEventsLogic extends StoreWithEvents { - @Init - static init(): StoreWithEvents { - return new StoreWithEvents(); - } - - addItem(key: string, value: string): void { - this.items.set(key, value); - emitWithHandler(new ItemAdded(key, value), 'onItemAdded'); - } - - // Handler executes on peer nodes - onItemAdded(event: ItemAdded): void { - this.eventCount.increment(); - env.log(`Item added: ${event.key} = ${event.value}`); - } - - @View() - getEventCount(): bigint { - return this.eventCount.value(); - } -} -``` - -## Best Practices - -### 1. Initialize CRDTs Inline - -```typescript -// โœ… GOOD - Runtime reuses persisted IDs -@State -export class MyApp { - items: UnorderedMap = new UnorderedMap(); -} - -// โŒ BAD - Constructor runs every time -@State -export class MyApp { - items: UnorderedMap; - - constructor() { - this.items = new UnorderedMap(); // Not reused! - } -} -``` - -### 2. Use @View() for Read-Only Methods - -```typescript -// โœ… GOOD - No persistence overhead -@View() -getItem(key: string): string | undefined { - return this.items.get(key); -} - -// โŒ BAD - Generates unnecessary deltas -getItem(key: string): string | undefined { - return this.items.get(key); -} -``` - -### 3. Handle Nested CRDTs Correctly - -```typescript -// โœ… GOOD - Use handles, mutate incrementally -const metrics = this.memberMetrics.get('alice'); -if (metrics) { - const counter = metrics.get('commits') ?? new Counter(); - counter.increment(); - metrics.set('commits', counter); -} - -// โŒ BAD - Don't try to clone entire structure -const metrics = this.memberMetrics.get('alice'); -if (metrics) { - // Don't do this - it doesn't work - const cloned = { ...metrics }; // Wrong! -} -``` - -### 4. Make Event Handlers Safe - -```typescript -// โœ… GOOD - Commutative, independent, idempotent -onItemAdded(event: ItemAdded): void { - this.itemCount.increment(); // CRDT operations are safe -} - -// โŒ BAD - Not safe for parallel execution -onItemAdded(event: ItemAdded): void { - // Don't make external API calls! - fetch('/api/notify', { ... }); // Wrong! - - // Don't depend on execution order! - if (this.items.has(event.key)) { // Race condition! - // ... - } -} -``` - -### 5. Use Private Storage for Secrets - -```typescript -// โœ… GOOD - Secrets never leave the node -const secrets = createPrivateEntry<{ token: string }>('private:secrets'); - -// โŒ BAD - Don't put secrets in CRDT state -@State -export class MyApp { - apiToken: string = ''; // Never do this! -} -``` - -## Troubleshooting - -### Build Errors - -**Issue**: TypeScript compilation errors -```bash -# Check TypeScript version -pnpm list typescript - -# Use verbose flag for details -npx calimero-sdk build src/index.ts -o build/service.wasm --verbose -``` - -**Issue**: Missing dependencies -```bash -# Ensure all dependencies are installed -pnpm install - -# Check QuickJS and WASI-SDK are downloaded (CLI handles this) -``` - -### Runtime Errors - -**Issue**: Method not found -- Verify method is in `@Logic` class -- Check method name matches call -- Ensure method is public (not private) - -**Issue**: CRDT operations failing -- Verify CRDT is initialized inline -- Check you're using CRDT collections, not plain objects -- Ensure nested CRDTs are handled as handles - -**Issue**: Events not propagating -- Verify `@Event` decorator on event class -- Check event is emitted during method execution -- Ensure handlers are in `@Logic` class - ## Examples The `calimero-sdk-js` repository includes comprehensive examples: @@ -811,12 +649,38 @@ The `calimero-sdk-js` repository includes comprehensive examples: **Run an example:** ```bash # Clone repository -git clone https://github.com/calimero-network/calimero-sdk-js -cd calimero-sdk-js +$: git clone https://github.com/calimero-network/calimero-sdk-js +> ... +> Cloning into 'calimero-sdk-js'... +> ... +> Resolving deltas: 100% (2299/2299), done. +$: cd calimero-sdk-js +# Build the sdk and cli packages +$: cd packages/sdk && pnpm build && cd ../cli && pnpm build && ../.. +> > @calimero-network/calimero-sdk-js@0.0.0 build /Users/X/Desktop/calimero-sdk-js/packages/sdk +> tsc + +> @calimero-network/calimero-cli-js@0.0.0 build /Users/X/Desktop/calimero-sdk-js/packages/cli +> tsc # Run example workflow -cd examples/counter -merobox bootstrap run workflows/counter-js.yml --log-level=trace +$: cd examples/counter +$: pnpm install && pnpm build:manual +> ... +> dependencies: +> + @calimero-network/calimero-sdk-js 0.0.0 <- ../../packages/sdk +> +> devDependencies: +> + @calimero-network/calimero-cli-js 0.0.0 <- ../../packages/cli +> ... +$: cd ../.. + +$: merobox bootstrap run workflows/counter-js.yml --log-level=trace +> ... +> ๐Ÿš€ Executing Workflow: Counter App Test +> ... +> ... +> Workflow finished sucessfully! ``` ## Comparison: JavaScript SDK vs Rust SDK @@ -832,12 +696,14 @@ merobox bootstrap run workflows/counter-js.yml --log-level=trace | **CRDT Collections** | Same API | Same API | **When to use JavaScript SDK:** + - Familiar with JavaScript/TypeScript - Faster prototyping - Less performance-critical applications - Want to leverage existing JS libraries (via Rollup) **When to use Rust SDK:** + - Need maximum performance - Already familiar with Rust - Complex algorithms or computations diff --git a/docs/builder-directory/sdk-guide.md b/docs/builder-directory/sdk-guide.md index 62dcf54..a1184d0 100644 --- a/docs/builder-directory/sdk-guide.md +++ b/docs/builder-directory/sdk-guide.md @@ -393,14 +393,21 @@ impl TeamMetrics { # Create new Rust project cargo new my-calimero-app cd my-calimero-app +``` +```bash # Add dependencies to Cargo.toml [dependencies] +# Use latest release or specified version calimero-sdk = { git = "https://github.com/calimero-network/core", branch = "master" } calimero-storage = { git = "https://github.com/calimero-network/core", branch = "master" } calimero-sdk-macros = { git = "https://github.com/calimero-network/core", branch = "master" } borsh = { version = "1.0", features = ["derive"] } +[build-dependencies] +# Use latest release or specified version +calimero-wasm-abi = { git = "https://github.com/calimero-network/core", branch = "master" } + [lib] crate-type = ["cdylib"] @@ -408,25 +415,76 @@ crate-type = ["cdylib"] features = ["macro"] ``` +Create build.rs + +> This build script automatically generates res/abi.json and res/state-schema.json from your Rust source code during cargo build by parsing src/lib.rs and extracting the ABI manifest using the calimero_wasm_abi emitter. + +```rust +use std::fs; +use std::path::Path; + +use calimero_wasm_abi::emitter::emit_manifest; + +fn main() { + println!("cargo:rerun-if-changed=src/lib.rs"); + + // Parse the source code + let src_path = Path::new("src/lib.rs"); + let src_content = fs::read_to_string(src_path).expect("Failed to read src/lib.rs"); + + // Generate ABI manifest using the emitter + let manifest = emit_manifest(&src_content).expect("Failed to emit ABI manifest"); + + // Serialize the manifest to JSON + let json = serde_json::to_string_pretty(&manifest).expect("Failed to serialize manifest"); + + // Write the ABI JSON to the res directory + let res_dir = Path::new("res"); + if !res_dir.exists() { + fs::create_dir_all(res_dir).expect("Failed to create res directory"); + } + + let abi_path = res_dir.join("abi.json"); + fs::write(&abi_path, json).expect("Failed to write ABI JSON"); + + // Extract and write the state schema + if let Ok(mut state_schema) = manifest.extract_state_schema() { + state_schema.schema_version = "wasm-abi/1".to_owned(); + + let state_schema_json = + serde_json::to_string_pretty(&state_schema).expect("Failed to serialize state schema"); + let state_schema_path = res_dir.join("state-schema.json"); + fs::write(&state_schema_path, state_schema_json) + .expect("Failed to write state schema JSON"); + } +} +``` + ### Build to WASM ```bash # Add WASM target -rustup target add wasm32-unknown-unknown +$: rustup target add wasm32-unknown-unknown +> ... adding target ... +> or +> info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date # Build WASM binary -cargo build --target wasm32-unknown-unknown --release - +$: cargo build --target wasm32-unknown-unknown --release +> Updating git repository `https://github.com/calimero-network/core` +> Updating crates.io index +> ... +> Finished `release` profile [optimized] target(s) in 25.67s # Output: target/wasm32-unknown-unknown/release/my_calimero_app.wasm ``` ### Extract ABI +> Application ABI gets extracted automatically when building the project by using build.rs file created in previous steps. + ```bash -# Extract ABI from WASM -calimero-abi extract \ - target/wasm32-unknown-unknown/release/my_calimero_app.wasm \ - -o abi.json +$: ls res +> abi.json my-calimero-app.wasm state-schema.json ``` ## Best Practices diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index dfa9f2a..486ac52 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -212,13 +212,21 @@ The backend logic is the core of a Calimero application. It can be written in Ru ```bash # Create a new application $: npx create-mero-app my-first-app - > Cloning into /var/folders/p2/_b7fvy792s3458_0jlf6r0jm0000gn/T/mero-create-Gi2ZvC/> repo... + > ? Select backend template โ€บ - Use arrow-keys. Return to submit. + > โฏ Rust (kv-store) + > JavaScript (kv-store-js) + > ... + > โœ” Select backend template โ€บ Rust (kv-store) + > ... + > Scaffolding project in /Users/X/Desktop/my-first-app + > Using template: Rust (kv-store) + > Cloning into '/var/folders/p2/_b7fvy792s3458_0jlf6r0jm0000gn/T/mero-create-SY6uxl/repo'... > Done. > Next steps: - > cd my-first-app - > pnpm install - > pnpm dev + > cd my-first-app + > pnpm install + > pnpm dev # Navigate to project $: cd my-first-app diff --git a/docs/operator-track/monitor-and-debug.md b/docs/operator-track/monitor-and-debug.md index de94efe..3578a2f 100644 --- a/docs/operator-track/monitor-and-debug.md +++ b/docs/operator-track/monitor-and-debug.md @@ -5,14 +5,19 @@ Observability and troubleshooting for Calimero nodes. ## Quick Health Checks ```bash -# Check node health -meroctl --node node1 health - # View logs -merobox logs node1 --follow +$: merobox logs node1 --follow +> Logs for node1: +> 2026-01-27T14:22:15.387049501Z [2m2026-01-27T14:22:15.386824Z[0m [35mTRACE[0m [2mhickory_proto::rr::record_data[0m[2m:[0m reading TXT +> .... # List contexts -meroctl --node node1 context list +$: meroctl --node node1 context list +> +--------------+----------------+---------------------+ +> | Context ID | Application ID | Root Hash | +> +=====================================================+ +> | | | Hash() | +> +--------------+------------------+-------------------+ ``` ## Admin Dashboard @@ -29,31 +34,56 @@ Access the web UI at `http://localhost:2528/admin-dashboard` (or your node URL). ```bash # Health check -curl http://localhost:2528/admin-api/health - -# Get peers count -curl http://localhost:2528/admin-api/peers-count +$: curl http://localhost:2528/admin-api/health +> {"data":{"status":"alive"}} # List contexts -curl http://localhost:2528/admin-api/contexts +$: curl http://localhost:2528/admin-api/contexts +> {"data": +> {"contexts":[ +> { +> "id":"9MYohRkkpT1QXtBGAcXYeB7yTtWNeFrVieK47tV4TSx9", +> "applicationId":"EdQAQGNLHBpM8atH18re56RmxL676WCJZEZvCPdXQbbw", +> "rootHash":"8cJivRyeGKQhk2zTAPXSZ4NH6AeuvEffpyXjwWa91KuH", +> "dagHeads":[[123,252,41,250,163,7,21,176,33,33,34,91,39,5,221,91,92,210,144,30,189,216,130,138,246,229,189,191,113,11,228,196]] +> }, +> { +> "id":"FfHXVWRqbSc2wrU2tEeuLQxFcmcpcfZd8Qk9yQFkm7W7", +> "applicationId":"HHQbab1Meo1GCUsjELf2WSt3os1WaPaA4oKEGxTFTYBf", +> "rootHash":"6JEnmTSgubFJSNz2qinpysSPDU7UmfbgrYYg6DX3PJEg", +> "dagHeads":[[15,10,180,62,244,86,70,185,211,94,229,62,139,252,124,29,104,5,4,85,135,204,28,220,45,32,8,155,200,35,5,27]] +> }] +> }} ``` ## Logs ```bash # View node logs -merobox logs node1 +$: merobox logs node1 +> Logs for node1: +> 2026-01-27T14:22:15.387049501Z [2m2026-01-27T14:22:15.386824Z[0m [35mTRACE[0m [2mhickory_proto::rr::record_data[0m[2m:[0m reading TXT +> .... # Follow logs in real-time -merobox logs node1 --follow +$: merobox logs node1 --follow +> Logs for node1: +> 2026-01-27T14:22:15.387049501Z [2m2026-01-27T14:22:15.386824Z[0m [35mTRACE[0m [2mhickory_proto::rr::record_data[0m[2m:[0m reading TXT +> .... # Or with Docker directly -docker logs calimero-node-1 --follow +$: docker logs calimero-node-1 --follow +> ... +> 2026-01-27T14:22:14.135154Z TRACE Swarm::poll:NetworkBehaviour::poll: netlink_proto::connection: forwarding responses to previous requests to the connection handle +> 2026-01-27T14:22:14.135161Z TRACE Swarm::poll:NetworkBehaviour::poll: netlink_proto::connection: forward_responses called +> 2026-01-27T14:22:14.135165Z TRACE Swarm::poll:NetworkBehaviour::poll: netlink_proto::connection: forward_responses done +> ... ``` ## Troubleshooting See [`core/crates/node/readme/troubleshooting.md`](https://github.com/calimero-network/core/blob/master/crates/node/readme/troubleshooting.md){:target="_blank"} for: + - Common issues and solutions - Performance tuning - Network problems @@ -62,6 +92,7 @@ See [`core/crates/node/readme/troubleshooting.md`](https://github.com/calimero-n ## Metrics Nodes expose metrics at: + - **Admin API**: `http://localhost:2528/admin-api/metrics` - **Prometheus**: Configure in node settings diff --git a/docs/operator-track/run-a-local-network.md b/docs/operator-track/run-a-local-network.md index 45e3b26..8fb7482 100644 --- a/docs/operator-track/run-a-local-network.md +++ b/docs/operator-track/run-a-local-network.md @@ -9,19 +9,47 @@ Merobox is the easiest way to run local networks. See [`merobox/README.md`](http **Quick start:** ```bash # Install merobox -pipx install merobox +$: pipx install merobox +> Installing to existing venv 'merobox' +> installed package merobox 0.2.13, installed using Python 3.13.3 +> These apps are now globally available: merobox +> done! โœจ ๐ŸŒŸ โœจ # Start 2-node network -merobox run --count 2 +$: merobox run --count 2 +> ... +> Deployment Summary: 2/2 nodes started successfully # Check status -merobox list -merobox health +$: merobox list +> โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”€โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ +> โ”ƒ โ”ƒ โ”ƒ โ”ƒ โ”ƒ RPC/Admโ€ฆ โ”ƒ โ”ƒ โ”ƒ +> โ”ƒ Name โ”ƒ Status โ”ƒ Image โ”ƒ P2P Port โ”ƒ Port โ”ƒ Chain ID โ”ƒ Created โ”ƒ +> โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”€โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ +> โ”‚ calimero-โ€ฆ โ”‚ running โ”‚ ghcr.io/โ€ฆ โ”‚ 2429 โ”‚ 2529 โ”‚ testnet-1 โ”‚ 2026-01โ€ฆ โ”‚ +> โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ 14:22:12 โ”‚ +> โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”€โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ +> โ”‚ calimero-โ€ฆ โ”‚ running โ”‚ ghcr.io/โ€ฆ โ”‚ 2427 โ”‚ 2527 โ”‚ testnet-2 โ”‚ 2026-01โ€ฆ โ”‚ +> โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ 14:22:12 โ”‚ +> โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +$: merobox health +> Checking health of 1 running node(s)... +> Calimero Node Health Status +> โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +> โ”‚ Node โ”‚ Health โ”‚ Authenticated โ”‚ Peers โ”‚ Status โ”‚ +> โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +> โ”‚ calimero-node-1 โ”‚ alive โ”‚ Unknown โ”‚ 0 โ”‚ Healthy โ”‚ +> โ”‚ calimero-node-2 โ”‚ alive โ”‚ Unknown โ”‚ 0 โ”‚ Healthy โ”‚ +> โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` **With workflow:** ```bash -merobox bootstrap run workflow.yml +$: merobox bootstrap run workflow.yml +> ... +> ๐Ÿš€ Executing Workflow: Example Application +> ... ``` ## Using merod Directly @@ -29,25 +57,46 @@ merobox bootstrap run workflow.yml For more control, run nodes directly without Docker: ```bash -# Install merod (from source) -cargo install --path core/crates/merod - -# Or build and use directly -cd core/crates/merod -cargo build --release +# Builds your crate and copies the binary into ~/.cargo/bin, so you can run it from anywhere. +$: cargo install --path ./crates/merod +> Installed package merod v0.1.0 (/Users/X/Desktop/core/crates/merod) (executable merod) +$: which merod +> /Users/frandomovic/.cargo/bin/merod + +# Or +# Builds the binary inside the project only; it's not globally available unless you reference it explicitly. +$: cd crates/merod +$: cargo build --release +> Compiling merod v0.1.0 (/Users/X/Desktop/core/crates/merod) +> Finished release [optimized + debuginfo] target(s) in 10.35s +> Installing merod v0.1.0 (/Users/X/Desktop/core/crates/merod) +> Installing /Users/X/Desktop/core/crates/merod/target/release/merod (executable) +> Installed package merod v0.1.0 (/Users/X/Desktop/core/crates/merod) (executable merod) ``` **Initialize and run a single node:** ```bash -# Initialize a node with default settings -merod --node-name node1 init - -# Or with custom ports -merod --node-name node1 init --server-port 2428 --swarm-port 2528 - -# Run the node -merod --node-name node1 run +Initialize a node with default settings +$: merod --node-name node1 init +> 2025-12-16T11:47:34.861762Z INFO merod::cli::init: Generated identity: PeerId>("12D3KooW9xPd2gxAouQ29vMfG1B3fpYPPS87VEZyrqzhuVQWc2VL") +> 2025-12-16T11:47:34.870745Z INFO merod::cli::init: Initialized a node in "/Users/X/.calimero/node1" + +Or from binary +$: cargo run --bin merod -- --node-name node1 init +> 2025-12-16T11:47:34.861762Z INFO merod::cli::init: Generated identity: PeerId("12D3KooW9xPd2gxAouQ29vMfG1B3fpYPPS87VEZyrqzhuVQWc2VL") +> 2025-12-16T11:47:34.870745Z INFO merod::cli::init: Initialized a node in "/Users/X/.calimero/node1" + +With custom ports: + +$: merod --node-name node1 init --server-port 2428 --swarm-port 2528 +> 2025-12-16T11:52:13.841762Z INFO merod::cli::init: Generated identity: PeerId("12D3KooW9xPd2gxAouQ29vMfG1B3fpYPPS87VEZyrqzhuVQWc2VL") +> 2025-12-16T11:52:13.840725Z INFO merod::cli::init: Initialized a node in "/Users/X/.calimero/node1" + +Or from binary +$: cargo run --bin merod -- --node-name node1 init --server-port 2428 --swarm-port 2528 +> 2025-12-16T11:52:13.841762Z INFO merod::cli::init: Generated identity: PeerId("12D3KooW9xPd2gxAouQ29vMfG1B3fpYPPS87VEZyrqzhuVQWc2VL") +> 2025-12-16T11:52:13.840725Z INFO merod::cli::init: Initialized a node in "/Users/X/.calimero/node1" ``` **Run multiple nodes manually:** diff --git a/docs/tools-apis/meroctl-cli.md b/docs/tools-apis/meroctl-cli.md index 48a7cfc..77e0f30 100644 --- a/docs/tools-apis/meroctl-cli.md +++ b/docs/tools-apis/meroctl-cli.md @@ -5,11 +5,32 @@ ## Installation ```bash -# From source (requires Rust) -cargo install --path core/crates/meroctl - -# Or via package manager (when available) -# brew install calimero-tap/meroctl +Installing from Calimero core repository. +Builds your crate and copies the binary into ~/.cargo/bin, so you can run it from anywhere. +$: cargo install --path ./crates/meroctl +> Installed package meroctl v0.1.0 (/Users/X/Desktop/core/crates/meroctl) (executable meroctl) +$: which meroctl +> /Users/frandomovic/.cargo/bin/meroctl + +Builds the binary inside the project only; it's not globally available unless you reference it explicitly. +$: cd crates/meroctl +$: cargo build --release +> Compiling meroctl v0.1.0 (/Users/X/Desktop/core/crates/meroctl) +> Finished release [optimized + debuginfo] target(s) in 10.35s +> Installing meroctl v0.1.0 (/Users/X/Desktop/core/crates/meroctl) +> Installing /Users/X/Desktop/core/crates/meroctl/target/release/meroctl (executable) +> Installed package meroctl v0.1.0 (/Users/X/Desktop/core/crates/meroctl) (executable meroctl) + +$: brew install meroctl +> โœ”๏ธŽ JSON API cask.jws.json Downloaded 15.3MB/ 15.3MB +> โœ”๏ธŽ JSON API formula.jws.json Downloaded 32.0MB/ 32.0MB +> ==> Fetching downloads for: meroctl +> โœ”๏ธŽ Formula meroctl (0.10.0-rc.35) Verified 11.2MB/ 11.2MB +> ==> Installing meroctl from calimero-network/tap +> ๐Ÿบ /opt/homebrew/Cellar/meroctl/0.10.0-rc.35: 4 files, 22.5MB, built in 1 second +> ==> Running brew cleanup meroctl... +> Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP=1. +> Hide these hints with HOMEBREW_NO_ENV_HINTS=1 (see man brew) ``` ## Configuration @@ -20,10 +41,10 @@ Connect to a node using one of these methods: ```bash # Using node alias (configured in ~/.calimero/config.toml) -meroctl --node node1 +$: meroctl --node node1 # Using direct API URL -meroctl --api http://localhost:2528 +$: meroctl --api http://localhost:2528 ``` ### Environment Variables @@ -43,7 +64,7 @@ Manage WASM applications on nodes: ```bash # List all applications -meroctl --node node1 app ls +meroctl --node node1 app ls$ # Get application details meroctl --node node1 app get