Skip to main content

blvm_sdk/module/
mod.rs

1//! Module Development APIs
2//!
3//! Provides APIs for developing modules that extend blvm-node.
4//!
5//! This module re-exports the necessary types and traits from `blvm-node` to provide
6//! a clean, developer-friendly interface for module development.
7//!
8//! The `wasm` submodule (when `wasm-modules` is enabled) is node-independent:
9//! embedders implement `WasmStorage`/`WasmTree` to bridge to their storage.
10
11#[cfg(feature = "node")]
12pub mod bootstrap;
13#[cfg(feature = "node")]
14pub mod cli_args;
15#[cfg(feature = "node")]
16pub mod database;
17#[cfg(feature = "node")]
18pub mod module_db;
19#[cfg(feature = "node")]
20pub mod storage;
21
22#[cfg(feature = "wasm-modules")]
23pub mod wasm;
24
25/// Minimal module entry point. Expands to full main with bootstrap, migrations, config load, run_module.
26///
27/// **Single-arg form** (preferred when `#[module]` has `config` and `migrations`):
28/// ```ignore
29/// run_module_main!(DemoModule);
30/// ```
31///
32/// **Explicit form** (when ModuleMeta is not implemented):
33/// ```ignore
34/// run_module_main!("demo", DemoModule, DemoConfig, migrations!(1 => up_initial, 2 => up_add_items_tree));
35/// ```
36#[cfg(feature = "node")]
37#[macro_export]
38macro_rules! run_module_main {
39    ($module_type:ty) => {
40        #[tokio::main]
41        async fn main() -> Result<(), Box<dyn std::error::Error>> {
42            let bootstrap = $crate::module::ModuleBootstrap::from_env()?;
43            blvm_node::utils::init_module_logging(
44                <$module_type as $crate::module::ModuleMeta>::MODULE_NAME
45                    .replace('-', "_")
46                    .as_str(),
47                None,
48            );
49            let db = $crate::module::ModuleDb::open(&bootstrap.data_dir)?;
50            db.run_migrations(<$module_type as $crate::module::ModuleMeta>::migrations())?;
51            let config = <<$module_type as $crate::module::ModuleMeta>::Config>::load(
52                bootstrap.data_dir.join("config.toml"),
53            )
54            .unwrap_or_default();
55            let module = <$module_type as $crate::module::ModuleMeta>::__module_new(config);
56            $crate::run_module! {
57                bootstrap: &bootstrap,
58                module_name: <$module_type as $crate::module::ModuleMeta>::MODULE_NAME,
59                module: module,
60                module_type: $module_type,
61                db: db.as_db(),
62            }?;
63            Ok(())
64        }
65    };
66    (
67        $module_name:expr,
68        $module_type:ty,
69        $config_type:ty,
70        $migrations:expr,
71    ) => {
72        #[tokio::main]
73        async fn main() -> Result<(), Box<dyn std::error::Error>> {
74            blvm_node::utils::init_module_logging($module_name.replace('-', "_").as_str(), None);
75            let bootstrap = $crate::module::ModuleBootstrap::from_env()?;
76            let db = $crate::module::ModuleDb::open(&bootstrap.data_dir)?;
77            db.run_migrations($migrations)?;
78            let config =
79                <$config_type>::load(bootstrap.data_dir.join("config.toml")).unwrap_or_default();
80            let module = <$module_type>::__module_new(config);
81            $crate::run_module! {
82                bootstrap: &bootstrap,
83                module_name: $module_name,
84                module: module,
85                module_type: $module_type,
86                db: db.as_db(),
87            }?;
88            Ok(())
89        }
90    };
91}
92
93/// Collect migrations for `run_migrations`. Sugar for `&[(1, up_initial), (2, up_add_cache), ...]`.
94///
95/// # Example
96/// ```ignore
97/// run_migrations(&db, migrations!(1 => up_initial, 2 => up_add_cache))?;
98/// ```
99#[cfg(feature = "node")]
100#[macro_export]
101macro_rules! migrations {
102    ($($v:literal => $up:ident),* $(,)?) => {
103        &[$(($v, $up as $crate::module::MigrationUp)),*]
104    };
105}
106
107/// Run a module with automatic connect, CLI/RPC/event registration, and dispatch.
108///
109/// Replaces manual main-loop boilerplate. CLI spec, RPC methods, and event types are
110/// auto-discovered from #[command], #[rpc_methods], and #[event_handlers]. On unload
111/// (invocation channel closed), the module exits cleanly.
112///
113/// # Example (unified module — preferred)
114/// ```ignore
115/// let bootstrap = ModuleBootstrap::from_env()?;
116/// run_module! {
117///     bootstrap: &bootstrap,
118///     module_name: "demo",
119///     module: DemoModule { config },
120///     module_type: DemoModule,
121///     db,
122/// }
123/// ```
124///
125/// # Example (explicit args — legacy)
126/// ```ignore
127/// run_module! {
128///     socket_path: args.socket_path.clone(),
129///     module_id: &args.module_id,
130///     module_name: "demo-module",
131///     version: env!("CARGO_PKG_VERSION"),
132///     cli: DemoCli,
133///     cli_type: DemoCli,
134///     module_type: DemoModule,
135///     module: DemoModule { config: config.clone() },
136///     db,
137/// }
138/// ```
139#[cfg(feature = "node")]
140#[macro_export]
141macro_rules! run_module {
142    (
143        bootstrap: $bootstrap:expr,
144        module_name: $module_name:expr,
145        module: $module:expr,
146        module_type: $module_type:ty,
147        db: $db:expr,
148    ) => {{
149        let __bootstrap = $bootstrap;
150        let __module = $module;
151        $crate::run_module! {
152            socket_path: __bootstrap.socket_path.clone(),
153            module_id: &__bootstrap.module_id,
154            module_name: $module_name,
155            version: env!("CARGO_PKG_VERSION"),
156            cli: __module.clone(),
157            cli_type: $module_type,
158            module_type: $module_type,
159            module: __module,
160            db: $db,
161        }
162    }};
163    (
164        bootstrap: $bootstrap:expr,
165        module_name: $module_name:expr,
166        module_type: $module_type:ty,
167        cli_type: $cli_type:ty,
168        db: $db:expr,
169        setup: $setup:expr,
170        event_types: $event_types:expr,
171    ) => {{
172        let __bootstrap = $bootstrap;
173        let __db = Arc::clone(&$db);
174        $crate::run_module! {
175            socket_path: __bootstrap.socket_path.clone(),
176            module_id: &__bootstrap.module_id,
177            module_name: $module_name,
178            version: env!("CARGO_PKG_VERSION"),
179            module_type: $module_type,
180            cli_type: $cli_type,
181            db: __db,
182            setup: $setup,
183            event_types: $event_types,
184            on_event: |e, m: &$module_type, ctx| {
185                let m = m.clone();
186                let ctx = ctx.clone();
187                async move { m.dispatch_event(e, &ctx).await }
188            },
189            data_dir: __bootstrap.data_dir.as_path(),
190        }
191    }};
192    (
193        bootstrap: $bootstrap:expr,
194        module_name: $module_name:expr,
195        module_type: $module_type:ty,
196        cli_type: $cli_type:ty,
197        db: $db:expr,
198        setup: $setup:expr,
199        event_types: $event_types:expr,
200        on_event: $on_event:expr,
201    ) => {{
202        let __bootstrap = $bootstrap;
203        let __db = Arc::clone(&$db);
204        $crate::run_module! {
205            socket_path: __bootstrap.socket_path.clone(),
206            module_id: &__bootstrap.module_id,
207            module_name: $module_name,
208            version: env!("CARGO_PKG_VERSION"),
209            module_type: $module_type,
210            cli_type: $cli_type,
211            db: __db,
212            setup: $setup,
213            event_types: $event_types,
214            on_event: $on_event,
215            data_dir: __bootstrap.data_dir.as_path(),
216        }
217    }};
218    (
219        socket_path: $socket_path:expr,
220        module_id: $module_id:expr,
221        module_name: $module_name:expr,
222        version: $version:expr,
223        module_type: $module_type:ty,
224        cli_type: $cli_type:ty,
225        db: $db:expr,
226        setup: $setup:expr,
227        event_types: $event_types:expr,
228        on_event: $on_event:expr,
229        data_dir: $data_dir:expr,
230    ) => {{
231        use blvm_node::module::ipc::protocol::{
232            InvocationMessage, InvocationResultMessage, InvocationResultPayload, InvocationType,
233        };
234        use std::sync::Arc;
235        use $crate::module::runner::{run_module_with_setup, InvocationContext};
236
237        let db = Arc::clone(&$db);
238
239        let dispatch = |invocation: InvocationMessage,
240                        ctx: InvocationContext,
241                        module: &$module_type,
242                        cli: &$cli_type| {
243            let (success, payload, error) = match &invocation.invocation_type {
244                InvocationType::Cli { subcommand, args } => {
245                    let args: Vec<String> = args.clone();
246                    match cli.dispatch_cli(&ctx, subcommand, &args) {
247                        Ok(stdout) => (
248                            true,
249                            Some(InvocationResultPayload::Cli {
250                                stdout,
251                                stderr: String::new(),
252                                exit_code: 0,
253                            }),
254                            None,
255                        ),
256                        Err(e) => (false, None, Some(e.to_string())),
257                    }
258                }
259                InvocationType::Rpc { method, params } => {
260                    let db_ref = ctx.db();
261                    match module.dispatch_rpc(method, params, db_ref) {
262                        Ok(v) => (true, Some(InvocationResultPayload::Rpc(v)), None),
263                        Err(e) => (false, None, Some(e.to_string())),
264                    }
265                }
266                InvocationType::ModuleApi { .. } => (
267                    false,
268                    None,
269                    Some("ModuleApi is not supported by run_module!; use run_module_with_setup_and_api".to_string()),
270                ),
271            };
272            InvocationResultMessage {
273                correlation_id: invocation.correlation_id,
274                success,
275                payload,
276                error,
277            }
278        };
279
280        let rpc_names = <$module_type>::rpc_method_names();
281        let cli_spec = <$cli_type>::cli_spec();
282
283        run_module_with_setup(
284            $socket_path,
285            $module_id,
286            $module_name,
287            $version,
288            cli_spec,
289            rpc_names.as_slice(),
290            $event_types,
291            dispatch,
292            $on_event,
293            $setup,
294            db,
295            $data_dir,
296        )
297        .await
298    }};
299    (
300        socket_path: $socket_path:expr,
301        module_id: $module_id:expr,
302        module_name: $module_name:expr,
303        version: $version:expr,
304        cli: $cli:expr,
305        cli_type: $cli_type:ty,
306        module_type: $module_type:ty,
307        module: $module:expr,
308        db: $db:expr,
309    ) => {{
310        use blvm_node::module::ipc::protocol::{
311            InvocationMessage, InvocationResultMessage, InvocationResultPayload, InvocationType,
312        };
313        use std::sync::Arc;
314        use $crate::module::runner::{run_module as run_module_fn, InvocationContext};
315
316        let cli = $cli;
317        let module = Arc::new($module);
318        let db = Arc::clone(&$db);
319
320        let dispatch = |invocation: InvocationMessage,
321                        ctx: InvocationContext,
322                        module: &Arc<$module_type>,
323                        cli: &$cli_type| {
324            let (success, payload, error) = match &invocation.invocation_type {
325                InvocationType::Cli { subcommand, args } => {
326                    let args: Vec<String> = args.clone();
327                    match cli.dispatch_cli(&ctx, subcommand, &args) {
328                        Ok(stdout) => (
329                            true,
330                            Some(InvocationResultPayload::Cli {
331                                stdout,
332                                stderr: String::new(),
333                                exit_code: 0,
334                            }),
335                            None,
336                        ),
337                        Err(e) => (false, None, Some(e.to_string())),
338                    }
339                }
340                InvocationType::Rpc { method, params } => {
341                    let db_ref = ctx.db();
342                    match module.dispatch_rpc(method, params, db_ref) {
343                        Ok(v) => (true, Some(InvocationResultPayload::Rpc(v)), None),
344                        Err(e) => (false, None, Some(e.to_string())),
345                    }
346                }
347                InvocationType::ModuleApi { .. } => (
348                    false,
349                    None,
350                    Some("ModuleApi is not supported by run_module!; use run_module_with_setup_and_api".to_string()),
351                ),
352            };
353            InvocationResultMessage {
354                correlation_id: invocation.correlation_id,
355                success,
356                payload,
357                error,
358            }
359        };
360
361        let rpc_names = <$module_type>::rpc_method_names();
362        let cli_spec = <$cli_type>::cli_spec();
363
364        run_module_fn(
365            $socket_path,
366            $module_id,
367            $module_name,
368            $version,
369            cli_spec,
370            rpc_names.as_slice(),
371            <$module_type>::event_types(),
372            dispatch,
373            |e, m: &Arc<$module_type>, ctx: &InvocationContext| {
374                let m = std::sync::Arc::clone(m);
375                let ctx = ctx.clone();
376                async move { m.dispatch_event(e, &ctx).await }
377            },
378            module,
379            cli,
380            db,
381        )
382        .await
383    }};
384    (
385        bootstrap: $bootstrap:expr,
386        module_name: $module_name:expr,
387        module: $module:expr,
388        module_type: $module_type:ty,
389        db: $db:expr,
390        on_connect: $on_connect:expr,
391        on_tick: $on_tick:expr,
392    ) => {{
393        let __bootstrap = $bootstrap;
394        let __module = $module;
395        $crate::run_module! {
396            socket_path: __bootstrap.socket_path.clone(),
397            module_id: &__bootstrap.module_id,
398            module_name: $module_name,
399            version: env!("CARGO_PKG_VERSION"),
400            cli: __module.clone(),
401            cli_type: $module_type,
402            module_type: $module_type,
403            module: __module,
404            db: $db,
405            on_connect: Some($on_connect),
406            on_tick: Some($on_tick),
407        }
408    }};
409    (
410        socket_path: $socket_path:expr,
411        module_id: $module_id:expr,
412        module_name: $module_name:expr,
413        version: $version:expr,
414        cli: $cli:expr,
415        cli_type: $cli_type:ty,
416        module_type: $module_type:ty,
417        module: $module:expr,
418        db: $db:expr,
419        on_connect: $on_connect:expr,
420        on_tick: $on_tick:expr,
421    ) => {{
422        use blvm_node::module::ipc::protocol::{
423            InvocationMessage, InvocationResultMessage, InvocationResultPayload, InvocationType,
424        };
425        use std::sync::Arc;
426        use $crate::module::runner::{run_module_with_tick, InvocationContext};
427
428        let cli = $cli;
429        let module = Arc::new($module);
430        let db = Arc::clone(&$db);
431
432        let dispatch = |invocation: InvocationMessage,
433                        ctx: InvocationContext,
434                        module: &Arc<$module_type>,
435                        cli: &$cli_type| {
436            let (success, payload, error) = match &invocation.invocation_type {
437                InvocationType::Cli { subcommand, args } => {
438                    let args: Vec<String> = args.clone();
439                    match cli.dispatch_cli(&ctx, subcommand, &args) {
440                        Ok(stdout) => (
441                            true,
442                            Some(InvocationResultPayload::Cli {
443                                stdout,
444                                stderr: String::new(),
445                                exit_code: 0,
446                            }),
447                            None,
448                        ),
449                        Err(e) => (false, None, Some(e.to_string())),
450                    }
451                }
452                InvocationType::Rpc { method, params } => {
453                    let db_ref = ctx.db();
454                    match module.dispatch_rpc(method, params, db_ref) {
455                        Ok(v) => (true, Some(InvocationResultPayload::Rpc(v)), None),
456                        Err(e) => (false, None, Some(e.to_string())),
457                    }
458                }
459                InvocationType::ModuleApi { .. } => (
460                    false,
461                    None,
462                    Some("ModuleApi is not supported by run_module!; use run_module_with_setup_and_api".to_string()),
463                ),
464            };
465            InvocationResultMessage {
466                correlation_id: invocation.correlation_id,
467                success,
468                payload,
469                error,
470            }
471        };
472
473        let rpc_names = <$module_type>::rpc_method_names();
474        let cli_spec = <$cli_type>::cli_spec();
475
476        run_module_with_tick(
477            $socket_path,
478            $module_id,
479            $module_name,
480            $version,
481            cli_spec,
482            rpc_names.as_slice(),
483            <$module_type>::event_types(),
484            dispatch,
485            |e, m: &Arc<$module_type>, ctx: &InvocationContext| {
486                let m = std::sync::Arc::clone(m);
487                let ctx = ctx.clone();
488                async move { m.dispatch_event(e, &ctx).await }
489            },
490            $on_connect,
491            $on_tick,
492            module,
493            cli,
494            db,
495        )
496        .await
497    }};
498}
499
500/// Register RPC methods with the node on connect.
501///
502/// Call this after connecting via `ModuleIntegration::connect`. Pass the node API and
503/// the method names (as string literals) that were registered with `#[rpc_method(name = "...")]`.
504///
505/// # Example
506/// ```ignore
507/// let integration = ModuleIntegration::connect(...).await?;
508/// let node_api = integration.node_api();
509/// register_rpc_methods!(node_api, "hello_greet", "hello_status").await?;
510/// ```
511#[macro_export]
512macro_rules! register_rpc_methods {
513    ($api:expr, $($method:expr),* $(,)?) => {
514        async {
515            let api = $api;
516            $(
517                api.register_rpc_endpoint($method.to_string(), String::new()).await?;
518            )*
519            Ok::<(), blvm_node::module::traits::ModuleError>(())
520        }
521    };
522}
523
524#[cfg(feature = "node")]
525pub mod ipc;
526#[cfg(feature = "node")]
527pub mod manifest;
528#[cfg(feature = "node")]
529pub mod prelude;
530#[cfg(feature = "node")]
531pub mod runner;
532#[cfg(feature = "node")]
533pub mod security;
534#[cfg(feature = "node")]
535pub mod traits;
536
537// Re-export main types for convenience (requires node)
538#[cfg(feature = "node")]
539pub use bootstrap::{ModuleBootstrap, ModuleConfig};
540#[cfg(feature = "node")]
541pub use database::{
542    open_module_db, run_migrations, run_migrations_down, run_migrations_with_down, Migration,
543    MigrationContext, MigrationDown, MigrationUp,
544};
545#[cfg(feature = "node")]
546pub use ipc::client::ModuleIpcClient;
547#[cfg(feature = "node")]
548pub use ipc::protocol::*;
549#[cfg(feature = "node")]
550pub use manifest::ModuleManifest;
551#[cfg(feature = "node")]
552pub use module_db::ModuleDb;
553#[cfg(feature = "node")]
554pub use runner::{
555    run_async, run_module, run_module_with_setup, run_module_with_setup_and_api,
556    run_module_with_tick, InvocationContext,
557};
558#[cfg(feature = "node")]
559pub use security::{Permission, PermissionSet};
560#[cfg(feature = "node")]
561pub use storage::{DatabaseStorageAdapter, ModuleStorage, ModuleStorageDatabaseBridge, ModuleTree};
562#[cfg(feature = "node")]
563pub use traits::*;