diff --git a/README.md b/README.md index c3567fa..bc2c1b0 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,71 @@ -# III SDK +# iii SDK -Multi-language SDK for the III Engine - a WebSocket-based function orchestration platform. +Official SDKs for the [iii engine](https://github.com/iii-hq/iii). -## Overview +[![npm](https://img.shields.io/npm/v/iii-sdk)](https://www.npmjs.com/package/iii-sdk) +[![PyPI](https://img.shields.io/pypi/v/iii-sdk)](https://pypi.org/project/iii-sdk/) +[![crates.io](https://img.shields.io/crates/v/iii-sdk)](https://crates.io/crates/iii-sdk) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) -The III SDK provides a unified interface for building distributed applications with function registration, invocation, and trigger management. It enables seamless communication between services through a WebSocket-based engine. +## Installing Packages -## Supported Languages +| Package | Language | Install | Docs | +| -------------------------------------------------- | -------------------- | --------------------- | ----------------------------------------- | +| [`iii-sdk`](https://www.npmjs.com/package/iii-sdk) | Node.js / TypeScript | `npm install iii-sdk` | [README](./packages/node/iii/README.md) | +| [`iii-sdk`](https://pypi.org/project/iii-sdk/) | Python | `pip install iii-sdk` | [README](./packages/python/iii/README.md) | +| [`iii-sdk`](https://crates.io/crates/iii-sdk) | Rust | Add to `Cargo.toml` | [README](./packages/rust/iii/README.md) | -- **Node.js** - TypeScript/JavaScript SDK with full async support -- **Python** - Async Python SDK with function registration -- **Rust** - High-performance async Rust SDK with automatic reconnection - -## Features - -- **Function Registration** - Register callable functions that can be invoked by other services -- **Function Invocation** - Call functions synchronously or asynchronously (fire-and-forget) -- **Trigger Management** - Create and manage triggers (API, events, schedules, etc.) -- **Custom Trigger Types** - Define your own trigger types with custom logic -- **Context-Aware Logging** - Built-in logging with execution context -- **OpenTelemetry Integration** - Full observability with traces, metrics, and logs (Node.js) -- **Automatic Reconnection** - Resilient WebSocket connections with auto-reconnect (Rust) - -## Quick Start +## Hello World ### Node.js -```bash -npm install iii-sdk -``` - ```javascript -import { III } from 'iii-sdk' +import { init } from 'iii-sdk'; -const iii = new III('ws://localhost:49134') +const iii = init('ws://localhost:49134'); -iii.registerFunction({ id: 'myFunction' }, (req) => { - return { status_code: 200, body: { message: 'Hello, world!' } } -}) +iii.registerFunction({ id: 'greet' }, async (input) => { + return { message: `Hello, ${input.name}!` }; +}); iii.registerTrigger({ type: 'http', - function_id: 'myFunction', - config: { api_path: '/hello', http_method: 'POST' }, -}) + function_id: 'greet', + config: { api_path: '/greet', http_method: 'POST' }, +}); -const result = await iii.call('myFunction', { param: 'value' }) +const result = await iii.trigger('greet', { name: 'world' }); ``` ### Python -```bash -pip install iii-sdk -``` - ```python +import asyncio from iii import III iii = III("ws://localhost:49134") -async def my_function(data): - return {"result": "success"} +async def greet(data): + return {"message": f"Hello, {data['name']}!"} -iii.register_function("my.function", my_function) +iii.register_function("greet", greet) -result = await iii.call("other.function", {"param": "value"}) -``` +async def main(): + await iii.connect() -### Rust + iii.register_trigger( + type="http", + function_id="greet", + config={"api_path": "/greet", "http_method": "POST"} + ) + + result = await iii.trigger("greet", {"name": "world"}) -```toml -[dependencies] -iii-sdk = { path = "path/to/iii" } +asyncio.run(main()) ``` +### Rust + ```rust use iii_sdk::III; use serde_json::json; @@ -83,43 +75,45 @@ async fn main() -> Result<(), Box> { let iii = III::new("ws://127.0.0.1:49134"); iii.connect().await?; - iii.register_function("my.function", |input| async move { - Ok(json!({ "message": "Hello, world!", "input": input })) + iii.register_function("greet", |input| async move { + let name = input.get("name").and_then(|v| v.as_str()).unwrap_or("world"); + Ok(json!({ "message": format!("Hello, {name}!") })) }); + iii.register_trigger("http", "greet", json!({ + "api_path": "/greet", + "http_method": "POST" + }))?; + let result: serde_json::Value = iii - .call("my.function", json!({ "param": "value" })) + .trigger("greet", json!({ "name": "world" })) .await?; - println!("result: {result}"); Ok(()) } ``` -## Documentation +## API -Detailed documentation for each SDK: - -- [Node.js SDK](./packages/node/iii/README.md) -- [Python SDK](./packages/python/iii/README.md) -- [Rust SDK](./packages/rust/iii/README.md) - -## Examples +| Operation | Node.js | Python | Rust | Description | +| ------------------------ | ---------------------------------------------------- | ------------------------------------------- | -------------------------------------------- | ------------------------------------------------------ | +| Initialize | `init(url)` | `III(url)` | `III::new(url)` | Create an SDK instance and connect to the engine | +| Connect | Auto on `init()` | `await iii.connect()` | `iii.connect().await?` | Open the WebSocket connection | +| Register function | `iii.registerFunction({ id }, handler)` | `iii.register_function(id, handler)` | `iii.register_function(id, \|input\| ...)` | Register a function that can be invoked by name | +| Register trigger | `iii.registerTrigger({ type, function_id, config })` | `iii.register_trigger(type, fn_id, config)` | `iii.register_trigger(type, fn_id, config)?` | Bind a trigger (HTTP, cron, queue, etc.) to a function | +| Invoke (await) | `await iii.trigger(id, data)` | `await iii.trigger(id, data)` | `iii.trigger(id, data).await?` | Invoke a function and wait for the result | +| Invoke (fire-and-forget) | `iii.triggerVoid(id, data)` | `iii.trigger_void(id, data)` | `iii.trigger_void(id, data)?` | Invoke a function without waiting | -Example applications demonstrating SDK usage: - -- [Node.js Example](./packages/node/iii-example/) -- [Python Example](./packages/python/iii-example/) -- [Rust Example](./packages/rust/iii-example/) +> `call()` and `callVoid()` / `call_void()` are deprecated and will be removed in a future release. Use `trigger()` and `triggerVoid()` / `trigger_void()`. ## Development ### Prerequisites - Node.js 20+ and pnpm (for Node.js SDK) -- Python 3.8+ and uv (for Python SDK) -- Rust 1.70+ and Cargo (for Rust SDK) -- III Engine running on `ws://localhost:49134` +- Python 3.10+ and uv (for Python SDK) +- Rust 1.85+ and Cargo (for Rust SDK) +- iii engine running on `ws://localhost:49134` ### Building @@ -137,19 +131,16 @@ cd packages/python/iii && pytest cd packages/rust/iii && cargo test ``` -## Architecture +## Examples -The III SDK communicates with the III Engine via WebSocket connections. The engine acts as a central orchestrator for: +See the [Quickstart guide](https://iii.dev/docs/quickstart) for step-by-step tutorials. -- Function registry and routing -- Trigger management and execution -- Inter-service communication -- Telemetry and observability +## Resources -## License +- [Documentation](https://iii.dev/docs) +- [iii Engine](https://github.com/iii-hq/iii) +- [Examples](https://github.com/iii-hq/iii-examples) -Apache License 2.0 - see [LICENSE](./LICENSE) for details. - -## Author +## License -Motia LLC +Apache 2.0 diff --git a/packages/node/iii/README.md b/packages/node/iii/README.md index 4d2c7a6..b42c200 100644 --- a/packages/node/iii/README.md +++ b/packages/node/iii/README.md @@ -1,100 +1,87 @@ -# III SDK for Node.js +# iii-sdk -## Installation +Node.js / TypeScript SDK for the [iii engine](https://github.com/iii-hq/iii). + +[![npm](https://img.shields.io/npm/v/iii-sdk)](https://www.npmjs.com/package/iii-sdk) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../../LICENSE) + +## Install ```bash npm install iii-sdk ``` -## Usage +## Hello World ```javascript -import { III } from 'iii-sdk' +import { init } from 'iii-sdk' -/** - * Make sure the III Core Instance is up and Running on the given URL. - */ -const iii = new III(process.env.III_BRIDGE_URL ?? 'ws://localhost:49134') +const iii = init('ws://localhost:49134') -iii.registerFunction({ id: 'myFunction' }, (req) => { - return { status_code: 200, body: { message: 'Hello, world!' } } +iii.registerFunction({ id: 'greet' }, async (input) => { + return { message: `Hello, ${input.name}!` } }) iii.registerTrigger({ type: 'http', - function_id: 'myFunction', - config: { api_path: 'myApiPath', http_method: 'POST' }, + function_id: 'greet', + config: { api_path: '/greet', http_method: 'POST' }, }) + +const result = await iii.trigger('greet', { name: 'world' }) ``` -### Registering Functions +## API -III Allows you to register functions that can be invoked by other services. +| Operation | Signature | Description | +| ------------------------ | ---------------------------------------------------- | ------------------------------------------------------------ | +| Initialize | `init(url, options?)` | Create and connect to the engine. Returns an `ISdk` instance | +| Register function | `iii.registerFunction({ id }, handler)` | Register a function that can be invoked by name | +| Register trigger | `iii.registerTrigger({ type, function_id, config })` | Bind a trigger (HTTP, cron, queue, etc.) to a function | +| Invoke (await) | `await iii.trigger(id, data, timeoutMs?)` | Invoke a function and wait for the result | +| Invoke (fire-and-forget) | `iii.triggerVoid(id, data)` | Invoke a function without waiting (fire-and-forget) | + +### Registering Functions ```javascript -iii.registerFunction({ id: 'myFunction' }, (req) => { - // ... do something - return { status_code: 200, body: { message: 'Hello, world!' } } +iii.registerFunction({ id: 'orders.create' }, async (input) => { + return { status_code: 201, body: { id: '123', item: input.body.item } } }) ``` ### Registering Triggers -III Allows you to register triggers that can be invoked by other services. - ```javascript iii.registerTrigger({ type: 'http', - function_id: 'myFunction', - config: { api_path: 'myApiPath', http_method: 'POST' }, + function_id: 'orders.create', + config: { api_path: '/orders', http_method: 'POST' }, }) ``` -### Registering Trigger Types - -Triggers are mostly created by III Core Modules, but you can also create your own triggers +### Invoking Functions ```javascript -iii.registerTrigger_type( - { - /** - * This is the id of the trigger type, it's unique. - * Then, you can register a trigger by calling the registerTrigger method. - */ - id: 'myTrigger_type', - description: 'My trigger type', - }, - { - /** - * Trigger config has: id, function_id, and config. - * Your logic should know what to do with the config. - */ - registerTrigger: async (config) => { - // ... do something - }, - unregisterTrigger: async (config) => { - // ... do something - }, - }, -) -``` +const result = await iii.trigger('orders.create', { item: 'widget' }) -### Invoking Functions +iii.triggerVoid('analytics.track', { event: 'page_view' }) +``` -III Allows you to invoke functions, they can be functions from the Core Modules or -functions registered by workers. +## Node Modules -```javascript -const result = await iii.call('myFunction', { param1: 'value1' }) -console.log(result) -``` +| Import | What it provides | +| ------------------- | ------------------------------------- | +| `iii-sdk` | Core SDK (`init`, types) | +| `iii-sdk/stream` | Stream client for real-time state | +| `iii-sdk/state` | State client for key-value operations | +| `iii-sdk/telemetry` | OpenTelemetry integration | -### Invoking Functions Async +## Deprecated -III Allows you to invoke functions asynchronously, they can be functions from the Core Modules or functions registered by workers. +`call()` and `callVoid()` are deprecated aliases for `trigger()` and `triggerVoid()`. They still work but will be removed in a future release. -```javascript -iii.callVoid('myFunction', { param1: 'value1' }) -``` +## Resources -This means the Engine won't hold the execution of the function, it will return immediately. Which means the function will be executed in the background. +- [Documentation](https://iii.dev/docs) +- [iii Engine](https://github.com/iii-hq/iii) +- [Examples](https://github.com/iii-hq/iii-examples) diff --git a/packages/python/README.md b/packages/python/README.md deleted file mode 100644 index f51dafc..0000000 --- a/packages/python/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Python Packages - -This directory contains Python packages for the III Engine. - -## Packages - -### iii - -The core SDK for communicating with the III Engine via WebSocket. The package is installed as `iii-sdk`. - -```bash -cd iii -pip install -e . -``` - -## Examples - -### iii-example - -Basic example demonstrating the III SDK. - -```bash -cd iii-example -uv sync -uv run python -m src.main -``` - -Prerequisite: the worker expects a III engine at `III_BRIDGE_URL` (default `ws://localhost:49134`) and Redis for the stream/event modules configured in `iii-example/config.yaml`. - -## Development - -### Install package in development mode - -```bash -pip install -e iii -``` - -### Type checking - -```bash -cd iii && mypy src -``` - -### Linting - -```bash -cd iii && ruff check src -``` diff --git a/packages/python/iii/README.md b/packages/python/iii/README.md index 9e0955d..74ed9d1 100644 --- a/packages/python/iii/README.md +++ b/packages/python/iii/README.md @@ -1,68 +1,125 @@ -# III SDK for Python +# iii-sdk -Python SDK for the III Engine. +Python SDK for the [iii engine](https://github.com/iii-hq/iii). -## Installation +[![PyPI](https://img.shields.io/pypi/v/iii-sdk)](https://pypi.org/project/iii-sdk/) +[![Python](https://img.shields.io/pypi/pyversions/iii-sdk)](https://pypi.org/project/iii-sdk/) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../../LICENSE) + +## Install ```bash pip install iii-sdk ``` -## Usage +## Hello World ```python import asyncio from iii import III -async def my_function(data): - return {"result": "success"} - iii = III("ws://localhost:49134") -iii.register_function("my.function", my_function) + +async def greet(data): + return {"message": f"Hello, {data['name']}!"} + +iii.register_function("greet", greet) async def main(): await iii.connect() - result = await iii.call("other.function", {"param": "value"}) + iii.register_trigger( + type="http", + function_id="greet", + config={"api_path": "/greet", "http_method": "POST"} + ) + + result = await iii.trigger("greet", {"name": "world"}) print(result) asyncio.run(main()) ``` -### Register API trigger +## API + +| Operation | Signature | Description | +| ------------------------ | ------------------------------------------------- | ------------------------------------------------------ | +| Initialize | `III(url)` | Create an SDK instance | +| Connect | `await iii.connect()` | Connect to the engine | +| Register function | `iii.register_function(id, handler)` | Register a function that can be invoked by name | +| Register trigger | `iii.register_trigger(type, function_id, config)` | Bind a trigger (HTTP, cron, queue, etc.) to a function | +| Invoke (await) | `await iii.trigger(id, data)` | Invoke a function and wait for the result | +| Invoke (fire-and-forget) | `iii.trigger_void(id, data)` | Invoke a function without waiting (fire-and-forget) | + +### Connection + +Python requires an explicit `await iii.connect()` call (no async constructors in Python). This sets up both the WebSocket connection and OpenTelemetry instrumentation. + +### Registering Functions ```python -import asyncio -from iii import III, ApiRequest, ApiResponse +async def create_order(data): + return {"status_code": 201, "body": {"id": "123", "item": data["body"]["item"]}} -iii = III("ws://localhost:49134") +iii.register_function("orders.create", create_order) +``` -async def create_todo(data): - req = ApiRequest(**data) - return ApiResponse(status=201, data={"id": "123", "title": req.body.get("title")}) +### Registering Triggers -iii.register_function("api.post.todo", create_todo) +Requires `await iii.connect()` first. -async def main(): - await iii.connect() +```python +iii.register_trigger( + type="http", + function_id="orders.create", + config={"api_path": "/orders", "http_method": "POST"} +) +``` - iii.register_trigger( - type="http", - function_id="api.post.todo", - config={ - "api_path": "/todo", - "http_method": "POST", - "description": "Create a new todo" - } - ) +### Invoking Functions -asyncio.run(main()) +Requires `await iii.connect()` first. + +```python +result = await iii.trigger("orders.create", {"body": {"item": "widget"}}) + +iii.trigger_void("analytics.track", {"event": "page_view"}) +``` + +## Modules + +| Import | What it provides | +| --------------- | --------------------------------- | +| `iii` | Core SDK (`III`, types) | +| `iii.stream` | Stream client for real-time state | +| `iii.telemetry` | OpenTelemetry integration | + +## Development + +### Install in development mode + +```bash +pip install -e . ``` -## Features +### Type checking + +```bash +mypy src +``` + +### Linting + +```bash +ruff check src +``` + +## Deprecated + +`call()` and `call_void()` are deprecated aliases for `trigger()` and `trigger_void()`. They still work but will be removed in a future release. + +## Resources -- WebSocket-based communication with III Engine -- Function registration and invocation -- Trigger registration -- Context-aware logging -- Async/await support +- [Documentation](https://iii.dev/docs) +- [iii Engine](https://github.com/iii-hq/iii) +- [Examples](https://github.com/iii-hq/iii-examples) diff --git a/packages/rust/iii/README.md b/packages/rust/iii/README.md index 6b59c95..e0a16e2 100644 --- a/packages/rust/iii/README.md +++ b/packages/rust/iii/README.md @@ -1,17 +1,23 @@ -# III SDK for Rust +# iii-sdk -Rust SDK for the III Engine. +Rust SDK for the [iii engine](https://github.com/iii-hq/iii). -## Installation +[![crates.io](https://img.shields.io/crates/v/iii-sdk)](https://crates.io/crates/iii-sdk) +[![docs.rs](https://img.shields.io/docsrs/iii-sdk)](https://docs.rs/iii-sdk) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../../LICENSE) -Add the crate to your `Cargo.toml`: +## Install + +Add to your `Cargo.toml`: ```toml [dependencies] -iii-sdk = { path = "../path/to/iii" } +iii-sdk = "0.3" +serde_json = "1" +tokio = { version = "1", features = ["full"] } ``` -## Usage +## Hello World ```rust use iii_sdk::III; @@ -22,12 +28,18 @@ async fn main() -> Result<(), Box> { let iii = III::new("ws://127.0.0.1:49134"); iii.connect().await?; - iii.register_function("my.function", |input| async move { - Ok(json!({ "message": "Hello, world!", "input": input })) + iii.register_function("greet", |input| async move { + let name = input.get("name").and_then(|v| v.as_str()).unwrap_or("world"); + Ok(json!({ "message": format!("Hello, {name}!") })) }); + iii.register_trigger("http", "greet", json!({ + "api_path": "/greet", + "http_method": "POST" + }))?; + let result: serde_json::Value = iii - .call("my.function", json!({ "param": "value" })) + .trigger("greet", json!({ "name": "world" })) .await?; println!("result: {result}"); @@ -35,16 +47,83 @@ async fn main() -> Result<(), Box> { } ``` -## Features +## API + +| Operation | Signature | Description | +| ------------------------ | -------------------------------------------- | ------------------------------------------------------ | +| Initialize | `III::new(url)` | Create an SDK instance | +| Connect | `iii.connect().await?` | Connect to the engine | +| Register function | `iii.register_function(id, \|input\| ...)` | Register a function that can be invoked by name | +| Register trigger | `iii.register_trigger(type, fn_id, config)?` | Bind a trigger (HTTP, cron, queue, etc.) to a function | +| Invoke (await) | `iii.trigger(id, data).await?` | Invoke a function and wait for the result | +| Invoke (fire-and-forget) | `iii.trigger_void(id, data)?` | Invoke a function without waiting (fire-and-forget) | + +### Connection + +Rust requires an explicit `iii.connect().await?` call. This starts a background task that handles WebSocket communication and automatic reconnection. It also sets up OpenTelemetry instrumentation. + +### Registering Functions + +```rust +iii.register_function("orders.create", |input| async move { + let item = input["body"]["item"].as_str().unwrap_or(""); + Ok(json!({ "status_code": 201, "body": { "id": "123", "item": item } })) +}); +``` + +### Registering Triggers + +Requires `iii.connect().await?` first. + +```rust +iii.register_trigger("http", "orders.create", json!({ + "api_path": "/orders", + "http_method": "POST" +}))?; +``` + +### Invoking Functions + +Requires `iii.connect().await?` first. + +```rust +let result = iii.trigger("orders.create", json!({ "body": { "item": "widget" } })).await?; + +iii.trigger_void("analytics.track", json!({ "event": "page_view" }))?; +``` + +### Streams + +```rust +use iii_sdk::Streams; + +let streams = Streams::new(iii.clone()); +streams.set_field("room::123", "users", json!(["alice", "bob"])).await?; +``` + +### OpenTelemetry + +Enable the `otel` feature for full tracing and metrics support: + +```toml +[dependencies] +iii-sdk = { version = "0.3", features = ["otel"] } +``` + +## Modules + +| Import | What it provides | +| -------------------- | --------------------------------------------------- | +| `iii_sdk` | Core SDK (`III`, types) | +| `iii_sdk::stream` | Stream client (`Streams`, `UpdateBuilder`) | +| `iii_sdk::telemetry` | OpenTelemetry integration (requires `otel` feature) | + +## Deprecated -- WebSocket-based communication with III Engine -- Function registration and invocation -- Trigger registration and trigger type handling -- Context-aware logging (`get_context().logger`) -- Async/await support with automatic reconnection +`call()` and `call_void()` are deprecated aliases for `trigger()` and `trigger_void()`. They still work but will be removed in a future release. -## Notes +## Resources -- `III::connect` starts a background task and handles reconnection automatically. -- The engine protocol currently supports `registertriggertype` but does not include an - `unregistertriggertype` message; `unregister_trigger_type` only removes local handlers. +- [Documentation](https://iii.dev/docs) +- [iii Engine](https://github.com/iii-hq/iii) +- [Examples](https://github.com/iii-hq/iii-examples)