From 5c4e5a28bf6c71370c703cfe2bd9422377e0153e Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 24 Aug 2026 14:12:43 +0200 Subject: [PATCH 1/5] feat: added flow type to definition macros --- crates/taurus-macros/src/flow_type.rs | 53 +++++++++++++++++++++++++++ crates/taurus-macros/src/lib.rs | 7 ++++ 2 files changed, 60 insertions(+) create mode 100644 crates/taurus-macros/src/flow_type.rs diff --git a/crates/taurus-macros/src/flow_type.rs b/crates/taurus-macros/src/flow_type.rs new file mode 100644 index 0000000..fa41fe0 --- /dev/null +++ b/crates/taurus-macros/src/flow_type.rs @@ -0,0 +1,53 @@ +//! Expands the function-like `taurus_macros::flow_type! { ... }` macro. Flow +//! types carry no handler body, like data types -- just metadata. + +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::parse::{AttrArgs, optional_string, translation_vec}; + +pub fn expand(input: TokenStream) -> syn::Result { + let args = AttrArgs::parse(input)?; + + let identifier = args.required_string("identifier")?; + let module = args.required_string("module")?; + let name = translation_vec(&args.translations("name")?); + let description = translation_vec(&args.translations("description")?); + let documentation = translation_vec(&args.translations("documentation")?); + let display_message = translation_vec(&args.translations("display_message")?); + let alias = translation_vec(&args.translations("alias")?); + let editable = args.flag("editable"); + let display_icon = optional_string(args.string("display_icon")?); + let linked: Vec = args.string_array("linked_data_type_identifiers")?; + let signature = args.string("signature")?.unwrap_or_default(); + + let meta_fn_ident = format_ident!( + "__taurus_flow_type_meta_{}", + identifier + .to_lowercase() + .replace(|c: char| !c.is_ascii_alphanumeric(), "_"), + ); + + Ok(quote! { + #[doc(hidden)] + fn #meta_fn_ident() -> crate::meta::FlowTypeMeta { + crate::meta::FlowTypeMeta { + identifier: #identifier, + module: #module, + name: #name, + description: #description, + documentation: #documentation, + display_message: #display_message, + alias: #alias, + editable: #editable, + display_icon: #display_icon, + linked_data_type_identifiers: vec![#(#linked),*], + signature: #signature, + } + } + + ::inventory::submit! { + crate::meta::FlowTypeRegistration(#meta_fn_ident) + } + }) +} diff --git a/crates/taurus-macros/src/lib.rs b/crates/taurus-macros/src/lib.rs index acf392b..7463ca8 100644 --- a/crates/taurus-macros/src/lib.rs +++ b/crates/taurus-macros/src/lib.rs @@ -12,6 +12,7 @@ //! entry it constructs. mod data_type; +mod flow_type; mod module; mod parse; mod runtime_function; @@ -53,6 +54,12 @@ pub fn data_type(input: TokenStream) -> TokenStream { run_fnlike(data_type::expand, input) } +/// Declares a flow type (was `flow_types/*.json`). +#[proc_macro] +pub fn flow_type(input: TokenStream) -> TokenStream { + run_fnlike(flow_type::expand, input) +} + /// Declares a module (was `module.json`). Exactly one per feature file. #[proc_macro] pub fn module(input: TokenStream) -> TokenStream { From 8e984e199c9b25d26196b0a7bd2ff8b5655521ed Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 24 Aug 2026 14:12:46 +0200 Subject: [PATCH 2/5] feat: added dev flag --- crates/taurus-macros/src/module.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/taurus-macros/src/module.rs b/crates/taurus-macros/src/module.rs index 5e63d73..9319d15 100644 --- a/crates/taurus-macros/src/module.rs +++ b/crates/taurus-macros/src/module.rs @@ -17,6 +17,7 @@ pub fn expand(input: TokenStream) -> syn::Result { let author = args.required_string("author")?; let icon = args.required_string("icon")?; let version = args.required_string("version")?; + let dev_only = args.flag("dev_only"); let meta_fn_ident = format_ident!( "__taurus_module_meta_{}", @@ -36,6 +37,7 @@ pub fn expand(input: TokenStream) -> syn::Result { author: #author, icon: #icon, version: #version, + dev_only: #dev_only, } } From fbcba35d095db0978a2012fe54f99e4feae53cba Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 24 Aug 2026 14:12:57 +0200 Subject: [PATCH 3/5] deps: added code0-flow to taurus code --- Cargo.lock | 1 + crates/taurus-core/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d8852b0..0be5b64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,6 +2868,7 @@ dependencies = [ "async-trait", "base64 0.23.1", "chrono", + "code0-flow", "futures-lite", "inventory", "log", diff --git a/crates/taurus-core/Cargo.toml b/crates/taurus-core/Cargo.toml index 84cf7b3..a96ada4 100644 --- a/crates/taurus-core/Cargo.toml +++ b/crates/taurus-core/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] tucana = { workspace = true } +code0-flow = { workspace = true, features = ["flow_config"] } base64 = { workspace = true } rand = { workspace = true } log = { workspace = true } From 1a4b968b6b97712f5192b6fb995643741e9d2212 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 24 Aug 2026 14:16:42 +0200 Subject: [PATCH 4/5] feat: added manual flow type --- .../taurus-core/src/runtime/functions/dev.rs | 26 +++++++++++++++++++ .../taurus-core/src/runtime/functions/mod.rs | 1 + 2 files changed, 27 insertions(+) create mode 100644 crates/taurus-core/src/runtime/functions/dev.rs diff --git a/crates/taurus-core/src/runtime/functions/dev.rs b/crates/taurus-core/src/runtime/functions/dev.rs new file mode 100644 index 0000000..0133c2e --- /dev/null +++ b/crates/taurus-core/src/runtime/functions/dev.rs @@ -0,0 +1,26 @@ +//! Development-only module for exercising flow-type features end-to-end +//! without touching a real domain module. Only registered while Taurus runs +//! with `ENVIRONMENT=development` (the default) -- see `dev_only` on +//! `taurus_macros::module!`. + +taurus_macros::module! { + identifier = "taurus-dev", + name(en_US = "Development"), + description(en_US = "Development-only flow types and definitions, not shipped to staging or production."), + documentation = "", + author = "CodeZero", + icon = "tabler:test-pipe", + version = "0.0.1", + dev_only, +} + +taurus_macros::flow_type! { + identifier = "MANUAL", + module = "taurus-dev", + signature = "(): void", + name(en_US = "Manual"), + description(en_US = "A flow started manually, with no configurable settings."), + display_message(en_US = "Manual"), + alias(en_US = "manual;trigger;dev;test"), + display_icon = "tabler:test-pipe", +} diff --git a/crates/taurus-core/src/runtime/functions/mod.rs b/crates/taurus-core/src/runtime/functions/mod.rs index 9f13846..95129a4 100644 --- a/crates/taurus-core/src/runtime/functions/mod.rs +++ b/crates/taurus-core/src/runtime/functions/mod.rs @@ -10,6 +10,7 @@ mod boolean; mod color; mod control; mod date; +mod dev; mod file; mod http; mod number; From 23e54eb07c4cec01081250f9d54812e4bbb918a4 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 24 Aug 2026 14:17:17 +0200 Subject: [PATCH 5/5] feat: register a flow type identifier + dev mode handling --- crates/taurus-core/src/export.rs | 8 +++ crates/taurus-core/src/meta.rs | 23 +++++++ crates/taurus-core/src/registry.rs | 101 ++++++++++++++++++++++++++--- 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/crates/taurus-core/src/export.rs b/crates/taurus-core/src/export.rs index 9153dd8..3b057a0 100644 --- a/crates/taurus-core/src/export.rs +++ b/crates/taurus-core/src/export.rs @@ -32,6 +32,14 @@ fn write(module: &Module, dir: &Path) -> io::Result<()> { &module.definition_data_types, |dt| &dt.identifier, )?; + write_each(&dir.join("flow_types"), &module.flow_types, |ft| { + &ft.identifier + })?; + write_each( + &dir.join("runtime_flow_types"), + &module.runtime_flow_types, + |ft| &ft.identifier, + )?; write_each(&dir.join("functions"), &module.function_definitions, |f| { &f.runtime_name })?; diff --git a/crates/taurus-core/src/meta.rs b/crates/taurus-core/src/meta.rs index bbd4e49..47126e4 100644 --- a/crates/taurus-core/src/meta.rs +++ b/crates/taurus-core/src/meta.rs @@ -54,6 +54,26 @@ pub struct ModuleMeta { pub author: &'static str, pub icon: &'static str, pub version: &'static str, + /// Only built by [`crate::registry::build_modules`] while Taurus is + /// running with `ENVIRONMENT=development` (the default) -- lets a module + /// still under development self-register without shipping to staging or + /// production. + pub dev_only: bool, +} + +pub struct FlowTypeMeta { + pub identifier: &'static str, + /// Identifier of the [`ModuleMeta`] this flow type belongs to. + pub module: &'static str, + pub name: Vec, + pub description: Vec, + pub documentation: Vec, + pub display_message: Vec, + pub alias: Vec, + pub editable: bool, + pub display_icon: Option<&'static str>, + pub linked_data_type_identifiers: Vec<&'static str>, + pub signature: &'static str, } pub struct MetaRegistration(pub fn() -> RuntimeFunctionMeta); @@ -62,5 +82,8 @@ inventory::collect!(MetaRegistration); pub struct DataTypeRegistration(pub fn() -> DataTypeMeta); inventory::collect!(DataTypeRegistration); +pub struct FlowTypeRegistration(pub fn() -> FlowTypeMeta); +inventory::collect!(FlowTypeRegistration); + pub struct ModuleRegistration(pub fn() -> ModuleMeta); inventory::collect!(ModuleRegistration); diff --git a/crates/taurus-core/src/registry.rs b/crates/taurus-core/src/registry.rs index 4a1f57b..8e60085 100644 --- a/crates/taurus-core/src/registry.rs +++ b/crates/taurus-core/src/registry.rs @@ -4,27 +4,51 @@ //! the result anywhere (Aquila, a file, ...) is a transport concern that //! belongs to the caller. +use std::collections::HashSet; + +use code0_flow::flow_config::env_with_default; +use code0_flow::flow_config::environment::Environment; + use crate::meta::{ - DataTypeMeta, DataTypeRegistration, MetaRegistration, ModuleMeta, ModuleRegistration, - RuntimeFunctionMeta, + DataTypeMeta, DataTypeRegistration, FlowTypeMeta, FlowTypeRegistration, MetaRegistration, + ModuleMeta, ModuleRegistration, RuntimeFunctionMeta, }; use tucana::shared::{ - DefinitionDataType, FunctionDefinition, Module, ParameterDefinition, RuntimeFunctionDefinition, - RuntimeParameterDefinition, + DefinitionDataType, FlowType, FunctionDefinition, Module, ParameterDefinition, + RuntimeFlowType, RuntimeFunctionDefinition, RuntimeParameterDefinition, }; -/// Builds every registered module, complete with its function and data-type -/// definitions. Panics if a function or data type declares a `module` that +/// Builds every registered module, complete with its function, data-type and +/// flow-type definitions. Skips modules declared `dev_only` unless Taurus is +/// running with `ENVIRONMENT=development` (the default), along with anything +/// that declares one of those modules as its owner. +/// +/// Panics if a function, data type, or flow type declares a `module` that /// has no matching `taurus_macros::module!` registration -- a broken link /// between a handler and its owning module is a programming error, not a -/// runtime condition to recover from. +/// runtime condition to recover from. This does not apply to modules that +/// were themselves excluded for being `dev_only`. pub fn build_modules() -> Vec { + let is_dev = env_with_default("ENVIRONMENT", Environment::Development) == Environment::Development; + + let mut excluded: HashSet<&'static str> = HashSet::new(); let mut modules: Vec = inventory::iter::() - .map(|reg| module_from_meta((reg.0)())) + .map(|reg| (reg.0)()) + .filter_map(|meta| { + if meta.dev_only && !is_dev { + excluded.insert(meta.identifier); + None + } else { + Some(module_from_meta(meta)) + } + }) .collect(); for reg in inventory::iter::() { let meta = (reg.0)(); + if excluded.contains(meta.module) { + continue; + } let module = find_module(&mut modules, meta.module, meta.identifier); let version = module.version.clone(); module @@ -37,6 +61,9 @@ pub fn build_modules() -> Vec { for reg in inventory::iter::() { let meta = (reg.0)(); + if excluded.contains(meta.module) { + continue; + } let module = find_module(&mut modules, meta.module, meta.identifier); let version = module.version.clone(); module @@ -44,6 +71,19 @@ pub fn build_modules() -> Vec { .push(data_type_definition(&meta, version)); } + for reg in inventory::iter::() { + let meta = (reg.0)(); + if excluded.contains(meta.module) { + continue; + } + let module = find_module(&mut modules, meta.module, meta.identifier); + let version = module.version.clone(); + module + .runtime_flow_types + .push(runtime_flow_type_definition(&meta, version.clone())); + module.flow_types.push(flow_type_definition(&meta, version)); + } + modules } @@ -148,6 +188,51 @@ fn function_definition(meta: &RuntimeFunctionMeta, version: String) -> FunctionD } } +fn flow_type_definition(meta: &FlowTypeMeta, version: String) -> FlowType { + FlowType { + identifier: meta.identifier.to_string(), + settings: Vec::new(), + editable: meta.editable, + name: meta.name.clone(), + description: meta.description.clone(), + documentation: meta.documentation.clone(), + display_message: meta.display_message.clone(), + alias: meta.alias.clone(), + version, + display_icon: meta.display_icon.unwrap_or_default().to_string(), + definition_source: None, + linked_data_type_identifiers: meta + .linked_data_type_identifiers + .iter() + .map(|s| s.to_string()) + .collect(), + signature: meta.signature.to_string(), + runtime_identifier: meta.identifier.to_string(), + } +} + +fn runtime_flow_type_definition(meta: &FlowTypeMeta, version: String) -> RuntimeFlowType { + RuntimeFlowType { + identifier: meta.identifier.to_string(), + runtime_settings: Vec::new(), + editable: meta.editable, + name: meta.name.clone(), + description: meta.description.clone(), + documentation: meta.documentation.clone(), + display_message: meta.display_message.clone(), + alias: meta.alias.clone(), + version, + display_icon: meta.display_icon.unwrap_or_default().to_string(), + definition_source: None, + linked_data_type_identifiers: meta + .linked_data_type_identifiers + .iter() + .map(|s| s.to_string()) + .collect(), + signature: meta.signature.to_string(), + } +} + fn data_type_definition(meta: &DataTypeMeta, version: String) -> DefinitionDataType { DefinitionDataType { identifier: meta.identifier.to_string(),