reflow_rt_capi 0.2.1

C ABI bindings for the Reflow runtime — the shared native surface consumed by Go (cgo), Kotlin/Java (JNI), and other non-Rust callers.
Documentation

reflow_rt_capi

C ABI bindings for the Reflow runtime — the native surface consumed by non-Rust SDKs.

Rust users should depend on reflow_rt directly. This crate exists so that SDKs in other languages share a single canonical ABI: Go via cgo, other ecosystems either bind the .h directly or wrap it in an idiomatic layer.

What it provides

  • A stable, C-compatible ABI around reflow_rt: network lifecycle, graph construction / loading, event streaming.
  • cdylib + staticlib build artifacts so consumers can dynamically link or bundle the runtime.
  • Auto-generated include/reflow_rt.h (via cbindgen).

Building

# Regular build — produces libreflow_rt_capi.{dylib,so,dll} + .a
cargo build -p reflow_rt_capi --release

# Regenerate the C header (requires the optional cbindgen build-dep)
cargo build -p reflow_rt_capi --features generate-header

The header lands at include/reflow_rt.h.

Conventions

All details are in src/lib.rs; the short version:

  • Handles are opaque pointers (rfl_network*, rfl_graph*, rfl_events*). Every *_new / *_load has a matching *_free that is NULL-safe.
  • Return codes use the rfl_status enum; out-parameters are written on success.
  • Strings owned by the library are freed with rfl_string_free. Never pass them to free(3).
  • Error messages are thread-local; retrieve with rfl_last_error_message after a failing call.
  • Threading: all handles are Send + Sync; the crate lazily spins up a shared tokio multi-thread runtime on first use.

Current surface (v0.2)

Graph lifecycle + builder

rfl_graph* rfl_graph_new(const char* name, int case_sensitive);
rfl_graph* rfl_graph_load_json(const char* json);
void       rfl_graph_free(rfl_graph*);
char*      rfl_graph_to_json(rfl_graph*);            /* reverse of load_json */

rfl_status rfl_graph_add_node     (rfl_graph*, const char* id, const char* component, const char* metadata_json);
rfl_status rfl_graph_remove_node  (rfl_graph*, const char* id);
rfl_status rfl_graph_set_node_metadata(rfl_graph*, const char* id, const char* metadata_json);

rfl_status rfl_graph_add_connection   (rfl_graph*, const char* out_node, const char* out_port,
                                                   const char* in_node,  const char* in_port,
                                                   const char* metadata_json);
rfl_status rfl_graph_remove_connection(rfl_graph*, const char* out_node, const char* out_port,
                                                   const char* in_node,  const char* in_port);

rfl_status rfl_graph_add_initial    (rfl_graph*, const char* node, const char* port,
                                                 const char* data_json, const char* metadata_json);
rfl_status rfl_graph_remove_initial (rfl_graph*, const char* node, const char* port);

rfl_status rfl_graph_add_inport    (rfl_graph*, const char* port_id, const char* node_id,
                                                const char* port_key, const char* port_type_json,
                                                const char* metadata_json);
rfl_status rfl_graph_add_outport   (rfl_graph*, const char* port_id, const char* node_id,
                                                const char* port_key, const char* port_type_json,
                                                const char* metadata_json);
rfl_status rfl_graph_remove_inport (rfl_graph*, const char* port_id);
rfl_status rfl_graph_remove_outport(rfl_graph*, const char* port_id);

Network lifecycle + builder

rfl_network* rfl_network_new(void);
rfl_network* rfl_network_from_graph(rfl_graph*);      /* consumes the graph */
rfl_status   rfl_network_start   (rfl_network*);
rfl_status   rfl_network_shutdown(rfl_network*);
void         rfl_network_free    (rfl_network*);

rfl_status rfl_network_add_node      (rfl_network*, const char* id, const char* template_id, const char* config_json);
rfl_status rfl_network_add_connection(rfl_network*, const char* from_actor, const char* from_port,
                                                    const char* to_actor,   const char* to_port);
rfl_status rfl_network_add_initial   (rfl_network*, const char* actor, const char* port, const char* message_json);

Event stream

rfl_events*  rfl_network_events(rfl_network*);
rfl_status   rfl_events_recv   (rfl_events*, uint32_t timeout_ms, char** out_json);
void         rfl_events_free   (rfl_events*);

Misc

char* rfl_version(void);
char* rfl_last_error_message(void);
void  rfl_string_free(char*);
void  rfl_runtime_shutdown(void);

Callback-driven actors

Language SDKs can register native functions as actors. The runtime calls them on every tick with a per-call rfl_actor_ctx* that exposes inputs, config, state, and an emitter.

typedef enum rfl_status (*rfl_actor_fn)(void* user_data, rfl_actor_ctx* ctx);
typedef void            (*rfl_actor_drop_fn)(void* user_data);

rfl_actor* rfl_actor_new(const char* component_name,
                         const char* const* inports,  size_t n_inports,
                         const char* const* outports, size_t n_outports,
                         int await_all_inports,
                         rfl_actor_fn callback,
                         void* user_data,
                         rfl_actor_drop_fn user_data_drop); /* may be NULL */
void       rfl_actor_free(rfl_actor*);

rfl_status rfl_network_register_actor(rfl_network*, const char* template_id, rfl_actor*);

Inside the callback (all returned strings are rfl_string_free-owned):

