-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(sdk): README doc-lies, actionable errors, and a compile-checked Hello World #1522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,89 @@ | ||||||||||||||||||||||||
| name: SDK README Guard | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||||
| paths: | ||||||||||||||||||||||||
| - 'sdk/**/README.md' | ||||||||||||||||||||||||
| - 'sdk/README.md' | ||||||||||||||||||||||||
| - 'sdk/packages/rust/iii/examples/readme_hello.rs' | ||||||||||||||||||||||||
| - 'sdk/packages/rust/iii/src/**' | ||||||||||||||||||||||||
| - 'sdk/packages/python/iii/src/**' | ||||||||||||||||||||||||
| - 'sdk/fixtures/readme-guard/**' | ||||||||||||||||||||||||
| - 'scripts/readme-guard/**' | ||||||||||||||||||||||||
| - '.github/workflows/sdk-readme.yml' | ||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||||
| paths: | ||||||||||||||||||||||||
| - 'sdk/**/README.md' | ||||||||||||||||||||||||
| - 'sdk/README.md' | ||||||||||||||||||||||||
| - 'sdk/packages/rust/iii/examples/readme_hello.rs' | ||||||||||||||||||||||||
| - 'sdk/packages/rust/iii/src/**' | ||||||||||||||||||||||||
| - 'sdk/packages/python/iii/src/**' | ||||||||||||||||||||||||
| - 'sdk/fixtures/readme-guard/**' | ||||||||||||||||||||||||
| - 'scripts/readme-guard/**' | ||||||||||||||||||||||||
| - '.github/workflows/sdk-readme.yml' | ||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||||
| group: ${{ github.workflow }}-${{ github.ref }} | ||||||||||||||||||||||||
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # This workflow runs in warn-only mode so it can ship alongside the first round | ||||||||||||||||||||||||
| # of README fixes without blocking merges. After the follow-up PR flips | ||||||||||||||||||||||||
| # `continue-on-error` to `false`, any new README drift will fail CI. | ||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||
| rust-readme: | ||||||||||||||||||||||||
| name: Rust — cargo check --example readme_hello | ||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||
| continue-on-error: true | ||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - uses: dtolnay/rust-toolchain@stable | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - uses: Swatinem/rust-cache@v2 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Compile the canonical Rust Hello World | ||||||||||||||||||||||||
| run: cargo check -p iii-sdk --example readme_hello | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| python-readme: | ||||||||||||||||||||||||
| name: Python — attribute-probe against real `iii.III` | ||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||
| continue-on-error: true | ||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - uses: actions/setup-python@v5 | ||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||
| python-version: '3.12' | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Install guard deps + editable SDK | ||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||
| python -m pip install --upgrade pip | ||||||||||||||||||||||||
| python -m pip install markdown-it-py | ||||||||||||||||||||||||
| python -m pip install -e sdk/packages/python/iii | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Verify golden-good fixture passes | ||||||||||||||||||||||||
| run: python scripts/readme-guard/check_python_readme.py sdk/fixtures/readme-guard/golden-good-python.md | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Verify golden-bad fixture fails (meta-test) | ||||||||||||||||||||||||
| # The guard *must* exit non-zero on this fixture. If it exits 0, the | ||||||||||||||||||||||||
| # guard is silently broken and would let real bugs ship. | ||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||
| set +e | ||||||||||||||||||||||||
| python scripts/readme-guard/check_python_readme.py sdk/fixtures/readme-guard/golden-bad-python.md | ||||||||||||||||||||||||
| rc=$? | ||||||||||||||||||||||||
| set -e | ||||||||||||||||||||||||
| if [ "$rc" = "0" ]; then | ||||||||||||||||||||||||
| echo "::error::Guard passed the golden-bad fixture — it is silently broken." | ||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||
| echo "Guard correctly rejected golden-bad (exit $rc)." | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Run guard against real READMEs | ||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||
| python scripts/readme-guard/check_python_readme.py \ | ||||||||||||||||||||||||
| sdk/packages/python/iii/README.md \ | ||||||||||||||||||||||||
| sdk/packages/node/iii/README.md \ | ||||||||||||||||||||||||
| sdk/README.md | ||||||||||||||||||||||||
|
Comment on lines
+84
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probing the Node README with the Python attribute probe is a no-op.
📝 Minimal fix - name: Run guard against real READMEs
run: |
python scripts/readme-guard/check_python_readme.py \
sdk/packages/python/iii/README.md \
- sdk/packages/node/iii/README.md \
sdk/README.md📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| #!/usr/bin/env python3 | ||
| """Verify Python README code blocks use only real `III` attributes. | ||
|
|
||
| Catches the class of bug where a README teaches `iii.connect()` while the | ||
| actual class exposes only `connect_async()`. Compile-only checks (py_compile, | ||
| mypy without type stubs) pass such code because `iii.connect()` is valid | ||
| Python syntax — it AttributeErrors only at runtime. | ||
|
|
||
| Usage: | ||
| python check_python_readme.py README.md [README.md ...] | ||
|
|
||
| Exit code 0 = all snippets reference real attributes, 1 = at least one | ||
| snippet calls a method that does not exist on `iii.III`. | ||
|
|
||
| The script uses `markdown-it-py` to parse code fences correctly (no regex) | ||
| and `ast` + `inspect` to resolve attribute references against the real | ||
| class. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import ast | ||
| import inspect | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| try: | ||
| from markdown_it import MarkdownIt | ||
| except ImportError: # pragma: no cover - bootstrap guidance | ||
| sys.stderr.write( | ||
| "check_python_readme: markdown-it-py is required. " | ||
| "Install with `pip install markdown-it-py`.\n" | ||
| ) | ||
| sys.exit(2) | ||
|
|
||
|
|
||
| def extract_python_fences(md_text: str) -> list[tuple[int, str]]: | ||
| """Return (line_number, source) for each ```python fenced block.""" | ||
| md = MarkdownIt("commonmark") | ||
| tokens = md.parse(md_text) | ||
| fences: list[tuple[int, str]] = [] | ||
| for tok in tokens: | ||
| if tok.type != "fence": | ||
| continue | ||
| info = (tok.info or "").strip().lower() | ||
| # Accept `python`, `py`, or info strings that start with `python` | ||
| # (e.g., `python3`, `python,ignore`). | ||
| if info == "python" or info == "py" or info.startswith("python"): | ||
| line = (tok.map[0] + 1) if tok.map else 0 | ||
| fences.append((line, tok.content)) | ||
| return fences | ||
|
|
||
|
|
||
| def iii_class_attrs() -> set[str]: | ||
| """Return the set of public attribute names available on `iii.III`.""" | ||
| from iii.iii import III # noqa: E402 - runtime import | ||
|
|
||
| return {name for name, _ in inspect.getmembers(III) if not name.startswith("_")} | ||
|
|
||
|
|
||
| def attr_calls_on_iii_var(source: str) -> list[tuple[int, str]]: | ||
| """Find method calls of the form `iii.<name>(...)` in a snippet. | ||
|
|
||
| Returns a list of (lineno, attr_name). Heuristic: we treat any bare | ||
| identifier named ``iii`` as an `III` instance. The Hello World pattern | ||
| `iii = register_worker(...)` makes this reliable. | ||
| """ | ||
| try: | ||
| tree = ast.parse(source) | ||
| except SyntaxError: | ||
| # Syntax errors are a separate class of problem; `py_compile` already | ||
| # catches them. Skip attribute checking when parse fails. | ||
| return [] | ||
|
Comment on lines
+68
to
+73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check whether syntax errors are suppressed here and whether another workflow step explicitly compiles README fences.
rg -n -C4 'except SyntaxError|return \[\]' scripts/readme-guard/check_python_readme.py
rg -n -C3 'py_compile|compileall|check_python_readme' --glob '*.yml' --glob '*.yaml'Repository: iii-hq/iii Length of output: 493 🏁 Script executed: #!/bin/bash
# Search for py_compile and compileall usage throughout the codebase
echo "=== Searching for py_compile usage ==="
rg -r 'py_compile' --no-heading
echo ""
echo "=== Searching for compileall usage ==="
rg -r 'compileall' --no-heading
echo ""
echo "=== Searching for check_python_readme invocation in all files ==="
rg -r 'check_python_readme' --no-headingRepository: iii-hq/iii Length of output: 1407 🏁 Script executed: #!/bin/bash
# View the full context of the check_file function and lines 93-94
echo "=== Full check_python_readme.py file ==="
wc -l scripts/readme-guard/check_python_readme.py
echo ""
cat -n scripts/readme-guard/check_python_readme.pyRepository: iii-hq/iii Length of output: 5648 🏁 Script executed: #!/bin/bash
# Check for workflow files and their content
echo "=== Workflow files ==="
fd -e yml -e yaml | head -20
echo ""
echo "=== Check for any CI/workflow references to readme or syntax ==="
rg -l 'readme|syntax' --glob '*.yml' --glob '*.yaml' --glob '.github'Repository: iii-hq/iii Length of output: 912 🏁 Script executed: #!/bin/bash
cat -n .github/workflows/sdk-readme.ymlRepository: iii-hq/iii Length of output: 3619 🏁 Script executed: #!/bin/bash
cat -n sdk/fixtures/readme-guard/golden-bad-python.mdRepository: iii-hq/iii Length of output: 655 Fail closed on Python syntax errors in README fences. Right now a malformed Python snippet returns Lines 93–94 in Proposed fix- try:
- tree = ast.parse(source)
- except SyntaxError:
- # Syntax errors are a separate class of problem; `py_compile` already
- # catches them. Skip attribute checking when parse fails.
- return []
+ tree = ast.parse(source)
@@
md_text = path.read_text()
for block_line, source in extract_python_fences(md_text):
- for call_lineno, attr in attr_calls_on_iii_var(source):
+ try:
+ calls = attr_calls_on_iii_var(source)
+ except SyntaxError as exc:
+ errors.append(
+ f"{path}:{block_line + (exc.lineno or 1) - 1}: "
+ f"Python README snippet has invalid syntax: {exc.msg}"
+ )
+ continue
+
+ for call_lineno, attr in calls:🤖 Prompt for AI Agents |
||
|
|
||
| calls: list[tuple[int, str]] = [] | ||
| for node in ast.walk(tree): | ||
| if not isinstance(node, ast.Call): | ||
| continue | ||
| fn = node.func | ||
| if not isinstance(fn, ast.Attribute): | ||
| continue | ||
| if not isinstance(fn.value, ast.Name): | ||
| continue | ||
| if fn.value.id != "iii": | ||
| continue | ||
| calls.append((node.lineno, fn.attr)) | ||
| return calls | ||
|
|
||
|
|
||
| def check_file(path: Path, allowed_attrs: set[str]) -> list[str]: | ||
| errors: list[str] = [] | ||
| md_text = path.read_text() | ||
| for block_line, source in extract_python_fences(md_text): | ||
| for call_lineno, attr in attr_calls_on_iii_var(source): | ||
| if attr in allowed_attrs: | ||
| continue | ||
| # Allow the dynamic `attr` sentinel names we know about: | ||
| # e.g., handler closures aren't on `iii`. | ||
| errors.append( | ||
| f"{path}:{block_line + call_lineno - 1}: " | ||
| f"iii.{attr}() is called but `iii.III` has no public " | ||
| f"attribute named `{attr}`. " | ||
| f"Did the README outlive a refactor?" | ||
| ) | ||
| return errors | ||
|
|
||
|
|
||
| def main() -> int: | ||
| if len(sys.argv) < 2: | ||
| sys.stderr.write(f"usage: {sys.argv[0]} README.md [README.md ...]\n") | ||
| return 2 | ||
|
|
||
| try: | ||
| allowed = iii_class_attrs() | ||
| except Exception as exc: # pragma: no cover - import failure path | ||
| sys.stderr.write( | ||
| f"check_python_readme: failed to import `iii.III` to probe its " | ||
| f"attributes: {exc}\n" | ||
| "Make sure the iii-sdk package is installed (editable mode is " | ||
| "fine) before running this check.\n" | ||
| ) | ||
| return 2 | ||
|
|
||
| all_errors: list[str] = [] | ||
| for arg in sys.argv[1:]: | ||
| all_errors.extend(check_file(Path(arg), allowed)) | ||
|
|
||
| for msg in all_errors: | ||
| sys.stderr.write(msg + "\n") | ||
|
|
||
| return 0 if not all_errors else 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,16 @@ These are iii official SDKs for Node, Python, and Rust. See the [engine README]( | |
| | [`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) | | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| All three SDKs connect to a running iii engine over WebSocket. Before running | ||
| any snippet below: | ||
|
|
||
| 1. Install the engine: `curl -fsSL https://install.iii.dev/iii/main/install.sh | sh` | ||
| 2. Start the engine: `iii --config config.yaml` (default URL: `ws://localhost:49134`) | ||
|
|
||
| See the [quickstart](https://iii.dev/docs/quickstart) to scaffold a full project. | ||
|
|
||
| ## Hello World | ||
|
|
||
| ### Node.js | ||
|
|
@@ -47,58 +57,67 @@ iii = register_worker("ws://localhost:49134") | |
| def greet(data): | ||
| return {"message": f"Hello, {data['name']}!"} | ||
|
|
||
| iii.register_function({"id": "greet"}, greet) | ||
| iii.register_function("greet", greet) | ||
|
|
||
| iii.register_trigger({ | ||
| "type": "http", | ||
| "function_id": "greet", | ||
| "config": {"api_path": "/greet", "http_method": "POST"} | ||
| "config": {"api_path": "/greet", "http_method": "POST"}, | ||
| }) | ||
|
|
||
| result = iii.trigger({"function_id": "greet", "payload": {"name": "world"}}) | ||
| ``` | ||
|
|
||
| ### Rust | ||
|
|
||
| ```rust | ||
| use iii_sdk::{register_worker, InitOptions, TriggerRequest, RegisterFunctionMessage, RegisterTriggerInput}; | ||
| use serde_json::json; | ||
| ```rust,no_run | ||
| use iii_sdk::builtin_triggers::{HttpMethod, HttpTriggerConfig}; | ||
| use iii_sdk::{IIITrigger, InitOptions, RegisterFunction, TriggerRequest, register_worker}; | ||
| use serde_json::{Value, json}; | ||
|
|
||
| #[tokio::main] | ||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| let iii = register_worker("ws://127.0.0.1:49134", InitOptions::default())?; | ||
| let iii = register_worker("ws://localhost:49134", InitOptions::default()); | ||
|
|
||
| iii.register_function(RegisterFunctionMessage::with_id("greet".into()), |input| async move { | ||
| iii.register_function(RegisterFunction::new_async("greet", |input: Value| async move { | ||
| let name = input.get("name").and_then(|v| v.as_str()).unwrap_or("world"); | ||
| Ok(json!({ "message": format!("Hello, {name}!") })) | ||
| }); | ||
| Ok::<Value, iii_sdk::IIIError>(json!({ "message": format!("Hello, {name}!") })) | ||
| })); | ||
|
|
||
| iii.register_trigger(RegisterTriggerInput { trigger_type: "http".into(), function_id: "greet".into(), config: json!({ | ||
| "api_path": "/greet", | ||
| "http_method": "POST" | ||
| }) })?; | ||
| iii.register_trigger( | ||
| IIITrigger::Http(HttpTriggerConfig::new("/greet").method(HttpMethod::Post)) | ||
| .for_function("greet"), | ||
| )?; | ||
|
|
||
| let result: serde_json::Value = iii | ||
| let result: Value = iii | ||
| .trigger(TriggerRequest::new("greet", json!({ "name": "world" }))) | ||
| .await?; | ||
|
|
||
| iii.shutdown(); | ||
| Ok(()) | ||
| } | ||
| ``` | ||
|
|
||
| ## API | ||
|
|
||
| | Operation | Node.js | Python | Rust | Description | | ||
| | ------------------------ | ---------------------------------------------------- | ------------------------------------------- | -------------------------------------------- | ------------------------------------------------------ | | ||
| | Initialize | `registerWorker(url)` | `register_worker(url, options?)` | `register_worker(url, options)` | Create an SDK instance and auto-connect | | ||
| | Register function | `iii.registerFunction(id, handler, options?)` | `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": ..., "function_id": ..., "config": ...})` | `iii.register_trigger(type, fn_id, config)?` | Bind a trigger (HTTP, cron, queue, etc.) to a function | | ||
| | Invoke (await) | `await iii.trigger({ function_id, payload })` | `await iii.trigger({"function_id": id, "payload": data})` | `iii.trigger(TriggerRequest::new(id, data)).await?` | Invoke a function and wait for the result | | ||
| | Invoke (fire-and-forget) | `iii.trigger({ function_id, payload, action: TriggerAction.Void() })` | Same | Same | Invoke without waiting | | ||
| The Rust snippet is kept in sync with `sdk/packages/rust/iii/examples/readme_hello.rs` | ||
| and verified by CI. | ||
|
|
||
| `registerWorker()` / `register_worker()` creates an SDK instance and auto-connects to the engine. It handles WebSocket communication, automatic reconnection, and OpenTelemetry instrumentation. All three SDKs expose the same API surface — register functions and triggers, then invoke them. | ||
| ## API | ||
|
|
||
| > `call`, `callVoid`, `triggerVoid` (and Python/Rust equivalents) have been removed. Use `trigger()` for all invocations. For fire-and-forget, use `trigger({ function_id, payload, action: TriggerAction.Void() })`. | ||
| | Operation | Node.js | Python | Rust | Description | | ||
| | ------------------------ | ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------ | | ||
| | Initialize | `registerWorker(url)` | `register_worker(url, options?)` | `register_worker(url, options)` | Create an SDK instance and auto-connect | | ||
| | Register function | `iii.registerFunction(id, handler, options?)` | `iii.register_function(id, handler)` | `iii.register_function(RegisterFunction::new_async(id, handler))` | Register a function that can be invoked by name | | ||
| | Register trigger | `iii.registerTrigger({ type, function_id, config })` | `iii.register_trigger({"type": ..., "function_id": ..., "config": ...})` | `iii.register_trigger(IIITrigger::Http(...).for_function(id))?` | Bind a trigger (HTTP, cron, queue, etc.) to a function | | ||
| | Invoke (await) | `await iii.trigger({ function_id, payload })` | `await iii.trigger({"function_id": id, "payload": data})` | `iii.trigger(TriggerRequest::new(id, payload)).await?` | Invoke a function and wait for the result | | ||
| | Invoke (fire-and-forget) | `iii.trigger({ function_id, payload, action: TriggerAction.Void() })` | `iii.trigger({"function_id": id, "payload": data, "action": TriggerAction.Void()})` | `iii.trigger(TriggerRequest::new(id, payload).with_action(TriggerAction::Void)).await?` | Invoke without waiting | | ||
|
Comment on lines
+111
to
+112
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm whether Python `III.trigger` is synchronous and locate README guidance that still says to await it.
rg -n -C3 '^\s*(async\s+)?def\s+trigger\s*\(' --glob 'iii.py'
rg -n -C2 'await\s+iii\.trigger|Node/Python|TriggerAction\.Void\(\)' --glob 'README.md'Repository: iii-hq/iii Length of output: 7795 Fix Python Line 111 incorrectly shows Proposed fix-| Invoke (await) | `await iii.trigger({ function_id, payload })` | `await iii.trigger({"function_id": id, "payload": data})` | `iii.trigger(TriggerRequest::new(id, payload)).await?` | Invoke a function and wait for the result |
+| Invoke (wait for result) | `await iii.trigger({ function_id, payload })` | `iii.trigger({"function_id": id, "payload": data})` | `iii.trigger(TriggerRequest::new(id, payload)).await?` | Invoke a function and wait for the result |
@@
-> `call`, `callVoid`, `triggerVoid` (and Python/Rust equivalents) have been removed. Use `trigger()` for all invocations. For fire-and-forget, use `trigger({ function_id, payload, action: TriggerAction.Void() })` (Node/Python) or `TriggerRequest::new(id, payload).with_action(TriggerAction::Void)` (Rust).
+> `call`, `callVoid`, `triggerVoid` (and Python/Rust equivalents) have been removed. Use `trigger()` for all invocations. For fire-and-forget, use `iii.trigger({ function_id, payload, action: TriggerAction.Void() })` in Node, `iii.trigger({"function_id": id, "payload": data, "action": TriggerAction.Void()})` in Python, or `TriggerRequest::new(id, payload).with_action(TriggerAction::Void)` in Rust.🤖 Prompt for AI Agents |
||
|
|
||
| `registerWorker()` / `register_worker()` creates an SDK instance and auto-connects | ||
| to the engine. It handles WebSocket communication, automatic reconnection, and | ||
| OpenTelemetry instrumentation. Rust's `register_worker` returns `III` directly | ||
| (not `Result`); Node/Python mirror this. All three SDKs expose the same concept | ||
| surface — register functions and triggers, then invoke them. | ||
|
|
||
| > `call`, `callVoid`, `triggerVoid` (and Python/Rust equivalents) have been removed. Use `trigger()` for all invocations. For fire-and-forget, use `trigger({ function_id, payload, action: TriggerAction.Void() })` (Node/Python) or `TriggerRequest::new(id, payload).with_action(TriggerAction::Void)` (Rust). | ||
|
|
||
| For language-specific details (modules, streams, OpenTelemetry), see the per-SDK READMEs linked in the table above. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Golden bad (Python) | ||
|
|
||
| This fixture MUST make the guard exit 1 — it contains the exact bug the | ||
| guard was built to catch: `iii.connect()` is valid Python syntax but the | ||
| `iii.III` class does not expose a public `connect` method. | ||
|
|
||
| ```python | ||
| from iii import register_worker | ||
|
|
||
| iii = register_worker("ws://localhost:49134") | ||
|
|
||
| def greet(data): | ||
| return {"message": f"Hello, {data['name']}!"} | ||
|
|
||
| iii.register_function("greet", greet) | ||
| iii.connect() # <-- intentional bug: no such method | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Golden good (Python) | ||
|
|
||
| This fixture exists so CI can prove the guard is *working* — not silently | ||
| letting everything pass. The guard must exit 0 on this file. | ||
|
|
||
| ```python | ||
| from iii import register_worker | ||
|
|
||
| iii = register_worker("ws://localhost:49134") | ||
|
|
||
| def greet(data): | ||
| return {"message": f"Hello, {data['name']}!"} | ||
|
|
||
| iii.register_function("greet", greet) | ||
|
|
||
| iii.register_trigger({ | ||
| "type": "http", | ||
| "function_id": "greet", | ||
| "config": {"api_path": "/greet", "http_method": "POST"}, | ||
| }) | ||
|
|
||
| result = iii.trigger({"function_id": "greet", "payload": {"name": "world"}}) | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path filters omit Node/root SDK sources that the guard probes.
The final guard step probes
sdk/packages/node/iii/README.mdandsdk/README.md, but the triggers do not includesdk/packages/node/iii/src/**or anything that would catch Node API-surface churn. If a Node public API renames, the Node README could regress without this workflow running. Either narrow the guard targets (see sibling comment) or expand the path filters to match what you're actually checking.🤖 Prompt for AI Agents