# bridge

> Bridge two iii engines — expose local functions on a remote engine and invoke/forward remote functions locally.

| field | value |
|-------|-------|
| version | 0.1.3 |
| type | binary |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, i686-pc-windows-msvc, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii worker add bridge@0.1.3
```

## configuration

```yaml
- expose:

  forward:

  url: ws://0.0.0.0:49134
```

## dependencies

- `configuration` @ `^0.19.0`

## readme

# bridge

Connects a local iii engine to a remote iii instance over a long-lived
`iii-sdk` WebSocket connection so functions on either side can call across the
boundary. `expose` entries make a local function callable *from* the remote
engine; `forward`/`invoke` entries make a remote function callable *from* this
engine. There are no trigger types.

## Install

```bash
iii worker add bridge
```

`iii worker add` fetches the binary, writes a config block into
`~/.iii/config.yaml`, and the engine starts the worker on the next
`iii start`.

## Function surface

| Function | Input | Output |
|---|---|---|
| `bridge.invoke` | `{ function_id, data?, timeout_ms? }` | remote function's return value, waits up to `timeout_ms` (default `30000`) |
| `bridge.invoke_async` | `{ function_id, data?, timeout_ms? }` | `null`, immediately — fire-and-forget; `timeout_ms` is ignored |
| forward entry (`local_function`) | whatever the remote `remote_function` expects | remote function's return value, waits up to the entry's `timeout_ms` (default `30000`) |
| expose entry (registered on the remote, name defaults to `local_function`) | whatever the local `local_function` expects | local function's return value; a local failure's real `code`/`message`/`stacktrace` is forwarded untouched |

`bridge.invoke`/`bridge.invoke_async`/forward calls collapse any remote-side
failure to `code: "bridge_error"` — the underlying remote error code is never
surfaced through those three paths. A malformed `bridge.invoke*` input
(missing `function_id`) fails with `code: "deserialization_error"` instead.

## Configuration

Configuration is owned by the `configuration` worker — edit it from the
console (**Configuration → Workers → bridge**) or seed it once via
`--config <file>.yaml` on first boot:

| Field | Default | Description |
|---|---|---|
| `url` | `ws://0.0.0.0:49134` | Remote engine WebSocket URL. Fallback chain: `config.url` → `III_URL` env var → the default above. |
| `expose[]` | `[]` | `{ local_function, remote_function? }` — local functions the remote engine may call; `remote_function` is the name registered on the remote (defaults to `local_function`). |
| `forward[]` | `[]` | `{ local_function, remote_function, timeout_ms? }` — local aliases that proxy outbound to a remote function; `timeout_ms` overrides the per-call default (`30000`). |

Hot-reload semantics: a `url` change connects a new remote client, re-registers
every `expose` entry on it, then swaps it in and gracefully shuts the old
client down — no restart needed. An unchanged `url` with new `expose`/`forward`
entries registers just the additions live. **Removing** a `forward`/`expose`
entry does not un-register its handler — the SDK has no unregister — so the
function id stays live but its handler returns `bridge_error` until the worker
is restarted.

## Requires removing the built-in `iii-bridge` worker

The built-in `iii-bridge` worker registers the same function ids
(`bridge.invoke`, `bridge.invoke_async`, plus any configured forward/expose
names). Two workers registering the same function id on one engine collide —
whichever registers last wins — so this worker requires `iii-bridge` to be
absent: omit it from the engine's `config.yaml` (a config that doesn't list a
worker won't run it). This is a duplicate-function-id collision, not a trigger
type conflict — `bridge` registers no trigger types.

On boot, this worker queries the engine for connected workers and refuses to
start with a clear error if `iii-bridge` is still active, so a stale config
fails loudly instead of silently racing the built-in worker for ownership of
`bridge.invoke`/`bridge.invoke_async`.

The engine's own state/queue/stream/configuration **bridge adapters** are
unaffected by this worker or by removing `iii-bridge`: those adapters open
their own direct SDK connections to bridge engine-internal subsystems and never
depended on the `iii-bridge` worker.

## Parity vs builtin

| Behavior | Builtin | This worker |
|---|---|---|
| `bridge.invoke` / `bridge.invoke_async` | exact paths, codes `deserialization_error`/`bridge_error` | same |
| Default timeout | 30s (`timeout_ms` overrides) | same |
| `invoke_async` result | `NoResult` (absent) | `null` (SDK functions must return a value) |
| Forward functions | one local function per entry, description `Forward to remote function {id}` | same |
| Expose functions | registered on the remote engine, name defaults to local, real error body forwarded | same |
| Remote URL | `config.url` → `III_URL` env → `ws://0.0.0.0:49134` | same |
| Trigger types | none | none |
| Config source | engine `config.yaml` (restart to change) | `configuration` worker entry `bridge`, hot-reload |
| Removing forward/expose entries | restart re-registers cleanly | entry disabled (handler errors); full removal needs worker restart |

## api reference

```json
{
  "functions": [
    {
      "description": "Invoke a function on the remote III instance",
      "metadata": {},
      "name": "bridge.invoke",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Exact parity with the builtin's `InvokeInput` (mod.rs:50-57).",
        "properties": {
          "data": {
            "default": null,
            "description": "Payload passed to the remote function, forwarded verbatim."
          },
          "function_id": {
            "description": "Id of the function to invoke on the remote iii instance.",
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Milliseconds to wait for the result. `bridge.invoke` defaults to 30s when omitted; `bridge.invoke_async` ignores this field (fire-and-forget).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "function_id"
        ],
        "title": "InvokeInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Raw JSON value returned or forwarded without validation.",
        "title": "RawValue",
        "type": [
          "null",
          "boolean",
          "number",
          "string",
          "array",
          "object"
        ]
      }
    },
    {
      "description": "Fire-and-forget invoke on the remote III instance",
      "metadata": {},
      "name": "bridge.invoke_async",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Exact parity with the builtin's `InvokeInput` (mod.rs:50-57).",
        "properties": {
          "data": {
            "default": null,
            "description": "Payload passed to the remote function, forwarded verbatim."
          },
          "function_id": {
            "description": "Id of the function to invoke on the remote iii instance.",
            "type": "string"
          },
          "timeout_ms": {
            "default": null,
            "description": "Milliseconds to wait for the result. `bridge.invoke` defaults to 30s when omitted; `bridge.invoke_async` ignores this field (fire-and-forget).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "function_id"
        ],
        "title": "InvokeInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "NullResult",
        "type": "null"
      }
    },
    {
      "description": "Internal: reload bridge configuration from the authoritative store on change.",
      "metadata": {},
      "name": "bridge::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ConfigChangeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "ConfigChangeAck",
        "type": "object"
      }
    }
  ],
  "triggers": []
}
```