int        rfl_ctx_has_input   (rfl_actor_ctx*, const char* port);
char*      rfl_ctx_input_json  (rfl_actor_ctx*, const char* port);
char*      rfl_ctx_config_json (rfl_actor_ctx*);
char*      rfl_ctx_state_get   (rfl_actor_ctx*, const char* key);
rfl_status rfl_ctx_state_set   (rfl_actor_ctx*, const char* key, const char* value_json);
rfl_status rfl_ctx_emit        (rfl_actor_ctx*, const char* port, const char* message_json);

Threading: callbacks run on a tokio worker. Language SDKs must arrange their own synchronization (Node ThreadSafeFunction, Python GIL acquire, JNI AttachCurrentThread, etc.).

user_data_drop fires exactly once, when the last reference to the actor is released by the runtime — use it to release your GC root.

Typed messages

Skip the JSON tax for hot paths. rfl_message* is an opaque handle; pass it to rfl_ctx_emit_message or rfl_ctx_take_input_message on the way in and out.

rfl_message* rfl_message_flow(void);
rfl_message* rfl_message_boolean(int);
rfl_message* rfl_message_integer(int64_t);
rfl_message* rfl_message_float(double);
rfl_message* rfl_message_string(const char*);
rfl_message* rfl_message_bytes(const uint8_t*, size_t);
rfl_message* rfl_message_object_from_json(const char*);
rfl_message* rfl_message_array_from_json(const char*);
rfl_message* rfl_message_error(const char*);
rfl_message* rfl_message_from_json(const char*);    /* fallback */

rfl_message_kind rfl_message_get_kind(const rfl_message*);
int   rfl_message_as_boolean (const rfl_message*, int* out);
int   rfl_message_as_integer (const rfl_message*, int64_t* out);
int   rfl_message_as_float   (const rfl_message*, double* out);
char* rfl_message_as_string  (const rfl_message*);     /* rfl_string_free */
char* rfl_message_as_json    (const rfl_message*);     /* rfl_string_free */
int   rfl_message_bytes_borrow(const rfl_message*, const uint8_t** out, size_t* len);
void  rfl_message_free(rfl_message*);

rfl_status   rfl_ctx_emit_message     (rfl_actor_ctx*, const char* port, rfl_message*); /* transfers ownership */
rfl_message* rfl_ctx_take_input_message(rfl_actor_ctx*, const char* port);

Streams

Producer + consumer sides for Message::StreamHandle. Frames travel through bounded flume channels; the message carries only the handle.

rfl_stream*  rfl_stream_new(size_t buffer_size, const char* origin_actor,
                            const char* origin_port, const char* content_type);
rfl_status   rfl_stream_send_begin(rfl_stream*, const char* content_type,
                                   uint64_t size_hint, int has_size_hint,
                                   const char* metadata_json);
rfl_status   rfl_stream_send_bytes(rfl_stream*, const uint8_t*, size_t);
rfl_status   rfl_stream_end       (rfl_stream*);
rfl_status   rfl_stream_error     (rfl_stream*, const char* message);
rfl_message* rfl_stream_into_message(rfl_stream*);   /* consumes, emit via rfl_ctx_emit_message */
void         rfl_stream_free     (rfl_stream*);

rfl_stream_recv* rfl_message_stream_take(rfl_message*);    /* one-shot */
rfl_status       rfl_stream_recv_next   (rfl_stream_recv*, uint32_t timeout_ms,
                                         rfl_stream_frame_kind* out_kind,
                                         const uint8_t** out_data, size_t* out_len,
                                         char** out_err);
void             rfl_stream_recv_free   (rfl_stream_recv*);

Subgraph builder

For subgraphs whose referenced components aren't all in the bundled catalog (e.g. callback actors registered by the SDK):

rfl_subgraph_builder* rfl_subgraph_builder_new(const char* graph_export_json);
rfl_status            rfl_subgraph_builder_register_actor(rfl_subgraph_builder*,
                                                          const char* component_name,
                                                          rfl_actor*);     /* transfers ownership */
rfl_status            rfl_subgraph_builder_fill_from_catalog(rfl_subgraph_builder*);
rfl_actor*            rfl_subgraph_builder_build(rfl_subgraph_builder*);   /* consumes builder */
void                  rfl_subgraph_builder_free(rfl_subgraph_builder*);

NetworkConfig

rfl_network_new_with_config(const char* json) — parse a serialized NetworkConfig. Default is still available via rfl_network_new().

Template catalog + subgraph (gated on the components feature)

Enabled by default; compile with --no-default-features to drop reflow_components for a thinner shared library.

/* Bundled component instantiation */
rfl_actor* rfl_template_actor_new(const char* template_id);
char*      rfl_template_list_json(void);         /* JSON array of ids */

/* Subgraph embedding — resolves referenced components from the catalog */
rfl_actor* rfl_subgraph_actor_new_from_json(const char* graph_export_json);

All three return an rfl_actor* you can hand to rfl_network_register_actor. This is how a non-Rust SDK registers the bundled standard library (~300 templates) and composes graph exports as first-class subgraph nodes.

Not yet in the ABI

  • Structured event types — events currently arrive as JSON via rfl_events_recv. Could be promoted to an rfl_event* handle with typed accessors; deferred until an SDK asks.
  • Graph introspection without JSON round-trip — today SDKs call rfl_graph_to_json and parse client-side. Fine for cold paths; not fine for frequent reads. Could add rfl_graph_node_ids, rfl_graph_node_component, rfl_graph_connections when that's a real hotspot.
  • Template metadata introspection — port shapes, description, config schema. Currently accessible only by instantiating the actor or consulting the published catalog docs.

License

MIT OR Apache-2.0.